[
  {
    "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*.install linguist-language=Shell\r\n\r\n# ---------------------------------------\r\n# Treat Golang files as Go (for /api/)\r\napi/**/*.go linguist-language=Go\r\n\r\n# ---------------------------------------\r\n# Treat frontend as JavaScript/TypeScript (optional)\r\nfrontend/**/*.ts linguist-language=TypeScript\r\nfrontend/**/*.js linguist-language=JavaScript\r\n\r\n# ---------------------------------------\r\n# Exclude documentation from stats\r\n*.md linguist-documentation\r\ndocs/** 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*.json linguist-generated\r\nfrontend/public/json/*.json linguist-generated=false\r\n*.lock linguist-generated\r\n*.yml linguist-generated\r\n*.yaml linguist-generated\r\n.github/** linguist-generated\r\n.vscode/** linguist-generated\r\n\r\n# ---------------------------------------\r\n# Standard text handling\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*                                                                           @community-scripts/Contributor\n\n# All changes in frontend\n/frontend/                                                                  @community-scripts/Frontend-Dev\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "\n# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\n[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].\n\nCommunity Impact Guidelines were inspired by\n[Mozilla's code of conduct enforcement ladder][Mozilla CoC].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][FAQ]. Translations are available\nat [https://www.contributor-covenant.org/translations][translations].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\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        ## ⚠️ **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        - 🔎 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        - 📜 **Read the script:** Familiarize yourself with the script's content and its purpose. This will help you understand the issue better and provide more relevant information\n\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     \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 \\\"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/zigbee2mqtt.sh)\\\" or \\\"update\\\"\"\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: issue_description\n    attributes:\n      label: 📝 Provide a clear and concise description of the issue.\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/jsYVk5JBxq\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-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/pull_request_template.md",
    "content": "<!--🛑 New scripts must be submitted to [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED) for testing.\nPRs without prior testing will be closed. -->\n\n## ✍️ Description\n\n\n## 🔗 Related Issue\n\nFixes #\n\n## ✅ Prerequisites (**X** in brackets)\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 website-related JSON files or metadata.\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/auto-update-app-headers.yml",
    "content": "name: Auto Update .app-files\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - \"ct/**.sh\"\n  workflow_dispatch:\n\njobs:\n  update-app-files:\n    if: github.repository == 'community-scripts/ProxmoxVE'\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: write\n      pull-requests: write\n\n    steps:\n      - name: Generate a token\n        id: generate-token\n        uses: actions/create-github-app-token@v1\n        with:\n          app-id: ${{ vars.APP_ID }}\n          private-key: ${{ secrets.APP_PRIVATE_KEY }}\n\n      - name: Generate a token for PR approval and merge\n        id: generate-token-merge\n        uses: actions/create-github-app-token@v1\n        with:\n          app-id: ${{ secrets.APP_ID_APPROVE_AND_MERGE }}\n          private-key: ${{ secrets.APP_KEY_APPROVE_AND_MERGE }}\n\n      # Step 1: Checkout repository\n      - name: Checkout repository\n        uses: actions/checkout@v2\n\n      # Step 2: Disable file mode changes detection\n      - name: Disable file mode changes\n        run: git config core.fileMode false\n\n      # Step 3: Set up Git user for committing changes\n      - name: Set up Git\n        run: |\n          git config --global user.name \"GitHub Actions\"\n          git config --global user.email \"github-actions[bot]@users.noreply.github.com\"\n\n      # Step 4: Install figlet\n      - name: Install figlet\n        run: sudo apt-get install -y figlet\n\n      # Step 5: Run the updated generate-app-files.sh script\n      - name: Run generate-app-files.sh\n        run: |\n          chmod +x .github/workflows/scripts/generate-app-headers.sh\n          .github/workflows/scripts/generate-app-headers.sh\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      # Step 6: Check if there are any changes\n      - name: Check if there are any changes\n        run: |\n          echo \"Checking for changes...\"\n          git add -A  # Untracked Dateien aufnehmen\n          git status\n          if git diff --cached --quiet; then\n            echo \"No changes detected.\"\n            echo \"changed=false\" >> \"$GITHUB_ENV\"\n          else\n            echo \"Changes detected:\"\n            git diff --stat --cached\n            echo \"changed=true\" >> \"$GITHUB_ENV\"\n          fi\n\n      # Step 7: Commit and create PR if changes exist\n      - name: Commit and create PR if changes exist\n        if: env.changed == 'true'\n        run: |\n          git commit -m \"Update .app files\"\n          git checkout -b pr-update-app-files\n          git push origin pr-update-app-files --force\n          gh pr create --title \"[core] update .app files\" \\\n                       --body \"This PR is auto-generated by a GitHub Action to update the .app files.\" \\\n                       --head pr-update-app-files \\\n                       --base main \\\n                       --label \"automated pr\"\n        env:\n          GH_TOKEN: ${{ steps.generate-token.outputs.token }}\n\n      - name: Approve pull request\n        if: env.changed == 'true'\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          PR_NUMBER=$(gh pr list --head \"pr-update-app-files\" --json number --jq '.[].number')\n          if [ -n \"$PR_NUMBER\" ]; then\n            gh pr review $PR_NUMBER --approve\n          fi\n\n      - name: Approve pull request and merge\n        if: env.changed == 'true'\n        env:\n          GH_TOKEN: ${{ steps.generate-token-merge.outputs.token }}\n        run: |\n          git config --global user.name \"github-actions-automege[bot]\"\n          git config --global user.email \"github-actions-automege[bot]@users.noreply.github.com\"\n          PR_NUMBER=$(gh pr list --head \"${BRANCH_NAME}\" --json number --jq '.[].number')\n          if [ -n \"$PR_NUMBER\" ]; then\n            gh pr review $PR_NUMBER --approve\n            gh pr merge $PR_NUMBER --squash --admin\n          fi\n\n      # Step 8: Output success message when no changes\n      - name: No changes detected\n        if: env.changed == 'false'\n        run: echo \"No changes to commit. Workflow completed successfully.\"\n"
  },
  {
    "path": ".github/workflows/autolabeler.yml",
    "content": "name: Auto Label Pull Requests\n\non:\n  workflow_dispatch:\n  pull_request_target:\n    branches: [\"main\"]\n    types: [opened, synchronize, reopened, edited]\n\njobs:\n  autolabeler:\n    if: github.repository == 'community-scripts/ProxmoxVE'\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n    env:\n      CONFIG_PATH: .github/autolabeler-config.json\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Install dependencies\n        run: npm install minimatch\n\n      - name: Label PR based on file changes and PR template\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const fs = require('fs').promises;\n            const path = require('path');\n            const { minimatch } = require('minimatch');\n\n            const configPath = path.resolve(process.env.CONFIG_PATH);\n            const fileContent = await fs.readFile(configPath, 'utf-8');\n            const autolabelerConfig = JSON.parse(fileContent);\n\n            const prNumber = context.payload.pull_request.number;\n            const prBody = context.payload.pull_request.body || \"\";\n\n            let labelsToAdd = new Set();\n\n            const prListFilesResponse = await github.rest.pulls.listFiles({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              pull_number: prNumber,\n            });\n            const prFiles = prListFilesResponse.data;\n\n            for (const [label, rules] of Object.entries(autolabelerConfig)) {\n              const shouldAddLabel = prFiles.some((prFile) => {\n                return rules.some((rule) => {\n                  const isFileStatusMatch = rule.fileStatus ? rule.fileStatus === prFile.status : true;\n                  const isIncludeGlobMatch = rule.includeGlobs.some((glob) => minimatch(prFile.filename, glob));\n                  const isExcludeGlobMatch = rule.excludeGlobs.some((glob) => minimatch(prFile.filename, glob));\n                  return isFileStatusMatch && isIncludeGlobMatch && !isExcludeGlobMatch;\n                });\n              });\n\n              if (shouldAddLabel) {\n                labelsToAdd.add(label);\n                // Add specific sub-labels for tools\n                if (label === \"tools\") {\n                  for (const prFile of prFiles) {\n                    const filename = prFile.filename;\n                    if (filename.startsWith(\"tools/addon/\")) labelsToAdd.add(\"addon\");\n                    if (filename.startsWith(\"tools/pve/\")) labelsToAdd.add(\"pve-tool\");\n                  }\n                }\n              }\n            }\n\n            // Always parse template checkboxes to add content-type labels (bugfix, feature, etc.)\n            const templateLabelMappings = {\n              \"🐞 **Bug fix**\": \"bugfix\",\n              \"✨ **New feature**\": \"feature\",\n              \"💥 **Breaking change**\": \"breaking change\",\n              \"🆕 **New script**\": \"new script\",\n              \"🔧 **Refactoring / Code Cleanup**\": \"refactor\",\n              \"📝 **Documentation update**\": \"documentation\"\n            };\n\n            for (const [checkbox, label] of Object.entries(templateLabelMappings)) {\n              const escapedCheckbox = checkbox.replace(/([.*+?^=!:${}()|[\\]\\/\\\\])/g, \"\\\\$1\");\n              const regex = new RegExp(`- \\\\[(x|X)\\\\]\\\\s*${escapedCheckbox}`, \"i\");\n\n              if (regex.test(prBody)) {\n                labelsToAdd.add(label);\n              }\n            }\n\n            // Handle website checkbox specially - only add if not already an update script with content label\n            const websiteCheckbox = \"🌍 **Website update**\";\n            const escapedWebsite = websiteCheckbox.replace(/([.*+?^=!:${}()|[\\]\\/\\\\])/g, \"\\\\$1\");\n            const websiteRegex = new RegExp(`- \\\\[(x|X)\\\\]\\\\s*${escapedWebsite}`, \"i\");\n\n            if (websiteRegex.test(prBody)) {\n              const hasJson = prFiles.some((f) => f.filename.startsWith(\"json/\"));\n              const hasUpdateScript = labelsToAdd.has(\"update script\");\n              const hasContentLabel = [\"bugfix\", \"feature\", \"refactor\"].some((l) => labelsToAdd.has(l));\n\n              // If it's an update script PR with json changes and a content label, skip adding website/json\n              // The PR should be categorized as update script with the content label\n              if (!(hasUpdateScript && hasJson && hasContentLabel)) {\n                labelsToAdd.add(\"website\");\n                if (hasJson) labelsToAdd.add(\"json\");\n              }\n            }\n\n            if (labelsToAdd.size === 0) {\n              labelsToAdd.add(\"needs triage\");\n            }\n\n            if (labelsToAdd.size > 0) {\n              await github.rest.issues.addLabels({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                issue_number: prNumber,\n                labels: Array.from(labelsToAdd),\n              });\n            }\n"
  },
  {
    "path": ".github/workflows/changelog-archive.yml",
    "content": "name: Archive Old Changelog Entries\n\non:\n  schedule:\n    # Run every Sunday at 00:00 UTC\n    - cron: '0 0 * * 0'\n  workflow_dispatch:\n\njobs:\n  archive-changelog:\n    if: github.repository == 'community-scripts/ProxmoxVE'\n    runs-on: ubuntu-latest\n    env:\n      BRANCH_NAME: github-action-archive-changelog\n      AUTOMATED_PR_LABEL: \"automated pr\"\n    permissions:\n      contents: write\n      pull-requests: write\n    steps:\n      - name: Generate a token\n        id: generate-token\n        uses: actions/create-github-app-token@v1\n        with:\n          app-id: ${{ vars.APP_ID }}\n          private-key: ${{ secrets.APP_PRIVATE_KEY }}\n\n      - name: Generate a token for PR approval and merge\n        id: generate-token-merge\n        uses: actions/create-github-app-token@v1\n        with:\n          app-id: ${{ secrets.APP_ID_APPROVE_AND_MERGE }}\n          private-key: ${{ secrets.APP_KEY_APPROVE_AND_MERGE }}\n\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Archive old changelog entries\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const fs = require('fs').promises;\n            const path = require('path');\n            \n            const KEEP_DAYS = 30;\n            const ARCHIVE_PATH = '.github/changelogs';\n            const CHANGELOG_PATH = 'CHANGELOG.md';\n            \n            // Calculate cutoff date\n            const cutoffDate = new Date();\n            cutoffDate.setDate(cutoffDate.getDate() - KEEP_DAYS);\n            cutoffDate.setHours(0, 0, 0, 0);\n            \n            console.log(`Cutoff date: ${cutoffDate.toISOString().split('T')[0]}`);\n            \n            // Read changelog and normalize line endings\n            let content = await fs.readFile(CHANGELOG_PATH, 'utf-8');\n            content = content.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n            const lines = content.split('\\n');\n            \n            // Parse entries\n            const datePattern = /^## (\\d{4})-(\\d{2})-(\\d{2})$/;\n            let header = [];\n            let recentEntries = [];\n            let archiveData = {};\n            let currentDate = null;\n            let currentContent = [];\n            let inHeader = true;\n            let inOldHistory = false;\n            let historyDetailsDepth = 0;\n            \n            for (let i = 0; i < lines.length; i++) {\n              const line = lines[i];\n              const match = line.match(datePattern);\n              \n              // Detect the start of History section: <details> followed by line with 📜 History\n              if (inHeader && !inOldHistory && line.trim() === '<details>') {\n                // Look ahead to see if this is the History section\n                const nextLine = lines[i + 1] || '';\n                if (nextLine.includes('📜 History')) {\n                  inOldHistory = true;\n                  historyDetailsDepth = 1;\n                  continue;\n                }\n              }\n              \n              // Track nested details tags to find the end of History section\n              if (inOldHistory) {\n                if (line.trim() === '<details>') {\n                  historyDetailsDepth++;\n                }\n                if (line.trim() === '</details>') {\n                  historyDetailsDepth--;\n                  if (historyDetailsDepth === 0) {\n                    // We've closed the main History details tag\n                    inOldHistory = false;\n                  }\n                }\n                continue;\n              }\n              \n              if (match) {\n                inHeader = false;\n                \n                // Save previous entry\n                if (currentDate && currentContent.length > 0) {\n                  const entryText = currentContent.join('\\n').trim();\n                  const dateObj = new Date(`${currentDate}T00:00:00Z`);\n                  \n                  // Always add to archive (by month)\n                  const year = currentDate.substring(0, 4);\n                  const month = currentDate.substring(5, 7);\n                  \n                  if (!archiveData[year]) archiveData[year] = {};\n                  if (!archiveData[year][month]) archiveData[year][month] = [];\n                  \n                  archiveData[year][month].push(`## ${currentDate}\\n\\n${entryText}`);\n                  \n                  // Also add to recent entries if within cutoff\n                  if (dateObj >= cutoffDate) {\n                    recentEntries.push(`## ${currentDate}\\n\\n${entryText}`);\n                  }\n                }\n                \n                currentDate = `${match[1]}-${match[2]}-${match[3]}`;\n                currentContent = [];\n              } else if (inHeader) {\n                header.push(line);\n              } else if (currentDate) {\n                currentContent.push(line);\n              }\n            }\n            \n            // Don't forget the last entry\n            if (currentDate && currentContent.length > 0) {\n              const entryText = currentContent.join('\\n').trim();\n              const dateObj = new Date(`${currentDate}T00:00:00Z`);\n              \n              // Always add to archive (by month)\n              const year = currentDate.substring(0, 4);\n              const month = currentDate.substring(5, 7);\n              \n              if (!archiveData[year]) archiveData[year] = {};\n              if (!archiveData[year][month]) archiveData[year][month] = [];\n              \n              archiveData[year][month].push(`## ${currentDate}\\n\\n${entryText}`);\n              \n              // Also add to recent entries if within cutoff\n              if (dateObj >= cutoffDate) {\n                recentEntries.push(`## ${currentDate}\\n\\n${entryText}`);\n              }\n            }\n            \n            console.log(`Recent entries: ${recentEntries.length}`);\n            console.log(`Years to archive: ${Object.keys(archiveData).length}`);\n            \n            // Month names in English\n            const monthNames = {\n              '01': 'January', '02': 'February', '03': 'March', '04': 'April',\n              '05': 'May', '06': 'June', '07': 'July', '08': 'August',\n              '09': 'September', '10': 'October', '11': 'November', '12': 'December'\n            };\n            \n            // Create/update archive files\n            for (const year of Object.keys(archiveData).sort().reverse()) {\n              const yearPath = path.join(ARCHIVE_PATH, year);\n              \n              try {\n                await fs.mkdir(yearPath, { recursive: true });\n              } catch (e) {\n                // Directory exists\n              }\n              \n              for (const month of Object.keys(archiveData[year]).sort().reverse()) {\n                const monthPath = path.join(yearPath, `${month}.md`);\n                \n                // Read existing content if exists\n                let existingContent = '';\n                try {\n                  existingContent = await fs.readFile(monthPath, 'utf-8');\n                  // Normalize line endings to prevent regex issues\n                  existingContent = existingContent.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n                } catch (e) {\n                  // File doesn't exist\n                }\n                \n                // Parse existing entries into a Map (date -> content) for deduplication\n                const allEntries = new Map();\n                \n                // Helper function to parse entries from content\n                const parseEntries = (content) => {\n                  const entries = new Map();\n                  const parts = content.split(/(?=^## \\d{4}-\\d{2}-\\d{2}$)/m);\n                  for (const part of parts) {\n                    const trimmed = part.trim();\n                    if (!trimmed) continue;\n                    const dateMatch = trimmed.match(/^## (\\d{4}-\\d{2}-\\d{2})/);\n                    if (dateMatch) {\n                      entries.set(dateMatch[1], trimmed);\n                    }\n                  }\n                  return entries;\n                };\n                \n                // Parse existing content\n                if (existingContent) {\n                  const existingEntries = parseEntries(existingContent);\n                  for (const [date, content] of existingEntries) {\n                    allEntries.set(date, content);\n                  }\n                }\n                \n                // Add new entries (existing entries take precedence to avoid overwriting)\n                let addedCount = 0;\n                for (const entry of archiveData[year][month]) {\n                  const dateMatch = entry.match(/^## (\\d{4}-\\d{2}-\\d{2})/);\n                  if (dateMatch && !allEntries.has(dateMatch[1])) {\n                    allEntries.set(dateMatch[1], entry.trim());\n                    addedCount++;\n                  }\n                }\n                \n                // Sort entries by date (newest first) and write\n                const sortedDates = [...allEntries.keys()].sort().reverse();\n                const sortedContent = sortedDates.map(date => allEntries.get(date)).join('\\n\\n');\n                \n                if (addedCount > 0 || !existingContent) {\n                  await fs.writeFile(monthPath, sortedContent + '\\n', 'utf-8');\n                  console.log(`Updated: ${monthPath} (${allEntries.size} total entries, +${addedCount} new)`);\n                }\n              }\n            }\n            \n            // Build history section\n            let historySection = [];\n            historySection.push('');\n            historySection.push('<details>');\n            historySection.push('<summary><h2>📜 History</h2></summary>');\n            historySection.push('');\n            \n            // Get all years from archive directory\n            let allYears = [];\n            try {\n              const archiveDir = await fs.readdir(ARCHIVE_PATH);\n              allYears = archiveDir.filter(f => /^\\d{4}$/.test(f)).sort().reverse();\n            } catch (e) {\n              allYears = Object.keys(archiveData).sort().reverse();\n            }\n            \n            for (const year of allYears) {\n              historySection.push('');\n              historySection.push('<details>');\n              historySection.push(`<summary><h3>${year}</h3></summary>`);\n              historySection.push('');\n              \n              // Get months for this year\n              let months = [];\n              try {\n                const yearDir = await fs.readdir(path.join(ARCHIVE_PATH, year));\n                months = yearDir\n                  .filter(f => f.endsWith('.md'))\n                  .map(f => f.replace('.md', ''))\n                  .sort()\n                  .reverse();\n              } catch (e) {\n                months = Object.keys(archiveData[year] || {}).sort().reverse();\n              }\n              \n              for (const month of months) {\n                const monthName = monthNames[month] || month;\n                const monthPath = path.join(ARCHIVE_PATH, year, `${month}.md`);\n                \n                // Count entries in month file\n                let entryCount = 0;\n                try {\n                  let monthContent = await fs.readFile(monthPath, 'utf-8');\n                  monthContent = monthContent.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n                  entryCount = (monthContent.match(/^## \\d{4}-\\d{2}-\\d{2}$/gm) || []).length;\n                } catch (e) {\n                  entryCount = (archiveData[year]?.[month] || []).length;\n                }\n                \n                const relativePath = `.github/changelogs/${year}/${month}.md`;\n                historySection.push('');\n                historySection.push('<details>');\n                historySection.push(`<summary><h4>${monthName} (${entryCount} entries)</h4></summary>`);\n                historySection.push('');\n                historySection.push(`[View ${monthName} ${year} Changelog](${relativePath})`);\n                historySection.push('');\n                historySection.push('</details>');\n              }\n              \n              historySection.push('');\n              historySection.push('</details>');\n            }\n            \n            historySection.push('');\n            historySection.push('</details>');\n            \n            // Build new CHANGELOG.md (History first, then recent entries)\n            const newChangelog = [\n              ...header,\n              '',\n              historySection.join('\\n'),\n              '',\n              recentEntries.join('\\n\\n')\n            ].join('\\n');\n            \n            await fs.writeFile(CHANGELOG_PATH, newChangelog, 'utf-8');\n            console.log('CHANGELOG.md updated successfully');\n\n      - name: Check for changes\n        id: verify-diff\n        run: |\n          git diff --quiet . || echo \"changed=true\" >> $GITHUB_ENV\n\n      - name: Commit and push changes\n        if: env.changed == 'true'\n        run: |\n          git config --global user.name \"github-actions[bot]\"\n          git config --global user.email \"github-actions[bot]@users.noreply.github.com\"\n          git add CHANGELOG.md .github/changelogs/\n          git commit -m \"Archive old changelog entries\"\n          git checkout -b $BRANCH_NAME || git checkout $BRANCH_NAME\n          git push origin $BRANCH_NAME --force\n\n      - name: Create pull request if not exists\n        if: env.changed == 'true'\n        env:\n          GH_TOKEN: ${{ steps.generate-token.outputs.token }}\n        run: |\n          PR_EXISTS=$(gh pr list --head \"${BRANCH_NAME}\" --json number --jq '.[].number')\n          if [ -z \"$PR_EXISTS\" ]; then\n            gh pr create --title \"[Github Action] Archive old changelog entries\" \\\n                         --body \"This PR is auto-generated by a Github Action to archive old changelog entries (older than 14 days) to .github/changelogs/YEAR/MONTH.md\" \\\n                         --head $BRANCH_NAME \\\n                         --base main \\\n                         --label \"$AUTOMATED_PR_LABEL\"\n          fi\n\n      - name: Approve and merge pull request\n        if: env.changed == 'true'\n        env:\n          GH_TOKEN: ${{ steps.generate-token-merge.outputs.token }}\n        run: |\n          git config --global user.name \"github-actions-automege[bot]\"\n          git config --global user.email \"github-actions-automege[bot]@users.noreply.github.com\"\n          PR_NUMBER=$(gh pr list --head \"${BRANCH_NAME}\" --json number --jq '.[].number')\n          if [ -n \"$PR_NUMBER\" ]; then\n            gh pr review $PR_NUMBER --approve\n            gh pr merge $PR_NUMBER --squash --admin\n          fi\n"
  },
  {
    "path": ".github/workflows/changelog-pr.yml",
    "content": "name: Create Changelog Pull Request\n\non:\n  push:\n    branches: [\"main\"]\n  workflow_dispatch:\n\njobs:\n  update-changelog-pull-request:\n    if: github.repository == 'community-scripts/ProxmoxVE'\n    runs-on: ubuntu-latest\n    env:\n      CONFIG_PATH: .github/changelog-pr-config.json\n      BRANCH_NAME: github-action-update-changelog\n      AUTOMATED_PR_LABEL: \"automated pr\"\n    permissions:\n      contents: write\n      pull-requests: write\n    steps:\n      - name: Generate a token\n        id: generate-token\n        uses: actions/create-github-app-token@v1\n        with:\n          app-id: ${{ vars.APP_ID }}\n          private-key: ${{ secrets.APP_PRIVATE_KEY }}\n\n      - name: Generate a token for PR approval and merge\n        id: generate-token-merge\n        uses: actions/create-github-app-token@v1\n        with:\n          app-id: ${{ secrets.APP_ID_APPROVE_AND_MERGE }}\n          private-key: ${{ secrets.APP_KEY_APPROVE_AND_MERGE }}\n\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Get latest dates in changelog\n        run: |\n          DATES=$(grep -E '^## [0-9]{4}-[0-9]{2}-[0-9]{2}' CHANGELOG.md | head -n 2 | awk '{print $2}')\n\n          LATEST_DATE=$(echo \"$DATES\" | sed -n '1p')\n          SECOND_LATEST_DATE=$(echo \"$DATES\" | sed -n '2p')\n          TODAY=$(date -u +%Y-%m-%d)\n\n          echo \"TODAY=$TODAY\" >> $GITHUB_ENV\n          if [[ \"$LATEST_DATE\" == \"$TODAY\" ]]; then\n            echo \"LATEST_DATE=$SECOND_LATEST_DATE\" >> $GITHUB_ENV\n          else\n            echo \"LATEST_DATE=$LATEST_DATE\" >> $GITHUB_ENV\n          fi\n\n      - name: Get categorized pull requests\n        id: get-categorized-prs\n        uses: actions/github-script@v7\n        with:\n          script: |\n            async function main() {\n              const fs = require('fs').promises;\n              const path = require('path');\n\n              const configPath = path.resolve(process.env.CONFIG_PATH);\n              const fileContent = await fs.readFile(configPath, 'utf-8');\n              const changelogConfig = JSON.parse(fileContent);\n\n              const categorizedPRs = changelogConfig.map(obj => ({\n                ...obj,\n                notes: [],\n                subCategories: obj.subCategories ?? (\n                  obj.labels.includes(\"update script\") ? [\n                    { title: \"🐞 Bug Fixes\", labels: [\"bugfix\"], notes: [] },\n                    { title: \"✨ New Features\", labels: [\"feature\"], notes: [] },\n                    { title: \"💥 Breaking Changes\", labels: [\"breaking change\"], notes: [] },\n                    { title: \"🔧 Refactor\", labels: [\"refactor\"], notes: [] },\n                  ] :\n                  obj.labels.includes(\"maintenance\") ? [\n                    { title: \"🐞 Bug Fixes\", labels: [\"bugfix\"], notes: [] },\n                    { title: \"✨ New Features\", labels: [\"feature\"], notes: [] },\n                    { title: \"💥 Breaking Changes\", labels: [\"breaking change\"], notes: [] },\n                    { title: \"📡 API\", labels: [\"api\"], notes: [] },\n                    { title: \"Github\", labels: [\"github\"], notes: [] },\n                    { title: \"📝 Documentation\", labels: [\"maintenance\"], notes: [] },\n                    { title: \"🔧 Refactor\", labels: [\"refactor\"], notes: [] }\n                  ] :\n                  obj.labels.includes(\"website\") ? [\n                    { title: \"🐞 Bug Fixes\", labels: [\"bugfix\"], notes: [] },\n                    { title: \"✨ New Features\", labels: [\"feature\"], notes: [] },\n                    { title: \"💥 Breaking Changes\", labels: [\"breaking change\"], notes: [] },\n                    { title: \"Script Information\", labels: [\"json\"], notes: [] }\n                  ] : []\n                )\n              }));\n\n              const latestDateInChangelog = new Date(process.env.LATEST_DATE);\n              latestDateInChangelog.setUTCHours(23, 59, 59, 999);\n\n              const { data: pulls } = await github.rest.pulls.list({\n                owner: context.repo.owner,\n                repo: \"ProxmoxVE\",\n                base: \"main\",\n                state: \"closed\",\n                sort: \"updated\",\n                direction: \"desc\",\n                per_page: 100,\n              });\n\n              const filteredPRs = pulls.filter(pr =>\n                pr.merged_at &&\n                new Date(pr.merged_at) > latestDateInChangelog &&\n                !pr.labels.some(label =>\n                  [\"invalid\", \"wontdo\", process.env.AUTOMATED_PR_LABEL].includes(label.name.toLowerCase())\n                )\n              );\n\n              for (const pr of filteredPRs) {\n                const prLabels = pr.labels.map(label => label.name.toLowerCase());\n                if (pr.user.login.includes(\"push-app-to-main[bot]\")) {\n\n                  const scriptName = pr.title;\n                    try {\n                      const { data: relatedIssues } = await github.rest.issues.listForRepo({\n                        owner: context.repo.owner,\n                        repo: \"ProxmoxVED\",\n                        state: \"all\",\n                        labels: [\"Started Migration To ProxmoxVE\"]\n                      });\n\n                      const matchingIssue = relatedIssues.find(issue =>\n                        issue.title.toLowerCase().includes(scriptName.toLowerCase())\n                      );\n\n                      if (matchingIssue) {\n                        const issueAuthor = matchingIssue.user.login;\n                        const issueAuthorUrl = `https://github.com/${issueAuthor}`;\n                        prNote = `- ${pr.title} [@${issueAuthor}](${issueAuthorUrl}) ([#${pr.number}](${pr.html_url}))`;\n                      }\n                      else {\n                        prNote = `- ${pr.title} ([#${pr.number}](${pr.html_url}))`;\n                      }\n                    } catch (error) {\n                      console.error(`Error fetching related issues: ${error}`);\n                      prNote = `- ${pr.title} ([#${pr.number}](${pr.html_url}))`;\n                    }\n                }else{\n                  prNote = `- ${pr.title} [@${pr.user.login}](https://github.com/${pr.user.login}) ([#${pr.number}](${pr.html_url}))`;\n                }\n\n\n                if (prLabels.includes(\"new script\")) {\n                  const newScriptCategory = categorizedPRs.find(category =>\n                  category.title === \"New Scripts\" || category.labels.includes(\"new script\"));\n                  if (newScriptCategory) {\n                  newScriptCategory.notes.push(prNote);\n                  }\n                } else {\n\n                  let categorized = false;\n                  const priorityCategories = categorizedPRs.slice();\n                  \n                  // Priority order for content-type labels (highest priority first)\n                  const subCategoryPriority = [\"breaking change\", \"bugfix\", \"feature\", \"refactor\"];\n                  \n                  for (const category of priorityCategories) {\n                  if (categorized) break;\n                  if (category.labels.some(label => prLabels.includes(label))) {\n                    if (category.subCategories && category.subCategories.length > 0) {\n                    // Find subcategory by priority order instead of first match\n                    let subCategory = null;\n                    for (const priorityLabel of subCategoryPriority) {\n                      if (prLabels.includes(priorityLabel)) {\n                        subCategory = category.subCategories.find(sub =>\n                          sub.labels.includes(priorityLabel)\n                        );\n                        if (subCategory) break;\n                      }\n                    }\n                    \n                    // Fallback: check for any other subcategory match (api, github, json, etc.)\n                    if (!subCategory) {\n                      subCategory = category.subCategories.find(sub =>\n                        sub.labels.some(label => prLabels.includes(label))\n                      );\n                    }\n\n                    if (subCategory) {\n                      subCategory.notes.push(prNote);\n                    } else {\n                      category.notes.push(prNote);\n                    }\n                    } else {\n                    category.notes.push(prNote);\n                    }\n                    categorized = true;\n                  }\n                  }\n\n                  // Fallback: Add to Uncategorized if no category matched\n                  if (!categorized) {\n                    const uncategorized = categorizedPRs.find(category =>\n                      category.title.includes(\"Uncategorized\") || category.labels.includes(\"needs triage\"));\n                    if (uncategorized) {\n                      uncategorized.notes.push(prNote);\n                    }\n                  }\n                }\n\n              }\n\n              return categorizedPRs;\n            }\n\n            return await main();\n\n      - name: Update CHANGELOG.md\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const fs = require('fs').promises;\n            const path = require('path');\n\n            const today = process.env.TODAY;\n            const latestDateInChangelog = process.env.LATEST_DATE;\n            const changelogPath = path.resolve('CHANGELOG.md');\n            const categorizedPRs = ${{ steps.get-categorized-prs.outputs.result }};\n\n            console.log(JSON.stringify(categorizedPRs, null, 2));\n\n\n            let newReleaseNotes = `## ${today}\\n\\n`;\n            for (const { title, notes, subCategories } of categorizedPRs) {\n              const hasSubcategories = subCategories && subCategories.length > 0;\n              const hasMainNotes = notes.length > 0;\n              const hasSubNotes = hasSubcategories && subCategories.some(sub => sub.notes && sub.notes.length > 0);\n\n              if (hasMainNotes || hasSubNotes) {\n                newReleaseNotes += `### ${title}\\n\\n`;\n              }\n\n              if (hasMainNotes) {\n                newReleaseNotes += `  ${notes.join(\"\\n\")}\\n\\n`;\n              }\n              if (hasSubcategories) {\n                for (const { title: subTitle, notes: subNotes } of subCategories) {\n                  if (subNotes && subNotes.length > 0) {\n                    newReleaseNotes += `  - #### ${subTitle}\\n\\n`; \n                    newReleaseNotes += `    ${subNotes.join(\"\\n    \")}\\n\\n`; \n                  }\n                }\n              }\n            }        \n            const changelogContent = await fs.readFile(changelogPath, 'utf-8');\n            const changelogIncludesTodaysReleaseNotes = changelogContent.includes(`\\n## ${today}`);\n\n            const regex = changelogIncludesTodaysReleaseNotes \n              ? new RegExp(`## ${today}.*(?=## ${latestDateInChangelog})`, \"gs\") \n              : new RegExp(`(?=## ${latestDateInChangelog})`, \"gs\");\n\n            const newChangelogContent = changelogContent.replace(regex, newReleaseNotes);\n            await fs.writeFile(changelogPath, newChangelogContent);\n\n      - name: Check for changes\n        id: verify-diff\n        run: |\n          git diff --quiet . || echo \"changed=true\" >> $GITHUB_ENV\n\n      - name: Commit and push changes\n        if: env.changed == 'true'\n        run: |\n          git config --global user.name \"github-actions[bot]\"\n          git config --global user.email \"github-actions[bot]@users.noreply.github.com\"\n          git add CHANGELOG.md\n          git commit -m \"Update CHANGELOG.md\"\n          git checkout -b $BRANCH_NAME || git checkout $BRANCH_NAME\n          git push origin $BRANCH_NAME --force\n\n      - name: Create pull request if not exists\n        if: env.changed == 'true'\n        env:\n          GH_TOKEN: ${{ steps.generate-token.outputs.token }}\n        run: |\n          PR_EXISTS=$(gh pr list --head \"${BRANCH_NAME}\" --json number --jq '.[].number')\n          if [ -z \"$PR_EXISTS\" ]; then\n            gh pr create --title \"[Github Action] Update CHANGELOG.md\" \\\n                         --body \"This PR is auto-generated by a Github Action to update the CHANGELOG.md file.\" \\\n                         --head $BRANCH_NAME \\\n                         --base main \\\n                         --label \"$AUTOMATED_PR_LABEL\"\n          fi\n\n      - name: Approve pull request\n        if: env.changed == 'true'\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          PR_NUMBER=$(gh pr list --head \"${BRANCH_NAME}\" --json number --jq '.[].number')\n          if [ -n \"$PR_NUMBER\" ]; then\n            gh pr review $PR_NUMBER --approve\n          fi\n\n      - name: Approve pull request and merge\n        if: env.changed == 'true'\n        env:\n          GH_TOKEN: ${{ steps.generate-token-merge.outputs.token }}\n        run: |\n          git config --global user.name \"github-actions-automege[bot]\"\n          git config --global user.email \"github-actions-automege[bot]@users.noreply.github.com\"\n          PR_NUMBER=$(gh pr list --head \"${BRANCH_NAME}\" --json number --jq '.[].number')\n          if [ -n \"$PR_NUMBER\" ]; then\n            gh pr review $PR_NUMBER --approve\n            gh pr merge $PR_NUMBER --squash --admin\n          fi\n"
  },
  {
    "path": ".github/workflows/check-node-versions.yml",
    "content": "name: Check Node.js Version Drift\n\non:\n  workflow_dispatch:\n  schedule:\n    # Runs weekly on Monday at 06:00 UTC\n    - cron: \"0 6 * * 1\"\n\npermissions:\n  contents: read\n  issues: write\n\njobs:\n  check-node-versions:\n    if: github.repository == 'community-scripts/ProxmoxVE'\n    runs-on: coolify-runner\n\n    steps:\n      - name: Checkout Repository\n        uses: actions/checkout@v4\n        with:\n          ref: main\n\n      - name: Install dependencies\n        run: |\n          sudo apt-get update -qq\n          sudo apt-get install -y -qq jq curl > /dev/null 2>&1\n\n      - name: Check upstream Node.js versions\n        id: check\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          set -euo pipefail\n\n          echo \"================================================\"\n          echo \"  Checking Node.js version drift in install scripts\"\n          echo \"================================================\"\n\n          # Alpine version -> Node major cache (populated on demand)\n          declare -A ALPINE_NODE_CACHE\n\n          # Resolve Node.js major version from Alpine package registry\n          # Usage: resolve_alpine_node \"3.21\" => sets REPLY to major version (e.g. \"22\")\n          resolve_alpine_node() {\n            local alpine_ver=\"$1\"\n            if [[ -n \"${ALPINE_NODE_CACHE[$alpine_ver]+x}\" ]]; then\n              REPLY=\"${ALPINE_NODE_CACHE[$alpine_ver]}\"\n              return\n            fi\n\n            local url=\"https://pkgs.alpinelinux.org/package/v${alpine_ver}/main/x86_64/nodejs\"\n            local page\n            page=$(curl -sf \"$url\" 2>/dev/null || echo \"\")\n            local full_ver=\"\"\n            if [[ -n \"$page\" ]]; then\n              # Parse: \"Version | 24.13.0-r1\" or similar table row\n              full_ver=$(echo \"$page\" | grep -oP 'Version\\s*\\|\\s*\\K[0-9]+\\.[0-9]+\\.[0-9]+' | head -1 || echo \"\")\n              if [[ -z \"$full_ver\" ]]; then\n                # Fallback: look for version pattern after \"Version\"\n                full_ver=$(echo \"$page\" | grep -oP '(?<=Version</td><td>)[0-9]+\\.[0-9]+\\.[0-9]+' | head -1 || echo \"\")\n              fi\n            fi\n\n            local major=\"\"\n            if [[ -n \"$full_ver\" ]]; then\n              major=\"${full_ver%%.*}\"\n            fi\n\n            ALPINE_NODE_CACHE[$alpine_ver]=\"$major\"\n            REPLY=\"$major\"\n          }\n\n          # Extract Node major from a Dockerfile content\n          # Sets: DF_NODE_MAJOR, DF_SOURCE (description of where we found it)\n          extract_dockerfile_node() {\n            local content=\"$1\"\n            DF_NODE_MAJOR=\"\"\n            DF_SOURCE=\"\"\n\n            # 1) FROM node:XX (e.g. node:24-alpine, node:22.9.0-bookworm-slim, node:20)\n            local node_from\n            node_from=$(echo \"$content\" | grep -oP '(?i)FROM\\s+(--platform=[^\\s]+\\s+)?node:\\K[0-9]+' | head -1 || echo \"\")\n            if [[ -n \"$node_from\" ]]; then\n              DF_NODE_MAJOR=\"$node_from\"\n              DF_SOURCE=\"FROM node:${node_from}\"\n              return\n            fi\n\n            # 2) nodesource/setup_XX.x\n            local nodesource\n            nodesource=$(echo \"$content\" | grep -oP 'nodesource/setup_\\K[0-9]+' | head -1 || echo \"\")\n            if [[ -n \"$nodesource\" ]]; then\n              DF_NODE_MAJOR=\"$nodesource\"\n              DF_SOURCE=\"nodesource/setup_${nodesource}.x\"\n              return\n            fi\n\n            # 3) FROM alpine:X.Y — resolve via Alpine packages\n            local alpine_ver\n            alpine_ver=$(echo \"$content\" | grep -oP '(?i)FROM\\s+(--platform=[^\\s]+\\s+)?alpine:\\K[0-9]+\\.[0-9]+' | head -1 || echo \"\")\n            if [[ -n \"$alpine_ver\" ]]; then\n              resolve_alpine_node \"$alpine_ver\"\n              if [[ -n \"$REPLY\" ]]; then\n                DF_NODE_MAJOR=\"$REPLY\"\n                DF_SOURCE=\"alpine:${alpine_ver} (pkg: nodejs ${DF_NODE_MAJOR})\"\n                return\n              fi\n            fi\n          }\n\n          # Extract Node major from engines.node in package.json\n          # Sets: ENGINES_NODE_RAW (raw string), ENGINES_MIN_MAJOR, ENGINES_IS_MINIMUM\n          extract_engines_node() {\n            local content=\"$1\"\n            ENGINES_NODE_RAW=\"\"\n            ENGINES_MIN_MAJOR=\"\"\n            ENGINES_IS_MINIMUM=\"false\"\n\n            ENGINES_NODE_RAW=$(echo \"$content\" | jq -r '.engines.node // empty' 2>/dev/null || echo \"\")\n            if [[ -z \"$ENGINES_NODE_RAW\" ]]; then\n              return\n            fi\n\n            # Detect if constraint is a minimum (>=, ^) vs exact pinning\n            if [[ \"$ENGINES_NODE_RAW\" =~ ^(\\>=|\\^|\\~) ]]; then\n              ENGINES_IS_MINIMUM=\"true\"\n            fi\n\n            # Extract the first number (major) from the constraint\n            # Handles: \">=24.13.1\", \"^22\", \">=18.0.0\", \">=18.15.0 <19 || ^20\", etc.\n            ENGINES_MIN_MAJOR=$(echo \"$ENGINES_NODE_RAW\" | grep -oP '\\d+' | head -1 || echo \"\")\n          }\n\n          # Check if our_version satisfies an engines.node constraint\n          # Returns 0 if satisfied, 1 if not\n          # Usage: version_satisfies_engines \"22\" \">=18.0.0\" \"true\"\n          version_satisfies_engines() {\n            local our=\"$1\"\n            local min_major=\"$2\"\n            local is_minimum=\"$3\"\n\n            if [[ -z \"$min_major\" || -z \"$our\" ]]; then\n              return 1\n            fi\n\n            if [[ \"$is_minimum\" == \"true\" ]]; then\n              # >= or ^ constraint: our version must be >= min_major\n              if [[ \"$our\" -ge \"$min_major\" ]]; then\n                return 0\n              fi\n            fi\n            return 1\n          }\n\n          # Search for files in subdirectories via GitHub API tree\n          # Usage: find_repo_file \"owner/repo\" \"branch\" \"filename\" => sets REPLY to raw URL or empty\n          find_repo_file() {\n            local repo=\"$1\"\n            local branch=\"$2\"\n            local filename=\"$3\"\n            REPLY=\"\"\n\n            # Try root first (fast)\n            local root_url=\"https://raw.githubusercontent.com/${repo}/${branch}/${filename}\"\n            if curl -sfI \"$root_url\" >/dev/null 2>&1; then\n              REPLY=\"$root_url\"\n              return\n            fi\n\n            # Search via GitHub API tree (recursive)\n            local tree_url=\"https://api.github.com/repos/${repo}/git/trees/${branch}?recursive=1\"\n            local tree_json\n            tree_json=$(curl -sf -H \"Authorization: token $GH_TOKEN\" \"$tree_url\" 2>/dev/null || echo \"\")\n            if [[ -z \"$tree_json\" ]]; then\n              return\n            fi\n\n            # Find first matching path (prefer shorter/root-level paths)\n            local match_path\n            match_path=$(echo \"$tree_json\" | jq -r --arg fn \"$filename\" \\\n              '.tree[]? | select(.path | endswith(\"/\" + $fn) or . == $fn) | .path' 2>/dev/null \\\n              | sort | head -1 || echo \"\")\n\n            if [[ -n \"$match_path\" ]]; then\n              REPLY=\"https://raw.githubusercontent.com/${repo}/${branch}/${match_path}\"\n            fi\n          }\n\n          # Extract Node major from .nvmrc or .node-version\n          # Sets: NVMRC_NODE_MAJOR\n          extract_nvmrc_node() {\n            local content=\"$1\"\n            NVMRC_NODE_MAJOR=\"\"\n            # .nvmrc/.node-version typically has: \"v22.9.0\", \"22\", \"lts/iron\", etc.\n            local ver\n            ver=$(echo \"$content\" | tr -d '[:space:]' | grep -oP '^v?\\K[0-9]+' | head -1 || echo \"\")\n            NVMRC_NODE_MAJOR=\"$ver\"\n          }\n\n          # Collect results\n          declare -a issue_scripts=()\n          declare -a report_lines=()\n          total=0\n          checked=0\n          drift_count=0\n\n          for script in install/*-install.sh; do\n            [[ ! -f \"$script\" ]] && continue\n            if ! grep -q 'setup_nodejs' \"$script\"; then\n              continue\n            fi\n\n            total=$((total + 1))\n            slug=$(basename \"$script\" | sed 's/-install\\.sh$//')\n\n            # Extract Source URL (GitHub only) from the \"# Source:\" line\n            # Supports both:\n            #   # Source: https://github.com/owner/repo\n            #   # Source: https://example.com | Github: https://github.com/owner/repo\n            # NOTE: Must filter for \"# Source:\" line first to avoid matching the License URL\n            source_url=$(head -20 \"$script\" | grep -i '# Source:' | grep -oP 'https://github\\.com/[^\\s|]+' | head -1 || echo \"\")\n            if [[ -z \"$source_url\" ]]; then\n              report_lines+=(\"| \\`$slug\\` | — | — | — | — | ⏭️ No GitHub source |\")\n              continue\n            fi\n\n            repo=$(echo \"$source_url\" | sed -E 's|https://github\\.com/||; s|/$||; s|\\.git$||')\n            if [[ -z \"$repo\" || \"$repo\" != */* ]]; then\n              report_lines+=(\"| \\`$slug\\` | — | — | — | — | ⏭️ Invalid repo |\")\n              continue\n            fi\n\n            checked=$((checked + 1))\n\n            # Extract our NODE_VERSION\n            our_version=$(grep -oP 'NODE_VERSION=\"(\\d+)\"' \"$script\" | head -1 | grep -oP '\\d+' || echo \"\")\n            if [[ -z \"$our_version\" ]]; then\n              if grep -q 'NODE_VERSION=\\$(' \"$script\"; then\n                our_version=\"dynamic\"\n              else\n                our_version=\"unset\"\n              fi\n            fi\n\n            # Determine default branch via GitHub API (fast, single call)\n            detected_branch=\"\"\n            api_default=$(curl -sf -H \"Authorization: token $GH_TOKEN\" \\\n              \"https://api.github.com/repos/${repo}\" 2>/dev/null \\\n              | jq -r '.default_branch // empty' 2>/dev/null || echo \"\")\n            if [[ -n \"$api_default\" ]]; then\n              detected_branch=\"$api_default\"\n            else\n              detected_branch=\"main\"\n            fi\n\n            # Fetch upstream Dockerfile (root + subdirectories)\n            df_content=\"\"\n            find_repo_file \"$repo\" \"$detected_branch\" \"Dockerfile\"\n            if [[ -n \"$REPLY\" ]]; then\n              df_content=$(curl -sf \"$REPLY\" 2>/dev/null || echo \"\")\n            fi\n\n            DF_NODE_MAJOR=\"\"\n            DF_SOURCE=\"\"\n            if [[ -n \"$df_content\" ]]; then\n              extract_dockerfile_node \"$df_content\"\n            fi\n\n            # Fetch upstream package.json (root + subdirectories)\n            pkg_content=\"\"\n            find_repo_file \"$repo\" \"$detected_branch\" \"package.json\"\n            if [[ -n \"$REPLY\" ]]; then\n              pkg_content=$(curl -sf \"$REPLY\" 2>/dev/null || echo \"\")\n            fi\n\n            ENGINES_NODE_RAW=\"\"\n            ENGINES_MIN_MAJOR=\"\"\n            ENGINES_IS_MINIMUM=\"false\"\n            if [[ -n \"$pkg_content\" ]]; then\n              extract_engines_node \"$pkg_content\"\n            fi\n\n            # Fallback: check .nvmrc or .node-version\n            NVMRC_NODE_MAJOR=\"\"\n            if [[ -z \"$DF_NODE_MAJOR\" && -z \"$ENGINES_MIN_MAJOR\" ]]; then\n              for nvmfile in .nvmrc .node-version; do\n                find_repo_file \"$repo\" \"$detected_branch\" \"$nvmfile\"\n                if [[ -n \"$REPLY\" ]]; then\n                  nvmrc_content=$(curl -sf \"$REPLY\" 2>/dev/null || echo \"\")\n                  if [[ -n \"$nvmrc_content\" ]]; then\n                    extract_nvmrc_node \"$nvmrc_content\"\n                    [[ -n \"$NVMRC_NODE_MAJOR\" ]] && break\n                  fi\n                fi\n              done\n            fi\n\n            # Determine upstream recommended major version\n            upstream_major=\"\"\n            upstream_hint=\"\"\n\n            if [[ -n \"$DF_NODE_MAJOR\" ]]; then\n              upstream_major=\"$DF_NODE_MAJOR\"\n              upstream_hint=\"$DF_SOURCE\"\n            elif [[ -n \"$ENGINES_MIN_MAJOR\" ]]; then\n              upstream_major=\"$ENGINES_MIN_MAJOR\"\n              upstream_hint=\"engines: $ENGINES_NODE_RAW\"\n            elif [[ -n \"$NVMRC_NODE_MAJOR\" ]]; then\n              upstream_major=\"$NVMRC_NODE_MAJOR\"\n              upstream_hint=\".nvmrc/.node-version\"\n            fi\n\n            # Build display values\n            engines_display=\"${ENGINES_NODE_RAW:-—}\"\n            dockerfile_display=\"${DF_SOURCE:-—}\"\n\n            # Compare\n            status=\"✅\"\n            if [[ \"$our_version\" == \"dynamic\" ]]; then\n              status=\"🔄 Dynamic\"\n            elif [[ \"$our_version\" == \"unset\" ]]; then\n              if [[ -n \"$upstream_major\" ]]; then\n                status=\"⚠️ NODE_VERSION not set (upstream=$upstream_major via $upstream_hint)\"\n              else\n                status=\"⚠️ NODE_VERSION not set (no upstream info found)\"\n              fi\n              issue_scripts+=(\"$slug|$our_version|$upstream_major|$upstream_hint|$repo\")\n              drift_count=$((drift_count + 1))\n            elif [[ -n \"$upstream_major\" && \"$our_version\" != \"$upstream_major\" ]]; then\n              # Check if engines.node is a minimum constraint that our version satisfies\n              if [[ -z \"$DF_NODE_MAJOR\" && \"$ENGINES_IS_MINIMUM\" == \"true\" ]] && \\\n                 version_satisfies_engines \"$our_version\" \"$ENGINES_MIN_MAJOR\" \"$ENGINES_IS_MINIMUM\"; then\n                status=\"✅ (engines: $ENGINES_NODE_RAW — ours: $our_version satisfies)\"\n              else\n                status=\"🔸 Drift → upstream=$upstream_major ($upstream_hint)\"\n                issue_scripts+=(\"$slug|$our_version|$upstream_major|$upstream_hint|$repo\")\n                drift_count=$((drift_count + 1))\n              fi\n            fi\n\n            report_lines+=(\"| \\`$slug\\` | $our_version | $engines_display | $dockerfile_display | [$repo](https://github.com/$repo) | $status |\")\n\n            # Rate-limit to avoid GitHub secondary rate limits\n            sleep 0.3\n\n          done\n\n          # Print summary\n          echo \"\"\n          echo \"=========================================\"\n          echo \"  Total scripts with setup_nodejs: $total\"\n          echo \"  Checked (with GitHub source):    $checked\"\n          echo \"  Version drift detected:          $drift_count\"\n          echo \"=========================================\"\n\n          # Export\n          {\n            echo \"drift_count=$drift_count\"\n            echo \"total=$total\"\n            echo \"checked=$checked\"\n          } >> \"$GITHUB_OUTPUT\"\n\n          # Save issue details for next step\n          printf '%s\\n' \"${issue_scripts[@]}\" > /tmp/drift_scripts.txt 2>/dev/null || touch /tmp/drift_scripts.txt\n\n          # Save full report\n          {\n            echo \"## Node.js Version Drift Report\"\n            echo \"\"\n            echo \"**Generated:** $(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n            echo \"**Scripts checked:** $total | **With GitHub source:** $checked | **Drift detected:** $drift_count\"\n            echo \"\"\n            echo \"| Script | Our Version | engines.node | Dockerfile | Upstream Repo | Status |\"\n            echo \"|--------|-------------|-------------|------------|---------------|--------|\"\n            printf '%s\\n' \"${report_lines[@]}\" | sort\n          } > /tmp/drift_report.md\n\n          cat /tmp/drift_report.md\n\n      - name: Create or update summary issue\n        if: steps.check.outputs.drift_count != '0'\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          set -euo pipefail\n\n          TITLE=\"[Automated] Node.js Version Drift Report\"\n          DATE=$(date -u +%Y-%m-%d)\n          DRIFT_COUNT=\"${{ steps.check.outputs.drift_count }}\"\n          TOTAL=\"${{ steps.check.outputs.total }}\"\n          CHECKED=\"${{ steps.check.outputs.checked }}\"\n\n          # Build checklist from drift data\n          CHECKLIST=\"\"\n          while IFS='|' read -r slug our_version upstream_major upstream_hint repo; do\n            [[ -z \"$slug\" ]] && continue\n            CHECKLIST+=\"- [ ] **\\`${slug}\\`** — ours: \\`${our_version}\\` → upstream: \\`${upstream_major}\\` (${upstream_hint}) — [repo](https://github.com/${repo})\"$'\\n'\n          done < /tmp/drift_scripts.txt\n\n          # Build full report table\n          REPORT=$(cat /tmp/drift_report.md)\n\n          BODY=$(cat <<ISSUE_EOF\n          ## Node.js Version Drift Report — ${DATE}\n\n          **${DRIFT_COUNT}** script(s) with version drift detected (out of ${CHECKED} checked / ${TOTAL} total).\n\n          ### Scripts requiring investigation\n\n          ${CHECKLIST}\n\n          ### How to resolve\n\n          1. Check upstream Dockerfile / package.json to confirm the required Node.js version\n          2. Test the script with the new Node version\n          3. Update \\`NODE_VERSION\\` in \\`install/<slug>-install.sh\\`\n          4. Update \\`NODE_VERSION\\` in \\`ct/<slug>.sh\\` (update section) if applicable\n          5. Check off the item above once done\n\n          <details>\n          <summary>Full report</summary>\n\n          ${REPORT}\n\n          </details>\n\n          ---\n          *This issue is automatically created/updated weekly by the Node.js version drift check workflow.*\n          *Last updated: ${DATE}*\n          ISSUE_EOF\n          )\n\n          # Check if a matching open issue already exists\n          EXISTING=$(gh issue list --state open --label \"automated,dependencies\" --search \"\\\"[Automated] Node.js Version Drift Report\\\"\" --json number --jq '.[0].number // empty' 2>/dev/null || echo \"\")\n\n          if [[ -n \"$EXISTING\" ]]; then\n            gh issue edit \"$EXISTING\" --body \"$BODY\"\n            echo \"Updated existing issue #$EXISTING\"\n          else\n            gh issue create \\\n              --title \"$TITLE\" \\\n              --body \"$BODY\" \\\n              --label \"automated,dependencies\"\n            echo \"Created new summary issue\"\n          fi\n\n      - name: Close issue if no drift\n        if: steps.check.outputs.drift_count == '0'\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          EXISTING=$(gh issue list --state open --label \"automated,dependencies\" --search \"\\\"[Automated] Node.js Version Drift Report\\\"\" --json number --jq '.[0].number // empty' 2>/dev/null || echo \"\")\n          if [[ -n \"$EXISTING\" ]]; then\n            gh issue close \"$EXISTING\" --comment \"All Node.js versions are in sync with upstream. Closing automatically.\"\n            echo \"Closed issue #$EXISTING\"\n          fi\n"
  },
  {
    "path": ".github/workflows/close-new-script-prs.yml",
    "content": "name: Close Unauthorized New Script PRs\n\non:\n  pull_request_target:\n    branches: [\"main\"]\n    types: [opened, labeled]\n\njobs:\n  check-new-script:\n    if: github.repository == 'community-scripts/ProxmoxVE'\n    runs-on: self-hosted\n    permissions:\n      pull-requests: write\n      contents: read\n    steps:\n      - name: Close PR if unauthorized new script submission\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const pr = context.payload.pull_request;\n            const prNumber = pr.number;\n            const author = pr.user.login;\n            const authorType = pr.user.type; // \"User\" or \"Bot\"\n            const owner = context.repo.owner;\n            const repo = context.repo.repo;\n\n            // --- Only act on PRs with the \"new script\" label ---\n            const labels = pr.labels.map(l => l.name);\n            if (!labels.includes(\"new script\")) {\n              core.info(`PR #${prNumber} does not have \"new script\" label — skipping.`);\n              return;\n            }\n\n            // --- Allow our bots ---\n            const allowedBots = [\n              \"push-app-to-main[bot]\",\n              \"push-app-to-main\",\n            ];\n\n            if (allowedBots.includes(author)) {\n              core.info(`PR #${prNumber} by allowed bot \"${author}\" — skipping.`);\n              return;\n            }\n\n            // --- Check if author is a member of the contributor team ---\n            const teamSlug = \"contributor\";\n            let isMember = false;\n\n            try {\n              const { status } = await github.rest.teams.getMembershipForUserInOrg({\n                org: owner,\n                team_slug: teamSlug,\n                username: author,\n              });\n              // status 200 means the user is a member (active or pending)\n              isMember = true;\n            } catch (error) {\n              if (error.status === 404) {\n                isMember = false;\n              } else {\n                core.warning(`Could not check team membership for ${author}: ${error.message}`);\n                // Fallback: check org membership\n                try {\n                  await github.rest.orgs.checkMembershipForUser({\n                    org: owner,\n                    username: author,\n                  });\n                  isMember = true;\n                } catch {\n                  isMember = false;\n                }\n              }\n            }\n\n            if (isMember) {\n              core.info(`PR #${prNumber} by contributor \"${author}\" — skipping.`);\n              return;\n            }\n\n            // --- Unauthorized: close the PR with a comment ---\n            core.info(`Closing PR #${prNumber} by \"${author}\" — not a contributor or allowed bot.`);\n\n            const comment = [\n              `👋 Hi @${author},`,\n              ``,\n              `Thank you for your interest in contributing a new script!`,\n              ``,\n              `However, **new scripts must first be submitted to our development repository** for testing and review before they can be merged here.`,\n              ``,\n              `> 🛑 New scripts must be submitted to [**ProxmoxVED**](https://github.com/community-scripts/ProxmoxVED) for testing.`,\n              `> PRs without prior testing will be closed.`,\n              ``,\n              `Please open your PR at **https://github.com/community-scripts/ProxmoxVED** instead.`,\n              `Once your script has been tested and approved there, it will be pushed to this repository automatically.`,\n              ``,\n              `This PR will now be closed. Thank you for understanding! 🙏`,\n            ].join(\"\\n\");\n\n            await github.rest.issues.createComment({\n              owner,\n              repo,\n              issue_number: prNumber,\n              body: comment,\n            });\n\n            await github.rest.pulls.update({\n              owner,\n              repo,\n              pull_number: prNumber,\n              state: \"closed\",\n            });\n\n            // Add a label to indicate why it was closed\n            await github.rest.issues.addLabels({\n              owner,\n              repo,\n              issue_number: prNumber,\n              labels: [\"not a script issue\"],\n            });\n"
  },
  {
    "path": ".github/workflows/close-tteck-issues.yaml",
    "content": "name: Auto-Close tteck Issues\non:\n  issues:\n    types: [opened]\n\njobs:\n  close_tteck_issues:\n    if: github.repository == 'community-scripts/ProxmoxVE'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Auto-close if tteck script detected\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const issue = context.payload.issue;\n            const content = `${issue.title}\\n${issue.body}`;\n            const issueNumber = issue.number;\n\n            // Check for tteck script mention\n            if (content.includes(\"tteck\") || content.includes(\"tteck/Proxmox\")) {\n              const message = `Hello, it looks like you are referencing the **old tteck repo**.\n\n            This repository is no longer used for active scripts.\n            **Please update your bookmarks** and use: [https://helper-scripts.com](https://helper-scripts.com)\n                    \n            Also make sure your Bash command starts with:\n            \\`\\`\\`bash\n            bash <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/...)\n            \\`\\`\\`\n            \n            This issue is being closed automatically.`;\n\n              await github.rest.issues.createComment({\n                ...context.repo,\n                issue_number: issueNumber,\n                body: message\n              });\n\n              // Optionally apply a label like \"not planned\"\n              await github.rest.issues.addLabels({\n                ...context.repo,\n                issue_number: issueNumber,\n                labels: [\"not planned\"]\n              });\n\n              // Close the issue\n              await github.rest.issues.update({\n                ...context.repo,\n                issue_number: issueNumber,\n                state: \"closed\"\n              });\n            }\n"
  },
  {
    "path": ".github/workflows/close_issue_in_dev.yaml",
    "content": "name: Close Matching Issue on PR Merge\non:\n  pull_request:\n    types:\n      - closed\njobs:\n  close_issue:\n    if: github.event.pull_request.merged == true && github.repository == 'community-scripts/ProxmoxVE'\n    runs-on: self-hosted\n\n    steps:\n      - name: Checkout target repo (merge commit)\n        uses: actions/checkout@v4\n        with:\n          repository: community-scripts/ProxmoxVE\n          ref: ${{ github.event.pull_request.merge_commit_sha }}\n          token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract and Process PR Title\n        id: extract_title\n        run: |\n          title=$(echo \"${{ github.event.pull_request.title }}\" | sed 's/^New Script://g' | tr '[:upper:]' '[:lower:]' | sed 's/ //g' | sed 's/-//g')\n          echo \"Processed Title: $title\"\n          echo \"title=$title\" >> $GITHUB_ENV\n\n      - name: Get slugs from merged PR\n        id: get_slugs\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          pr_files=$(gh pr view ${{ github.event.pull_request.number }} --repo community-scripts/ProxmoxVE --json files -q '.files[].path' 2>/dev/null || true)\n          slugs=\"\"\n          for path in $pr_files; do\n            [[ -f \"$path\" ]] || continue\n            if [[ \"$path\" == frontend/public/json/*.json ]]; then\n              s=$(jq -r '.slug // empty' \"$path\" 2>/dev/null)\n              [[ -n \"$s\" ]] && slugs=\"$slugs $s\"\n            elif [[ \"$path\" == ct/*.sh ]] || [[ \"$path\" == install/*.sh ]] || [[ \"$path\" == tools/*.sh ]] || [[ \"$path\" == turnkey/*.sh ]] || [[ \"$path\" == vm/*.sh ]]; then\n              base=$(basename \"$path\" .sh)\n              if [[ \"$path\" == install/* && \"$base\" == *-install ]]; then\n                s=\"${base%-install}\"\n              else\n                s=\"$base\"\n              fi\n              [[ -n \"$s\" ]] && slugs=\"$slugs $s\"\n            fi\n          done\n          slugs=$(echo $slugs | xargs -n1 | sort -u | tr '\\n' ' ')\n          if [[ -z \"$slugs\" && -n \"$title\" ]]; then\n            slugs=\"$title\"\n          fi\n          if [[ -z \"$slugs\" ]]; then\n            echo \"count=0\" >> \"$GITHUB_OUTPUT\"\n            exit 0\n          fi\n          echo \"$slugs\" > pocketbase_slugs.txt\n          echo \"count=$(echo $slugs | wc -w)\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Search for Issues with Similar Titles\n        id: find_issue\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          issues=$(gh issue list --repo community-scripts/ProxmoxVED --json number,title --jq '.[] | {number, title}')\n          \n          best_match_score=0\n          best_match_number=0\n          \n          for issue in $(echo \"$issues\" | jq -r '. | @base64'); do\n            _jq() {\n              echo ${issue} | base64 --decode | jq -r ${1}\n            }\n\n            issue_title=$(_jq '.title' | tr '[:upper:]' '[:lower:]' | sed 's/ //g' | sed 's/-//g')\n            issue_number=$(_jq '.number')\n\n            match_score=$(echo \"$title\" | grep -o \"$issue_title\" | wc -l)\n            \n            if [ \"$match_score\" -gt \"$best_match_score\" ]; then\n              best_match_score=$match_score\n              best_match_number=$issue_number\n            fi\n          done\n\n          if [ \"$best_match_number\" != \"0\" ]; then\n            echo \"issue_number=$best_match_number\" >> $GITHUB_ENV\n          else\n            echo \"No matching issue found.\"\n            exit 0\n          fi\n\n      - name: Comment on the Best-Matching Issue and Close It\n        if: env.issue_number != ''\n        env:\n          GH_TOKEN: ${{ secrets.PAT_MICHEL }}\n        run: |\n          gh issue comment $issue_number --repo community-scripts/ProxmoxVED --body \"Merged with #${{ github.event.pull_request.number }} in ProxmoxVE\"\n          gh issue close $issue_number --repo community-scripts/ProxmoxVED\n\n      - name: Set is_dev to false in PocketBase\n        if: steps.get_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          PR_URL: ${{ github.server_url }}/${{ github.repository }}/pull/${{ github.event.pull_request.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) {\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                  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('pocketbase_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            const prUrl = process.env.PR_URL || '';\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 patchRes = await request(recordsUrl + '/' + record.id, {\n                method: 'PATCH',\n                headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                body: JSON.stringify({\n                  name: record.name || record.slug,\n                  last_update_commit: prUrl,\n                  is_dev: false\n                })\n              });\n              if (!patchRes.ok) {\n                console.warn('PATCH failed for slug ' + slug + ': ' + patchRes.body);\n                continue;\n              }\n              console.log('Set is_dev=false for slug: ' + slug);\n            }\n            console.log('Done.');\n          })().catch(e => { console.error(e); process.exit(1); });\n          ENDSCRIPT\n        shell: bash\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/github-release.yml",
    "content": "name: Create Daily Release\n\non:\n  schedule:\n    - cron: '1 0 * * *'  # Runs daily at 00:01 UTC\n  workflow_dispatch:\n  \njobs:\n  create-daily-release:\n    if: github.repository == 'community-scripts/ProxmoxVE'\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Clean CHANGELOG (remove HTML header)\n        run: sed -n '/^## /,$p' CHANGELOG.md > changelog_cleaned.md\n\n      - name: Extract relevant changelog section\n        run: |\n          YESTERDAY=$(date -u --date=\"yesterday\" +%Y-%m-%d)\n          echo \"Checking for changes on: $YESTERDAY\"\n\n          # Extract the section from \"## $YESTERDAY\" until the next \"## YYYY-MM-DD\"\n          sed -n \"/^## $YESTERDAY/,/^## [0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}/p\" changelog_cleaned.md | head -n -1 > changelog_tmp_full.md\n\n          # Truncate the extracted section to 5000 characters\n          head -c 5000 changelog_tmp_full.md > changelog_tmp.md\n\n          echo \"=== Extracted Changelog ===\"\n          cat changelog_tmp.md\n          echo \"===========================\"\n\n          # Abort if no content was found\n          if [ ! -s changelog_tmp.md ]; then\n            echo \"No changes found for $YESTERDAY, skipping release.\"\n            exit 0\n          fi\n\n      - name: Create GitHub release\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          YESTERDAY=$(date -u --date=\"yesterday\" +%Y-%m-%d)\n          gh release create \"$YESTERDAY\" -t \"$YESTERDAY\" -F changelog_tmp.md\n"
  },
  {
    "path": ".github/workflows/lock-issue.yaml",
    "content": "name: Lock closed issues\n\non:\n  schedule:\n    - cron: \"0 0 * * *\"  # Run daily at midnight\n  workflow_dispatch:\n\npermissions:\n  issues: write\n  pull-requests: write\n\njobs:\n  lock:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Lock old issues and PRs\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const daysBeforeLock = 3;\n            const lockDate = new Date();\n            lockDate.setDate(lockDate.getDate() - daysBeforeLock);\n            \n            // Exclude patterns (case-insensitive)\n            const excludePatterns = [\n              /automated pr/i,\n              /\\[bot\\]/i,\n              /dependabot/i\n            ];\n            \n            // Search for closed, unlocked issues older than 3 days (paginated, oldest first)\n            let page = 1;\n            let totalLocked = 0;\n            \n            while (true) {\n              const issues = await github.rest.search.issuesAndPullRequests({\n                q: `repo:${context.repo.owner}/${context.repo.repo} is:closed is:unlocked updated:<${lockDate.toISOString().split('T')[0]}`,\n                sort: 'updated',\n                order: 'asc',\n                per_page: 100,\n                page: page\n              });\n              \n              if (issues.data.items.length === 0) break;\n              \n              console.log(`Page ${page}: ${issues.data.items.length} items (total available: ${issues.data.total_count})`);\n              \n              for (const item of issues.data.items) {\n                // Skip excluded items\n                const shouldExclude = excludePatterns.some(pattern => pattern.test(item.title));\n                if (shouldExclude) {\n                  console.log(`Skipped #${item.number}: \"${item.title}\" (matches exclude pattern)`);\n                  continue;\n                }\n                \n                try {\n                  // Lock the issue/PR silently\n                  await github.rest.issues.lock({\n                    ...context.repo,\n                    issue_number: item.number,\n                    lock_reason: 'resolved'\n                  });\n                  \n                  totalLocked++;\n                  console.log(`Locked #${item.number} (${item.pull_request ? 'PR' : 'Issue'})`);\n                } catch (error) {\n                  console.log(`Failed to lock #${item.number}: ${error.message}`);\n                }\n              }\n              \n              page++;\n              \n              // GitHub search API limit: max 10000 results (100 pages * 100) - temporarily increased\n              if (page > 100) break;\n            }\n            \n            console.log(`Total locked: ${totalLocked} issues/PRs`);\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: read\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        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            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            // author_association: OWNER = repo/org owner, MEMBER = org member (includes Contributors team)\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            // Formats (first line of comment):\n            //   /pocketbase <slug> field=value [field=value ...]    ← field updates (simple values)\n            //   /pocketbase <slug> set <field>                       ← value from code block below\n            //   /pocketbase <slug> note list|add|edit|remove ...     ← note management\n            //   /pocketbase <slug> method list                        ← list install methods\n            //   /pocketbase <slug> method <type> cpu=N ram=N hdd=N   ← edit install method resources\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            // Extract code block content from comment body (```...``` or ```lang\\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              '**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 resources:**\\n' +\n              '```\\n' +\n              '/pocketbase <slug> method list\\n' +\n              '/pocketbase <slug> method <type> hdd=10\\n' +\n              '/pocketbase <slug> method <type> cpu=4 ram=2048 hdd=20\\n' +\n              '```\\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            // ── Allowed fields and their types ─────────────────────────────────\n            // ── PocketBase: authenticate (shared by all paths) ─────────────────\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 (shared by all paths) ──────────\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            // ── Route: dispatch to subcommand handler ──────────────────────────\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 (noteMatch) {\n              // ── NOTE SUBCOMMAND (reads/writes notes_json on script record) ────\n              const noteAction  = noteMatch[1].toLowerCase();\n              const noteArgsStr = rest.substring(noteMatch[0].length).trim();\n\n              // Parse notes_json from the already-fetched script record\n              // PocketBase may return JSON fields as already-parsed objects\n              let notesArr = [];\n              try {\n                const rawNotes = record.notes_json;\n                notesArr = Array.isArray(rawNotes) ? rawNotes : JSON.parse(rawNotes || '[]');\n              } catch (e) { notesArr = []; }\n\n              // Token parser: unquoted-word OR \"quoted string\" (supports \\\" escapes)\n              function parseNoteTokens(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              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              async function patchNotesJson(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_json: JSON.stringify(arr) })\n                });\n                if (!res.ok) {\n                  await addReaction('-1');\n                  await postComment('❌ **PocketBase Bot**: Failed to update `notes_json`:\\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 = parseNoteTokens(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 patchNotesJson(notesArr);\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 = parseNoteTokens(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 patchNotesJson(notesArr);\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 = parseNoteTokens(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 patchNotesJson(notesArr);\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 (reads/writes install_methods_json on script record) ──\n              const methodArgs     = rest.replace(/^method\\s*/i, '').trim();\n              const methodListMode = !methodArgs || methodArgs.toLowerCase() === 'list';\n\n              // Parse install_methods_json from the already-fetched script record\n              // PocketBase may return JSON fields as already-parsed objects\n              let methodsArr = [];\n              try {\n                const rawMethods = record.install_methods_json;\n                methodsArr = Array.isArray(rawMethods) ? rawMethods : JSON.parse(rawMethods || '[]');\n              } catch (e) { methodsArr = []; }\n\n              function formatMethodsList(arr) {\n                if (arr.length === 0) return '*None*';\n                return arr.map(function (im, i) {\n                  const r = im.resources || {};\n                  return (i + 1) + '. **`' + (im.type || '?') + '`** — CPU: `' + (r.cpu != null ? r.cpu : '?') +\n                    '` · RAM: `' + (r.ram != null ? r.ram : '?') + ' MB` · HDD: `' + (r.hdd != null ? r.hdd : '?') + ' GB`';\n                }).join('\\n');\n              }\n\n              async function patchInstallMethodsJson(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_json: JSON.stringify(arr) })\n                });\n                if (!res.ok) {\n                  await addReaction('-1');\n                  await postComment('❌ **PocketBase Bot**: Failed to update `install_methods_json`:\\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              } else {\n                // Parse: <type> cpu=N ram=N hdd=N\n                const methodParts = methodArgs.match(/^(\\S+)\\s+(.+)$/);\n                if (!methodParts) {\n                  await addReaction('-1');\n                  await postComment(\n                    '❌ **PocketBase Bot**: Invalid `method` syntax.\\n\\n' +\n                    '**Usage:**\\n```\\n/pocketbase ' + slug + ' method list\\n/pocketbase ' + slug + ' method <type> hdd=10\\n/pocketbase ' + slug + ' method <type> cpu=4 ram=2048 hdd=20\\n```'\n                  );\n                  process.exit(0);\n                }\n                const targetType   = methodParts[1].toLowerCase();\n                const resourcesStr = methodParts[2];\n\n                // Parse resource fields (only cpu/ram/hdd allowed)\n                const RESOURCE_FIELDS = { cpu: true, ram: true, hdd: true };\n                const resourceChanges = {};\n                const rePairs = /([a-z]+)=(\\d+)/gi;\n                let m;\n                while ((m = rePairs.exec(resourcesStr)) !== null) {\n                  const key = m[1].toLowerCase();\n                  if (RESOURCE_FIELDS[key]) resourceChanges[key] = parseInt(m[2], 10);\n                }\n                if (Object.keys(resourceChanges).length === 0) {\n                  await addReaction('-1');\n                  await postComment('❌ **PocketBase Bot**: No valid resource fields found. Use `cpu=N`, `ram=N`, `hdd=N`.');\n                  process.exit(0);\n                }\n\n                // Find matching method by type name (case-insensitive)\n                const idx = methodsArr.findIndex(function (im) {\n                  return (im.type || '').toLowerCase() === targetType;\n                });\n                if (idx === -1) {\n                  await addReaction('-1');\n                  const availableTypes = methodsArr.map(function (im) { return im.type || '?'; });\n                  await postComment(\n                    '❌ **PocketBase Bot**: No install method with type `' + targetType + '` found for `' + slug + '`.\\n\\n' +\n                    '**Available types:** `' + (availableTypes.length ? availableTypes.join('`, `') : '(none)') + '`\\n\\n' +\n                    'Use `/pocketbase ' + slug + ' method list` to see all methods.'\n                  );\n                  process.exit(0);\n                }\n\n                if (!methodsArr[idx].resources) methodsArr[idx].resources = {};\n                if (resourceChanges.cpu != null) methodsArr[idx].resources.cpu = resourceChanges.cpu;\n                if (resourceChanges.ram != null) methodsArr[idx].resources.ram = resourceChanges.ram;\n                if (resourceChanges.hdd != null) methodsArr[idx].resources.hdd = resourceChanges.hdd;\n\n                await patchInstallMethodsJson(methodsArr);\n\n                const changesLines = Object.entries(resourceChanges)\n                  .map(function ([k, v]) { return '- `' + k + '` → `' + v + (k === 'ram' ? ' MB' : k === 'hdd' ? ' GB' : '') + '`'; })\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                  '*Executed by @' + actor + '*'\n                );\n              }\n\n            } else if (setMatch) {\n              // ── SET SUBCOMMAND (multi-line / HTML / special chars via 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              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 fieldsStr = rest;\n\n              // Skipped: slug, script_created/updated, created (auto), categories/\n              // install_methods/notes/type (relations), github_data/install_methods_json/\n              // notes_json (auto-generated), execute_in (select relation), last_update_commit (auto)\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                port:             'number',\n                default_user:     'nullable_string',\n                default_passwd:   'nullable_string',\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              // Field=value parser (handles quoted values and empty=null)\n              function parseFields(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 (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              const parsedFields = parseFields(fieldsStr);\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 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                '*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              config_path: data.config_path,\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/scripts/generate-app-headers.sh",
    "content": "#!/usr/bin/env bash\n\n# Function for generating Figlet headers\ngenerate_headers() {\n  local base_dir=$1\n  local target_subdir=$2\n  local search_pattern=$3\n\n  local headers_dir=\"${base_dir}/headers\"\n  mkdir -p \"$headers_dir\"\n  rm -f \"$headers_dir\"/*\n\n  # Recursive or non-recursive search\n  if [[ \"$search_pattern\" == \"**\" ]]; then\n    shopt -s globstar nullglob\n    file_list=(\"${base_dir}\"/**/*.sh)\n    shopt -u globstar\n  else\n    file_list=(\"${base_dir}\"/*.sh)\n  fi\n\n  for script in \"${file_list[@]}\"; do\n    [[ -f \"$script\" ]] || continue\n\n    app_name=$(grep -oP '^APP=\"\\K[^\"]+' \"$script\" 2>/dev/null)\n    if [[ -n \"$app_name\" ]]; then\n      output_file=\"${headers_dir}/$(basename \"${script%.*}\")\"\n      figlet_output=$(figlet -w 500 -f slant \"$app_name\")\n      if [[ -n \"$figlet_output\" ]]; then\n        echo \"$figlet_output\" >\"$output_file\"\n        echo \"Generated: $output_file\"\n      else\n        echo \"Figlet failed for $app_name in $script\"\n      fi\n    else\n      echo \"No APP name found in $script, skipping.\"\n    fi\n  done\n}\n\n# ct\ngenerate_headers \"./ct\" \"headers\" \"*\"\n\n# tools (addon, pve, ...)\ngenerate_headers \"./tools\" \"headers\" \"**\"\n\n# vm\ngenerate_headers \"./vm\" \"headers\" \"*\"\n\necho \"Completed processing all sections.\"\n"
  },
  {
    "path": ".github/workflows/stale_pr_close.yml",
    "content": "name: Stale PR Management\non:\n  schedule:\n    - cron: \"0 0 * * *\"\n  workflow_dispatch:\n  pull_request_target:\n    types:\n      - labeled\n\njobs:\n  stale-prs:\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n      issues: write\n      contents: read\n    steps:\n      - name: Handle stale PRs\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const now = new Date();\n            const owner = context.repo.owner;\n            const repo = context.repo.repo;\n            \n            // --- When stale label is added, comment immediately ---\n            if (context.eventName === \"pull_request_target\" && context.payload.action === \"labeled\") {\n              const label = context.payload.label?.name;\n              if (label === \"stale\") {\n                const author = context.payload.pull_request.user.login;\n                await github.rest.issues.createComment({\n                  owner,\n                  repo,\n                  issue_number: context.payload.pull_request.number,\n                  body: `@${author} This PR has been marked as stale. It will be closed if no new commits are added in 7 days.`\n                });\n              }\n              return;\n            }\n            \n            // --- Scheduled run: check all stale PRs ---\n            const { data: prs } = await github.rest.pulls.list({\n              owner,\n              repo,\n              state: \"open\",\n              per_page: 100\n            });\n            \n            for (const pr of prs) {\n              const hasStale = pr.labels.some(l => l.name === \"stale\");\n              if (!hasStale) continue;\n              \n              // Get timeline events to find when stale label was added\n              const { data: events } = await github.rest.issues.listEvents({\n                owner,\n                repo,\n                issue_number: pr.number,\n                per_page: 100\n              });\n              \n              // Find the most recent time the stale label was added\n              const staleLabelEvents = events\n                .filter(e => e.event === \"labeled\" && e.label?.name === \"stale\")\n                .sort((a, b) => new Date(b.created_at) - new Date(a.created_at));\n              \n              if (staleLabelEvents.length === 0) continue;\n              \n              const staleLabelDate = new Date(staleLabelEvents[0].created_at);\n              const daysSinceStale = (now - staleLabelDate) / (1000 * 60 * 60 * 24);\n              \n              // Check for new commits since stale label was added\n              const { data: commits } = await github.rest.pulls.listCommits({\n                owner,\n                repo,\n                pull_number: pr.number\n              });\n              \n              const lastCommitDate = new Date(commits[commits.length - 1].commit.author.date);\n              const author = pr.user.login;\n              \n              // If there are new commits after the stale label, remove it\n              if (lastCommitDate > staleLabelDate) {\n                await github.rest.issues.removeLabel({\n                  owner,\n                  repo,\n                  issue_number: pr.number,\n                  name: \"stale\"\n                });\n                await github.rest.issues.createComment({\n                  owner,\n                  repo,\n                  issue_number: pr.number,\n                  body: `@${author} Recent activity detected. Removing stale label.`\n                });\n              }\n              // If 7 days have passed since stale label, close the PR\n              else if (daysSinceStale > 7) {\n                await github.rest.pulls.update({\n                  owner,\n                  repo,\n                  pull_number: pr.number,\n                  state: \"closed\"\n                });\n                await github.rest.issues.createComment({\n                  owner,\n                  repo,\n                  issue_number: pr.number,\n                  body: `@${author} Closing stale PR due to inactivity (no commits for 7 days after stale label).`\n                });\n              }\n            }\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 patchRes = await request(recordsUrl + '/' + record.id, {\n                method: 'PATCH',\n                headers: { 'Authorization': token, 'Content-Type': 'application/json' },\n                body: JSON.stringify({\n                  script_updated: new Date().toISOString().split('T')[0],\n                  last_update_commit: process.env.PR_URL || process.env.COMMIT_URL || ''\n                })\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": ".vscode/.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": ".vscode/.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": "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<details>\n<summary><h2>📜 History</h2></summary>\n\n\n<details>\n<summary><h3>2026</h3></summary>\n\n\n<details>\n<summary><h4>March (14 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-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\n## 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))"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021-2026 tteck | community-scripts ORG \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  <img src=\"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png\" height=\"120px\" alt=\"Proxmox VE Helper-Scripts Logo\" />\n  \n  <h1>Proxmox VE Helper-Scripts</h1>\n  <p><em>A Community Legacy in Memory of @tteck</em></p>\n\n  <p>\n    <a href=\"https://helper-scripts.com\">\n      <img src=\"https://img.shields.io/badge/🌐_Website-Visit-4c9b3f?style=for-the-badge&labelColor=2d3748\" alt=\"Website\" />\n    </a>\n    <a href=\"https://discord.gg/3AnUqsXnmK\">\n      <img src=\"https://img.shields.io/badge/💬_Discord-Join-7289da?style=for-the-badge&labelColor=2d3748\" alt=\"Discord\" />\n    </a>\n    <a href=\"https://ko-fi.com/community_scripts\">\n      <img src=\"https://img.shields.io/badge/❤️_Support-Donate-FF5F5F?style=for-the-badge&labelColor=2d3748\" alt=\"Donate\" />\n    </a>\n  </p>\n\n  <p>\n    <a href=\"https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/README.md\">\n      <img src=\"https://img.shields.io/badge/🤝_Contribute-Guidelines-ff4785?style=for-the-badge&labelColor=2d3748\" alt=\"Contribute\" />\n    </a>\n    <a href=\"https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/USER_SUBMITTED_GUIDES.md\">\n      <img src=\"https://img.shields.io/badge/📚_Guides-Read-0077b5?style=for-the-badge&labelColor=2d3748\" alt=\"Guides\" />\n    </a>\n    <a href=\"https://github.com/community-scripts/ProxmoxVE/blob/main/CHANGELOG.md\">\n      <img src=\"https://img.shields.io/badge/📋_Changelog-View-6c5ce7?style=for-the-badge&labelColor=2d3748\" alt=\"Changelog\" />\n    </a>\n  </p>\n\n  <br />\n\n **Simplify your Proxmox VE setup with community-driven automation scripts**  \n Originally created by tteck, now maintained and expanded by the community\n\n</div>\n\n<br />\n\n<div align=\"center\">\n  <sub>🙌 <strong>Shoutout to</strong></sub>\n  <br />\n  <br />\n  <a href=\"https://selfh.st/\">\n    <img src=\"https://img.shields.io/badge/selfh.st-Icons_for_Self--Hosted-2563eb?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAxOGMtNC40MSAwLTgtMy41OS04LThzMy41OS04IDgtOCA4IDMuNTkgOCA4LTMuNTkgOC04IDh6IiBmaWxsPSJ3aGl0ZSIvPjwvc3ZnPg==&labelColor=1e3a8a\" alt=\"selfh.st Icons\" />\n  </a>\n  <br />\n  <sub><a href=\"https://github.com/selfhst/icons\">View on GitHub</a> • Consistent, beautiful icons for 5000+ self-hosted apps</sub>\n</div>\n\n---\n\n## 🎯 Key Features\n\n<div align=\"center\">\n\n<table>\n  <tr>\n    <td align=\"center\" width=\"25%\">\n      <h3>⚡ Quick Setup</h3>\n      <p>One-command installations for popular services and containers</p>\n    </td>\n    <td align=\"center\" width=\"25%\">\n      <h3>⚙️ Flexible Config</h3>\n      <p>Simple mode for beginners, advanced options for power users</p>\n    </td>\n    <td align=\"center\" width=\"25%\">\n      <h3>🔄 Auto Updates</h3>\n      <p>Keep your installations current with built-in update mechanisms</p>\n    </td>\n    <td align=\"center\" width=\"25%\">\n      <h3>🛠️ Easy Management</h3>\n      <p>Post-install scripts for configuration and troubleshooting</p>\n    </td>\n  </tr>\n  <tr>\n    <td align=\"center\" width=\"25%\">\n      <h3>👥 Community Driven</h3>\n      <p>Actively maintained with contributions from users worldwide</p>\n    </td>\n    <td align=\"center\" width=\"25%\">\n      <h3>📖 Well Documented</h3>\n      <p>Comprehensive guides and community support</p>\n    </td>\n    <td align=\"center\" width=\"25%\">\n      <h3>🔒 Secure</h3>\n      <p>Regular security updates and best practices</p>\n    </td>\n    <td align=\"center\" width=\"25%\">\n      <h3>⚡ Performance</h3>\n      <p>Optimized configurations for best performance</p>\n    </td>\n  </tr>\n</table>\n\n</div>\n\n---\n\n## 📋 Requirements\n\n<div align=\"center\">\n\n<table>\n  <tr>\n    <td align=\"center\" width=\"33%\">\n      <h3>🖥️ Proxmox VE</h3>\n      <p>Version: 8.4.x | 9.0.x | 9.1.x</p>\n    </td>\n    <td align=\"center\" width=\"33%\">\n      <h3>🐧 Operating System</h3>\n      <p>Debian-based with Proxmox Tools</p>\n    </td>\n    <td align=\"center\" width=\"33%\">\n      <h3>🌐 Network</h3>\n      <p>Internet connection required</p>\n    </td>\n  </tr>\n</table>\n\n</div>\n\n---\n\n## 📥 Getting Started\n\nChoose your preferred installation method:\n\n### Method 1: One-Click Web Installer\n\nThe fastest way to get started:\n\n1. Visit **[community-scripts.org](https://community-scripts.org/)** 🌐\n2. Search for your desired script (e.g., \"Home Assistant\", \"Docker\")\n3. Copy the bash command displayed on the script page\n4. Open your **Proxmox Shell** and paste the command\n5. Press Enter and follow the interactive prompts\n\n### Method 2: PVEScripts-Local\n\nInstall a convenient script manager directly in your Proxmox UI:\n\n```bash\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/pve-scripts-local.sh)\"\n```\n\nThis adds a menu to your Proxmox interface for easy script access without visiting the website.\n\n📖 **Learn more:** [ProxmoxVE-Local Repository](https://github.com/community-scripts/ProxmoxVE-Local)\n\n---\n\n## 💬 Join the Community\n\n<div align=\"center\">\n\n<table>\n  <tr>\n    <td align=\"center\" width=\"33%\">\n      <h3>💬 Discord</h3>\n      <p>Real-time chat, support, and discussions</p>\n      <a href=\"https://discord.gg/3AnUqsXnmK\">\n        <img src=\"https://img.shields.io/badge/Join-7289da?style=for-the-badge&logo=discord&logoColor=white\" alt=\"Discord\" />\n      </a>\n    </td>\n    <td align=\"center\" width=\"33%\">\n      <h3>💭 Discussions</h3>\n      <p>Feature requests, Q&A, and ideas</p>\n      <a href=\"https://github.com/community-scripts/ProxmoxVE/discussions\">\n        <img src=\"https://img.shields.io/badge/Discuss-238636?style=for-the-badge&logo=github&logoColor=white\" alt=\"Discussions\" />\n      </a>\n    </td>\n    <td align=\"center\" width=\"33%\">\n      <h3>🐛 Issues</h3>\n      <p>Bug reports and issue tracking</p>\n      <a href=\"https://github.com/community-scripts/ProxmoxVE/issues\">\n        <img src=\"https://img.shields.io/badge/Report-d73a4a?style=for-the-badge&logo=github&logoColor=white\" alt=\"Issues\" />\n      </a>\n    </td>\n  </tr>\n</table>\n\n</div>\n\n---\n\n## 🛠️ Contribute\n\n<div align=\"center\">\n\n<table>\n  <tr>\n    <td align=\"center\" width=\"25%\">\n      <h3>💻 Code</h3>\n      <p>Add new scripts or improve existing ones</p>\n    </td>\n    <td align=\"center\" width=\"25%\">\n      <h3>📝 Documentation</h3>\n      <p>Write guides, improve READMEs, translate content</p>\n    </td>\n    <td align=\"center\" width=\"25%\">\n      <h3>🧪 Testing</h3>\n      <p>Test scripts and report compatibility issues</p>\n    </td>\n    <td align=\"center\" width=\"25%\">\n      <h3>💡 Ideas</h3>\n      <p>Suggest features or workflow improvements</p>\n    </td>\n  </tr>\n</table>\n\n</div>\n\n<div align=\"center\">\n  <br />\n  \n  👉 Check our **[Contributing Guidelines](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/README.md)** to get started\n  \n</div>\n\n---\n\n## ❤️ Support the Project\n\nThis project is maintained by volunteers in memory of tteck. Your support helps us maintain infrastructure, improve documentation, and give back to important causes.\n\n**🎗️ 30% of all donations go directly to cancer research and hospice care**\n\n<div align=\"center\">\n\n<a href=\"https://ko-fi.com/community_scripts\">\n  <img src=\"https://img.shields.io/badge/☕_Buy_us_a_coffee-Support_on_Ko--fi-FF5F5F?style=for-the-badge&labelColor=2d3748\" alt=\"Support on Ko-fi\" />\n</a>\n\n<br />\n<sub>Every contribution helps keep this project alive and supports meaningful causes</sub>\n\n</div>\n\n---\n\n## 📈 Project Statistics\n<p align=\"center\">\n  <img\n    src=\"https://repobeats.axiom.co/api/embed/57edde03e00f88d739bdb5b844ff7d07dd079375.svg\"\n    alt=\"Repobeats analytics\"\n    width=\"650\"\n  />\n</p>\n\n<p align=\"center\">\n  <a href=\"https://star-history.com/#community-scripts/ProxmoxVE&Date\">\n    <picture>\n      <source\n        media=\"(prefers-color-scheme: dark)\"\n        srcset=\"https://api.star-history.com/svg?repos=community-scripts/ProxmoxVE&type=Date&theme=dark\"\n      />\n      <source\n        media=\"(prefers-color-scheme: light)\"\n        srcset=\"https://api.star-history.com/svg?repos=community-scripts/ProxmoxVE&type=Date\"\n      />\n      <img\n        alt=\"Star History Chart\"\n        src=\"https://api.star-history.com/svg?repos=community-scripts/ProxmoxVE&type=Date\"\n        width=\"650\"\n      />\n    </picture>\n  </a>\n</p>\n\n---\n\n## 📜 License\n\nThis project is licensed under the **[MIT License](LICENSE)** - feel free to use, modify, and distribute.\n\n---\n\n<div align=\"center\">\n  <sub>Made with ❤️ by the Proxmox community in memory of tteck</sub>\n  <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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: jkrgr0\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://docs.2fauth.app/ | Github: https://github.com/Bubka/2FAuth\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    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    mv \"/opt/2fauth-backup/.env\" \"/opt/2fauth/.env\"\n    mv \"/opt/2fauth-backup/storage\" \"/opt/2fauth/storage\"\n    cd \"/opt/2fauth\" || return\n    chown -R www-data: \"/opt/2fauth\"\n    chmod -R 755 \"/opt/2fauth\"\n    export COMPOSER_ALLOW_SUPERUSER=1\n    $STD composer install --no-dev --prefer-dist\n    php artisan 2fauth:install\n    $STD systemctl restart nginx\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} 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/actualbudget.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://actualbudget.org/ | Github: https://github.com/actualbudget/actual\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_info \"Old Installation Found, you need to migrate your data and recreate to a new container\"\n    msg_info \"Please follow the instructions on the Actual Budget website to migrate your data\"\n    msg_info \"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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://adguard.com/ | Github: https://github.com/AdguardTeam/AdGuardHome\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/seanmorley15/AdventureLog\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 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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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=Linux64&fromVersion=0\" | grep -o 'https://.*\\.zip')\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/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://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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-caddy.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: cobalt (cobaltgit)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Johann3s-H (An!ma)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://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}/x86_64-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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/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://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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-it-tools.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: nicedevil007 (NiceDevil)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://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_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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: hoholms\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://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  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  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/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.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/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://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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-amd64.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/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://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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: andrej-kocijan (Andrej Kocijan)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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  check_container_resources\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-x86_64-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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/lejianwen/rustdesk-server/releases/download/${RELEASE}/rustdesk-server-linux-amd64.zip\" -o \"$temp_file1\"\n    $STD unzip \"$temp_file1\"\n    cp -r amd64/* /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 amd64\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-amd64.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/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/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  check_container_storage\n  check_container_resources\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/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://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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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_amd64-${RELEASE}.tar.bz2\" -o ts3server.tar.bz2\n    tar -xf ./ts3server.tar.bz2\n    cp -ru teamspeak3-server_linux_amd64/* /opt/teamspeak-server/\n    rm -f ~/ts3server.tar.bz*\n    rm -rf teamspeak3-server_linux_amd64\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-amd64\" -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/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://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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: pshankinclarke (lazarillo)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-wireguard.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.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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/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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Andy Grunwald (andygrunwald)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://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/apt-cacher-ng.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://archivebox.io/ | Github: https://github.com/ArchiveBox/ArchiveBox\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/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://release-argus.io/ | Github: https://github.com/release-argus/Argus\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-amd64\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://aria2.github.io/ | Github: https://github.com/aria2/aria2\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: thost96 (thost96)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.authelia.com/ | Github: https://github.com/authelia/authelia\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://autobrr.com/ | Github: https://github.com/autobrr/autobrr\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_x86_64.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/community-scripts/ProxmoxVE/main/misc/build.func)\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://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/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/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    $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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: ksad (enirys31)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://garethgeorge.github.io/backrest/ | Github: https://github.com/garethgeorge/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_x86_64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://sabre.io/baikal/ | Github: https://github.com/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/bar-assistant.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: bvdberg01 | CanbiZ\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.bazarr.media/ | Github: https://github.com/morpheus65535/bazarr\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    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/community-scripts/ProxmoxVE/main/misc/build.func)\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/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    $STD npm install http-server -g\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    msg_ok \"Updated BentoPDF\"\n\n    msg_info \"Starting Service\"\n    if grep -q '8080' /etc/systemd/system/bentopdf.service; then\n      sed -i -e 's|/bentopdf|/bentopdf/dist|' \\\n        -e 's|npx.*|npx http-server -g -b -d false -r --no-dotfiles|' \\\n        /etc/systemd/system/bentopdf.service\n      systemctl daemon-reload\n    fi\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}http://${IP}:8080${CL}\"\n"
  },
  {
    "path": "ct/beszel.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: Michelle Zitzerman (Sinofage)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://beszel.dev/ | Github: https://github.com/henrygd/beszel\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    msg_ok \"Updated Beszel\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-*-x86_64-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/bitmagnet.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: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/bitmagnet-io/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://0xerr0r.github.io/blocky | Github: https://github.com/0xERR0R/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_x86_64.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/booklore.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/booklore-app/BookLore\n\nAPP=\"BookLore\"\nvar_tags=\"${var_tags:-books;library}\"\nvar_cpu=\"${var_cpu:-3}\"\nvar_ram=\"${var_ram:-3072}\"\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/booklore ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"booklore\" \"booklore-app/BookLore\"; then\n    JAVA_VERSION=\"25\" setup_java\n    NODE_VERSION=\"22\" setup_nodejs\n    setup_mariadb\n    setup_yq\n    ensure_dependencies ffmpeg\n\n    msg_info \"Stopping Service\"\n    systemctl stop booklore\n    msg_ok \"Stopped Service\"\n\n    if grep -qE \"^BOOKLORE_(DATA_PATH|BOOKDROP_PATH|BOOKS_PATH|PORT)=\" /opt/booklore_storage/.env 2>/dev/null; then\n      msg_info \"Migrating old environment variables\"\n      sed -i 's/^BOOKLORE_DATA_PATH=/APP_PATH_CONFIG=/g' /opt/booklore_storage/.env\n      sed -i 's/^BOOKLORE_BOOKDROP_PATH=/APP_BOOKDROP_FOLDER=/g' /opt/booklore_storage/.env\n      sed -i '/^BOOKLORE_BOOKS_PATH=/d' /opt/booklore_storage/.env\n      sed -i '/^BOOKLORE_PORT=/d' /opt/booklore_storage/.env\n      msg_ok \"Migrated old environment variables\"\n    fi\n\n    msg_info \"Backing up old installation\"\n    mv /opt/booklore /opt/booklore_bak\n    msg_ok \"Backed up old installation\"\n\n    fetch_and_deploy_gh_release \"booklore\" \"booklore-app/BookLore\" \"tarball\"\n\n    msg_info \"Building Frontend\"\n    cd /opt/booklore/booklore-ui\n    $STD npm install --force\n    $STD npm run build --configuration=production\n    msg_ok \"Built Frontend\"\n\n    msg_info \"Embedding Frontend into Backend\"\n    mkdir -p /opt/booklore/booklore-api/src/main/resources/static\n    cp -r /opt/booklore/booklore-ui/dist/booklore/browser/* /opt/booklore/booklore-api/src/main/resources/static/\n    msg_ok \"Embedded Frontend into Backend\"\n\n    msg_info \"Building Backend\"\n    cd /opt/booklore/booklore-api\n    APP_VERSION=$(get_latest_github_release \"booklore-app/BookLore\")\n    yq eval \".app.version = \\\"${APP_VERSION}\\\"\" -i src/main/resources/application.yaml\n    $STD ./gradlew clean build -x test --no-daemon\n    mkdir -p /opt/booklore/dist\n    JAR_PATH=$(find /opt/booklore/booklore-api/build/libs -maxdepth 1 -type f -name \"booklore-api-*.jar\" ! -name \"*plain*\" | head -n1)\n    if [[ -z \"$JAR_PATH\" ]]; then\n      msg_error \"Backend JAR not found\"\n      exit\n    fi\n    cp \"$JAR_PATH\" /opt/booklore/dist/app.jar\n    msg_ok \"Built Backend\"\n\n    if systemctl is-active --quiet nginx 2>/dev/null; then\n      msg_info \"Removing Nginx (no longer needed)\"\n      systemctl disable --now nginx\n      $STD apt-get purge -y nginx nginx-common\n      msg_ok \"Removed Nginx\"\n    fi\n\n    if ! grep -q \"^SERVER_PORT=\" /opt/booklore_storage/.env 2>/dev/null; then\n      echo \"SERVER_PORT=6060\" >>/opt/booklore_storage/.env\n    fi\n\n    sed -i 's|ExecStart=.*|ExecStart=/usr/bin/java -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+UseCompactObjectHeaders -XX:MaxRAMPercentage=75.0 -XX:+ExitOnOutOfMemoryError -jar /opt/booklore/dist/app.jar|' /etc/systemd/system/booklore.service\n    systemctl daemon-reload\n\n    msg_info \"Starting Service\"\n    systemctl start booklore\n    rm -rf /opt/booklore_bak\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}:6060${CL}\"\n"
  },
  {
    "path": "ct/bookstack.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/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  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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: luismco\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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    read -rp \"${TAB3}Did you make a backup via application WebUI? (y/n): \" backuped\n    if [[ \"$backuped\" =~ ^[Yy]$ ]]; then\n      msg_info \"Stopping Services\"\n      systemctl stop bytestash-backend bytestash-frontend\n      msg_ok \"Services Stopped\"\n\n      CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"bytestash\" \"jordan-dalby/ByteStash\" \"tarball\"\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    else\n      msg_error \"PLEASE MAKE A BACKUP FIRST!\"\n      exit\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} 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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://caddyserver.com/ | Github: https://github.com/caddyserver/caddy\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: mikolaj92\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://changedetection.io/ | Github: https://github.com/dgtlmoon/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\n  msg_ok \"Updated ${APP}\"\n\n  msg_info \"Updating Playwright\"\n  $STD pip3 install playwright --upgrade\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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/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\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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:-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 /opt/checkmk_version.txt ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  RELEASE=$(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)\n  msg_info \"Updating ${APP} to v${RELEASE}\"\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_amd64.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 ${APP}\"\n  msg_ok \"Updated successfully!\"\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}/monitoring${CL}\"\n"
  },
  {
    "path": "ct/cleanuparr.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: Lucas Zampieri (zampierilucas) | MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: edoardop13\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://cloudreve.org/ | Github: https://github.com/cloudreve/cloudreve\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_amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: havardthom\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/cockpit-project/cockpit\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: jdacode\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.commafeed.com/#/welcome | Github: https://github.com/Athou/commafeed\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: finkerle\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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    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-x64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya | MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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 /var ]]; 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    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/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://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/cosmos.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: Michel Roegl-Brunner (michelroegl-brunner)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://cosmos-cloud.io/ | Github: https://github.com/azukaar/Cosmos-Server\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/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://docs.craftycontrol.com/pages/getting-started/installation/linux/\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://cronicle.net/ | Github: https://github.com/jhuckaby/Cronicle\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Jakub Matraszek (jmatraszek)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.cross-seed.org | Github: https://github.com/cross-seed/cross-seed\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/daemonsync.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://daemonsync.me/\n\nAPP=\"Daemon Sync\"\nvar_tags=\"${var_tags:-sync}\"\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 /var ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  msg_info \"Updating LXC\"\n  $STD apt update\n  $STD apt -y upgrade\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 it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:8084${CL}\"\n"
  },
  {
    "path": "ct/databasus.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/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    cp /opt/databasus/.env /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.14.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    cd /opt/databasus/frontend\n    $STD npm ci\n    $STD npm 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 /opt/databasus/.env\n    rm -f /opt/databasus.env.bak\n    chown postgres:postgres /opt/databasus/.env\n    msg_ok \"Restored Configuration\"\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/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/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    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 rake assets:precompile\n    $STD bundle exec rails db:migrate\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: DragoQC | Co-Author: nickheyer\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://discopanel.app/ | Github: https://github.com/nickheyer/discopanel\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: ekke85 | MickLesk\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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} $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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Migration: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://docmost.com/ | Github: https://github.com/docmost/docmost\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/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://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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/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/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    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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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    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 reload 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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: fstof\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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_x86_64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Kristian Skov\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/drawio.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: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.drawio.com/ | Github: https://github.com/jgraph/drawio\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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/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-*_amd64-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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://emby.media/ | Github: https://github.com/MediaBrowser/Emby.Releases\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64.deb\"\n    curl -fsSL -o \"$DEB_FILE\" \"https://www.emqx.com/en/downloads/enterprise/v${RELEASE}/emqx-enterprise-${RELEASE}-debian12-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\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/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_gh_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_gh_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/ersatztv.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 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://ersatztv.org/ | Github: https://github.com/ErsatzTV/ErsatzTV\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-x64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/evcc-io/evcc\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kkroboth\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://firefly-iii.org/ | Github: https://github.com/firefly-iii/firefly-iii\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/fladder.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: wendyliga\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: remz1337\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: luismco\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://flowiseai.com/ | Github: https://github.com/FlowiseAI/Flowise\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  msg_info \"Updating FlowiseAI (this may take some time)\"\n  systemctl stop flowise\n  $STD npm install -g flowise --upgrade\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/community-scripts/ProxmoxVE/main/misc/build.func)\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/dotnetfactory/fluid-calendar\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/forgejo.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Arian Nasr (arian-nasr)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Authors: MickLesk (CanbiZ) | Co-Author: remz1337\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://frigate.video/ | Github: https://github.com/blakeblackshear/frigate\n\nAPP=\"Frigate\"\nvar_tags=\"${var_tags:-nvr}\"\nvar_cpu=\"${var_cpu:-4}\"\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/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://fumadocs.vercel.app/ | Github: https://github.com/fuma-nama/fumadocs\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/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://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}/x86_64-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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/ghost.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: fabrice1236\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://ghost.org/ | Github: https://github.com/TryGhost/Ghost\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\" 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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: lucasfell\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://ghostfol.io/ | Github: https://github.com/ghostfolio/ghostfolio\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    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/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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: Rogue-King\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://about.gitea.com/ | Github: https://github.com/go-gitea/gitea\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-amd64\"\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/glance.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: kristocopani\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Giovanni Pellerano (evilaliv3)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Nícolas Pastorello (opastorello)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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_amd64\"\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/gokapi.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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_amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://gotify.net/ | Github: https://github.com/gotify/server\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-amd64.zip\"\n    chmod +x /opt/gotify/gotify-linux-amd64\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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.0\" 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\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} 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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/gristlabs/grist-core\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    cp -r /opt/grist_bak/docs/* /opt/grist/docs/\n    cp /opt/grist_bak/grist-sessions.db /opt/grist/grist-sessions.db\n    cp /opt/grist_bak/landing.db /opt/grist/landing.db\n    cd /opt/grist\n    $STD yarn install\n    $STD yarn run install:ee\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://grocy.info/ | Github: https://github.com/grocy/grocy\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: HydroshieldMKII\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-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-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-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/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/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/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/bitmagnet",
    "content": "    ____  _ __                                   __ \n   / __ )(_) /_____ ___  ____ _____ _____  ___  / /_\n  / __  / / __/ __ `__ \\/ __ `/ __ `/ __ \\/ _ \\/ __/\n / /_/ / / /_/ / / / / / /_/ / /_/ / / / /  __/ /_  \n/_____/_/\\__/_/ /_/ /_/\\__,_/\\__, /_/ /_/\\___/\\__/  \n                            /____/                  \n"
  },
  {
    "path": "ct/headers/blocky",
    "content": "    ____  __           __        \n   / __ )/ /___  _____/ /____  __\n  / __  / / __ \\/ ___/ //_/ / / /\n / /_/ / / /_/ / /__/ ,< / /_/ / \n/_____/_/\\____/\\___/_/|_|\\__, /  \n                        /____/   \n"
  },
  {
    "path": "ct/headers/booklore",
    "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/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/daemonsync",
    "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/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/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/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/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/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/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/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/homepage",
    "content": "    __  __                                          \n   / / / /___  ____ ___  ___  ____  ____ _____ ____ \n  / /_/ / __ \\/ __ `__ \\/ _ \\/ __ \\/ __ `/ __ `/ _ \\\n / __  / /_/ / / / / / /  __/ /_/ / /_/ / /_/ /  __/\n/_/ /_/\\____/_/ /_/ /_/\\___/ .___/\\__,_/\\__, /\\___/ \n                          /_/          /____/       \n"
  },
  {
    "path": "ct/headers/homer",
    "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/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/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/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/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/litellm",
    "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/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/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/miniflux",
    "content": "    __  ____       _ ______          \n   /  |/  (_)___  (_) __/ /_  ___  __\n  / /|_/ / / __ \\/ / /_/ / / / / |/_/\n / /  / / / / / / / __/ / /_/ />  <  \n/_/  /_/_/_/ /_/_/_/ /_/\\__,_/_/|_|  \n                                     \n"
  },
  {
    "path": "ct/headers/minio",
    "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/navidrome",
    "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/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/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/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/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/pf2etools",
    "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/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/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/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/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/stirling-pdf",
    "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/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/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/trilium",
    "content": "  ______     _ ___               \n /_  __/____(_) (_)_  ______ ___ \n  / / / ___/ / / / / / / __ `__ \\\n / / / /  / / / / /_/ / / / / / /\n/_/ /_/  /_/_/_/\\__,_/_/ /_/ /_/ \n                                 \n"
  },
  {
    "path": "ct/headers/trip",
    "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/typesense",
    "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/verdaccio",
    "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/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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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}\"\n\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/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://healthchecks.io/ | Github: https://github.com/healthchecks/healthchecks\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://heimdall.site/ | Github: https://github.com/linuxserver/Heimdall\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: miviro\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-x86_64\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.hivemq.com/ | Github: https://github.com/hivemq/hivemq-community-edition\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/homarr-labs/homarr\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-amd64.tar.gz\"\n\n    msg_info \"Updating Homarr\"\n    cp /opt/homarr/redis.conf /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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64-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 helper-scripts.com --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|helper-scripts.com\\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/sysadminsmedia/homebox\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/homepage.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://gethomepage.dev/ | Github: https://github.com/gethomepage/homepage\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    $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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/hortusfox.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/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    fetch_and_deploy_gh_release \"hortusfox\" \"danielbrendel/hortusfox-web\" \"tarball\"\n\n    msg_info \"Updating HortusFox\"\n    cd /opt/hortusfox\n    mv /opt/hortusfox-backup/.env /opt/hortusfox/.env\n    $STD composer install --no-dev --optimize-autoloader\n    $STD php asatru migrate --no-interaction\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/immich.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: vhsdream\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://immich.app | Github: https://github.com/immich-app/immich\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=amd64] 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 iGPU 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      msg_ok \"Intel iGPU dependencies updated\"\n    fi\n    rm ./Dockerfile\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.5.6\"\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    [[ -f ~/.vchord_version ]] && mv ~/.vchord_version ~/.vectorchord\n    if check_for_gh_release \"VectorChord\" \"tensorchord/VectorChord\" \"${VCHORD_RELEASE}\" \"updated together with Immich after testing\"; then\n      fetch_and_deploy_gh_release \"VectorChord\" \"tensorchord/VectorChord\" \"binary\" \"${VCHORD_RELEASE}\" \"/tmp\" \"postgresql-16-vchord_*_amd64.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    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 -R immich:immich \"$INSTALL_DIR\"\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      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 \"Updating HW-accelerated machine-learning\"\n      $STD uv add --no-sync --optional openvino onnxruntime-openvino==1.24.1 --active -n -p \"${ML_PYTHON}\" --managed-python\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      patchelf --clear-execstack \"${VIRTUAL_ENV}/lib/python3.13/site-packages/onnxruntime/capi/onnxruntime_pybind11_state.cpython-313-x86_64-linux-gnu.so\"\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    chown -R immich:immich \"$INSTALL_DIR\"\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\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:=$(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:=$(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:=$(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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.inspircd.org/ | Github: https://github.com/inspircd/inspircd\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Benito Rodríguez (b3ni)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://invoiceninja.com/ | Github: https://github.com/invoiceninja/invoiceninja\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.iobroker.net/#en/intro | Github: https://github.com/ioBroker/ioBroker.js-controller\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  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/itsm-ng.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: Florianb63\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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.LinuxAMDx64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Mips2648\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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:-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 /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    fetch_and_deploy_gh_release \"intel-igc-core-2\" \"intel/intel-graphics-compiler\" \"binary\" \"latest\" \"\" \"intel-igc-core-2_*_amd64.deb\"\n    fetch_and_deploy_gh_release \"intel-igc-opencl-2\" \"intel/intel-graphics-compiler\" \"binary\" \"latest\" \"\" \"intel-igc-opencl-2_*_amd64.deb\"\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    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\n  msg_info \"Updating Jellyfin\"\n  ensure_dependencies libjemalloc2\n  if [[ ! -f /usr/lib/libjemalloc.so ]]; then\n    ln -sf /usr/lib/x86_64-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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/joplin-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: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://joplinapp.org/ | Github: https://github.com/laurent22/joplin\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/community-scripts/ProxmoxVE/main/misc/build.func)\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/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz) & vhsdream\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://karakeep.app/ | Github: https://github.com/karakeep-app/karakeep\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    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    sed -i \"s/^SERVER_VERSION=.*$/SERVER_VERSION=${CHECK_UPDATE_RELEASE#v}/\" /etc/karakeep/karakeep.env\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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}\"\n\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_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_URL\" ]] || [[ -z \"$KASM_VERSION\" ]]; 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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.kavitareader.com/ | Github: https://github.com/Kareadita/Kavita\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: remz1337\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/keycloak/keycloak\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/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/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  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/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.kimai.org/ | Github: https://github.com/kimai/kimai\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: snazzybean\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://koel.dev/ | Github: https://github.com/koel/koel\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://koillection.github.io/ | Github: https://github.com/benjaminjonard/koillection\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      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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 the LXC at following IP address:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}${IP}${CL}\"\n"
  },
  {
    "path": "ct/komga.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: madelyn (DysfunctionalProgramming)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://komga.org/ | Github: https://github.com/gotson/komga\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/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://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_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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: ulmentflam\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tomfrenzel\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: MountyMapleSyrup (MountyMapleSyrup)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Stroopwafe1\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://leantime.io | Github: https://github.com/Leantime/leantime\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/librenms.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: michelroegl-brunner\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.librenms.org/ | Github: https://github.com/librenms/librenms\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Joseph Stubberfield (stubbers)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-x86_64-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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://lidarr.audio/ | Github: https://github.com/Lidarr/Lidarr\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    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-x64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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:-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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (MickLesk)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://linkding.link/ | Github: https://github.com/sissbruecker/linkding\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\"\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/x86_64-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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya | MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://linkstack.org/ | Github: https://github.com/linkstackorg/linkstack\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/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://linkwarden.app/ | Github: https://github.com/linkwarden/linkwarden\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://listmonk.app/ | Github: https://github.com/knadh/listmonk\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_amd64.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/litellm.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: stout01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/BerriAI/litellm\n\nAPP=\"LiteLLM\"\nvar_tags=\"${var_tags:-ai;interface}\"\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/litellm.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Stopping Service\"\n  systemctl stop litellm\n  msg_ok \"Stopped Service\"\n\n  VENV_PATH=\"/opt/litellm/.venv\"\n  PYTHON_VERSION=\"3.13\" USE_UVX=\"YES\" setup_uv\n\n  msg_info \"Updating LiteLLM\"\n  $STD \"$VENV_PATH/bin/python\" -m pip install --upgrade litellm[proxy] prisma\n  msg_ok \"LiteLLM updated\"\n\n  msg_info \"Updating DB Schema\"\n  $STD uv --directory=/opt/litellm run litellm --config /opt/litellm/litellm.yaml --use_prisma_db_push --skip_server_startup\n  msg_ok \"DB Schema Updated\"\n\n  msg_info \"Starting Service\"\n  systemctl start litellm\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}:4000${CL}\"\n"
  },
  {
    "path": "ct/livebook.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: dkuku\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: remz1337\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: hoholms\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://lubelogger.com/ | Github: https://github.com/hargata/lubelog\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/lyrionmusicserver.sh",
    "content": "#!/usr/bin/env bash\n\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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[^\"]*amd64\\.deb(?=\"[^>]*>)' | head -n 1)\n  RELEASE=$(echo \"$DEB_URL\" | grep -oP 'lyrionmusicserver_\\K[0-9.]+(?=_amd64\\.deb)')\n  DEB_FILE=\"/tmp/lyrionmusicserver_${RELEASE}_amd64.deb\"\n  if [[ ! -f /opt/lyrion_version.txt ]] || [[ \"${RELEASE}\" != \"$(cat /opt/lyrion_version.txt)\" ]]; then\n    msg_info \"Updating $APP to ${RELEASE}\"\n    curl -fsSL -o \"$DEB_FILE\" \"$DEB_URL\"\n    $STD apt install \"$DEB_FILE\" -y\n    systemctl restart lyrion\n    $STD rm -f \"$DEB_FILE\"\n    echo \"${RELEASE}\" >/opt/${APP}_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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://mafl.hywax.space/ | Github: https://github.com/hywax/mafl\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://magicmirror.builders/ | Github: https://github.com/MagicMirrorOrg/MagicMirror\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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  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/community-scripts/ProxmoxVE/main/misc/build.func)\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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01 | Co-Author: SunFlowerOwl\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/matterbridge.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 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Kaedon Cleland-Host (dracentis)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://mealie.io | Github: https://github.com/mealie-recipes/mealie\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    msg_ok \"Backup completed\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"mealie\" \"mealie-recipes/mealie\" \"tarball\" \"latest\" \"/opt/mealie\"\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    $STD sed -i \"s|https://github.com/mealie-recipes/mealie/commit/|https://github.com/mealie-recipes/mealie/releases/tag/|g\" /opt/mealie/frontend/pages/admin/site-settings.vue\n    $STD sed -i \"s|value: data.buildId,|value: \\\"v${MEALIE_VERSION}\\\",|g\" /opt/mealie/frontend/pages/admin/site-settings.vue\n    $STD sed -i \"s|value: data.production ? i18n.t(\\\"about.production\\\") : i18n.t(\\\"about.development\\\"),|value: \\\"bare-metal\\\",|g\" /opt/mealie/frontend/pages/admin/site-settings.vue\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    msg_info \"Updating NLTK Data\"\n    mkdir -p /nltk_data/\n    cd /opt/mealie\n    $STD uv run python -m nltk.downloader -d /nltk_data averaged_perceptron_tagger_eng\n    msg_ok \"Updated NLTK Data\"\n\n    msg_info \"Restoring Configuration\"\n    mv -f /opt/mealie.env /opt/mealie/mealie.env\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    chmod +x /opt/mealie/start.sh\n    msg_ok \"Configuration restored\"\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\n"
  },
  {
    "path": "ct/mediamanager.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: vhsdream\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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_amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/pymedusa/Medusa\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/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.meilisearch.com/ | Github: https://github.com/meilisearch/meilisearch\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/usememos/memos\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\" \"latest\" \"/opt/memos\" \"memos*linux_amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://meshcentral.com/ | Github: https://github.com/Ylianst/MeshCentral\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/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/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    $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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/miniflux.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: omernaveedxyz\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://miniflux.app/ | Github: https://github.com/miniflux/v2\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/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/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-amd64/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/mongodb.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.monicahq.com/ | Github: https://github.com/monicahq/monica\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://ipcheck.ing/ | Github: https://github.com/jason5ng32/MyIP\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  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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/gnmyt/myspeed\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.mysql.com/products/community | https://www.phpmyadmin.net\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://n8n.io/ | Github: https://github.com/n8n-io/n8n\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/navidrome.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/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/neo4j.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 tteck\n# Author: tteck | Co-Author: havardthom\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\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\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 [[ ! -f /etc/netbird/config.json ]]; 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/netbox.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: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://netboxlabs.com/ | Github: https://github.com/netbox-community/netbox\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/community-scripts/ProxmoxVE/main/misc/build.func)\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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://nextcloudpi.com/\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/nextpvr.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\n# https://github.com/tteck/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/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://nginxui.com | Github: https://github.com/0xJacky/nginx-ui\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-64.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/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://nginxproxymanager.com/ | Github: https://github.com/NginxProxyManager/nginx-proxy-manager\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 [[ $(grep -E '^VERSION_ID=' /etc/os-release) == *\"12\"* ]]; then\n    msg_error \"Wrong Debian version detected!\"\n    msg_error \"Please create a snapshot first. You must upgrade your LXC to Debian Trixie before updating. Visit: https://github.com/community-scripts/ProxmoxVE/discussions/7489\"\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      apt-get purge -y nodejs npm\n      apt-get 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  RELEASE=$(curl -fsSL https://api.github.com/repos/NginxProxyManager/nginx-proxy-manager/releases/latest |\n    grep \"tag_name\" |\n    awk '{print substr($2, 3, length($2)-4) }')\n\n  CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"nginxproxymanager\" \"NginxProxyManager/nginx-proxy-manager\" \"tarball\" \"v${RELEASE}\" \"/opt/nginxproxymanager\"\n  \n  msg_info \"Stopping Services\"\n  systemctl stop openresty\n  systemctl stop npm\n  msg_ok \"Stopped Services\"\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  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 \"s|\\\"version\\\": \\\"2.0.0\\\"|\\\"version\\\": \\\"$RELEASE\\\"|\" /opt/nginxproxymanager/backend/package.json\n  sed -i \"s|\\\"version\\\": \\\"2.0.0\\\"|\\\"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  # Replace node-sass with sass in package.json before installation\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 \"Updating Certbot\"\n  [ -f /etc/apt/trusted.gpg.d/openresty-archive-keyring.gpg ] && rm -f /etc/apt/trusted.gpg.d/openresty-archive-keyring.gpg\n  [ -f /etc/apt/sources.list.d/openresty.list ] && rm -f /etc/apt/sources.list.d/openresty.list\n  [ ! -f /etc/apt/trusted.gpg.d/openresty.gpg ] && curl -fsSL https://openresty.org/package/pubkey.gpg | gpg --dearmor --yes -o /etc/apt/trusted.gpg.d/openresty.gpg\n  [ ! -f /etc/apt/sources.list.d/openresty.sources ] && cat <<'EOF' >/etc/apt/sources.list.d/openresty.sources\nTypes: deb\nURIs: http://openresty.org/package/debian/\nSuites: bookworm\nComponents: openresty\nSigned-By: /etc/apt/trusted.gpg.d/openresty.gpg\nEOF\n  $STD apt update\n  $STD apt -y install openresty\n  if [ -d /opt/certbot ]; then\n    $STD /opt/certbot/bin/pip install --upgrade pip setuptools wheel\n    $STD /opt/certbot/bin/pip install --upgrade certbot certbot-dns-cloudflare\n  fi\n  msg_ok \"Updated Certbot\"\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 enable -q --now openresty\n  systemctl enable -q --now npm\n  systemctl restart openresty\n  msg_ok \"Started Services\"\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}:81${CL}\"\n"
  },
  {
    "path": "ct/nightscout.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: aendel\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.nocodb.com/ | Github: https://github.com/nocodb/nocodb\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://nodered.org/ | Github: https://github.com/node-red/node-red\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/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/NodeBB/NodeBB\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: luismco\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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/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/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://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/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://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_x64\\.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_x64.deb\"\"\n    export DEBIAN_FRONTEND=noninteractive\n    export DEBCONF_NOWARNINGS=yes\n    $STD dpkg -i nxwitness-server-$RELEASE-linux_x64.deb\n    rm -f /tmp/nxwitness-server-$RELEASE-linux_x64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: havardthom\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: havardthom | Co-Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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  msg_info \"Updating Omada Controller\"\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  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  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}:8043${CL}\"\n"
  },
  {
    "path": "ct/ombi.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://ombi.io/ | Github: https://github.com/Ombi-app/Ombi\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://openarchiver.com/ | Github: https://github.com/LogicLabs-OU/OpenArchiver\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/community-scripts/ProxmoxVE/main/misc/build.func)\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://opencloud.eu | Github: https://github.com/opencloud-eu/opencloud\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=\"v5.2.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    rm -f /usr/bin/{OpenCloud,opencloud}\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"OpenCloud\" \"opencloud-eu/opencloud\" \"singlefile\" \"${RELEASE}\" \"/usr/bin\" \"opencloud-*-linux-amd64\"\n    mv /usr/bin/OpenCloud /usr/bin/opencloud\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Jonathan (jd-apprentice)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://opengist.io/ | Github: https://github.com/thomiceli/opengist\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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/openwebui.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 tteck\n# Author: tteck | Co-Author: havardthom | Co-Author: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64.tar.zst\n      msg_ok \"Download Complete\"\n\n      if [ -f \"ollama-linux-amd64.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-amd64.tar.zst\n        rm -rf ollama-linux-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: emoscardini\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: emoscardini\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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_amd64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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=\"22\" 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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://owncast.online/ | Github: https://github.com/owncast/owncast\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/pairdrop.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://pairdrop.net/ | Github: https://github.com/schlagmichdoch/PairDrop\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://pangolin.net/ | Github: https://github.com/fosrl/pangolin\n\nAPP=\"Pangolin\"\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\"; 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    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_amd64\"\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    msg_info \"Running database migrations\"\n    cd /opt/pangolin\n    ENVIRONMENT=prod $STD npx drizzle-kit push --config drizzle.sqlite.config.ts\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/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/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/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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://docs.paperless-ngx.com/ | Github: https://github.com/paperless-ngx/paperless-ngx\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    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/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/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    cp /opt/papra/apps/papra-server/.env /opt/papra_env.bak\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    cp /opt/papra_env.bak /opt/papra/apps/papra-server/.env\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://docs.part-db.de/ | Github: https://github.com/Part-DB/Part-DB-server\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  RELEASE=$(get_latest_github_release \"Part-DB/Part-DB-server\")\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    msg_info \"Updating $APP to v${RELEASE}\"\n    cd /opt\n    mv /opt/partdb/ /opt/partdb-backup\n    curl -fsSL \"https://github.com/Part-DB/Part-DB-server/archive/refs/tags/v${RELEASE}.zip\" -o \"/opt/v${RELEASE}.zip\"\n    $STD unzip \"v${RELEASE}.zip\"\n    mv /opt/Part-DB-server-${RELEASE}/ /opt/partdb\n\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\n    export COMPOSER_ALLOW_SUPERUSER=1\n    $STD composer install --no-dev -o --no-interaction\n    $STD yarn install\n    $STD yarn build\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/v${RELEASE}.zip\"\n    rm -r /opt/partdb-backup\n    echo \"${RELEASE}\" >~/.partdb\n    msg_ok \"Updated $APP to v${RELEASE}\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\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/PatcMmon/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 ! grep -q \"PORT=3001\" /opt/patchmon/backend/.env; then\n    msg_warn \"⚠️ The next PatchMon update will include breaking changes (port changes).\"\n    msg_warn \"See details here: https://github.com/community-scripts/ProxmoxVE/pull/11888\"\n    msg_warn \"Press Enter to continue with the update, or Ctrl+C to abort...\"\n    read -r\n  fi\n\n  RELEASE=\"v1.4.2\"\n  NODE_VERSION=\"24\" setup_nodejs\n  if check_for_gh_release \"PatchMon\" \"PatchMon/PatchMon\" \"${RELEASE}\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop patchmon-server\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    cp /opt/patchmon/backend/.env /opt/backend.env\n    cp /opt/patchmon/frontend/.env /opt/frontend.env\n    msg_ok \"Backup Created\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"PatchMon\" \"PatchMon/PatchMon\" \"tarball\" \"${RELEASE}\" \"/opt/patchmon\"\n\n    msg_info \"Updating PatchMon\"\n    VERSION=$(get_latest_github_release \"PatchMon/PatchMon\")\n    SERVER_PORT=\"$(sed -n '/SERVER_PORT/s/[^=]*=//p' /opt/backend.env)\"\n    sed -i 's/PORT=3399/PORT=3001/' /opt/backend.env\n    sed -i -e \"s/VERSION=.*/VERSION=$VERSION/\" \\\n      -e '/^VITE_API_URL/d' /opt/frontend.env\n    export NODE_ENV=production\n    cd /opt/patchmon\n    $STD npm install --no-audit --no-fund --no-save --ignore-scripts\n    cd /opt/patchmon/frontend\n    mv /opt/frontend.env /opt/patchmon/frontend/.env\n    $STD npm install --no-audit --no-fund --no-save --ignore-scripts --include=dev\n    $STD npm run build\n    cd /opt/patchmon/backend\n    mv /opt/backend.env /opt/patchmon/backend/.env\n    $STD npm run db:generate\n    $STD npx prisma migrate deploy\n    cp /opt/patchmon/docker/nginx.conf.template /etc/nginx/sites-available/patchmon.conf\n    sed -i -e 's|proxy_pass .*|proxy_pass http://127.0.0.1:3001;|' \\\n      -e '\\|try_files |i\\        root /opt/patchmon/frontend/dist;' \\\n      -e 's|alias.*|alias /opt/patchmon/frontend/dist/assets;|' \\\n      -e '\\|expires 1y|i\\        root /opt/patchmon/frontend/dist;' /etc/nginx/sites-available/patchmon.conf\n    if [[ -n \"$SERVER_PORT\" ]] && [[ \"$SERVER_PORT\" != \"443\" ]]; then\n      sed -i \"s/listen [[:digit:]].*/listen ${SERVER_PORT};/\" /etc/nginx/sites-available/patchmon.conf\n    fi\n    ln -sf /etc/nginx/sites-available/patchmon.conf /etc/nginx/sites-enabled/\n    rm -f /etc/nginx/sites-enabled/default\n    $STD nginx -t\n    systemctl restart nginx\n    msg_ok \"Updated PatchMon\"\n\n    msg_info \"Starting Service\"\n    if grep -q '/usr/bin/node' /etc/systemd/system/patchmon-server.service; then\n      sed -i 's|ExecStart=.*|ExecStart=/usr/bin/npm run start|' /etc/systemd/system/patchmon-server.service\n      systemctl daemon-reload\n    fi\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Nícolas Pastorello (opastorello)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.paymenter.org | Github: https://github.com/paymenter/paymenter\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: remz1337\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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    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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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    cp -r /opt/pelican-panel/.env /opt/\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/\n    rm -rf * .*\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    mv /opt/.env /opt/pelican-panel/\n    $SQLITE_INSTALL && mv /opt/*.sqlite /opt/pelican-panel/database/\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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_amd64\"\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/pf2etools.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: TheRealVira\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://pf2etools.com/ | Github: https://github.com/Pf2eToolsOrg/Pf2eTools\n\nAPP=\"Pf2eTools\"\nvar_tags=\"${var_tags:-wiki}\"\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\n  if [[ ! -d \"/opt/${APP}\" ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"pf2etools\" \"Pf2eToolsOrg/Pf2eTools\"; then\n    msg_info \"Updating System\"\n    $STD apt update\n    $STD apt -y upgrade\n    msg_ok \"Updated System\"\n\n    rm -rf /opt/Pf2eTools\n    fetch_and_deploy_gh_release \"pf2etools\" \"Pf2eToolsOrg/Pf2eTools\" \"tarball\" \"latest\" \"/opt/Pf2eTools\"\n\n    msg_info \"Updating ${APP}\"\n    cd /opt/Pf2eTools\n    $STD npm install\n    $STD npm run build\n    chown -R www-data: \"/opt/${APP}\"\n    chmod -R 755 \"/opt/${APP}\"\n    msg_ok \"Updated ${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}http://${IP}${CL}\"\n"
  },
  {
    "path": "ct/photoprism.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.photoprism.app/ | Github: https://github.com/photoprism/photoprism\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-amd64.tar.gz\"\n\n    LIBHEIF_URL=$(curl -fsSL \"https://dl.photoprism.app/dist/libheif/\" | grep -oP \"libheif-bookworm-amd64-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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/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://plant-it.org/ | Github: https://github.com/MDeLuise/plant-it\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster) | MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://pocketbase.io/ | Github: https://github.com/pocketbase/pocketbase\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Snarkenfaugister\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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 helper-scripts.com --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|helper-scripts.com\\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Nícolas Pastorello (opastorello)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://privatebin.info/ | Github: https://github.com/PrivateBin/PrivateBin\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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 /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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.projectsend.org/ | Github: https://github.com/projectsend/projectsend\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Andy Grunwald (andygrunwald)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://prometheus.io/ | Github: https://github.com/prometheus/alertmanager\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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Marfnl\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Andy Grunwald (andygrunwald)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\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# Source: https://prometheus.io/ | Github: https://github.com/prometheus/prometheus\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-amd64.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/prowlarr.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://prowlarr.com/ | Github: https://github.com/Prowlarr/Prowlarr\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    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-x64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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: 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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: thost96 (thost96)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: liecno\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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_amd64\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: rcourtman & vhsdream\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: michelroegl-brunner\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/community-scripts/ProxmoxVE-Local\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.qbittorrent.org/ | Github: https://github.com/qbittorrent/qBittorrent\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/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/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\" \"binary\" \"latest\" \"/usr/bin/qdrant\"\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/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/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_x86_64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck | Co-Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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    setup_deb822_repo \\\n      \"rabbitmq\" \\\n      \"https://keys.openpgp.org/vks/v1/by-fingerprint/0A9AF2115F4687BD29803A206B73A36E6026DFCA\" \\\n      \"https://deb1.rabbitmq.com/rabbitmq-server/debian/trixie\" \\\n      \"trixie\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://radarr.video/ | Github: https://github.com/Radarr/Radarr\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    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-x64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://radicale.org/ | Github: https://github.com/Kozea/Radicale\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: vhsdream | MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://rxresume.org | Github: https://github.com/amruthpillai/reactive-resume\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    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    mv /opt/reactive-resume.env.bak /opt/reactive-resume/.env\n    msg_ok \"Updated Reactive Resume\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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  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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-amd64\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MrYadro\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://recyclarr.dev/wiki/ | Github: https://github.com/recyclarr/recyclarr\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\n    msg_info \"Updating ${APP}\"\n\n    fetch_and_deploy_gh_release \"recyclarr\" \"recyclarr/recyclarr\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"recyclarr-linux-x64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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/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  if [ ! -d /var/cache/nginx/tiles ]; then\n    msg_info \"Installing Nginx Tile Cache\"\n    mkdir -p /var/cache/nginx/tiles\n    $STD apt install -y nginx\n    cat <<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\n    chown -R www-data:www-data /var/cache/nginx\n    chmod -R 750 /var/cache/nginx\n    systemctl restart nginx\n    echo \"reitti.ui.tiles.cache.url=http://127.0.0.1\" >> /opt/reitti/application.properties\n    systemctl restart reitti\n    msg_info \"Installed Nginx Tile Cache\"\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    chown -R www-data:www-data /var/cache/nginx\n    chmod -R 750 /var/cache/nginx\n    systemctl restart nginx\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  \n  if check_for_gh_release \"photon\" \"komoot/photon\"; then\n    if [[ -f \"$HOME/.photon\" ]] && [[ \"$(cat \"$HOME/.photon\")\" == 0.7 ]]; then\n      CURRENT_VERSION=\"$(<\"$HOME/.photon\")\"\n      echo\n      echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n      echo \"Photon v1 upgrade detected (breaking change)\"\n      echo\n      echo \"Your current version: $CURRENT_VERSION\"\n      echo\n      echo \"Photon v1 requires a manual migration before updating.\"\n      echo\n      echo \"You need to:\"\n      echo \"  1. Remove existing geocoding data (not actual reitti data):\"\n      echo \"     rm -rf /opt/photon_data\"\n      echo\n      echo \"  2. Follow the inial setup guide again:\"\n      echo \"     https://github.com/community-scripts/ProxmoxVE/discussions/8737\"\n      echo\n      echo \"  3. Re-download and import Photon data for v1\"\n      echo\n      read -rp \"Do you want to continue anyway? (y/N): \" CONTINUE\n      echo\n        \n      if [[ ! \"$CONTINUE\" =~ ^[Yy]$ ]]; then\n        msg_info \"Migration required. Update cancelled.\"\n        exit 0\n      fi\n        \n      msg_warn \"Continuing without migration may break Photon in the future!\"\n    fi\n  \n    msg_info \"Stopping Service\"\n    systemctl stop photon\n    msg_ok \"Stopped Service\"\n\n    rm -f /opt/photon/photon.jar\n    USE_ORIGINAL_FILENAME=\"true\" fetch_and_deploy_gh_release \"photon\" \"komoot/photon\" \"singlefile\" \"latest\" \"/opt/photon\" \"photon-*.jar\"\n    mv /opt/photon/photon-*.jar /opt/photon/photon.jar\n\n    msg_info \"Starting Service\"\n    systemctl start photon\n    systemctl restart 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}:8080${CL}\"\n"
  },
  {
    "path": "ct/resiliosync.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: David Bennett (dbinit)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 '25s/localhost/0.0.0.0/g' /opt/revealjs/gulpfile.js\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: MickLesk (CanbiZ) | DevelopmentCats | AlphaLawless\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://romm.app | Github: https://github.com/rommapp/romm\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    ln -sfn /var/lib/romm/resources /opt/romm/frontend/dist/assets/romm/resources\n    ln -sfn /var/lib/romm/assets /opt/romm/frontend/dist/assets/romm/assets\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Migration: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\" \"lejianwen/rustdesk-server\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-server-hbbr*amd64.deb\"\n    fetch_and_deploy_gh_release \"rustdesk-hbbs\" \"lejianwen/rustdesk-server\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-server-hbbs*amd64.deb\"\n    fetch_and_deploy_gh_release \"rustdesk-utils\" \"lejianwen/rustdesk-server\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-server-utils*amd64.deb\"\n    fetch_and_deploy_gh_release \"rustdesk-api\" \"lejianwen/rustdesk-api\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-api-server*amd64.deb\"\n\n    msg_info \"Starting services\"\n    systemctl start -q rustdesk-*\n    msg_ok \"Services started\"\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}${IP}:21114${CL}\"\n"
  },
  {
    "path": "ct/rustypaste.sh",
    "content": "#!/usr/bin/env bash\nsource <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: GoldenSpringness\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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\" \"*x86_64-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\" \"*x86_64-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/community-scripts/ProxmoxVE/main/misc/build.func)\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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://sabnzbd.org/ | Github: https://github.com/sabnzbd/sabnzbd\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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: bvdberg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\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/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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: JasonGreenC\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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    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/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/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/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/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/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://docs.seerr.dev/ | Github: https://github.com/seerr-team/seerr\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://semaphoreui.com/ | Github: https://github.com/semaphoreui/semaphore\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 [[ -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. Unfortunately, this will reset your application and make it a fresh\"\n    msg_warn \"installation. All your data will be lost!\"\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      systemctl stop semaphore\n      rm -rf /opt/semaphore/semaphore_db.bolt\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      SEM_PW=$(cat ~/semaphore.creds)\n      systemctl start semaphore\n      $STD semaphore user add --admin --login admin --email admin@helper-scripts.com --name Administrator --password \"${SEM_PW}\" --config /opt/semaphore/config.json\n\n      msg_ok \"Moved from BoltDB to SQLite\"\n    fi\n  fi\n\n  if check_for_gh_release \"semaphore\" \"semaphoreui/semaphore\"; then\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_amd64.deb\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\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/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=\"22\" setup_nodejs\n  PYTHON_VERSION=\"3.12\" 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    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    $STD uv pip install -r ./requirements-base.txt\n    if [[ $(sed -n '/_BYPASS=/s/[^=]*=//p' /etc/shelfmark/.env) == \"true\" ]] && [[ $(sed -n '/BYPASSER=/s/[^=]*=//p' /etc/shelfmark/.env) == \"false\" ]]; then\n      $STD uv pip install -r ./requirements-shelfmark.txt\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/signoz.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: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://signoz.io/ | Github: https://github.com/SigNoz/signoz\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_amd64.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_amd64.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_amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Dominik Siebel (dsiebel)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://silverbullet.md | Github: https://github.com/silverbulletmd/silverbullet\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-x86_64.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/community-scripts/ProxmoxVE/main/misc/build.func)\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/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-x64.zip\"\n\n    msg_info \"Restoring config\"\n    mv /opt/slskd.yml.bak /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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://snipeitapp.com/ | Github: https://github.com/grokability/snipe-it\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: TuroYT\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/sonarqube.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: prop4n\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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    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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://sonarr.tv/ | Github: https://github.com/Sonarr/Sonarr\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  if [[ ! -d /var/lib/sonarr/ ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\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-x64.tar.gz\"\n\n  msg_info \"Starting Service\"\n  systemctl start sonarr\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}:8989${CL}\"\n"
  },
  {
    "path": "ct/sonobarr.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: GoldenSpringness\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/sparkyfitness.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: Tom Frenzel (tomfrenzel)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: AlphaLawless\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: rcastley\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-x64-*.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Kristian Skov\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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.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/stirling-pdf.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.stirlingpdf.com/ | Github: https://github.com/Stirling-Tools/Stirling-PDF\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/strapi.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: pespinel\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: luismco\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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_amd64\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\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://sure.am | Github: https://github.com/we-promise/sure\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: EEJoshua\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://tandoor.dev/ | Github: https://github.com/TandoorRecipes/recipes\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 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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://tautulli.com/ | Github: https://github.com/Tautulli/Tautulli\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/teamspeak-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: tremor021 (Slaviša Arežina)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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_amd64-\\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_amd64-${RELEASE}.tar.bz2\" -o ts3server.tar.bz2\n    tar -xf ./ts3server.tar.bz2\n    cp -ru teamspeak3-server_linux_amd64/* /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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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-8.0\"; then\n    $STD apt remove -y aspnetcore-runtime-8.0\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-9.0\n  fi\n\n  RELEASE=$(curl -fsSL https://technitium.com/dns/ | grep -oP 'Version \\K[\\d.]+')\n  if [[ ! -f ~/.technitium || ${RELEASE} != \"$(cat ~/.technitium)\" ]]; then\n    msg_info \"Updating Technitium DNS\"\n    curl -fsSL \"https://download.technitium.com/dns/DnsServerPortable.tar.gz\" -o /opt/DnsServerPortable.tar.gz\n    $STD tar zxvf /opt/DnsServerPortable.tar.gz -C /opt/technitium/dns/\n    rm -f /opt/DnsServerPortable.tar.gz\n    echo \"${RELEASE}\" >~/.technitium\n    systemctl restart technitium\n    msg_ok \"Updated Technitium DNS\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Dominik Siebel (dsiebel)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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.amd64.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/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/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/termix.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/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\"\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    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 '/^pid \\/app\\/nginx/d' /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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: kristocopani\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://thelounge.chat/ | Github: https://github.com/thelounge/thelounge\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/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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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\" \"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_amd64\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/msgbyte/tianji\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/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/steveiliop56/tinyauth\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    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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.traccar.org/ | Github: https://github.com/traccar/traccar\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: durzo\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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:-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 /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\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    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 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/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://tracktor.bytedge.in | Github: https://github.com/javedh-dev/tracktor\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://traefik.io/ | Github: https://github.com/traefik/traefik\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_amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/trilium.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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/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/tududi.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: vhsdream\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://tududi.com/ | Github: https://github.com/chrisvel/tududi\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: chrisbenincasa\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://tunarr.com/ | Github: https://github.com/chrisbenincasa/tunarr\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-x64.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\" \"*-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 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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: twingate-andrewb\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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 ${APP}\"\n  ensure_dependencies 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/typesense.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: tlissak | Co-Author MickLesk\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://typesense.org/\n\nAPP=\"TypeSense\"\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 /etc/typesense/typesense-server.ini ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n  if check_for_gh_release \"typesense\" \"typesense/typesense\"; then\n    msg_info \"Updating Typesense\"\n    $STD apt update\n    $STD apt -y upgrade\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\nstart\nbuild_container\ndescription\n\nmsg_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}:8108${CL}\"\n"
  },
  {
    "path": "ct/ubuntu.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: zackwithak13\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.uhfapp.com/server | Github: https://github.com/swapplications/uhf-server-dist\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-x64-*.zip\"\n    fetch_and_deploy_gh_release \"uhf-server\" \"swapplications/uhf-server-dist\" \"prebuild\" \"latest\" \"/opt/uhf-server\" \"UHF.Server-linux-x64-*.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://umami.is/ | Github: https://github.com/umami-software/umami\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    fetch_and_deploy_gh_release \"umami\" \"umami-software/umami\" \"tarball\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: elvito\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: wimb0\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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://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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Eduard González (wanetty)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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_amd64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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_amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://uptime.kuma.pet/ | Github: https://github.com/louislam/uptime-kuma\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Kristian Skov\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: pshankinclarke (lazarillo)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/verdaccio.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: BrynnJKnight\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://verdaccio.org/ | Github: https://github.com/verdaccio/verdaccio\n\nAPP=\"Verdaccio\"\nvar_tags=\"${var_tags:-dev-tools;npm;registry}\"\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/verdaccio.service ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  msg_info \"Updating LXC Container\"\n  $STD apt update\n  $STD apt upgrade -y\n  msg_ok \"Updated LXC Container\"\n\n  NODE_VERSION=\"24\" NODE_MODULE=\"verdaccio\" setup_nodejs\n  systemctl restart verdaccio\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}:4873${CL}\"\n"
  },
  {
    "path": "ct/victoriametrics.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: Slaviša Arežina (tremor021)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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    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-amd64-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-amd64-v[0-9.]+\\.tar\\.gz$')\n\n    fetch_and_deploy_gh_release \"victoriametrics\" \"VictoriaMetrics/VictoriaMetrics\" \"prebuild\" \"latest\" \"/opt/victoriametrics\" \"$victoriametrics_filename\"\n    fetch_and_deploy_gh_release \"vmutils\" \"VictoriaMetrics/VictoriaMetrics\" \"prebuild\" \"latest\" \"/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-amd64-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-amd64-v[0-9.]+\\.tar\\.gz$')\n        \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    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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://vikunja.io/ | Github: https://github.com/go-vikunja/vikunja\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/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://wallabag.org/ | Github: https://github.com/wallabag/wallabag\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/ellite/wallos\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: rrole\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://wanderer.to | Github: https://github.com/open-wanderer/wanderer\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\" \"binary\" \"latest\" \"/opt/wanderer/source/search\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: BvdBerg01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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}_x86_64-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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Don Locke (DonLocke)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/wavelog/wavelog\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Omar Minaya\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://wazuh.com/\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/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://wealthfolio.app/ | Github: https://github.com/afadil/wealthfolio\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  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/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/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\" \"CrazyWolf13/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\" \"CrazyWolf13/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/wikijs.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 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://js.wiki/ | Github: https://github.com/requarks/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: Dunky13\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\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/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/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://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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: StellaeAlis\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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_amd64.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 .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|/yamtrack/staticfiles/|/opt/yamtrack/src/staticfiles/|' /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/yt-dlp-webui.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/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-amd64\"\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/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/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: community-scripts\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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    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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tremor021\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.zigbee2mqtt.io/ | Github: https://github.com/Koenkk/zigbee2mqtt\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 || 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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (Canbiz)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/diced/zipline\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: dave-yap (dave-yap)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://zitadel.com/ | Github: https://github.com/zitadel/zitadel\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-amd64.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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://zoraxy.aroz.org/ | Github: https://github.com/tobychui/zoraxy\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_amd64\"\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/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://zotregistry.dev/ | Github: https://github.com/project-zot/zot\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-amd64\"\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/community-scripts/ProxmoxVE/main/misc/build.func)\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://zwave-js.github.io/zwave-js-ui/#/ | Github: https://github.com/zwave-js/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": "docs/DEV_MODE.md",
    "content": "# Dev Mode - Debugging & Development Guide\n\nDevelopment modes provide powerful debugging and testing capabilities for container creation and installation processes.\n\n## Quick Start\n\n```bash\n# Single mode\nexport dev_mode=\"motd\"\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/wallabag.sh)\"\n\n# Multiple modes (comma-separated)\nexport dev_mode=\"motd,keep,trace\"\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/wallabag.sh)\"\n\n# Combine with verbose output\nexport var_verbose=\"yes\"\nexport dev_mode=\"pause,logs\"\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/wallabag.sh)\"\n```\n\n## Available Modes\n\n### 1. **motd** - Early SSH/MOTD Setup\n\nSets up SSH access and MOTD **before** the main application installation.\n\n**Use Case**:\n\n- Quick access to container for manual debugging\n- Continue installation manually if something goes wrong\n- Verify container networking before main install\n\n**Behavior**:\n\n```\n✔ Container created\n✔ Network configured\n[DEV] Setting up MOTD and SSH before installation\n✔ [DEV] MOTD/SSH ready - container accessible\n# Container is now accessible via SSH while installation proceeds\n```\n\n**Combined with**: `keep`, `breakpoint`, `logs`\n\n---\n\n### 2. **keep** - Preserve Container on Failure\n\nNever delete the container when installation fails. Skips cleanup prompt.\n\n**Use Case**:\n\n- Repeated tests of the same installation\n- Debugging failed installations\n- Manual fix attempts\n\n**Behavior**:\n\n```\n✖ Installation failed in container 107 (exit code: 1)\n✔ Container creation log: /tmp/create-lxc-107-abc12345.log\n✔ Installation log: /tmp/install-lxc-107-abc12345.log\n\n🔧 [DEV] Keep mode active - container 107 preserved\nroot@proxmox:~#\n```\n\n**Container remains**: `pct enter 107` to access and debug\n\n**Combined with**: `motd`, `trace`, `logs`\n\n---\n\n### 3. **trace** - Bash Command Tracing\n\nEnables `set -x` for complete command-line tracing. Shows every command before execution.\n\n**Use Case**:\n\n- Deep debugging of installation logic\n- Understanding script flow\n- Identifying where errors occur exactly\n\n**Behavior**:\n\n```\n+(/opt/wallabag/bin/console): /opt/wallabag/bin/console cache:warmup\n+(/opt/wallabag/bin/console): env APP_ENV=prod /opt/wallabag/bin/console cache:warmup\n+(/opt/wallabag/bin/console): [[ -d /opt/wallabag/app/cache ]]\n+(/opt/wallabag/bin/console): rm -rf /opt/wallabag/app/cache/*\n```\n\n**⚠️ Warning**: Exposes passwords and secrets in log output! Only use in isolated environments.\n\n**Log Output**: All trace output saved to logs (see `logs` mode)\n\n**Combined with**: `keep`, `pause`, `logs`\n\n---\n\n### 4. **pause** - Step-by-Step Execution\n\nPauses after each major step (`msg_info`). Requires manual Enter press to continue.\n\n**Use Case**:\n\n- Inspect container state between steps\n- Understand what each step does\n- Identify which step causes problems\n\n**Behavior**:\n\n```\n⏳ Setting up Container OS\n[PAUSE] Press Enter to continue...\n⏳ Updating Container OS\n[PAUSE] Press Enter to continue...\n⏳ Installing Dependencies\n[PAUSE] Press Enter to continue...\n```\n\n**Between pauses**: You can open another terminal and inspect the container\n\n```bash\n# In another terminal while paused\npct enter 107\nroot@container:~# df -h  # Check disk usage\nroot@container:~# ps aux # Check running processes\n```\n\n**Combined with**: `motd`, `keep`, `logs`\n\n---\n\n### 5. **breakpoint** - Interactive Shell on Error\n\nOpens interactive shell inside the container when an error occurs instead of cleanup prompt.\n\n**Use Case**:\n\n- Live debugging in the actual container\n- Manual command testing\n- Inspect container state at point of failure\n\n**Behavior**:\n\n```\n✖ Installation failed in container 107 (exit code: 1)\n✔ Container creation log: /tmp/create-lxc-107-abc12345.log\n✔ Installation log: /tmp/install-lxc-107-abc12345.log\n\n🐛 [DEV] Breakpoint mode - opening shell in container 107\nType 'exit' to return to host\nroot@wallabag:~#\n\n# Now you can debug:\nroot@wallabag:~# tail -f /root/.install-abc12345.log\nroot@wallabag:~# mysql -u root -p$PASSWORD wallabag\nroot@wallabag:~# apt-get install -y strace\nroot@wallabag:~# exit\n\nContainer 107 still running. Remove now? (y/N): n\n🔧 Container 107 kept for debugging\n```\n\n**Combined with**: `keep`, `logs`, `trace`\n\n---\n\n### 6. **logs** - Persistent Logging\n\nSaves all logs to `/var/log/community-scripts/` with timestamps. Logs persist even on successful installation.\n\n**Use Case**:\n\n- Post-mortem analysis\n- Performance analysis\n- Automated testing with log collection\n- CI/CD integration\n\n**Behavior**:\n\n```\nLogs location: /var/log/community-scripts/\n\ncreate-lxc-abc12345-20251117_143022.log    (host-side creation)\ninstall-abc12345-20251117_143022.log       (container-side installation)\n```\n\n**Access logs**:\n\n```bash\n# View creation log\ntail -f /var/log/community-scripts/create-lxc-*.log\n\n# Search for errors\ngrep ERROR /var/log/community-scripts/*.log\n\n# Analyze performance\ngrep \"msg_info\\|msg_ok\" /var/log/community-scripts/create-*.log\n```\n\n**With trace mode**: Creates detailed trace of all commands\n\n```bash\ngrep \"^+\" /var/log/community-scripts/install-*.log\n```\n\n**Combined with**: All other modes (recommended for CI/CD)\n\n---\n\n### 7. **dryrun** - Simulation Mode\n\nShows all commands that would be executed without actually running them.\n\n**Use Case**:\n\n- Test script logic without making changes\n- Verify command syntax\n- Understand what will happen\n- Pre-flight checks\n\n**Behavior**:\n\n```\n[DRYRUN] apt-get update\n[DRYRUN] apt-get install -y curl\n[DRYRUN] mkdir -p /opt/wallabag\n[DRYRUN] cd /opt/wallabag\n[DRYRUN] git clone https://github.com/wallabag/wallabag.git .\n```\n\n**No actual changes made**: Container/system remains unchanged\n\n**Combined with**: `trace` (shows dryrun trace), `logs` (shows what would run)\n\n---\n\n## Mode Combinations\n\n### Development Workflow\n\n```bash\n# First test: See what would happen\nexport dev_mode=\"dryrun,logs\"\nbash -c \"$(curl ...)\"\n\n# Then test with tracing and pauses\nexport dev_mode=\"pause,trace,logs\"\nbash -c \"$(curl ...)\"\n\n# Finally full debug with early SSH access\nexport dev_mode=\"motd,keep,breakpoint,logs\"\nbash -c \"$(curl ...)\"\n```\n\n### CI/CD Integration\n\n```bash\n# Automated testing with full logging\nexport dev_mode=\"logs\"\nexport var_verbose=\"yes\"\nbash -c \"$(curl ...)\"\n\n# Capture logs for analysis\ntar czf installation-logs-$(date +%s).tar.gz /var/log/community-scripts/\n```\n\n### Production-like Testing\n\n```bash\n# Keep containers for manual verification\nexport dev_mode=\"keep,logs\"\nfor i in {1..5}; do\n  bash -c \"$(curl ...)\"\ndone\n\n# Inspect all created containers\npct list\npct enter 100\n```\n\n### Live Debugging\n\n```bash\n# SSH in early, step through installation, debug on error\nexport dev_mode=\"motd,pause,breakpoint,keep\"\nbash -c \"$(curl ...)\"\n```\n\n---\n\n## Environment Variables Reference\n\n### Dev Mode Variables\n\n- `dev_mode` (string): Comma-separated list of modes\n  - Format: `\"motd,keep,trace\"`\n  - Default: Empty (no dev modes)\n\n### Output Control\n\n- `var_verbose=\"yes\"`: Show all command output (disables silent mode)\n  - Pairs well with: `trace`, `pause`, `logs`\n\n### Examples with vars\n\n```bash\n# Maximum verbosity and debugging\nexport var_verbose=\"yes\"\nexport dev_mode=\"motd,trace,pause,logs\"\nbash -c \"$(curl ...)\"\n\n# Silent debug (logs only)\nexport dev_mode=\"keep,logs\"\nbash -c \"$(curl ...)\"\n\n# Interactive debugging\nexport var_verbose=\"yes\"\nexport dev_mode=\"motd,breakpoint\"\nbash -c \"$(curl ...)\"\n```\n\n---\n\n## Troubleshooting with Dev Mode\n\n### \"Installation failed at step X\"\n\n```bash\nexport dev_mode=\"pause,logs\"\n# Step through until the failure point\n# Check container state between pauses\npct enter 107\n```\n\n### \"Password/credentials not working\"\n\n```bash\nexport dev_mode=\"motd,keep,trace\"\n# With trace mode, see exact password handling (be careful with logs!)\n# Use motd to SSH in and test manually\nssh root@container-ip\n```\n\n### \"Permission denied errors\"\n\n```bash\nexport dev_mode=\"breakpoint,keep\"\n# Get shell at failure point\n# Check file permissions, user context, SELinux status\nls -la /path/to/file\nwhoami\n```\n\n### \"Networking issues\"\n\n```bash\nexport dev_mode=\"motd\"\n# SSH in with motd mode before main install\nssh root@container-ip\nping 8.8.8.8\nnslookup example.com\n```\n\n### \"Need to manually complete installation\"\n\n```bash\nexport dev_mode=\"motd,keep\"\n# Container accessible via SSH while installation runs\n# After failure, SSH in and manually continue\nssh root@container-ip\n# ... manual commands ...\nexit\n# Then use 'keep' mode to preserve container for inspection\n```\n\n---\n\n## Log Files Locations\n\n### Default (without `logs` mode)\n\n- Host creation: `/tmp/create-lxc-<SESSION_ID>.log`\n- Container install: Copied to `/tmp/install-lxc-<CTID>-<SESSION_ID>.log` on failure\n\n### With `logs` mode\n\n- Host creation: `/var/log/community-scripts/create-lxc-<SESSION_ID>-<TIMESTAMP>.log`\n- Container install: `/var/log/community-scripts/install-<SESSION_ID>-<TIMESTAMP>.log`\n\n### View logs\n\n```bash\n# Tail in real-time\ntail -f /var/log/community-scripts/*.log\n\n# Search for errors\ngrep -r \"exit code [1-9]\" /var/log/community-scripts/\n\n# Filter by session\ngrep \"ed563b19\" /var/log/community-scripts/*.log\n```\n\n---\n\n## Best Practices\n\n### ✅ DO\n\n- Use `logs` mode for CI/CD and automated testing\n- Use `motd` for early SSH access during long installations\n- Use `pause` when learning the installation flow\n- Use `trace` when debugging logic issues (watch for secrets!)\n- Combine modes for comprehensive debugging\n- Archive logs after successful tests\n\n### ❌ DON'T\n\n- Use `trace` in production or with untrusted networks (exposes secrets)\n- Leave `keep` mode enabled for unattended scripts (containers accumulate)\n- Use `dryrun` and expect actual changes\n- Commit `dev_mode` exports to production deployment scripts\n- Use `breakpoint` in non-interactive environments (will hang)\n\n---\n\n## Examples\n\n### Example 1: Debug a Failed Installation\n\n```bash\n# Initial test to see the failure\nexport dev_mode=\"keep,logs\"\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/wallabag.sh)\"\n\n# Container 107 kept, check logs\ntail /var/log/community-scripts/install-*.log\n\n# SSH in to debug\npct enter 107\nroot@wallabag:~# cat /root/.install-*.log | tail -100\nroot@wallabag:~# apt-get update  # Retry the failing command\nroot@wallabag:~# exit\n\n# Re-run with manual step-through\nexport dev_mode=\"motd,pause,keep\"\nbash -c \"$(curl ...)\"\n```\n\n### Example 2: Verify Installation Steps\n\n```bash\nexport dev_mode=\"pause,logs\"\nexport var_verbose=\"yes\"\nbash -c \"$(curl ...)\"\n\n# Press Enter through each step\n# Monitor container in another terminal\n# pct enter 107\n# Review logs in real-time\n```\n\n### Example 3: CI/CD Pipeline Integration\n\n```bash\n#!/bin/bash\nexport dev_mode=\"logs\"\nexport var_verbose=\"no\"\n\nfor app in wallabag nextcloud wordpress; do\n  echo \"Testing $app installation...\"\n  APP=\"$app\" bash -c \"$(curl ...)\" || {\n    echo \"FAILED: $app\"\n    tar czf logs-$app.tar.gz /var/log/community-scripts/\n    exit 1\n  }\n  echo \"SUCCESS: $app\"\ndone\n\necho \"All installations successful\"\ntar czf all-logs.tar.gz /var/log/community-scripts/\n```\n\n---\n\n## Advanced Usage\n\n### Custom Log Analysis\n\n```bash\n# Extract all errors\ngrep \"ERROR\\|exit code [1-9]\" /var/log/community-scripts/*.log\n\n# Performance timeline\ngrep \"^$(date +%Y-%m-%d)\" /var/log/community-scripts/*.log | grep \"msg_\"\n\n# Memory usage during install\ngrep \"free\\|available\" /var/log/community-scripts/*.log\n```\n\n### Integration with External Tools\n\n```bash\n# Send logs to Elasticsearch\ncurl -X POST \"localhost:9200/installation-logs/_doc\" \\\n  -H 'Content-Type: application/json' \\\n  -d @/var/log/community-scripts/install-*.log\n\n# Archive for compliance\ntar czf installation-records-$(date +%Y%m).tar.gz \\\n  /var/log/community-scripts/\ngpg --encrypt installation-records-*.tar.gz\n```\n\n---\n\n## Support & Issues\n\nWhen reporting installation issues, always include:\n\n```bash\n# Collect all relevant information\nexport dev_mode=\"logs\"\n# Run the failing installation\n# Then provide:\ntar czf debug-logs.tar.gz /var/log/community-scripts/\n```\n\nInclude the `debug-logs.tar.gz` when reporting issues for better diagnostics.\n"
  },
  {
    "path": "docs/EXIT_CODES.md",
    "content": "# Exit Code Reference\n\nComprehensive documentation of all exit codes used in ProxmoxVE scripts.\n\n## Table of Contents\n\n- [Generic/Shell Errors (1-255)](#genericshell-errors)\n- [Package Manager Errors (100-101, 255)](#package-manager-errors)\n- [Node.js/npm Errors (243-254)](#nodejsnpm-errors)\n- [Python/pip Errors (210-212)](#pythonpip-errors)\n- [Database Errors (231-254)](#database-errors)\n- [Proxmox Custom Codes (200-231)](#proxmox-custom-codes)\n\n---\n\n## Generic/Shell Errors\n\nStandard Unix/Linux exit codes used across all scripts.\n\n| Code    | Description                             | Common Causes                             | Solutions                                      |\n| ------- | --------------------------------------- | ----------------------------------------- | ---------------------------------------------- |\n| **1**   | General error / Operation not permitted | Permission denied, general failure        | Check user permissions, run as root if needed  |\n| **2**   | Misuse of shell builtins                | Syntax error in script                    | Review script syntax, check bash version       |\n| **126** | Command cannot execute                  | Permission problem, not executable        | `chmod +x script.sh` or check file permissions |\n| **127** | Command not found                       | Missing binary, wrong PATH                | Install required package, check PATH variable  |\n| **128** | Invalid argument to exit                | Invalid exit code passed                  | Use exit codes 0-255 only                      |\n| **130** | Terminated by Ctrl+C (SIGINT)           | User interrupted script                   | Expected behavior, no action needed            |\n| **137** | Killed (SIGKILL)                        | Out of memory, forced termination         | Check memory usage, increase RAM allocation    |\n| **139** | Segmentation fault                      | Memory access violation, corrupted binary | Reinstall package, check system stability      |\n| **143** | Terminated (SIGTERM)                    | Graceful shutdown signal                  | Expected during container stops                |\n\n---\n\n## Package Manager Errors\n\nAPT, DPKG, and package installation errors.\n\n| Code    | Description                | Common Causes                           | Solutions                                         |\n| ------- | -------------------------- | --------------------------------------- | ------------------------------------------------- |\n| **100** | APT: Package manager error | Broken packages, dependency conflicts   | `apt --fix-broken install`, `dpkg --configure -a` |\n| **101** | APT: Configuration error   | Malformed sources.list, bad repo config | Check `/etc/apt/sources.list`, run `apt update`   |\n| **255** | DPKG: Fatal internal error | Corrupted package database              | `dpkg --configure -a`, restore from backup        |\n\n---\n\n## Node.js/npm Errors\n\nNode.js runtime and package manager errors.\n\n| Code    | Description                                | Common Causes                  | Solutions                                      |\n| ------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------- |\n| **243** | Node.js: Out of memory                     | JavaScript heap exhausted      | Increase `--max-old-space-size`, optimize code |\n| **245** | Node.js: Invalid command-line option       | Wrong Node.js flags            | Check Node.js version, verify CLI options      |\n| **246** | Node.js: Internal JavaScript Parse Error   | Syntax error in JS code        | Review JavaScript syntax, check dependencies   |\n| **247** | Node.js: Fatal internal error              | Node.js runtime crash          | Update Node.js, check for known bugs           |\n| **248** | Node.js: Invalid C++ addon / N-API failure | Native module incompatibility  | Rebuild native modules, update packages        |\n| **249** | Node.js: Inspector error                   | Debug/inspect protocol failure | Disable inspector, check port conflicts        |\n| **254** | npm/pnpm/yarn: Unknown fatal error         | Package manager crash          | Clear cache, reinstall package manager         |\n\n---\n\n## Python/pip Errors\n\nPython runtime and package installation errors.\n\n| Code    | Description                          | Common Causes                           | Solutions                                                |\n| ------- | ------------------------------------ | --------------------------------------- | -------------------------------------------------------- |\n| **210** | Python: Virtualenv missing or broken | venv not created, corrupted environment | `python3 -m venv venv`, recreate virtualenv              |\n| **211** | Python: Dependency resolution failed | Conflicting package versions            | Use `pip install --upgrade`, check requirements.txt      |\n| **212** | Python: Installation aborted         | EXTERNALLY-MANAGED, permission denied   | Use `--break-system-packages` or venv, check permissions |\n\n---\n\n## Database Errors\n\n### PostgreSQL (231-234)\n\n| Code    | Description             | Common Causes                      | Solutions                                             |\n| ------- | ----------------------- | ---------------------------------- | ----------------------------------------------------- |\n| **231** | Connection failed       | Server not running, wrong socket   | `systemctl start postgresql`, check connection string |\n| **232** | Authentication failed   | Wrong credentials                  | Verify username/password, check `pg_hba.conf`         |\n| **233** | Database does not exist | Database not created               | `CREATE DATABASE`, restore from backup                |\n| **234** | Fatal error in query    | Syntax error, constraint violation | Review SQL syntax, check constraints                  |\n\n### MySQL/MariaDB (241-244)\n\n| Code    | Description             | Common Causes                      | Solutions                                            |\n| ------- | ----------------------- | ---------------------------------- | ---------------------------------------------------- |\n| **241** | Connection failed       | Server not running, wrong socket   | `systemctl start mysql`, check connection parameters |\n| **242** | Authentication failed   | Wrong credentials                  | Verify username/password, grant privileges           |\n| **243** | Database does not exist | Database not created               | `CREATE DATABASE`, restore from backup               |\n| **244** | Fatal error in query    | Syntax error, constraint violation | Review SQL syntax, check constraints                 |\n\n### MongoDB (251-254)\n\n| Code    | Description           | Common Causes        | Solutions                                  |\n| ------- | --------------------- | -------------------- | ------------------------------------------ |\n| **251** | Connection failed     | Server not running   | `systemctl start mongod`, check port 27017 |\n| **252** | Authentication failed | Wrong credentials    | Verify username/password, create user      |\n| **253** | Database not found    | Database not created | Database auto-created on first write       |\n| **254** | Fatal query error     | Invalid query syntax | Review MongoDB query syntax                |\n\n---\n\n## Proxmox Custom Codes\n\nCustom exit codes specific to ProxmoxVE scripts.\n\n### Container Creation Errors (200-209)\n\n| Code    | Description                                    | Common Causes                                           | Solutions                                               |\n| ------- | ---------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- |\n| **200** | Failed to create lock file                     | Permission denied, disk full                            | Check `/tmp` permissions, free disk space               |\n| **203** | Missing CTID variable                          | Script configuration error                              | Set CTID in script or via prompt                        |\n| **204** | Missing PCT_OSTYPE variable                    | Template selection failed                               | Verify template availability                            |\n| **205** | Invalid CTID (<100)                            | CTID below minimum value                                | Use CTID ≥ 100 (1-99 reserved for Proxmox)              |\n| **206** | CTID already in use                            | Container/VM with same ID exists                        | Check `pct list` and `/etc/pve/lxc/`, use different ID  |\n| **207** | Password contains unescaped special characters | Special chars like `-`, `/`, `\\`, `*` at start/end      | Avoid leading special chars, use alphanumeric passwords |\n| **208** | Invalid configuration                          | DNS format (`.home` vs `home`), MAC format (`-` vs `:`) | Remove leading dots from DNS, use `:` in MAC addresses  |\n| **209** | Container creation failed                      | Multiple possible causes                                | Check logs in `/tmp/pct_create_*.log`, verify template  |\n\n### Cluster & Storage Errors (210, 214, 217)\n\n| Code    | Description                       | Common Causes                      | Solutions                                                   |\n| ------- | --------------------------------- | ---------------------------------- | ----------------------------------------------------------- |\n| **210** | Cluster not quorate               | Cluster nodes down, network issues | Check cluster status: `pvecm status`, fix node connectivity |\n| **211** | Timeout waiting for template lock | Concurrent download in progress    | Wait for other download to complete (60s timeout)           |\n| **214** | Not enough storage space          | Disk full, quota exceeded          | Free disk space, increase storage allocation                |\n| **217** | Storage does not support rootdir  | Wrong storage type selected        | Use storage supporting containers (dir, zfspool, lvm-thin)  |\n\n### Container Verification Errors (215-216)\n\n| Code    | Description                      | Common Causes                    | Solutions                                                 |\n| ------- | -------------------------------- | -------------------------------- | --------------------------------------------------------- |\n| **215** | Container created but not listed | Ghost state, incomplete creation | Check `/etc/pve/lxc/CTID.conf`, remove manually if needed |\n| **216** | RootFS entry missing in config   | Incomplete container creation    | Delete container, retry creation                          |\n\n### Template Errors (218, 220-223, 225)\n\n| Code    | Description                               | Common Causes                                    | Solutions                                                   |\n| ------- | ----------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------- |\n| **218** | Template file corrupted or incomplete     | Download interrupted, file <1MB, invalid archive | Delete template, run `pveam update && pveam download`       |\n| **220** | Unable to resolve template path           | Template storage not accessible                  | Check storage availability, verify permissions              |\n| **221** | Template file exists but not readable     | Permission denied                                | `chmod 644 template.tar.zst`, check storage permissions     |\n| **222** | Template download failed after 3 attempts | Network issues, storage problems                 | Check internet connectivity, verify storage space           |\n| **223** | Template not available after download     | Storage sync issue, I/O delay                    | Wait a few seconds, verify storage is mounted               |\n| **225** | No template available for OS/Version      | Unsupported OS version, catalog outdated         | Run `pveam update`, check `pveam available -section system` |\n\n### LXC Stack Errors (231)\n\n| Code    | Description                    | Common Causes                               | Solutions                                    |\n| ------- | ------------------------------ | ------------------------------------------- | -------------------------------------------- |\n| **231** | LXC stack upgrade/retry failed | Outdated `pve-container`, Debian 13.1 issue | See [Debian 13.1 Fix Guide](#debian-131-fix) |\n\n---\n\n## Special Case: Debian 13.1 \"unsupported version\" Error\n\n### Problem\n\n```\nTASK ERROR: unable to create CT 129 - unsupported debian version '13.1'\n```\n\n### Root Cause\n\nOutdated `pve-container` package doesn't recognize Debian 13 (Trixie).\n\n### Solutions\n\n#### Option 1: Full System Upgrade (Recommended)\n\n```bash\napt update\napt full-upgrade -y\nreboot\n```\n\nVerify fix:\n\n```bash\ndpkg -l pve-container\n# PVE 8: Should show 5.3.3+\n# PVE 9: Should show 6.0.13+\n```\n\n#### Option 2: Update Only pve-container\n\n```bash\napt update\napt install --only-upgrade pve-container -y\n```\n\n**Warning:** If Proxmox fails to boot after this, your system was inconsistent. Perform Option 1 instead.\n\n#### Option 3: Verify Repository Configuration\n\nMany users disable Enterprise repos but forget to add no-subscription repos.\n\n**For PVE 9 (Trixie):**\n\n```bash\ncat /etc/apt/sources.list.d/pve-no-subscription.list\n```\n\nShould contain:\n\n```\ndeb http://download.proxmox.com/debian/pve trixie pve-no-subscription\ndeb http://download.proxmox.com/debian/ceph-squid trixie no-subscription\n```\n\n**For PVE 8 (Bookworm):**\n\n```\ndeb http://download.proxmox.com/debian/pve bookworm pve-no-subscription\ndeb http://download.proxmox.com/debian/ceph-quincy bookworm no-subscription\n```\n\nThen:\n\n```bash\napt update\napt full-upgrade -y\n```\n\n### Reference\n\nOfficial discussion: [GitHub #8126](https://github.com/community-scripts/ProxmoxVE/discussions/8126)\n\n---\n\n## Troubleshooting Tips\n\n### Finding Error Details\n\n1. **Check logs:**\n\n   ```bash\n   tail -n 50 /tmp/pct_create_*.log\n   ```\n\n2. **Enable verbose mode:**\n\n   ```bash\n   bash -x script.sh  # Shows every command executed\n   ```\n\n3. **Check container status:**\n\n   ```bash\n   pct list\n   pct status CTID\n   ```\n\n4. **Verify storage:**\n   ```bash\n   pvesm status\n   df -h\n   ```\n\n### Common Patterns\n\n- **Exit 0 with error message:** Configuration validation failed (check DNS, MAC, password format)\n- **Exit 206 but container not visible:** Ghost container state - check `/etc/pve/lxc/` manually\n- **Exit 209 generic error:** Check `/tmp/pct_create_*.log` for specific `pct create` failure reason\n- **Exit 218 or 222:** Template issues - delete and re-download template\n\n---\n\n## Quick Reference Chart\n\n| Exit Code Range | Category           | Typical Issue                               |\n| --------------- | ------------------ | ------------------------------------------- |\n| 1-2, 126-143    | Shell/System       | Permissions, signals, missing commands      |\n| 100-101, 255    | Package Manager    | APT/DPKG errors, broken packages            |\n| 200-209         | Container Creation | CTID, password, configuration               |\n| 210-217         | Storage/Cluster    | Disk space, quorum, storage type            |\n| 218-225         | Templates          | Download, corruption, availability          |\n| 231-254         | Databases/Runtime  | PostgreSQL, MySQL, MongoDB, Node.js, Python |\n\n---\n\n## Contributing\n\nFound an undocumented exit code or have a solution to share? Please:\n\n1. Open an issue on [GitHub](https://github.com/community-scripts/ProxmoxVE/issues)\n2. Include:\n   - Exit code number\n   - Error message\n   - Steps to reproduce\n   - Solution that worked for you\n\n---\n\n_Last updated: November 2025_\n_ProxmoxVE Version: 2.x_\n"
  },
  {
    "path": "docs/README.md",
    "content": "# 📚 ProxmoxVE Documentation\n\nComplete guide to all ProxmoxVE documentation - quickly find what you need.\n\n---\n\n## 🎯 **Quick Navigation by Goal**\n\n### 👤 **I want to...**\n\n**Contribute a new application**\n→ Start with: [contribution/README.md](contribution/README.md)\n→ Then: [ct/DETAILED_GUIDE.md](ct/DETAILED_GUIDE.md) + [install/DETAILED_GUIDE.md](install/DETAILED_GUIDE.md)\n\n**Understand the architecture**\n→ Read: [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md)\n→ Then: [misc/README.md](misc/README.md)\n\n**Debug a failed installation**\n→ Check: [EXIT_CODES.md](EXIT_CODES.md)\n→ Then: [DEV_MODE.md](DEV_MODE.md)\n→ See also: [misc/error_handler.func/](misc/error_handler.func/)\n\n**Configure system defaults**\n→ Read: [guides/DEFAULTS_SYSTEM_GUIDE.md](guides/DEFAULTS_SYSTEM_GUIDE.md)\n\n**Deploy containers automatically**\n→ Read: [guides/UNATTENDED_DEPLOYMENTS.md](guides/UNATTENDED_DEPLOYMENTS.md)\n\n**Develop a function library**\n→ Study: [misc/](misc/) documentation\n\n---\n\n## 👤 **Quick Start by Role**\n\n### **I'm a...**\n\n**New Contributor**\n→ Start: [contribution/README.md](contribution/README.md)\n→ Then: Choose your path below\n\n**Container Creator**\n→ Read: [ct/README.md](ct/README.md)\n→ Deep Dive: [ct/DETAILED_GUIDE.md](ct/DETAILED_GUIDE.md)\n→ Reference: [misc/build.func/](misc/build.func/)\n\n**Installation Script Developer**\n→ Read: [install/README.md](install/README.md)\n→ Deep Dive: [install/DETAILED_GUIDE.md](install/DETAILED_GUIDE.md)\n→ Reference: [misc/tools.func/](misc/tools.func/)\n\n**VM Provisioner**\n→ Read: [vm/README.md](vm/README.md)\n→ Reference: [misc/cloud-init.func/](misc/cloud-init.func/)\n\n**Tools Developer**\n→ Read: [tools/README.md](tools/README.md)\n→ Reference: [misc/build.func/](misc/build.func/)\n\n**API Integrator**\n→ Read: [api/README.md](api/README.md)\n→ Reference: [misc/api.func/](misc/api.func/)\n\n**System Operator**\n→ Start: [EXIT_CODES.md](EXIT_CODES.md)\n→ Then: [guides/DEFAULTS_SYSTEM_GUIDE.md](guides/DEFAULTS_SYSTEM_GUIDE.md)\n→ Automate: [guides/UNATTENDED_DEPLOYMENTS.md](guides/UNATTENDED_DEPLOYMENTS.md)\n→ Debug: [DEV_MODE.md](DEV_MODE.md)\n\n**Architect**\n→ Read: [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md)\n→ Deep Dive: [misc/README.md](misc/README.md)\n\n---\n\n## 📂 **Documentation Structure**\n\n### Project-Mirrored Directories\n\nEach major project directory has documentation:\n\n```\nProxmoxVE/\n├─ ct/                 ↔ docs/ct/ (README.md + DETAILED_GUIDE.md)\n├─ install/           ↔ docs/install/ (README.md + DETAILED_GUIDE.md)\n├─ vm/                ↔ docs/vm/ (README.md)\n├─ tools/            ↔ docs/tools/ (README.md)\n├─ api/              ↔ docs/api/ (README.md)\n├─ misc/             ↔ docs/misc/ (9 function libraries)\n└─ [system-wide]     ↔ docs/guides/ (configuration & deployment guides)\n```\n\n### Core Documentation\n\n| Document | Purpose | Audience |\n|----------|---------|----------|\n| [contribution/README.md](contribution/README.md) | How to contribute | Contributors |\n| [ct/DETAILED_GUIDE.md](ct/DETAILED_GUIDE.md) | Create ct scripts | Container developers |\n| [install/DETAILED_GUIDE.md](install/DETAILED_GUIDE.md) | Create install scripts | Installation developers |\n| [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md) | Architecture deep-dive | Architects, advanced users |\n| [guides/DEFAULTS_SYSTEM_GUIDE.md](guides/DEFAULTS_SYSTEM_GUIDE.md) | Configuration system | Operators, power users |\n| [guides/CONFIGURATION_REFERENCE.md](guides/CONFIGURATION_REFERENCE.md) | Configuration options reference | Advanced users |\n| [guides/UNATTENDED_DEPLOYMENTS.md](guides/UNATTENDED_DEPLOYMENTS.md) | Automated deployments | DevOps, automation |\n| [EXIT_CODES.md](EXIT_CODES.md) | Exit code reference | Troubleshooters |\n| [DEV_MODE.md](DEV_MODE.md) | Debugging tools | Developers |\n\n---\n\n## 📂 **Directory Guide**\n\n### [ct/](ct/) - Container Scripts\nDocumentation for `/ct` - Container creation scripts that run on the Proxmox host.\n\n**Includes**:\n- Overview of container creation process\n- Deep dive: [DETAILED_GUIDE.md](ct/DETAILED_GUIDE.md) - Complete reference with examples\n- Reference to [misc/build.func/](misc/build.func/)\n- Quick start for creating new containers\n\n### [install/](install/) - Installation Scripts\nDocumentation for `/install` - Scripts that run inside containers to install applications.\n\n**Includes**:\n- Overview of 10-phase installation pattern\n- Deep dive: [DETAILED_GUIDE.md](install/DETAILED_GUIDE.md) - Complete reference with examples\n- Reference to [misc/tools.func/](misc/tools.func/)\n- Alpine vs Debian differences\n\n### [vm/](vm/) - Virtual Machine Scripts\nDocumentation for `/vm` - VM creation scripts using cloud-init provisioning.\n\n**Includes**:\n- Overview of VM provisioning\n- Link to [misc/cloud-init.func/](misc/cloud-init.func/)\n- VM vs Container comparison\n- Cloud-init examples\n\n### [tools/](tools/) - Tools & Utilities\nDocumentation for `/tools` - Management tools and add-ons.\n\n**Includes**:\n- Overview of tools structure\n- Integration points\n- Contributing new tools\n- Common operations\n\n### [api/](api/) - API Integration\nDocumentation for `/api` - Telemetry and API backend.\n\n**Includes**:\n- API overview\n- Integration methods\n- API endpoints\n- Privacy information\n\n### [misc/](misc/) - Function Libraries\nDocumentation for `/misc` - 9 core function libraries with complete references.\n\n**Contains**:\n- **build.func/** - Container orchestration (7 files)\n- **core.func/** - Utilities and messaging (5 files)\n- **error_handler.func/** - Error handling (5 files)\n- **api.func/** - API integration (5 files)\n- **install.func/** - Container setup (5 files)\n- **tools.func/** - Package installation (6 files)\n- **alpine-install.func/** - Alpine setup (5 files)\n- **alpine-tools.func/** - Alpine tools (5 files)\n- **cloud-init.func/** - VM provisioning (5 files)\n\n---\n\n## 🎓 **Learning Paths**\n\n### Path 1: First-Time Contributor (2-3 hours)\n\n1. [contribution/README.md](contribution/README.md) - Quick Start\n2. Pick your area:\n   - Containers → [ct/README.md](ct/README.md) + [ct/DETAILED_GUIDE.md](ct/DETAILED_GUIDE.md)\n   - Installation → [install/README.md](install/README.md) + [install/DETAILED_GUIDE.md](install/DETAILED_GUIDE.md)\n   - VMs → [vm/README.md](vm/README.md)\n3. Study existing similar script\n4. Create your contribution\n5. Submit PR\n\n### Path 2: Intermediate Developer (4-6 hours)\n\n1. [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md)\n2. Dive into function libraries:\n   - [misc/build.func/README.md](misc/build.func/README.md)\n   - [misc/tools.func/README.md](misc/tools.func/README.md)\n   - [misc/install.func/README.md](misc/install.func/README.md)\n3. Study advanced examples\n4. Create complex applications\n\n### Path 3: Advanced Architect (8+ hours)\n\n1. All of Intermediate Path\n2. Study all 9 function libraries in depth\n3. [guides/DEFAULTS_SYSTEM_GUIDE.md](guides/DEFAULTS_SYSTEM_GUIDE.md) - Configuration system\n4. [DEV_MODE.md](DEV_MODE.md) - Debugging and development\n5. Design new features or function libraries\n\n### Path 4: Troubleshooter (30 minutes - 1 hour)\n\n1. [EXIT_CODES.md](EXIT_CODES.md) - Find error code\n2. [DEV_MODE.md](DEV_MODE.md) - Run with debugging\n3. Check relevant function library docs\n4. Review logs and fix\n\n---\n\n## 📊 **By the Numbers**\n\n| Metric | Count |\n|--------|:---:|\n| **Documentation Files** | 63 |\n| **Total Lines** | 15,000+ |\n| **Function Libraries** | 9 |\n| **Functions Documented** | 150+ |\n| **Code Examples** | 50+ |\n| **Flowcharts** | 15+ |\n| **Do/Don't Sections** | 20+ |\n| **Real-World Examples** | 30+ |\n\n---\n\n## 🔍 **Find It Fast**\n\n### By Feature\n- **How do I create a container?** → [ct/DETAILED_GUIDE.md](ct/DETAILED_GUIDE.md)\n- **How do I create an install script?** → [install/DETAILED_GUIDE.md](install/DETAILED_GUIDE.md)\n- **How do I create a VM?** → [vm/README.md](vm/README.md)\n- **How do I install Node.js?** → [misc/tools.func/](misc/tools.func/)\n- **How do I debug?** → [DEV_MODE.md](DEV_MODE.md)\n\n### By Error\n- **Exit code 206?** → [EXIT_CODES.md](EXIT_CODES.md)\n- **Network failed?** → [misc/install.func/](misc/install.func/)\n- **Package error?** → [misc/tools.func/](misc/tools.func/)\n\n### By Role\n- **Contributor** → [contribution/README.md](contribution/README.md)\n- **Operator** → [guides/DEFAULTS_SYSTEM_GUIDE.md](guides/DEFAULTS_SYSTEM_GUIDE.md)\n- **Automation** → [guides/UNATTENDED_DEPLOYMENTS.md](guides/UNATTENDED_DEPLOYMENTS.md)\n- **Developer** → [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md)\n- **Architect** → [misc/README.md](misc/README.md)\n\n---\n\n## ✅ **Documentation Features**\n\n- ✅ **Project-mirrored structure** - Organized like the actual project\n- ✅ **Complete function references** - Every function documented\n- ✅ **Real-world examples** - Copy-paste ready code\n- ✅ **Visual flowcharts** - ASCII diagrams of workflows\n- ✅ **Integration guides** - How components connect\n- ✅ **Troubleshooting** - Common issues and solutions\n- ✅ **Best practices** - DO/DON'T sections throughout\n- ✅ **Learning paths** - Structured curriculum by role\n- ✅ **Quick references** - Fast lookup by error code\n- ✅ **Comprehensive navigation** - This page\n\n---\n\n## 🚀 **Start Here**\n\n**New to ProxmoxVE?** → [contribution/README.md](contribution/README.md)\n\n**Looking for something specific?** → Choose your role above or browse by directory\n\n**Need to debug?** → [EXIT_CODES.md](EXIT_CODES.md)\n\n**Want to understand architecture?** → [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md)\n\n---\n\n## 🤝 **Contributing Documentation**\n\nFound an error? Want to improve docs?\n\n1. See: [contribution/README.md](contribution/README.md) for full contribution guide\n2. Open issue: [GitHub Issues](https://github.com/community-scripts/ProxmoxVE/issues)\n3. Or submit PR with improvements\n\n---\n\n## 📝 **Status**\n\n- **Last Updated**: December 2025\n- **Version**: 2.3 (Consolidated & Reorganized)\n- **Completeness**: ✅ 100% - All components documented\n- **Quality**: ✅ Production-ready\n- **Structure**: ✅ Clean and organized\n\n---\n\n**Welcome to ProxmoxVE! Start with [CONTRIBUTION_GUIDE.md](CONTRIBUTION_GUIDE.md) or choose your role above.** 🚀\n"
  },
  {
    "path": "docs/TECHNICAL_REFERENCE.md",
    "content": "# Technical Reference: Configuration System Architecture\n\n> **For Developers and Advanced Users**\n>\n> _Deep dive into how the defaults and configuration system works_\n\n---\n\n## Table of Contents\n\n1. [System Architecture](#system-architecture)\n2. [File Format Specifications](#file-format-specifications)\n3. [Function Reference](#function-reference)\n4. [Variable Precedence](#variable-precedence)\n5. [Data Flow Diagrams](#data-flow-diagrams)\n6. [Security Model](#security-model)\n7. [Implementation Details](#implementation-details)\n\n---\n\n## System Architecture\n\n### Component Overview\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                    Installation Script                       │\n│  (pihole-install.sh, docker-install.sh, etc.)              │\n└────────────────────┬────────────────────────────────────────┘\n                     │\n                     v\n┌─────────────────────────────────────────────────────────────┐\n│                   build.func Library                         │\n│  ┌──────────────────────────────────────────────────────┐   │\n│  │  variables()                                         │   │\n│  │  - Initialize NSAPP, var_install, etc.             │   │\n│  └──────────────────────────────────────────────────────┘   │\n│  ┌──────────────────────────────────────────────────────┐   │\n│  │  install_script()                                    │   │\n│  │  - Display mode menu                                │   │\n│  │  - Route to appropriate workflow                    │   │\n│  └──────────────────────────────────────────────────────┘   │\n│  ┌──────────────────────────────────────────────────────┐   │\n│  │  base_settings()                                     │   │\n│  │  - Apply built-in defaults                          │   │\n│  │  - Read environment variables (var_*)               │   │\n│  └──────────────────────────────────────────────────────┘   │\n│  ┌──────────────────────────────────────────────────────┐   │\n│  │  load_vars_file()                                    │   │\n│  │  - Safe file parsing (NO source/eval)              │   │\n│  │  - Whitelist validation                             │   │\n│  │  - Value sanitization                               │   │\n│  └──────────────────────────────────────────────────────┘   │\n│  ┌──────────────────────────────────────────────────────┐   │\n│  │  default_var_settings()                              │   │\n│  │  - Load user defaults                               │   │\n│  │  - Display summary                                  │   │\n│  └──────────────────────────────────────────────────────┘   │\n│  ┌──────────────────────────────────────────────────────┐   │\n│  │  maybe_offer_save_app_defaults()                     │   │\n│  │  - Offer to save current settings                   │   │\n│  │  - Handle updates vs. new saves                     │   │\n│  └──────────────────────────────────────────────────────┘   │\n└─────────────────────────────────────────────────────────────┘\n                     │\n                     v\n┌─────────────────────────────────────────────────────────────┐\n│           Configuration Files (on Disk)                      │\n│  ┌──────────────────────────────────────────────────────┐   │\n│  │  /usr/local/community-scripts/default.vars          │   │\n│  │  (User global defaults)                             │   │\n│  └──────────────────────────────────────────────────────┘   │\n│  ┌──────────────────────────────────────────────────────┐   │\n│  │  /usr/local/community-scripts/defaults/*.vars       │   │\n│  │  (App-specific defaults)                            │   │\n│  └──────────────────────────────────────────────────────┘   │\n└─────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## File Format Specifications\n\n### User Defaults: `default.vars`\n\n**Location**: `/usr/local/community-scripts/default.vars`\n\n**MIME Type**: `text/plain`\n\n**Encoding**: UTF-8 (no BOM)\n\n**Format Specification**:\n\n```\n# File Format: Simple key=value pairs\n# Purpose: Store global user defaults\n# Security: Sanitized values, whitelist validation\n\n# Comments and blank lines are ignored\n# Line format: var_name=value\n# No spaces around the equals sign\n# String values do not need quoting (but may be quoted)\n\n[CONTENT]\nvar_cpu=4\nvar_ram=2048\nvar_disk=20\nvar_hostname=mydefault\nvar_brg=vmbr0\nvar_gateway=192.168.1.1\n```\n\n**Formal Grammar**:\n\n```\nFILE       := (BLANK_LINE | COMMENT_LINE | VAR_LINE)*\nBLANK_LINE := \\n\nCOMMENT_LINE := '#' [^\\n]* \\n\nVAR_LINE   := VAR_NAME '=' VAR_VALUE \\n\nVAR_NAME   := 'var_' [a-z_]+\nVAR_VALUE  := [^\\n]*  # Any printable characters except newline\n```\n\n**Constraints**:\n\n| Constraint        | Value                    |\n| ----------------- | ------------------------ |\n| Max file size     | 64 KB                    |\n| Max line length   | 1024 bytes               |\n| Max variables     | 100                      |\n| Allowed var names | `var_[a-z_]+`            |\n| Value validation  | Whitelist + Sanitization |\n\n**Example Valid File**:\n\n```bash\n# Global User Defaults\n# Created: 2024-11-28\n\n# Resource defaults\nvar_cpu=4\nvar_ram=2048\nvar_disk=20\n\n# Network defaults\nvar_brg=vmbr0\nvar_gateway=192.168.1.1\nvar_mtu=1500\nvar_vlan=100\n\n# System defaults\nvar_timezone=Europe/Berlin\nvar_hostname=default-container\n\n# Storage\nvar_container_storage=local\nvar_template_storage=local\n\n# Security\nvar_ssh=yes\nvar_protection=0\nvar_unprivileged=1\n```\n\n### App Defaults: `<app>.vars`\n\n**Location**: `/usr/local/community-scripts/defaults/<appname>.vars`\n\n**Format**: Identical to `default.vars`\n\n**Naming Convention**: `<nsapp>.vars`\n\n- `nsapp` = lowercase app name with spaces removed\n- Examples:\n  - `pihole` → `pihole.vars`\n  - `opnsense` → `opnsense.vars`\n  - `docker compose` → `dockercompose.vars`\n\n**Example App Defaults**:\n\n```bash\n# App-specific defaults for PiHole (pihole)\n# Generated on 2024-11-28T15:32:00Z\n# These override user defaults when installing pihole\n\nvar_unprivileged=1\nvar_cpu=2\nvar_ram=1024\nvar_disk=10\nvar_brg=vmbr0\nvar_net=veth\nvar_gateway=192.168.1.1\nvar_hostname=pihole\nvar_timezone=Europe/Berlin\nvar_container_storage=local\nvar_template_storage=local\nvar_tags=dns,pihole\n```\n\n---\n\n## Function Reference\n\n### `load_vars_file()`\n\n**Purpose**: Safely load variables from .vars files without using `source` or `eval`\n\n**Signature**:\n\n```bash\nload_vars_file(filepath)\n```\n\n**Parameters**:\n\n| Param    | Type   | Required | Example                                     |\n| -------- | ------ | -------- | ------------------------------------------- |\n| filepath | String | Yes      | `/usr/local/community-scripts/default.vars` |\n\n**Returns**:\n\n- `0` on success\n- `1` on error (file missing, parse error, etc.)\n\n**Environment Side Effects**:\n\n- Sets all parsed `var_*` variables as shell variables\n- Does NOT unset variables if file missing (safe)\n- Does NOT affect other variables\n\n**Implementation Pattern**:\n\n```bash\nload_vars_file() {\n  local file=\"$1\"\n\n  # File must exist\n  [ -f \"$file\" ] || return 0\n\n  # Parse line by line (not with source/eval)\n  local line key val\n  while IFS='=' read -r key val || [ -n \"$key\" ]; do\n    # Skip comments and empty lines\n    [[ \"$key\" =~ ^[[:space:]]*# ]] && continue\n    [[ -z \"$key\" ]] && continue\n\n    # Validate key is in whitelist\n    _is_whitelisted_key \"$key\" || continue\n\n    # Sanitize and export value\n    val=\"$(_sanitize_value \"$val\")\"\n    [ $? -eq 0 ] && export \"$key=$val\"\n  done < \"$file\"\n\n  return 0\n}\n```\n\n**Usage Examples**:\n\n```bash\n# Load user defaults\nload_vars_file \"/usr/local/community-scripts/default.vars\"\n\n# Load app-specific defaults\nload_vars_file \"$(get_app_defaults_path)\"\n\n# Check if successful\nif load_vars_file \"$vars_path\"; then\n  echo \"Settings loaded successfully\"\nelse\n  echo \"Failed to load settings\"\nfi\n\n# Values are now available as variables\necho \"Using $var_cpu cores\"\necho \"Allocating ${var_ram} MB RAM\"\n```\n\n---\n\n### `get_app_defaults_path()`\n\n**Purpose**: Get the full path for app-specific defaults file\n\n**Signature**:\n\n```bash\nget_app_defaults_path()\n```\n\n**Parameters**: None\n\n**Returns**:\n\n- String: Full path to app defaults file\n\n**Implementation**:\n\n```bash\nget_app_defaults_path() {\n  local n=\"${NSAPP:-${APP,,}}\"\n  echo \"/usr/local/community-scripts/defaults/${n}.vars\"\n}\n```\n\n**Usage Examples**:\n\n```bash\n# Get app defaults path\napp_defaults=\"$(get_app_defaults_path)\"\necho \"App defaults at: $app_defaults\"\n\n# Check if app defaults exist\nif [ -f \"$(get_app_defaults_path)\" ]; then\n  echo \"App defaults available\"\nfi\n\n# Load app defaults\nload_vars_file \"$(get_app_defaults_path)\"\n```\n\n---\n\n### `default_var_settings()`\n\n**Purpose**: Load and display user global defaults\n\n**Signature**:\n\n```bash\ndefault_var_settings()\n```\n\n**Parameters**: None\n\n**Returns**:\n\n- `0` on success\n- `1` on error\n\n**Workflow**:\n\n```\n1. Find default.vars location\n   (usually /usr/local/community-scripts/default.vars)\n\n2. Create if missing\n\n3. Load variables from file\n\n4. Map var_verbose → VERBOSE variable\n\n5. Call base_settings (apply to container config)\n\n6. Call echo_default (display summary)\n```\n\n**Implementation Pattern**:\n\n```bash\ndefault_var_settings() {\n  local VAR_WHITELIST=(\n    var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_gpu\n    var_gateway var_hostname var_ipv6_method var_mac var_mtu\n    var_net var_ns var_pw var_ram var_tags var_tun var_unprivileged\n    var_verbose var_vlan var_ssh var_ssh_authorized_key\n    var_container_storage var_template_storage\n  )\n\n  # Ensure file exists\n  _ensure_default_vars\n\n  # Find and load\n  local dv=\"$(_find_default_vars)\"\n  load_vars_file \"$dv\"\n\n  # Map verbose flag\n  if [[ -n \"${var_verbose:-}\" ]]; then\n    case \"${var_verbose,,}\" in\n      1 | yes | true | on) VERBOSE=\"yes\" ;;\n      *) VERBOSE=\"${var_verbose}\" ;;\n    esac\n  fi\n\n  # Apply and display\n  base_settings \"$VERBOSE\"\n  echo_default\n}\n```\n\n---\n\n### `maybe_offer_save_app_defaults()`\n\n**Purpose**: Offer to save current settings as app-specific defaults\n\n**Signature**:\n\n```bash\nmaybe_offer_save_app_defaults()\n```\n\n**Parameters**: None\n\n**Returns**: None (side effects only)\n\n**Behavior**:\n\n1. After advanced installation completes\n2. Offers user: \"Save as App Defaults for <APP>?\"\n3. If yes:\n   - Saves to `/usr/local/community-scripts/defaults/<app>.vars`\n   - Only whitelisted variables included\n   - Previous defaults backed up (if exists)\n4. If no:\n   - No action taken\n\n**Flow**:\n\n```bash\nmaybe_offer_save_app_defaults() {\n  local app_vars_path=\"$(get_app_defaults_path)\"\n\n  # Build current settings from memory\n  local new_tmp=\"$(_build_current_app_vars_tmp)\"\n\n  # Check if already exists\n  if [ -f \"$app_vars_path\" ]; then\n    # Show diff and ask: Update? Keep? View Diff?\n    _show_app_defaults_diff_menu \"$new_tmp\" \"$app_vars_path\"\n  else\n    # New defaults - just save\n    if whiptail --yesno \"Save as App Defaults for $APP?\" 10 60; then\n      mv \"$new_tmp\" \"$app_vars_path\"\n      chmod 644 \"$app_vars_path\"\n    fi\n  fi\n}\n```\n\n---\n\n### `_sanitize_value()`\n\n**Purpose**: Remove dangerous characters/patterns from configuration values\n\n**Signature**:\n\n```bash\n_sanitize_value(value)\n```\n\n**Parameters**:\n\n| Param | Type   | Required |\n| ----- | ------ | -------- |\n| value | String | Yes      |\n\n**Returns**:\n\n- `0` (success) + sanitized value on stdout\n- `1` (failure) + nothing if dangerous\n\n**Dangerous Patterns**:\n\n| Pattern   | Threat               | Example              |\n| --------- | -------------------- | -------------------- |\n| `$(...)`  | Command substitution | `$(rm -rf /)`        |\n| `` ` ` `` | Command substitution | `` `whoami` ``       |\n| `;`       | Command separator    | `value; rm -rf /`    |\n| `&`       | Background execution | `value & malicious`  |\n| `<(`      | Process substitution | `<(cat /etc/passwd)` |\n\n**Implementation**:\n\n```bash\n_sanitize_value() {\n  case \"$1\" in\n  *'$('* | *'`'* | *';'* | *'&'* | *'<('*)\n    echo \"\"\n    return 1  # Reject dangerous value\n    ;;\n  esac\n  echo \"$1\"\n  return 0\n}\n```\n\n**Usage Examples**:\n\n```bash\n# Safe value\n_sanitize_value \"192.168.1.1\"  # Returns: 192.168.1.1 (status: 0)\n\n# Dangerous value\n_sanitize_value \"$(whoami)\"     # Returns: (empty) (status: 1)\n\n# Usage in code\nif val=\"$(_sanitize_value \"$user_input\")\"; then\n  export var_hostname=\"$val\"\nelse\n  msg_error \"Invalid value: contains dangerous characters\"\nfi\n```\n\n---\n\n### `_is_whitelisted_key()`\n\n**Purpose**: Check if variable name is in allowed whitelist\n\n**Signature**:\n\n```bash\n_is_whitelisted_key(key)\n```\n\n**Parameters**:\n\n| Param | Type   | Required | Example   |\n| ----- | ------ | -------- | --------- |\n| key   | String | Yes      | `var_cpu` |\n\n**Returns**:\n\n- `0` if key is whitelisted\n- `1` if key is NOT whitelisted\n\n**Implementation**:\n\n```bash\n_is_whitelisted_key() {\n  local k=\"$1\"\n  local w\n  for w in \"${VAR_WHITELIST[@]}\"; do\n    [ \"$k\" = \"$w\" ] && return 0\n  done\n  return 1\n}\n```\n\n**Usage Examples**:\n\n```bash\n# Check if variable can be saved\nif _is_whitelisted_key \"var_cpu\"; then\n  echo \"var_cpu can be saved\"\nfi\n\n# Reject unknown variables\nif ! _is_whitelisted_key \"var_custom\"; then\n  msg_error \"var_custom is not supported\"\nfi\n```\n\n---\n\n## Variable Precedence\n\n### Loading Order\n\nWhen a container is being created, variables are resolved in this order:\n\n```\nStep 1: Read ENVIRONMENT VARIABLES\n   ├─ Check if var_cpu is already set in shell environment\n   ├─ Check if var_ram is already set\n   └─ ...all var_* variables\n\nStep 2: Load APP-SPECIFIC DEFAULTS\n   ├─ Check if /usr/local/community-scripts/defaults/pihole.vars exists\n   ├─ Load all var_* from that file\n   └─ These override built-ins but NOT environment variables\n\nStep 3: Load USER GLOBAL DEFAULTS\n   ├─ Check if /usr/local/community-scripts/default.vars exists\n   ├─ Load all var_* from that file\n   └─ These override built-ins but NOT app-specific\n\nStep 4: Use BUILT-IN DEFAULTS\n   └─ Hardcoded in script (lowest priority)\n```\n\n### Precedence Examples\n\n**Example 1: Environment Variable Wins**\n\n```bash\n# Shell environment has highest priority\n$ export var_cpu=16\n$ bash pihole-install.sh\n\n# Result: Container gets 16 cores\n# (ignores app defaults, user defaults, built-ins)\n```\n\n**Example 2: App Defaults Override User Defaults**\n\n```bash\n# User Defaults: var_cpu=4\n# App Defaults: var_cpu=2\n$ bash pihole-install.sh\n\n# Result: Container gets 2 cores\n# (app-specific setting takes precedence)\n```\n\n**Example 3: All Defaults Missing (Built-ins Used)**\n\n```bash\n# No environment variables set\n# No app defaults file\n# No user defaults file\n$ bash pihole-install.sh\n\n# Result: Uses built-in defaults\n# (var_cpu might be 2 by default)\n```\n\n### Implementation in Code\n\n```bash\n# Typical pattern in build.func\n\nbase_settings() {\n  # Priority 1: Environment variables (already set if export used)\n  CT_TYPE=${var_unprivileged:-\"1\"}          # Use existing or default\n\n  # Priority 2: Load app defaults (may override above)\n  if [ -f \"$(get_app_defaults_path)\" ]; then\n    load_vars_file \"$(get_app_defaults_path)\"\n  fi\n\n  # Priority 3: Load user defaults\n  if [ -f \"/usr/local/community-scripts/default.vars\" ]; then\n    load_vars_file \"/usr/local/community-scripts/default.vars\"\n  fi\n\n  # Priority 4: Apply built-in defaults (lowest)\n  CORE_COUNT=${var_cpu:-\"${APP_CPU_DEFAULT:-2}\"}\n  RAM_SIZE=${var_ram:-\"${APP_RAM_DEFAULT:-1024}\"}\n\n  # Result: var_cpu has been set through precedence chain\n}\n```\n\n---\n\n## Data Flow Diagrams\n\n### Installation Flow: Advanced Settings\n\n```\n┌──────────────┐\n│  Start Script│\n└──────┬───────┘\n       │\n       v\n┌──────────────────────────────┐\n│ Display Installation Mode    │\n│ Menu (5 options)             │\n└──────┬───────────────────────┘\n       │ User selects \"Advanced Settings\"\n       v\n┌──────────────────────────────────┐\n│ Call: base_settings()            │\n│ (Apply built-in defaults)        │\n└──────┬───────────────────────────┘\n       │\n       v\n┌──────────────────────────────────┐\n│ Call: advanced_settings()        │\n│ (Show 19-step wizard)            │\n│ - Ask CPU, RAM, Disk, Network... │\n└──────┬───────────────────────────┘\n       │\n       v\n┌──────────────────────────────────┐\n│ Show Summary                     │\n│ Review all chosen values         │\n└──────┬───────────────────────────┘\n       │ User confirms\n       v\n┌──────────────────────────────────┐\n│ Create Container                 │\n│ Using current variable values    │\n└──────┬───────────────────────────┘\n       │\n       v\n┌──────────────────────────────────┐\n│ Installation Complete            │\n└──────┬───────────────────────────┘\n       │\n       v\n┌──────────────────────────────────────┐\n│ Offer: Save as App Defaults?         │\n│ (Save current settings)              │\n└──────┬───────────────────────────────┘\n       │\n       ├─ YES → Save to defaults/<app>.vars\n       │\n       └─ NO  → Exit\n```\n\n### Variable Resolution Flow\n\n```\nCONTAINER CREATION STARTED\n         │\n         v\n   ┌─────────────────────┐\n   │ Check ENVIRONMENT   │\n   │ for var_cpu, var_..│\n   └──────┬──────────────┘\n          │ Found? Use them (Priority 1)\n          │ Not found? Continue...\n          v\n   ┌──────────────────────────┐\n   │ Load App Defaults        │\n   │ /defaults/<app>.vars     │\n   └──────┬───────────────────┘\n          │ File exists? Parse & load (Priority 2)\n          │ Not found? Continue...\n          v\n   ┌──────────────────────────┐\n   │ Load User Defaults       │\n   │ /default.vars            │\n   └──────┬───────────────────┘\n          │ File exists? Parse & load (Priority 3)\n          │ Not found? Continue...\n          v\n   ┌──────────────────────────┐\n   │ Use Built-in Defaults    │\n   │ (Hardcoded values)       │\n   └──────┬───────────────────┘\n          │\n          v\n   ┌──────────────────────────┐\n   │ All Variables Resolved   │\n   │ Ready for container      │\n   │ creation                 │\n   └──────────────────────────┘\n```\n\n---\n\n## Security Model\n\n### Threat Model\n\n| Threat                       | Mitigation                                        |\n| ---------------------------- | ------------------------------------------------- |\n| **Arbitrary Code Execution** | No `source` or `eval`; manual parsing only        |\n| **Variable Injection**       | Whitelist of allowed variable names               |\n| **Command Substitution**     | `_sanitize_value()` blocks `$()`, backticks, etc. |\n| **Path Traversal**           | Files locked to `/usr/local/community-scripts/`   |\n| **Permission Escalation**    | Files created with restricted permissions         |\n| **Information Disclosure**   | Sensitive variables not logged                    |\n\n### Security Controls\n\n#### 1. Input Validation\n\n```bash\n# Only specific variables allowed\nif ! _is_whitelisted_key \"$key\"; then\n  skip_this_variable\nfi\n\n# Values sanitized\nif ! val=\"$(_sanitize_value \"$value\")\"; then\n  reject_entire_line\nfi\n```\n\n#### 2. Safe File Parsing\n\n```bash\n# ❌ DANGEROUS (OLD)\nsource /path/to/config.conf\n# Could execute: rm -rf / or any code\n\n# ✅ SAFE (NEW)\nload_vars_file \"/path/to/config.conf\"\n# Only reads var_name=value pairs, no execution\n```\n\n#### 3. Whitelisting\n\n```bash\n# Only these variables can be configured\nvar_cpu, var_ram, var_disk, var_brg, ...\nvar_hostname, var_pw, var_ssh, ...\n\n# NOT allowed:\nvar_malicious, var_hack, custom_var, ...\n```\n\n#### 4. Value Constraints\n\n```bash\n# No command injection patterns\nif [[ \"$value\" =~ ($|`|;|&|<\\() ]]; then\n  reject_value\nfi\n```\n\n---\n\n## Implementation Details\n\n### Module: `build.func`\n\n**Load Order** (in actual scripts):\n\n1. `#!/usr/bin/env bash` - Shebang\n2. `source /dev/stdin <<<$(curl ... api.func)` - API functions\n3. `source /dev/stdin <<<$(curl ... build.func)` - Build functions\n4. `variables()` - Initialize variables\n5. `check_root()` - Security check\n6. `install_script()` - Main flow\n\n**Key Sections**:\n\n```bash\n# Section 1: Initialization & Variables\n- variables()\n- NSAPP, var_install, INTEGER pattern, etc.\n\n# Section 2: Storage Management\n- storage_selector()\n- ensure_storage_selection_for_vars_file()\n\n# Section 3: Base Settings\n- base_settings()          # Apply defaults to all var_*\n- echo_default()           # Display current settings\n\n# Section 4: Variable Loading\n- load_vars_file()         # Safe parsing\n- _is_whitelisted_key()    # Validation\n- _sanitize_value()        # Threat mitigation\n\n# Section 5: Defaults Management\n- default_var_settings()   # Load user defaults\n- get_app_defaults_path()  # Get app defaults path\n- maybe_offer_save_app_defaults()  # Save option\n\n# Section 6: Installation Flow\n- install_script()         # Main entry point\n- advanced_settings()      # 20-step wizard\n```\n\n### Regex Patterns Used\n\n| Pattern                | Purpose               | Example Match           |\n| ---------------------- | --------------------- | ----------------------- |\n| `^[0-9]+([.][0-9]+)?$` | Integer validation    | `4`, `192.168`          |\n| `^var_[a-z_]+$`        | Variable name         | `var_cpu`, `var_ssh`    |\n| `*'$('*`               | Command substitution  | `$(whoami)`             |\n| `*\\`\\*`                | Backtick substitution | `` `cat /etc/passwd` `` |\n\n---\n\n## Appendix: Migration Reference\n\n### Old Pattern (Deprecated)\n\n```bash\n# ❌ OLD: config-file.func\nsource config-file.conf          # Executes arbitrary code\nif [ \"$USE_DEFAULTS\" = \"yes\" ]; then\n  apply_settings_directly\nfi\n```\n\n### New Pattern (Current)\n\n```bash\n# ✅ NEW: load_vars_file()\nif load_vars_file \"$(get_app_defaults_path)\"; then\n  echo \"Settings loaded securely\"\nfi\n```\n\n### Function Mapping\n\n| Old              | New                               | Location   |\n| ---------------- | --------------------------------- | ---------- |\n| `read_config()`  | `load_vars_file()`                | build.func |\n| `write_config()` | `_build_current_app_vars_tmp()`   | build.func |\n| None             | `maybe_offer_save_app_defaults()` | build.func |\n| None             | `get_app_defaults_path()`         | build.func |\n\n---\n\n**End of Technical Reference**\n"
  },
  {
    "path": "docs/api/README.md",
    "content": "# API Integration Documentation (/api)\n\nThis directory contains comprehensive documentation for API integration and the `/api` directory.\n\n## Overview\n\nThe `/api` directory contains the Proxmox Community Scripts API backend for diagnostic reporting, telemetry, and analytics integration.\n\n## Key Components\n\n### Main API Service\nLocated in `/api/main.go`:\n- RESTful API for receiving telemetry data\n- Installation statistics tracking\n- Error reporting and analytics\n- Performance monitoring\n\n### Integration with Scripts\nThe API is integrated into all installation scripts via `api.func`:\n- Sends installation start/completion events\n- Reports errors and exit codes\n- Collects anonymous usage statistics\n- Enables project analytics\n\n## Documentation Structure\n\nAPI documentation covers:\n- API endpoint specifications\n- Integration methods\n- Data formats and schemas\n- Error handling\n- Privacy and data handling\n\n## Key Resources\n\n- **[misc/api.func/](../misc/api.func/)** - API function library documentation\n- **[misc/api.func/README.md](../misc/api.func/README.md)** - Quick reference\n- **[misc/api.func/API_FUNCTIONS_REFERENCE.md](../misc/api.func/API_FUNCTIONS_REFERENCE.md)** - Complete function reference\n\n## API Functions\n\nThe `api.func` library provides:\n\n### `post_to_api()`\nSend container installation data to API.\n\n**Usage**:\n```bash\npost_to_api CTID STATUS APP_NAME\n```\n\n### `post_update_to_api()`\nReport application update status.\n\n**Usage**:\n```bash\npost_update_to_api CTID APP_NAME VERSION\n```\n\n### `get_error_description()`\nGet human-readable error description from exit code.\n\n**Usage**:\n```bash\nERROR_DESC=$(get_error_description EXIT_CODE)\n```\n\n## API Integration Points\n\n### In Container Creation (`ct/AppName.sh`)\n- Called by build.func to report container creation\n- Sends initial container setup data\n- Reports success or failure\n\n### In Installation Scripts (`install/appname-install.sh`)\n- Called at start of installation\n- Called on installation completion\n- Called on error conditions\n\n### Data Collected\n- Container/VM ID\n- Application name and version\n- Installation duration\n- Success/failure status\n- Error codes (if failure)\n- Anonymous usage metrics\n\n## Privacy\n\nAll API data:\n- ✅ Anonymous (no personal data)\n- ✅ Aggregated for statistics\n- ✅ Used only for project improvement\n- ✅ No tracking of user identities\n- ✅ Can be disabled if desired\n\n## API Architecture\n\n```\nInstallation Scripts\n    │\n    ├─ Call: api.func functions\n    │\n    └─ POST to: https://api.community-scripts.org\n                │\n                ├─ Receives data\n                ├─ Validates format\n                ├─ Stores metrics\n                └─ Aggregates statistics\n                    │\n                    └─ Used for:\n                       ├─ Download tracking\n                       ├─ Error trending\n                       ├─ Feature usage stats\n                       └─ Project health monitoring\n```\n\n## Common API Tasks\n\n- **Enable API reporting** → Built-in by default, no configuration needed\n- **Disable API** → Set `api_disable=\"yes\"` before running\n- **View API data** → Visit https://community-scripts.org/stats\n- **Report API errors** → [GitHub Issues](https://github.com/community-scripts/ProxmoxVE/issues)\n\n## Debugging API Issues\n\nIf API calls fail:\n1. Check internet connectivity\n2. Verify API endpoint availability\n3. Review error codes in [EXIT_CODES.md](../EXIT_CODES.md)\n4. Check API function logs\n5. Report issues on GitHub\n\n## API Endpoint\n\n**Base URL**: `https://api.community-scripts.org`\n\n**Endpoints**:\n- `POST /install` - Report container installation\n- `POST /update` - Report application update\n- `GET /stats` - Public statistics\n\n---\n\n**Last Updated**: December 2025\n**Maintainers**: community-scripts team\n"
  },
  {
    "path": "docs/contribution/AI.md",
    "content": "﻿# 🤖 AI Contribution Guidelines for ProxmoxVE\n\n> **This documentation is intended for all AI assistants (GitHub Copilot, Claude, ChatGPT, etc.) contributing to this project.**\n\n## 🎯 Core Principles\n\n### 1. **Maximum Use of `tools.func` Functions**\n\nWe have an extensive library of helper functions. **NEVER** implement your own solutions when a function already exists!\n\n### 2. **No Pointless Variables**\n\nOnly create variables when they:\n\n- Are used multiple times\n- Improve readability\n- Are intended for configuration\n\n### 3. **Consistent Script Structure**\n\nAll scripts follow an identical structure. Deviations are not acceptable.\n\n### 4. **Bare-Metal Installation**\n\nWe do **NOT use Docker** for our installation scripts. All applications are installed directly on the system.\n\n---\n\n## 📁 Script Types and Their Structure\n\n### CT Script (`ct/AppName.sh`)\n\n```bash\n#!/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: AuthorName (GitHubUsername)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://application-url.com\n\nAPP=\"AppName\"\nvar_tags=\"${var_tags:-tag1;tag2;tag3}\"\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/appname ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"appname\" \"YourUsername/YourRepo\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop appname\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Backing up Data\"\n    cp -r /opt/appname/data /opt/appname_data_backup\n    msg_ok \"Backed up Data\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"appname\" \"owner/repo\" \"tarball\" \"latest\" \"/opt/appname\"\n\n    # Build steps...\n\n    msg_info \"Restoring Data\"\n    cp -r /opt/appname_data_backup/. /opt/appname/data\n    rm -rf /opt/appname_data_backup\n    msg_ok \"Restored Data\"\n\n    msg_info \"Starting Service\"\n    systemctl start appname\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}:PORT${CL}\"\n```\n\n### Install Script (`install/AppName-install.sh`)\n\n```bash\n#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: AuthorName (GitHubUsername)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://application-url.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-get install -y \\\n  dependency1 \\\n  dependency2\nmsg_ok \"Installed Dependencies\"\n\n# Runtime Setup (ALWAYS use our functions!)\nNODE_VERSION=\"22\" setup_nodejs\n# or\nPG_VERSION=\"16\" setup_postgresql\n# or\nsetup_uv\n# etc.\n\nfetch_and_deploy_gh_release \"appname\" \"owner/repo\" \"tarball\" \"latest\" \"/opt/appname\"\n\nmsg_info \"Setting up Application\"\ncd /opt/appname\n# Build/Setup Schritte...\nmsg_ok \"Set up Application\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/appname.service\n[Unit]\nDescription=AppName Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/appname\nExecStart=/path/to/executable\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now appname\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n---\n\n## 🔧 Available Helper Functions\n\n### Release Management\n\n| Function                      | Description                         | Example                                                       |\n| ----------------------------- | ----------------------------------- | ------------------------------------------------------------- |\n| `fetch_and_deploy_gh_release` | Fetches and installs GitHub Release | `fetch_and_deploy_gh_release \"app\" \"owner/repo\"`              |\n| `check_for_gh_release`        | Checks for new version              | `if check_for_gh_release \"app\" \"YourUsername/YourRepo\"; then` |\n\n**Modes for `fetch_and_deploy_gh_release`:**\n\n```bash\n# Tarball/Source (Standard)\nfetch_and_deploy_gh_release \"appname\" \"owner/repo\"\n\n# Binary (.deb)\nfetch_and_deploy_gh_release \"appname\" \"owner/repo\" \"binary\"\n\n# Prebuilt Archive\nfetch_and_deploy_gh_release \"appname\" \"owner/repo\" \"prebuild\" \"latest\" \"/opt/appname\" \"filename.tar.gz\"\n\n# Single Binary\nfetch_and_deploy_gh_release \"appname\" \"owner/repo\" \"singlefile\" \"latest\" \"/opt/appname\" \"binary-linux-amd64\"\n```\n\n**Clean Install Flag:**\n\n```bash\nCLEAN_INSTALL=1 fetch_and_deploy_gh_release \"appname\" \"owner/repo\"\n```\n\n### Runtime/Language Setup\n\n| Function       | Variable(s)                   | Example                                              |\n| -------------- | ----------------------------- | ---------------------------------------------------- |\n| `setup_nodejs` | `NODE_VERSION`, `NODE_MODULE` | `NODE_VERSION=\"22\" setup_nodejs`                     |\n| `setup_uv`     | `PYTHON_VERSION`              | `PYTHON_VERSION=\"3.12\" setup_uv`                     |\n| `setup_go`     | `GO_VERSION`                  | `GO_VERSION=\"1.22\" setup_go`                         |\n| `setup_rust`   | `RUST_VERSION`, `RUST_CRATES` | `RUST_CRATES=\"monolith\" setup_rust`                  |\n| `setup_ruby`   | `RUBY_VERSION`                | `RUBY_VERSION=\"3.3\" setup_ruby`                      |\n| `setup_java`   | `JAVA_VERSION`                | `JAVA_VERSION=\"21\" setup_java`                       |\n| `setup_php`    | `PHP_VERSION`, `PHP_MODULES`  | `PHP_VERSION=\"8.3\" PHP_MODULES=\"redis,gd\" setup_php` |\n\n### Database Setup\n\n| Function              | Variable(s)                          | Example                                                     |\n| --------------------- | ------------------------------------ | ----------------------------------------------------------- |\n| `setup_postgresql`    | `PG_VERSION`, `PG_MODULES`           | `PG_VERSION=\"16\" setup_postgresql`                          |\n| `setup_postgresql_db` | `PG_DB_NAME`, `PG_DB_USER`           | `PG_DB_NAME=\"mydb\" PG_DB_USER=\"myuser\" setup_postgresql_db` |\n| `setup_mariadb_db`    | `MARIADB_DB_NAME`, `MARIADB_DB_USER` | `MARIADB_DB_NAME=\"mydb\" setup_mariadb_db`                   |\n| `setup_mysql`         | `MYSQL_VERSION`                      | `setup_mysql`                                               |\n| `setup_mongodb`       | `MONGO_VERSION`                      | `setup_mongodb`                                             |\n| `setup_clickhouse`    | -                                    | `setup_clickhouse`                                          |\n\n### Tools & Utilities\n\n| Function            | Description                        |\n| ------------------- | ---------------------------------- |\n| `setup_adminer`     | Installs Adminer for DB management |\n| `setup_composer`    | Install PHP Composer               |\n| `setup_ffmpeg`      | Install FFmpeg                     |\n| `setup_imagemagick` | Install ImageMagick                |\n| `setup_gs`          | Install Ghostscript                |\n| `setup_hwaccel`     | Configure hardware acceleration    |\n\n### Helper Utilities\n\n| Function                      | Description                  | Example                                   |\n| ----------------------------- | ---------------------------- | ----------------------------------------- |\n| `import_local_ip`             | Sets `$LOCAL_IP` variable    | `import_local_ip`                         |\n| `ensure_dependencies`         | Checks/installs dependencies | `ensure_dependencies curl jq`             |\n| `install_packages_with_retry` | APT install with retry       | `install_packages_with_retry nginx redis` |\n\n---\n\n## ❌ Anti-Patterns (NEVER use!)\n\n### 1. Pointless Variables\n\n```bash\n# ❌ WRONG - unnecessary variables\nAPP_NAME=\"myapp\"\nAPP_DIR=\"/opt/${APP_NAME}\"\nAPP_USER=\"root\"\nAPP_PORT=\"3000\"\ncd $APP_DIR\n\n# ✅ CORRECT - use directly\ncd /opt/myapp\n```\n\n### 2. Custom Download Logic\n\n```bash\n# ❌ WRONG - custom wget/curl logic\nRELEASE=$(curl -s https://api.github.com/repos/YourUsername/YourRepo/releases/latest | jq -r '.tag_name')\nwget https://github.com/YourUsername/YourRepo/archive/${RELEASE}.tar.gz\ntar -xzf ${RELEASE}.tar.gz\nmv repo-${RELEASE} /opt/myapp\n\n# ✅ CORRECT - use our function\nfetch_and_deploy_gh_release \"myapp\" \"YourUsername/YourRepo\" \"tarball\" \"latest\" \"/opt/myapp\"\n```\n\n### 3. Custom Version-Check Logic\n\n```bash\n# ❌ WRONG - custom version check\nCURRENT=$(cat /opt/myapp/version.txt)\nLATEST=$(curl -s https://api.github.com/repos/YourUsername/YourRepo/releases/latest | jq -r '.tag_name')\nif [[ \"$CURRENT\" != \"$LATEST\" ]]; then\n  # update...\nfi\n\n# ✅ CORRECT - use our function\nif check_for_gh_release \"myapp\" \"YourUsername/YourRepo\"; then\n  # update...\nfi\n```\n\n### 4. Docker-based Installation\n\n```bash\n# ❌ WRONG - using Docker\ndocker pull myapp/myapp:latest\ndocker run -d --name myapp myapp/myapp:latest\n\n# ✅ CORRECT - Bare-Metal Installation\nfetch_and_deploy_gh_release \"myapp\" \"YourUsername/YourRepo\"\nnpm install && npm run build\n```\n\n### 5. Custom Runtime Installation\n\n```bash\n# ❌ WRONG - custom Node.js installation\ncurl -fsSL https://deb.nodesource.com/setup_22.x | bash -\napt install -y nodejs\n\n# ✅ CORRECT - use our function\nNODE_VERSION=\"22\" setup_nodejs\n```\n\n### 6. Redundant echo Statements\n\n```bash\n# ❌ WRONG - custom logging messages\necho \"Installing dependencies...\"\napt install -y curl\necho \"Done!\"\n\n# ✅ CORRECT - use msg_info/msg_ok\nmsg_info \"Installing Dependencies\"\n$STD apt install -y curl\nmsg_ok \"Installed Dependencies\"\n```\n\n### 7. Missing $STD Usage\n\n```bash\n# ❌ WRONG - apt without $STD\napt install -y nginx\n\n# ✅ CORRECT - with $STD for silent output\n$STD apt install -y nginx\n```\n\n### 8. Wrapping `tools.func` Functions in msg Blocks\n\n```bash\n# ❌ WRONG - tools.func functions have their own msg_info/msg_ok!\nmsg_info \"Installing Node.js\"\nNODE_VERSION=\"22\" setup_nodejs\nmsg_ok \"Installed Node.js\"\n\nmsg_info \"Updating Application\"\nCLEAN_INSTALL=1 fetch_and_deploy_gh_release \"appname\" \"owner/repo\" \"tarball\" \"latest\" \"/opt/appname\"\nmsg_ok \"Updated Application\"\n\n# ✅ CORRECT - call directly without msg wrapper\nNODE_VERSION=\"22\" setup_nodejs\n\nCLEAN_INSTALL=1 fetch_and_deploy_gh_release \"appname\" \"owner/repo\" \"tarball\" \"latest\" \"/opt/appname\"\n```\n\n**Functions with built-in messages (NEVER wrap in msg blocks):**\n\n- `fetch_and_deploy_gh_release`\n- `check_for_gh_release`\n- `setup_nodejs`\n- `setup_postgresql` / `setup_postgresql_db`\n- `setup_mariadb` / `setup_mariadb_db`\n- `setup_mongodb`\n- `setup_mysql`\n- `setup_ruby`\n- `setup_go`\n- `setup_java`\n- `setup_php`\n- `setup_uv`\n- `setup_rust`\n- `setup_composer`\n- `setup_ffmpeg`\n- `setup_imagemagick`\n- `setup_gs`\n- `setup_adminer`\n- `setup_hwaccel`\n\n### 9. Creating Unnecessary System Users\n\n```bash\n# ❌ WRONG - LXC containers run as root, no separate user needed\nuseradd -m -s /usr/bin/bash appuser\nchown -R appuser:appuser /opt/appname\nsudo -u appuser npm install\n\n# ✅ CORRECT - run directly as root\ncd /opt/appname\n$STD npm install\n```\n\n### 10. Using `export` in .env Files\n\n```bash\n# ❌ WRONG - export is unnecessary in .env files\ncat <<EOF >/opt/appname/.env\nexport DATABASE_URL=postgres://...\nexport SECRET_KEY=abc123\nexport NODE_ENV=production\nEOF\n\n# ✅ CORRECT - simple KEY=VALUE format (files are sourced with set -a)\ncat <<EOF >/opt/appname/.env\nDATABASE_URL=postgres://...\nSECRET_KEY=abc123\nNODE_ENV=production\nEOF\n```\n\n### 11. Using External Shell Scripts\n\n```bash\n# ❌ WRONG - external script that gets executed\ncat <<'EOF' >/opt/appname/install_script.sh\n#!/bin/bash\ncd /opt/appname\nnpm install\nnpm run build\nEOF\nchmod +x /opt/appname/install_script.sh\n$STD bash /opt/appname/install_script.sh\nrm -f /opt/appname/install_script.sh\n\n# ✅ CORRECT - run commands directly\ncd /opt/appname\n$STD npm install\n$STD npm run build\n```\n\n### 12. Using `sudo` in LXC Containers\n\n```bash\n# ❌ WRONG - sudo is unnecessary in LXC (already root)\nsudo -u postgres psql -c \"CREATE DATABASE mydb;\"\nsudo -u appuser npm install\n\n# ✅ CORRECT - use functions or run directly as root\nPG_DB_NAME=\"mydb\" PG_DB_USER=\"myuser\" setup_postgresql_db\n\ncd /opt/appname\n$STD npm install\n```\n\n### 13. Unnecessary `systemctl daemon-reload`\n\n```bash\n# ❌ WRONG - daemon-reload is only needed when MODIFYING existing services\ncat <<EOF >/etc/systemd/system/appname.service\n# ... service config ...\nEOF\nsystemctl daemon-reload  # Unnecessary for new services!\nsystemctl enable -q --now appname\n\n# ✅ CORRECT - new services don't need daemon-reload\ncat <<EOF >/etc/systemd/system/appname.service\n# ... service config ...\nEOF\nsystemctl enable -q --now appname\n```\n\n### 14. Creating Custom Credentials Files\n\n```bash\n# ❌ WRONG - custom credentials file is not part of the standard template\nmsg_info \"Saving Credentials\"\ncat <<EOF >~/appname.creds\nDatabase User: ${DB_USER}\nDatabase Pass: ${DB_PASS}\nEOF\nmsg_ok \"Saved Credentials\"\n\n# ✅ CORRECT - credentials are stored in .env or shown in final message only\n# If you use setup_postgresql_db / setup_mariadb_db, a standard ~/[appname].creds is created automatically\n```\n\n### 15. Wrong Footer Pattern\n\n```bash\n# ❌ WRONG - old cleanup pattern with msg blocks\nmotd_ssh\ncustomize\n\nmsg_info \"Cleaning up\"\n$STD apt-get -y autoremove\n$STD apt-get -y autoclean\nmsg_ok \"Cleaned\"\n\n# ✅ CORRECT - use cleanup_lxc function\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n### 16. Manual Database Creation Instead of Functions\n\n```bash\n# ❌ WRONG - manual database creation\nDB_USER=\"myuser\"\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 mydb WITH OWNER $DB_USER;\"\n$STD sudo -u postgres psql -d mydb -c \"CREATE EXTENSION IF NOT EXISTS postgis;\"\n\n# ✅ CORRECT - use setup_postgresql_db function\n# This sets PG_DB_USER, PG_DB_PASS, PG_DB_NAME automatically\nPG_DB_NAME=\"mydb\" PG_DB_USER=\"myuser\" PG_DB_EXTENSIONS=\"postgis\" setup_postgresql_db\n```\n\n### 17. Writing Files Without Heredocs\n\n```bash\n# ❌ WRONG - echo / printf / tee\necho \"# Config\" > /opt/app/config.yml\necho \"port: 3000\" >> /opt/app/config.yml\n\nprintf \"# Config\\nport: 3000\\n\" > /opt/app/config.yml\ncat config.yml | tee /opt/app/config.yml\n```\n\n```bash\n# ✅ CORRECT - always use a single heredoc\ncat <<EOF >/opt/app/config.yml\n# Config\nport: 3000\nEOF\n```\n\n---\n\n## 📝 Important Rules\n\n### Variable Declarations (CT Script)\n\n```bash\n# Standard declarations (ALWAYS present)\nAPP=\"AppName\"\nvar_tags=\"${var_tags:-tag1;tag2}\"\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```\n\n### Update-Script Pattern\n\n```bash\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  # 1. Check if installation exists\n  if [[ ! -d /opt/appname ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  # 2. Check for update\n  if check_for_gh_release \"appname\" \"YourUsername/YourRepo\"; then\n    # 3. Stop service\n    msg_info \"Stopping Service\"\n    systemctl stop appname\n    msg_ok \"Stopped Service\"\n\n    # 4. Backup data (if present)\n    msg_info \"Backing up Data\"\n    cp -r /opt/appname/data /opt/appname_data_backup\n    msg_ok \"Backed up Data\"\n\n    # 5. Perform clean install\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"appname\" \"owner/repo\" \"tarball\" \"latest\" \"/opt/appname\"\n\n    # 6. Rebuild (if needed)\n    cd /opt/appname\n    $STD npm install\n    $STD npm run build\n\n    # 7. Restore data\n    msg_info \"Restoring Data\"\n    cp -r /opt/appname_data_backup/. /opt/appname/data\n    rm -rf /opt/appname_data_backup\n    msg_ok \"Restored Data\"\n\n    # 8. Start service\n    msg_info \"Starting Service\"\n    systemctl start appname\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit  # IMPORTANT: Always end with exit!\n}\n```\n\n### Systemd Service Pattern\n\n```bash\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/appname.service\n[Unit]\nDescription=AppName Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/appname\nEnvironment=NODE_ENV=production\nExecStart=/usr/bin/node /opt/appname/server.js\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now appname\nmsg_ok \"Created Service\"\n```\n\n### Installation Script Footer\n\n```bash\n# ALWAYS at the end of the install script:\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n---\n\n## 📖 Reference: Good Example Scripts\n\nLook at these recent well-implemented applications as reference:\n\n### Container Scripts (Latest 10)\n\n- [ct/thingsboard.sh](../ct/thingsboard.sh) - IoT platform with proper update_script\n- [ct/unifi-os-server.sh](../ct/unifi-os-server.sh) - Complex setup with podman\n- [ct/trip.sh](../ct/trip.sh) - Simple Ruby app\n- [ct/fladder.sh](../ct/fladder.sh) - Media app with database\n- [ct/qui.sh](../ct/qui.sh) - Lightweight utility\n- [ct/kutt.sh](../ct/kutt.sh) - Node.js with PostgreSQL\n- [ct/flatnotes.sh](../ct/flatnotes.sh) - Python notes app\n- [ct/investbrain.sh](../ct/investbrain.sh) - Finance app\n- [ct/gwn-manager.sh](../ct/gwn-manager.sh) - Network management\n- [ct/sportarr.sh](../ct/sportarr.sh) - Specialized \\*Arr variant\n\n### Install Scripts (Latest)\n\n- [install/unifi-os-server-install.sh](../install/unifi-os-server-install.sh) - Complex setup with API integration\n- [install/trip-install.sh](../install/trip-install.sh) - Rails application setup\n- [install/mail-archiver-install.sh](../install/mail-archiver-install.sh) - Email-related service\n\n**Key things to notice:**\n\n- Proper error handling with `catch_errors`\n- Use of `check_for_gh_release` and `fetch_and_deploy_gh_release`\n- Correct backup/restore patterns in `update_script`\n- Footer always ends with `motd_ssh`, `customize`, `cleanup_lxc`\n- Website metadata requested via the website (Report issue on script page) if needed\n\n---\n\n## Website Metadata (Reference)\n\nWebsite metadata (name, slug, description, logo, categories, etc.) is **not** added as files in the repo. Contributors request or update it via the **website**: go to the script's page and use the **Report issue** button; the flow will guide you. The structure below is a **reference** for what metadata exists (e.g. for the form or when describing what you need).\n\n### JSON Structure (Reference)\n\n```json\n{\n  \"name\": \"AppName\",\n  \"slug\": \"appname\",\n  \"categories\": [1],\n  \"date_created\": \"2026-01-16\",\n  \"type\": \"ct\",\n  \"updateable\": true,\n  \"privileged\": false,\n  \"interface_port\": 3000,\n  \"documentation\": \"https://docs.appname.com/\",\n  \"website\": \"https://appname.com/\",\n  \"logo\": \"https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/appname.webp\",\n  \"config_path\": \"/opt/appname/.env\",\n  \"description\": \"Short description of the application and its purpose.\",\n  \"install_methods\": [\n    {\n      \"type\": \"default\",\n      \"script\": \"ct/appname.sh\",\n      \"resources\": {\n        \"cpu\": 2,\n        \"ram\": 2048,\n        \"hdd\": 8,\n        \"os\": \"Debian\",\n        \"version\": \"13\"\n      }\n    }\n  ],\n  \"default_credentials\": {\n    \"username\": null,\n    \"password\": null\n  },\n  \"notes\": []\n}\n```\n\n### Required Fields\n\n| Field                 | Type    | Description                                        |\n| --------------------- | ------- | -------------------------------------------------- |\n| `name`                | string  | Display name of the application                    |\n| `slug`                | string  | Lowercase, no spaces, used for filenames           |\n| `categories`          | array   | Category ID(s) - see category list below           |\n| `date_created`        | string  | Creation date (YYYY-MM-DD)                         |\n| `type`                | string  | `ct` for container, `vm` for virtual machine       |\n| `updateable`          | boolean | Whether update_script is implemented               |\n| `privileged`          | boolean | Whether container needs privileged mode            |\n| `interface_port`      | number  | Primary web interface port (or `null`)             |\n| `documentation`       | string  | Link to official docs                              |\n| `website`             | string  | Link to official website                           |\n| `logo`                | string  | URL to application logo (preferably selfhst icons) |\n| `config_path`         | string  | Path to main config file (or empty string)         |\n| `description`         | string  | Brief description of the application               |\n| `install_methods`     | array   | Installation configurations                        |\n| `default_credentials` | object  | Default username/password (or null)                |\n| `notes`               | array   | Additional notes/warnings                          |\n\n### Categories\n\n| ID  | Category                  |\n| --- | ------------------------- |\n| 0   | Miscellaneous             |\n| 1   | Proxmox & Virtualization  |\n| 2   | Operating Systems         |\n| 3   | Containers & Docker       |\n| 4   | Network & Firewall        |\n| 5   | Adblock & DNS             |\n| 6   | Authentication & Security |\n| 7   | Backup & Recovery         |\n| 8   | Databases                 |\n| 9   | Monitoring & Analytics    |\n| 10  | Dashboards & Frontends    |\n| 11  | Files & Downloads         |\n| 12  | Documents & Notes         |\n| 13  | Media & Streaming         |\n| 14  | \\*Arr Suite               |\n| 15  | NVR & Cameras             |\n| 16  | IoT & Smart Home          |\n| 17  | ZigBee, Z-Wave & Matter   |\n| 18  | MQTT & Messaging          |\n| 19  | Automation & Scheduling   |\n| 20  | AI / Coding & Dev-Tools   |\n| 21  | Webservers & Proxies      |\n| 22  | Bots & ChatOps            |\n| 23  | Finance & Budgeting       |\n| 24  | Gaming & Leisure          |\n| 25  | Business & ERP            |\n\n### Notes Format\n\n```json\n\"notes\": [\n    {\n        \"text\": \"Change the default password after first login!\",\n        \"type\": \"warning\"\n    },\n    {\n        \"text\": \"Requires at least 4GB RAM for optimal performance.\",\n        \"type\": \"info\"\n    }\n]\n```\n\n**Note types:** `info`, `warning`, `error`\n\n### Examples with Credentials\n\n```json\n\"default_credentials\": {\n    \"username\": \"admin\",\n    \"password\": \"admin\"\n}\n```\n\nOr no credentials:\n\n```json\n\"default_credentials\": {\n    \"username\": null,\n    \"password\": null\n}\n```\n\n---\n\n## 🔍 Checklist Before PR Creation\n\n- [ ] No Docker installation used\n- [ ] `fetch_and_deploy_gh_release` used for GitHub releases\n- [ ] `check_for_gh_release` used for update checks\n- [ ] `setup_*` functions used for runtimes (nodejs, postgresql, etc.)\n- [ ] **`tools.func` functions NOT wrapped in msg_info/msg_ok blocks**\n- [ ] No redundant variables (only when used multiple times)\n- [ ] `$STD` before all apt/npm/build commands\n- [ ] `msg_info`/`msg_ok`/`msg_error` for logging (only for custom code)\n- [ ] Correct script structure followed (see templates)\n- [ ] Update function present and functional (CT scripts)\n- [ ] Data backup implemented in update function (if applicable)\n- [ ] `motd_ssh`, `customize`, `cleanup_lxc` at the end of install scripts\n- [ ] No custom download/version-check logic\n- [ ] All links point to `community-scripts/ProxmoxVE` (not `ProxmoxVED`!)\n- [ ] Website metadata requested via the website (Report issue) if needed\n- [ ] Category IDs are valid (0-25)\n- [ ] Default OS version is Debian 13 or newer (unless special requirement)\n- [ ] Default resources are reasonable for the application\n\n---\n\n## 💡 Tips for AI Assistants\n\n1. **ALWAYS search `tools.func` first** before implementing custom solutions\n2. **Use recent scripts as reference** (Thingsboard, UniFi OS, Trip, Flatnotes, etc.)\n3. **Ask when uncertain** instead of introducing wrong patterns\n4. **Test via GitHub** - push to your fork and test with curl (not local bash)\n   ```bash\n   bash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/myapp.sh)\"\n   # Wait 10-30 seconds after pushing - GitHub takes time to update files\n   ```\n5. **Consistency > Creativity** - follow established patterns strictly\n6. **Check the templates** - they show the correct structure\n7. **Don't wrap tools.func functions** - they handle their own msg_info/msg_ok output\n8. **Minimal variables** - only create variables that are truly reused multiple times\n9. **Always use $STD** - ensures silent/non-interactive execution\n10. **Reference good examples** - look at recent additions in each category\n\n---\n\n## 🍒 Important: Cherry-Picking Your Files for PR Submission\n\n⚠️ **CRITICAL**: When you submit your PR, you must use git cherry-pick to send ONLY your 2 files!\n\nWhy? Because `setup-fork.sh` modifies 600+ files to update links. If you commit all changes, your PR will be impossible to merge.\n\n**See**: [README.md - Cherry-Pick Section](README.md#-cherry-pick-submitting-only-your-changes) for complete instructions on:\n\n- Creating a clean submission branch\n- Cherry-picking only your files (ct/myapp.sh, install/myapp-install.sh)\n- Verifying your PR has only 2 file changes (not 600+)\n\n**Quick reference**:\n\n```bash\n# Create clean branch from upstream\ngit fetch upstream\ngit checkout -b submit/myapp upstream/main\n\n# Cherry-pick your commit(s) or manually add your 2 files\n# Then push to your fork and create PR\n```\n\n---\n\n## 📚 Further Documentation\n\n- [CONTRIBUTING.md](CONTRIBUTING.md) - General contribution guidelines\n- [GUIDE.md](GUIDE.md) - Detailed developer documentation\n- [HELPER_FUNCTIONS.md](HELPER_FUNCTIONS.md) - Complete tools.func reference\n- [README.md](README.md) - Cherry-pick guide and workflow instructions\n- [../TECHNICAL_REFERENCE.md](../TECHNICAL_REFERENCE.md) - Technical deep dive\n- [../EXIT_CODES.md](../EXIT_CODES.md) - Exit code reference\n- [templates_ct/](templates_ct/) - CT script templates\n- [templates_install/](templates_install/) - Install script templates\n- [templates_json/](templates_json/) - Metadata structure reference; submit via website\n"
  },
  {
    "path": "docs/contribution/CODE-AUDIT.md",
    "content": "# 🧪 Code Audit: LXC Script Flow\n\nThis guide explains the current execution flow and what to verify during reviews.\n\n## Execution Flow (CT + Install)\n\n1. `ct/appname.sh` runs on the Proxmox host and sources `misc/build.func`.\n2. `build.func` orchestrates prompts, container creation, and invokes the install script.\n3. Inside the container, `misc/install.func` exposes helper functions via `$FUNCTIONS_FILE_PATH`.\n4. `install/appname-install.sh` performs the application install.\n5. The CT script prints the completion message.\n\n## Audit Checklist\n\n### CT Script (ct/)\n\n- Sources `misc/build.func` from `community-scripts/ProxmoxVE/main` (setup-fork.sh updates for forks).\n- Uses `check_for_gh_release` + `fetch_and_deploy_gh_release` for updates.\n- No Docker-based installs.\n\n### Install Script (install/)\n\n- Sources `$FUNCTIONS_FILE_PATH`.\n- Uses `tools.func` helpers (setup\\_\\*).\n- Ends with `motd_ssh`, `customize`, `cleanup_lxc`.\n\n### Website Metadata\n\n- Website metadata for new/updated scripts is requested via the website (Report issue on script page) where applicable.\n\n### Testing\n\n- Test via curl from your fork (CT script only).\n- Wait 10-30 seconds after push.\n\n## References\n\n- `docs/contribution/templates_ct/AppName.sh`\n- `docs/contribution/templates_install/AppName-install.sh`\n- `docs/contribution/templates_json/AppName.json`\n- `docs/contribution/GUIDE.md`\n"
  },
  {
    "path": "docs/contribution/CONTRIBUTING.md",
    "content": "# Community Scripts Contribution Guide\n\n## **Welcome to the communty-scripts Repository!**\n\n📜 These documents outline the essential coding standards for all our scripts and JSON files. Adhering to these standards ensures that our codebase remains consistent, readable, and maintainable. By following these guidelines, we can improve collaboration, reduce errors, and enhance the overall quality of our project.\n\n### Why Coding Standards Matter\n\nCoding standards are crucial for several reasons:\n\n1. **Consistency**: Consistent code is easier to read, understand, and maintain. It helps new team members quickly get up to speed and reduces the learning curve.\n2. **Readability**: Clear and well-structured code is easier to debug and extend. It allows developers to quickly identify and fix issues.\n3. **Maintainability**: Code that follows a standard structure is easier to refactor and update. It ensures that changes can be made with minimal risk of introducing new bugs.\n4. **Collaboration**: When everyone follows the same standards, it becomes easier to collaborate on code. It reduces friction and misunderstandings during code reviews and merges.\n\n### Scope of These Documents\n\nThese documents cover the coding standards for the following types of files in our project:\n\n- **`install/$AppName-install.sh` Scripts**: These scripts are responsible for the installation of applications.\n- **`ct/$AppName.sh` Scripts**: These scripts handle the creation and updating of containers.\n- **Website metadata**: Display data (name, description, logo, etc.) is requested via the website (Report issue on the script page), not via files in the repo.\n\nEach section provides detailed guidelines on various aspects of coding, including shebang usage, comments, variable naming, function naming, indentation, error handling, command substitution, quoting, script structure, and logging. Additionally, examples are provided to illustrate the application of these standards.\n\nBy following the coding standards outlined in this document, we ensure that our scripts and JSON files are of high quality, making our project more robust and easier to manage. Please refer to this guide whenever you create or update scripts and JSON files to maintain a high standard of code quality across the project. 📚🔍\n\nLet's work together to keep our codebase clean, efficient, and maintainable! 💪🚀\n\n## Getting Started\n\nBefore contributing, please ensure that you have the following setup:\n\n1. **Visual Studio Code** (recommended for script development)\n2. **Recommended VS Code 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### Important Notes\n\n- Use [AppName.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/templates_ct/AppName.sh) and [AppName-install.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/templates_install/AppName-install.sh) as templates when creating new scripts.\n\n---\n\n# 🚀 The Application Script (ct/AppName.sh)\n\n- You can find all coding standards, as well as the structure for this file [here](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/templates_ct/AppName.md).\n- These scripts are responsible for container creation, setting the necessary variables and handling the update of the application once installed.\n\n---\n\n# 🛠 The Installation Script (install/AppName-install.sh)\n\n- You can find all coding standards, as well as the structure for this file [here](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/templates_install/AppName-install.md).\n- These scripts are responsible for the installation of the application.\n\n---\n\n## 🚀 Building Your Own Scripts\n\nStart with the [template script](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/templates_install/AppName-install.sh)\n\n---\n\n## 🤝 Contribution Process\n\n### 1. Fork the repository\n\nFork to your GitHub account\n\n### 2. Clone your fork on your local environment\n\n```bash\ngit clone https://github.com/yourUserName/ForkName\n```\n\n### 3. Create a new branch\n\n```bash\ngit switch -c your-feature-branch\n```\n\n### 4. Run setup-fork.sh to auto-configure your fork\n\n```bash\nbash docs/contribution/setup-fork.sh --full\n```\n\nThis script automatically:\n\n- Detects your GitHub username\n- Updates ALL curl URLs to point to your fork (for testing)\n- Creates `.git-setup-info` with your config\n- Backs up all modified files (\\*.backup)\n\n**IMPORTANT**: This modifies 600+ files! Use cherry-pick when submitting your PR (see below).\n\n### 5. Commit ONLY your new application files\n\n```bash\ngit commit -m \"Your commit message\"\n```\n\n### 5. Push to your fork\n\n```bash\ngit push origin your-feature-branch\n```\n\n### 6. Cherry-Pick: Submit Only Your Files for PR\n\n⚠️ **IMPORTANT**: setup-fork.sh modified 600+ files. You MUST only submit your 2 new files!\n\nSee [README.md - Cherry-Pick Guide](README.md#-cherry-pick-submitting-only-your-changes) for step-by-step instructions.\n\nQuick version:\n\n```bash\n# Create clean branch from upstream\ngit fetch upstream\ngit checkout -b submit/myapp upstream/main\n\n# Copy only your files\ncp ../your-work-branch/ct/myapp.sh ct/myapp.sh\ncp ../your-work-branch/install/myapp-install.sh install/myapp-install.sh\n\n# Commit and verify\ngit add ct/myapp.sh install/myapp-install.sh\ngit commit -m \"feat: add MyApp\"\ngit diff upstream/main --name-only  # Should show ONLY your 2 files\n\n# Push and create PR\ngit push origin submit/myapp\n```\n\n### 7. Create a Pull Request\n\nOpen a Pull Request from `submit/myapp` → `community-scripts/ProxmoxVE/main`.\n\nVerify the PR shows ONLY these 2 files:\n\n- `ct/myapp.sh`\n- `install/myapp-install.sh`\n\n---\n\n# 🛠️ Developer Mode & Debugging\n\nWhen building or testing scripts, you can use the `dev_mode` variable to enable powerful debugging features. These flags can be combined (comma-separated).\n\n**Usage**:\n```bash\n# Example: Run with trace and keep the container even if it fails\ndev_mode=\"trace,keep\" bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/myapp.sh)\"\n```\n\n### Available Flags:\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 (e.g., before customization). |\n| `breakpoint` | Allows hardcoded `breakpoint` calls in scripts to drop to a shell. |\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 (MOTD). |\n\n---\n\n## 📚 Pages\n\n- [CT Template: AppName.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/templates_ct/AppName.sh)\n- [Install Template: AppName-install.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/templates_install/AppName-install.sh)\n- [JSON Template: AppName.json](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/templates_json/AppName.json) — metadata structure reference; submit via the website (Report issue on script page)\n"
  },
  {
    "path": "docs/contribution/FORK_SETUP.md",
    "content": "# 🍴 Fork Setup Guide\n\n**Just forked ProxmoxVE? Run this first!**\n\n## Quick Start\n\n```bash\n# Clone your fork\ngit clone https://github.com/YOUR_USERNAME/ProxmoxVE.git\ncd ProxmoxVE\n\n# Run setup script (auto-detects your username from git)\nbash docs/contribution/setup-fork.sh --full\n```\n\nThat's it! ✅\n\n---\n\n## What Does It Do?\n\nThe `setup-fork.sh` script automatically:\n\n1. **Detects** your GitHub username from git config\n2. **Updates ALL hardcoded links** to point to your fork:\n   - Documentation links pointing to `community-scripts/ProxmoxVE`\n   - **Curl download URLs** in scripts (e.g., `curl ... github.com/community-scripts/ProxmoxVE/main/...`)\n3. **Creates** `.git-setup-info` with your configuration details\n4. **Backs up** all modified files (\\*.backup for safety)\n\n### Why Updating Curl Links Matters\n\nYour scripts contain `curl` commands that download dependencies from GitHub (build.func, tools.func, etc.):\n\n```bash\n# First line of ct/myapp.sh\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n```\n\n**WITHOUT setup-fork.sh:**\n\n- Script URLs still point to `community-scripts/ProxmoxVE/main`\n- If you test locally with `bash ct/myapp.sh`, you're testing local files, but the script's curl commands would download from **upstream** repo\n- Your modifications aren't actually being tested via the curl commands! ❌\n\n**AFTER setup-fork.sh:**\n\n- Script URLs are updated to `YourUsername/ProxmoxVE/main`\n- When you test via curl from GitHub: `bash -c \"$(curl ... YOUR_USERNAME/ProxmoxVE/main/ct/myapp.sh)\"`, it downloads from **your fork**\n- The script's curl commands also point to your fork, so you're actually testing your changes! ✅\n- ⏱️ **Important:** GitHub takes 10-30 seconds to recognize pushed files - wait before testing!\n\n```bash\n# Example: What setup-fork.sh changes\n\n# BEFORE (points to upstream):\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n\n# AFTER (points to your fork):\nsource <(curl -fsSL https://raw.githubusercontent.com/john/ProxmoxVE/main/misc/build.func)\n```\n\n---\n\n## Usage\n\n### Auto-Detect (Recommended)\n\n```bash\nbash docs/contribution/setup-fork.sh --full\n```\n\nAutomatically reads your GitHub username from `git remote origin url`\n\n### Specify Username\n\n```bash\nbash docs/contribution/setup-fork.sh --full john\n```\n\nUpdates links to `github.com/john/ProxmoxVE`\n\n### Custom Repository Name\n\n```bash\nbash docs/contribution/setup-fork.sh --full john my-fork\n```\n\nUpdates links to `github.com/john/my-fork`\n\n---\n\n## What Gets Updated?\n\nThe script updates hardcoded links in these areas when using `--full`:\n\n- `ct/`, `install/`, `vm/` scripts\n- `misc/` function libraries\n- `docs/` (including `docs/contribution/`)\n- Code examples in documentation\n\n---\n\n## After Setup\n\n1. **Review changes**\n\n   ```bash\n   git diff docs/\n   ```\n\n2. **Read git workflow tips**\n\n   ```bash\n   cat .git-setup-info\n   ```\n\n3. **Start contributing**\n\n   ```bash\n   git checkout -b feature/my-app\n   # Make your changes...\n   git commit -m \"feat: add my awesome app\"\n   ```\n\n4. **Follow the guide**\n   ```bash\n   cat docs/contribution/GUIDE.md\n   ```\n\n---\n\n## Common Workflows\n\n### Keep Your Fork Updated\n\n```bash\n# Add upstream if you haven't already\ngit remote add upstream https://github.com/community-scripts/ProxmoxVE.git\n\n# Get latest from upstream\ngit fetch upstream\ngit rebase upstream/main\ngit push origin main\n```\n\n### Create a Feature Branch\n\n```bash\ngit checkout -b feature/docker-improvements\n# Make changes...\ngit push origin feature/docker-improvements\n# Then create PR on GitHub\n```\n\n### Sync Before Contributing\n\n```bash\ngit fetch upstream\ngit rebase upstream/main\ngit push -f origin main  # Update your fork's main\ngit checkout -b feature/my-feature\n```\n\n---\n\n## Troubleshooting\n\n### \"Git is not installed\" or \"not a git repository\"\n\n```bash\n# Make sure you cloned the repo first\ngit clone https://github.com/YOUR_USERNAME/ProxmoxVE.git\ncd ProxmoxVE\nbash docs/contribution/setup-fork.sh --full\n```\n\n### \"Could not auto-detect GitHub username\"\n\n```bash\n# Your git origin URL isn't set up correctly\ngit remote -v\n# Should show your fork URL, not community-scripts\n\n# Fix it:\ngit remote set-url origin https://github.com/YOUR_USERNAME/ProxmoxVE.git\nbash docs/contribution/setup-fork.sh --full\n```\n\n### \"Permission denied\"\n\n```bash\n# Make script executable\nchmod +x docs/contribution/setup-fork.sh\nbash docs/contribution/setup-fork.sh --full\n```\n\n### Reverted Changes by Accident?\n\n```bash\n# Backups are created automatically\ngit checkout docs/*.backup\n# Or just re-run setup-fork.sh\nbash docs/contribution/setup-fork.sh --full\n```\n\n---\n\n## Next Steps\n\n1. ✅ Run `bash docs/contribution/setup-fork.sh --full`\n2. 📖 Read [docs/contribution/GUIDE.md](GUIDE.md)\n3. 🍴 Choose your contribution path:\n   - **Containers** → [docs/ct/README.md](docs/ct/README.md)\n   - **Installation** → [docs/install/README.md](docs/install/README.md)\n   - **VMs** → [docs/vm/README.md](docs/vm/README.md)\n   - **Tools** → [docs/tools/README.md](docs/tools/README.md)\n4. 💻 Create your feature branch and contribute!\n\n---\n\n## Questions?\n\n- **Fork Setup Issues?** → See [Troubleshooting](#troubleshooting) above\n- **How to Contribute?** → [docs/contribution/GUIDE.md](GUIDE.md)\n- **Git Workflows?** → `cat .git-setup-info`\n- **Project Structure?** → [docs/README.md](docs/README.md)\n\n---\n\n## Happy Contributing! 🚀\n"
  },
  {
    "path": "docs/contribution/GUIDE.md",
    "content": "# 🎯 **ProxmoxVE Contribution Guide**\n\n**Everything you need to know to contribute to ProxmoxVE**\n\n> **Last Updated**: December 2025\n> **Difficulty**: Beginner → Advanced\n> **Time to Setup**: 15 minutes\n> **Time to Contribute**: 1-3 hours\n\n---\n\n## 📋 Table of Contents\n\n- [Quick Start](#quick-start)\n- [Repository Structure](#repository-structure)\n- [Development Setup](#development-setup)\n- [Creating New Applications](#creating-new-applications)\n- [Updating Existing Applications](#updating-existing-applications)\n- [Code Standards](#code-standards)\n- [Testing Your Changes](#testing-your-changes)\n- [Submitting a Pull Request](#submitting-a-pull-request)\n- [Troubleshooting](#troubleshooting)\n- [FAQ](#faq)\n\n---\n\n## Quick Start\n\n### Setup Your Fork (First Time Only)\n\n```bash\n# 1. Fork the repository on GitHub\n# Visit: https://github.com/community-scripts/ProxmoxVE\n# Click: Fork (top right)\n\n# 2. Clone your fork\ngit clone https://github.com/YOUR_USERNAME/ProxmoxVE.git\ncd ProxmoxVE\n\n# 3. Run fork setup script (automatically configures everything)\nbash docs/contribution/setup-fork.sh --full\n# --full updates ct/, install/, vm/, docs/, misc/ links for fork testing\n\n# 4. Read the git workflow tips\ncat .git-setup-info\n```\n\n### 60 Seconds to First Contribution\n\n```bash\n# 1. Create feature branch\ngit checkout -b add/my-awesome-app\n\n# 2. Create application scripts from templates\ncp docs/contribution/templates_ct/AppName.sh ct/myapp.sh\ncp docs/contribution/templates_install/AppName-install.sh install/myapp-install.sh\n\n# 3. Edit your scripts\nnano ct/myapp.sh\nnano install/myapp-install.sh\n\n# 4. Commit and push to your fork\ngit add ct/myapp.sh install/myapp-install.sh\ngit commit -m \"feat: add MyApp container and install scripts\"\ngit push origin add/my-awesome-app\n\n# 5. Test via curl from your fork (GitHub may take 10-30 seconds)\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/myapp.sh)\"\n\n# 6. Use cherry-pick to submit only your files (see Cherry-Pick section)\n# DO NOT submit the 600+ files modified by setup-fork.sh!\n\n# 7. Open Pull Request on GitHub\n# Create PR from: your-fork/add/my-awesome-app → community-scripts/ProxmoxVE/main\n\n# To add or change website metadata (description, logo, etc.), use the Report issue button on the script's page on the website.\n```\n\n**💡 Tip**: See `../FORK_SETUP.md` for detailed fork setup and troubleshooting\n\n---\n\n## Repository Structure\n\n### Top-Level Organization\n\n```\nProxmoxVE/\n├── ct/                          # 🏗️  Container creation scripts (host-side)\n│   ├── pihole.sh\n│   ├── docker.sh\n│   └── ... (40+ applications)\n│\n├── install/                     # 🛠️  Installation scripts (container-side)\n│   ├── pihole-install.sh\n│   ├── docker-install.sh\n│   └── ... (40+ applications)\n│\n├── vm/                          # 💾 VM creation scripts\n│   ├── ubuntu2404-vm.sh\n│   ├── debian-vm.sh\n│   └── ... (15+ operating systems)\n│\n├── misc/                        # 📦 Shared function libraries\n│   ├── build.func               # Main orchestrator (3800+ lines)\n│   ├── core.func                # UI/utilities\n│   ├── error_handler.func       # Error management\n│   ├── tools.func               # Tool installation\n│   ├── install.func             # Container setup\n│   ├── cloud-init.func          # VM configuration\n│   ├── api.func                 # Telemetry\n│   ├── alpine-install.func      # Alpine-specific\n│   └── alpine-tools.func        # Alpine tools\n│\n├── docs/                        # 📚 Documentation\n│   ├── ct/DETAILED_GUIDE.md     # Container script guide\n│   ├── install/DETAILED_GUIDE.md # Install script guide\n│   └── contribution/README.md   # Contribution overview\n│\n├── tools/                       # 🔧 Proxmox management tools\n│   └── pve/\n│\n└── README.md                    # Project overview\n```\n\n### Naming Conventions\n\n```\nContainer Script:      ct/AppName.sh\nInstallation Script:   install/appname-install.sh\nDefaults:             defaults/appname.vars\nUpdate Script:        /usr/bin/update (inside container)\n\nExamples:\n  ct/pihole.sh                → install/pihole-install.sh\n  ct/docker.sh                → install/docker-install.sh\n  ct/nextcloud-vm.sh          → install/nextcloud-vm-install.sh\n```\n\n**Rules**:\n\n- Container script name: **Title Case** (PiHole, Docker, NextCloud)\n- Install script name: **lowercase** with **hyphens** (pihole-install, docker-install)\n- Must match: `ct/AppName.sh` ↔ `install/appname-install.sh`\n- Directory names: lowercase (always)\n- Variable names: lowercase (except APP constant)\n\n---\n\n## Development Setup\n\n### Prerequisites\n\n1. **Proxmox VE 8.0+** with at least:\n   - 4 CPU cores\n   - 8 GB RAM\n   - 50 GB disk space\n   - Ubuntu 20.04 / Debian 11+ on host\n\n2. **Git** installed\n\n   ```bash\n   apt-get install -y git\n   ```\n\n3. **Text Editor** (VS Code recommended)\n   ```bash\n   # VS Code extensions:\n   # - Bash IDE\n   # - Shellcheck\n   # - Markdown All in One\n   ```\n\n### Local Development Workflow\n\n#### Option A: Development Fork (Recommended)\n\n```bash\n# 1. Fork on GitHub (one-time)\n# Visit: https://github.com/community-scripts/ProxmoxVE\n# Click: Fork\n\n# 2. Clone your fork\ngit clone https://github.com/YOUR_USERNAME/ProxmoxVE.git\ncd ProxmoxVE\n\n# 3. Add upstream remote for updates\ngit remote add upstream https://github.com/community-scripts/ProxmoxVE.git\n\n# 4. Create feature branch\ngit checkout -b feat/add-myapp\n\n# 5. Make changes\n# ... edit files ...\n\n# 6. Keep fork updated\ngit fetch upstream\ngit rebase upstream/main\n\n# 7. Push and open PR\ngit push origin feat/add-myapp\n```\n\n#### Option B: Testing on a Proxmox Host (still via curl)\n\n```bash\n# 1. SSH into Proxmox host\nssh root@192.168.1.100\n\n# 2. Test via curl from your fork (CT script only)\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/myapp.sh)\"\n# ⏱️ Wait 10-30 seconds after pushing - GitHub takes time to update\n```\n\n> **Note:** Do not edit URLs manually or run install scripts directly. The CT script calls the install script inside the container.\n\n#### Option C: Using Curl (Recommended for Real Testing)\n\n```bash\n# Always test via curl from your fork (GitHub takes 10-30 seconds after push)\ngit push origin feature/myapp\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/myapp.sh)\"\n# This tests the actual GitHub URLs, not local files\n```\n\n#### Option D: Static Checks (Without Proxmox)\n\n```bash\n# You can validate syntax and linting locally (limited)\n# Note: This does NOT replace real Proxmox testing\n\n# Run ShellCheck\nshellcheck ct/myapp.sh\nshellcheck install/myapp-install.sh\n\n# Syntax check\nbash -n ct/myapp.sh\nbash -n install/myapp-install.sh\n```\n\n---\n\n## Creating New Applications\n\n### Step 1: Choose Your Template\n\n**For Simple Web Apps** (Node.js, Python, PHP):\n\n```bash\ncp ct/example.sh ct/myapp.sh\ncp install/example-install.sh install/myapp-install.sh\n```\n\n**For Database Apps** (PostgreSQL, MariaDB, MongoDB):\n\nUse the standard templates and the database helpers from `tools.func` (no Docker).\n\n**For Alpine Linux Apps** (lightweight):\n\n```bash\n# Use ct/alpine.sh as reference\n# Edit install script to use Alpine packages (apk not apt)\n```\n\n### Step 2: Update Container Script\n\n**File**: `ct/myapp.sh`\n\n```bash\n#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/misc/build.func)\n\n# Update these:\nAPP=\"MyAwesomeApp\"                    # Display name\nvar_tags=\"category;tag2;tag3\"         # Max 3-4 tags\nvar_cpu=\"2\"                          # Realistic CPU cores\nvar_ram=\"2048\"                       # Min RAM needed (MB)\nvar_disk=\"10\"                        # Min disk (GB)\nvar_os=\"debian\"                      # OS type\nvar_version=\"12\"                     # OS version\nvar_unprivileged=\"1\"                 # Security (1=unprivileged)\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/myapp ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  if check_for_gh_release \"myapp\" \"owner/repo\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop myapp\n    msg_ok \"Stopped Service\"\n\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"myapp\" \"owner/repo\" \"tarball\" \"latest\" \"/opt/myapp\"\n\n    # ... update logic (migrations, rebuilds, etc.) ...\n\n    msg_info \"Starting Service\"\n    systemctl start myapp\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}:PORT${CL}\"\n```\n\n**Checklist**:\n\n- [ ] APP variable matches filename\n- [ ] var_tags semicolon-separated (no spaces)\n- [ ] Realistic CPU/RAM/disk values\n- [ ] update_script() implemented\n- [ ] Correct OS and version\n- [ ] Success message with access URL\n\n### Step 3: Update Installation Script\n\n**File**: `install/myapp-install.sh`\n\n```bash\n#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: YourUsername\n# License: MIT\n# Source: https://github.com/example/myapp\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\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\n\nfetch_and_deploy_gh_release \"myapp\" \"owner/repo\" \"tarball\" \"latest\" \"/opt/myapp\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n**Checklist**:\n\n- [ ] Functions loaded from `$FUNCTIONS_FILE_PATH`\n- [ ] All installation phases present (deps, tools, app, config, cleanup)\n- [ ] Using `$STD` for output suppression\n- [ ] Version file saved\n- [ ] Final cleanup with `cleanup_lxc`\n- [ ] No hardcoded versions (use GitHub API)\n\n### Step 4: Create ASCII Header (Optional)\n\n**File**: `ct/headers/myapp`\n\n```\n╔═══════════════════════════════════════╗\n║                                       ║\n║          🎉 MyAwesomeApp 🎉          ║\n║                                       ║\n║  Your app is being installed...       ║\n║                                       ║\n╚═══════════════════════════════════════╝\n```\n\nSave in: `ct/headers/myapp` (no extension)\n\n### Step 5: Create Defaults File (Optional)\n\n**File**: `defaults/myapp.vars`\n\n```bash\n# Default configuration for MyAwesomeApp\nvar_cpu=4\nvar_ram=4096\nvar_disk=15\nvar_hostname=myapp-container\nvar_timezone=UTC\n```\n\n---\n\n## Updating Existing Applications\n\n### Step 1: Identify What Changed\n\n```bash\n# Check logs or GitHub releases\ncurl -fsSL https://api.github.com/repos/app/repo/releases/latest | jq '.'\n\n# Review breaking changes\n# Update dependencies if needed\n```\n\n### Step 2: Update Installation Script\n\n```bash\n# Edit: install/existingapp-install.sh\n\n# 1. Update version (if hardcoded)\nRELEASE=\"2.0.0\"\n\n# 2. Update package dependencies (if any changed)\n$STD apt-get install -y newdependency\n\n# 3. Update configuration (if format changed)\n# Update sed replacements or config files\n\n# 4. Test thoroughly before committing\n```\n\n### Step 3: The Standard Update Pattern\n\nThe `update_script()` function in `ct/appname.sh` should follow a robust pattern:\n\n1. **Check for updates**: Use `check_for_gh_release` to skip logic if no new version exists.\n2. **Stop services**: Stop all relevant services (`systemctl stop appname`).\n3. **Backup existing installation**: Move the old folder (e.g., `mv /opt/app /opt/app_bak`).\n4. **Deploy new version**: Use `CLEAN_INSTALL=1 fetch_and_deploy_gh_release`.\n5. **Restore configuration**: Copy `.env` or config files back from the backup.\n6. **Rebuild/Migrate**: Run `npm install`, `composer install`, or DB migrations.\n7. **Start services**: Restart services and cleanup the backup.\n\n**Example from `ct/bookstack.sh`**:\n```bash\nfunction update_script() {\n  if check_for_gh_release \"bookstack\" \"BookStackApp/BookStack\"; then\n    msg_info \"Stopping Services\"\n    systemctl stop apache2\n    \n    msg_info \"Backing up data\"\n    mv /opt/bookstack /opt/bookstack-backup\n    \n    fetch_and_deploy_gh_release \"bookstack\" \"BookStackApp/BookStack\" \"tarball\"\n    \n    msg_info \"Restoring backup\"\n    cp /opt/bookstack-backup/.env /opt/bookstack/.env\n    # ... restore uploads ...\n    \n    msg_info \"Configuring\"\n    cd /opt/bookstack\n    $STD composer install --no-dev\n    $STD php artisan migrate --force\n    \n    systemctl start apache2\n    rm -rf /opt/bookstack-backup\n    msg_ok \"Updated successfully!\"\n  fi\n}\n```\n\n---\n\n## Code Standards\n\n### Bash Style Guide\n\n#### Variable Naming\n\n```bash\n# ✅ Good\nAPP=\"MyApp\"                 # Constants (UPPERCASE)\nvar_cpu=\"2\"                # Configuration (var_*)\ncontainer_id=\"100\"         # Local variables (lowercase)\nDB_PASSWORD=\"secret\"       # Environment-like (UPPERCASE)\n\n# ❌ Bad\nmyapp=\"MyApp\"              # Inconsistent\nVAR_CPU=\"2\"               # Wrong convention\ncontainerid=\"100\"         # Unclear purpose\n```\n\n#### Function Naming\n\n```bash\n# ✅ Good\nfunction setup_database() { }       # Descriptive\nfunction check_version() { }        # Verb-noun pattern\nfunction install_dependencies() { } # Clear action\n\n# ❌ Bad\nfunction setup() { }                # Too vague\nfunction db_setup() { }             # Inconsistent pattern\nfunction x() { }                    # Cryptic\n```\n\n#### Quoting\n\n```bash\n# ✅ Good\necho \"${APP}\"                       # Always quote variables\nif [[ \"$var\" == \"value\" ]]; then   # Use [[ ]] for conditionals\necho \"Using $var in string\"        # Variables in double quotes\n\n# ❌ Bad\necho $APP                          # Unquoted variables\nif [ \"$var\" = \"value\" ]; then      # Use [[ ]] instead\necho 'Using $var in string'        # Single quotes prevent expansion\n```\n\n#### Command Formatting\n\n```bash\n# ✅ Good: Multiline for readability\n$STD apt-get install -y \\\n  package1 \\\n  package2 \\\n  package3\n\n# ✅ Good: Complex commands with variables\nif ! wget -q \"https://example.com/${file}\"; then\n  msg_error \"Failed to download\"\n  exit 1\nfi\n\n# ❌ Bad: Too long on one line\n$STD apt-get install -y package1 package2 package3 package4 package5 package6\n\n# ❌ Bad: No error checking\nwget https://example.com/file\n```\n\n#### Error Handling\n\n```bash\n# ✅ Good: Check critical commands\nif ! some_command; then\n  msg_error \"Command failed\"\n  exit 1\nfi\n\n# ✅ Good: Use catch_errors for automatic trapping\ncatch_errors\n\n# ❌ Bad: Silently ignore failures\nsome_command || true\nsome_command 2>/dev/null\n\n# ❌ Bad: Unclear what failed\nif ! (cmd1 && cmd2 && cmd3); then\n  msg_error \"Something failed\"\nfi\n```\n\n### Documentation Standards\n\n#### Header Comments\n\n```bash\n#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: YourUsername\n# Co-Author: AnotherAuthor (for collaborative work)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/app/repo\n# Description: Brief description of what this script does\n```\n\n#### Inline Comments\n\n```bash\n# ✅ Good: Explain WHY, not WHAT\n# Use alphanumeric only to avoid shell escaping issues\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n\n# ✅ Good: Comment complex logic\n# Detect if running Alpine vs Debian for proper package manager\nif grep -qi 'alpine' /etc/os-release; then\n  PKG_MGR=\"apk\"\nelse\n  PKG_MGR=\"apt\"\nfi\n\n# ❌ Bad: Comment obvious code\n# Set the variable\nvar=\"value\"\n\n# ❌ Bad: Outdated comments\n# TODO: Fix this (written 2 years ago, not fixed)\n```\n\n### File Organization\n\n```bash\n#!/usr/bin/env bash                  # [1] Shebang (first line)\n# Copyright & Metadata               # [2] Comments\n                                     # [3] Blank line\n# Load functions                     # [4] Import section\nsource <(curl -fsSL ...)\n                                     # [5] Blank line\n# Configuration                      # [6] Variables/Config\nAPP=\"MyApp\"\nvar_cpu=\"2\"\n                                     # [7] Blank line\n# Initialization                     # [8] Setup\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n                                     # [9] Blank line\n# Functions                          # [10] Function definitions\nfunction update_script() { }\nfunction custom_setup() { }\n                                     # [11] Blank line\n# Main execution                     # [12] Script logic\nstart\nbuild_container\n```\n\n---\n\n## Testing Your Changes\n\n### Pre-Submission Testing\n\n#### 1. Syntax Check\n\n```bash\n# Verify bash syntax\nbash -n ct/myapp.sh\nbash -n install/myapp-install.sh\n\n# If no output: ✅ Syntax is valid\n# If error output: ❌ Fix syntax before submitting\n```\n\n#### 2. ShellCheck Static Analysis\n\n```bash\n# Install ShellCheck\napt-get install -y shellcheck\n\n# Check scripts\nshellcheck ct/myapp.sh\nshellcheck install/myapp-install.sh\n\n# Review warnings and fix if applicable\n# Some warnings can be intentional (use # shellcheck disable=...)\n```\n\n#### 3. Real Proxmox Testing\n\n```bash\n# Best: Test on actual Proxmox system\n\n# 1. SSH into Proxmox host\nssh root@YOUR_PROXMOX_IP\n\n# 2. Test via curl from your fork (CT script only)\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/myapp.sh)\"\n# ⏱️ Wait 10-30 seconds after pushing - GitHub takes time to update\n\n# 3. Test interaction:\n#    - Select installation mode\n#    - Confirm settings\n#    - Monitor installation\n\n# 4. Verify container created\npct list | grep myapp\n\n# 5. Log into container and verify app\npct exec 100 bash\n```\n\n#### 4. Edge Case Testing\n\n```bash\n# Test with different settings:\n\n# Test 1: Advanced (19-step) installation\n# When prompted: Select \"2\" for Advanced\n\n# Test 2: User Defaults\n# Before running: Create ~/.community-scripts/default.vars\n# When prompted: Select \"3\" for User Defaults\n\n# Test 3: Error handling\n# Simulate network outage (block internet)\n# Verify script handles gracefully\n\n# Test 4: Update function\n# Create initial container (via curl from fork)\n# Wait for new release\n# Test update: bash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/myapp.sh)\"\n# Verify it detects and applies update\n```\n\n### Testing Checklist\n\nBefore submitting PR:\n\n```bash\n# Code quality\n- [ ] Syntax: bash -n passes\n- [ ] ShellCheck: No critical warnings\n- [ ] Naming: Follows conventions\n- [ ] Formatting: Consistent indentation\n\n# Functionality\n- [ ] Container creation: Successful\n- [ ] Installation: Completes without errors\n- [ ] Access URL: Works and app responds\n- [ ] Update function: Detects new versions\n- [ ] Cleanup: No temporary files left\n\n# Documentation\n- [ ] Copyright header present\n- [ ] App name matches filenames\n- [ ] Default values realistic\n- [ ] Success message clear and helpful\n\n# Compatibility\n- [ ] Works on Debian 12\n- [ ] Works on Ubuntu 22.04\n- [ ] (Optional) Works on Alpine 3.20\n```\n\n---\n\n## Submitting a Pull Request\n\n### Step 1: Prepare Your Branch\n\n```bash\n# Update with latest changes\ngit fetch upstream\ngit rebase upstream/main\n\n# If conflicts occur:\ngit rebase --abort\n# Resolve conflicts manually then:\ngit add .\ngit rebase --continue\n```\n\n### Step 2: Push Your Changes\n\n```bash\ngit push origin feat/add-myapp\n\n# If already pushed:\ngit push origin feat/add-myapp --force-with-lease\n```\n\n### Step 3: Create Pull Request on GitHub\n\n**Visit**: https://github.com/community-scripts/ProxmoxVE/pulls\n\n**Click**: \"New Pull Request\"\n\n**Select**: `community-scripts:main` ← `YOUR_USERNAME:feat/myapp`\n\n### Step 4: Fill PR Description\n\nUse this template:\n\n```markdown\n## Description\n\nBrief description of what this PR adds/fixes\n\n## Type of Change\n\n- [ ] New application (ct/AppName.sh + install/appname-install.sh)\n- [ ] Update existing application\n- [ ] Bug fix\n- [ ] Documentation update\n- [ ] Other: **\\_\\_\\_**\n\n## Testing\n\n- [ ] Tested on Proxmox VE 8.x\n- [ ] Container creation successful\n- [ ] Application installation successful\n- [ ] Application is accessible at URL\n- [ ] Update function works (if applicable)\n- [ ] No temporary files left after installation\n\n## Application Details (for new apps only)\n\n- **App Name**: MyApp\n- **Source**: https://github.com/app/repo\n- **Default OS**: Debian 12\n- **Recommended Resources**: 2 CPU, 2GB RAM, 10GB Disk\n- **Tags**: category;tag2;tag3\n- **Access URL**: http://IP:PORT/path\n\n## Checklist\n\n- [ ] My code follows the style guidelines\n- [ ] I have performed a self-review\n- [ ] I have tested the script via curl from my fork (after git push)\n- [ ] GitHub had time to update (waited 10-30 seconds)\n- [ ] ShellCheck shows no critical warnings\n- [ ] Documentation is accurate and complete\n- [ ] I have added/updated relevant documentation\n```\n\n### Step 5: Respond to Review Comments\n\n**Maintainers may request changes**:\n\n- Fix syntax/style issues\n- Add better error handling\n- Optimize resource usage\n- Update documentation\n\n**To address feedback**:\n\n```bash\n# Make requested changes\ngit add .\ngit commit -m \"Address review feedback: ...\"\ngit push origin feat/add-myapp\n\n# PR automatically updates!\n# No need to create new PR\n```\n\n### Step 6: Celebrate! 🎉\n\nOnce merged, your contribution will be part of ProxmoxVE and available to all users!\n\n---\n\n## Troubleshooting\n\n### \"Repository not found\" when cloning\n\n```bash\n# Check your fork exists\n# Visit: https://github.com/YOUR_USERNAME/ProxmoxVE\n\n# If not there: Click \"Fork\" on original repo first\n```\n\n### \"Permission denied\" when pushing\n\n```bash\n# Setup SSH key\nssh-keygen -t ed25519 -C \"your_email@example.com\"\ncat ~/.ssh/id_ed25519.pub  # Copy this\n\n# Add to GitHub: Settings → SSH Keys → New Key\n\n# Or use HTTPS with token:\ngit remote set-url origin https://YOUR_TOKEN@github.com/YOUR_USERNAME/ProxmoxVE.git\n```\n\n### Script syntax errors\n\n```bash\n# Use ShellCheck to identify issues\nshellcheck install/myapp-install.sh\n\n# Common issues:\n# - Unmatched quotes: \"string' or 'string\"\n# - Missing semicolons before then: if [...]; then\n# - Wrong quoting: echo $VAR instead of echo \"${VAR}\"\n```\n\n### Container creation fails immediately\n\n```bash\n# 1. Check Proxmox resources\nfree -h              # Check RAM\ndf -h                # Check disk space\npct list            # Check CTID availability\n\n# 2. Check script URL\n# Make sure curl -s in script points to your fork\n\n# 3. Review errors\n# Run with verbose: bash -x ct/myapp.sh\n```\n\n### App not accessible after creation\n\n```bash\n# 1. Verify container running\npct list\npct status CTID\n\n# 2. Check if service running inside\npct exec CTID systemctl status myapp\n\n# 3. Check firewall\n# Proxmox host: iptables -L\n# Container: iptables -L\n\n# 4. Verify listening port\npct exec CTID netstat -tlnp | grep LISTEN\n```\n\n---\n\n## FAQ\n\n### Q: Do I need to be a Bash expert?\n\n**A**: No! The codebase has many examples you can copy. Most contributions are straightforward script creation following the established patterns.\n\n### Q: Can I add a new application that's not open source?\n\n**A**: No. ProxmoxVE focuses on open-source applications (GPL, MIT, Apache, etc.). Closed-source applications won't be accepted.\n\n### Q: How long until my PR is reviewed?\n\n**A**: Maintainers are volunteers. Reviews typically happen within 1-2 weeks. Complex changes may take longer.\n\n### Q: Can I test without a Proxmox system?\n\n**A**: Partially. You can verify syntax and ShellCheck compliance locally, but real container testing requires Proxmox. Consider using:\n\n- Proxmox in a VM (VirtualBox/KVM)\n- Test instances on Hetzner/DigitalOcean\n- Ask maintainers to test for you\n\n### Q: My update function is very complex - is that OK?\n\n**A**: Yes! Update functions can be complex if needed. Just ensure:\n\n- Backup user data before updating\n- Restore user data after update\n- Test thoroughly before submitting\n- Add clear comments explaining logic\n\n### Q: Can I add new dependencies to build.func?\n\n**A**: Generally no. build.func is the orchestrator and should remain stable. New functions should go in:\n\n- `tools.func` - Tool installation\n- `core.func` - Utility functions\n- `install.func` - Container setup\n\nAsk in an issue first if you're unsure.\n\n### Q: What if the application has many configuration options?\n\n**A**: You have options:\n\n**Option 1**: Use Advanced mode (19-step wizard)\n\n```bash\n# Extend advanced_settings() if app needs special vars\n```\n\n**Option 2**: Create custom setup menu\n\n```bash\nfunction custom_config() {\n  OPTION=$(whiptail --inputbox \"Enter database name:\" 8 60)\n  # ... use $OPTION in installation\n}\n```\n\n**Option 3**: Leave as defaults + documentation\n\n```bash\n# In success message:\necho \"Edit /opt/myapp/config.json to customize settings\"\n```\n\n### Q: Can I contribute Windows/macOS/ARM support?\n\n**A**:\n\n- **Windows**: Not planned (ProxmoxVE is Linux/Proxmox focused)\n- **macOS**: Can contribute Docker-based alternatives\n- **ARM**: Yes! Many apps work on ARM. Add to vm/pimox-\\*.sh scripts\n\n---\n\n## Getting Help\n\n### Resources\n\n- **Documentation**: `/docs` directory and wikis\n- **Function Reference**: `/misc/*.md` wiki files\n- **Examples**: Look at similar applications in `/ct` and `/install`\n- **GitHub Issues**: https://github.com/community-scripts/ProxmoxVE/issues\n- **Discussions**: https://github.com/community-scripts/ProxmoxVE/discussions\n\n### Ask Questions\n\n1. **Check existing issues** - Your question may be answered\n2. **Search documentation** - See `/docs` and `/misc/*.md`\n3. **Ask in Discussions** - For general questions\n4. **Open an Issue** - For bugs or specific problems\n\n### Report Bugs\n\nWhen reporting bugs, include:\n\n- Which application\n- What happened (error message)\n- What you expected\n- Your Proxmox version\n- Container OS and version\n\nExample:\n\n```\nTitle: pihole-install.sh fails on Alpine 3.20\n\nDescription:\nInstallation fails with error: \"PHP-FPM not found\"\n\nExpected:\nPiHole should install successfully\n\nEnvironment:\n- Proxmox VE 8.2\n- Alpine 3.20\n- Container CTID 110\n\nError Output:\n[ERROR] in line 42: exit code 127: while executing command php-fpm --start\n```\n\n---\n\n## Contribution Statistics\n\n**ProxmoxVE by the Numbers**:\n\n- 🎯 40+ applications supported\n- 👥 100+ contributors\n- 📊 10,000+ GitHub stars\n- 🚀 50+ releases\n- 📈 100,000+ downloads/month\n\n**Your contribution makes a difference!**\n\n---\n\n## Code of Conduct\n\nBy contributing, you agree to:\n\n- ✅ Be respectful and inclusive\n- ✅ Follow the style guidelines\n- ✅ Test your changes thoroughly\n- ✅ Provide clear commit messages\n- ✅ Respond to review feedback\n\n---\n\n**Ready to contribute?** Start with the [Quick Start](#quick-start) section!\n\n**Questions?** Open an issue or start a discussion on GitHub.\n\n**Thank you for your contribution!** 🙏\n"
  },
  {
    "path": "docs/contribution/HELPER_FUNCTIONS.md",
    "content": "# 🛠️ Helper Functions Reference\n\n**Quick reference for all helper functions available in `tools.func`**\n\n> These functions are automatically available in install scripts via `$FUNCTIONS_FILE_PATH`\n\n---\n\n## 📋 Table of Contents\n\n- [Scripts to Watch](#scripts-to-watch)\n- [Runtime & Language Setup](#runtime--language-setup)\n- [Database Setup](#database-setup)\n- [GitHub Release Helpers](#github-release-helpers)\n- [Tools & Utilities](#tools--utilities)\n- [SSL/TLS](#ssltls)\n- [Utility Functions](#utility-functions)\n- [Package Management](#package-management)\n\n---\n\n## 📚 Scripts to Watch\n\n**Learn from real, well-implemented scripts. Each app requires TWO files that work together:**\n\n| File               | Location                     | Purpose                                                                  |\n| ------------------ | ---------------------------- | ------------------------------------------------------------------------ |\n| **CT Script**      | `ct/appname.sh`              | Runs on **Proxmox host** - creates container, contains `update_script()` |\n| **Install Script** | `install/appname-install.sh` | Runs **inside container** - installs and configures the app              |\n\n> ⚠️ **Both files are ALWAYS required!** The CT script calls the install script automatically during container creation.\n\nInstall scripts are **not** run directly by users; they are invoked by the CT script inside the container.\n\n### Node.js + PostgreSQL\n\n**Koel** - Music streaming with PHP + Node.js + PostgreSQL\n| File | Link |\n| ----------------- | -------------------------------------------------------- |\n| CT (update logic) | [ct/koel.sh](../../ct/koel.sh) |\n| Install | [install/koel-install.sh](../../install/koel-install.sh) |\n\n**Actual Budget** - Finance app with npm global install\n| File | Link |\n| ----------------- | ------------------------------------------------------------------------ |\n| CT (update logic) | [ct/actualbudget.sh](../../ct/actualbudget.sh) |\n| Install | [install/actualbudget-install.sh](../../install/actualbudget-install.sh) |\n\n### Python + uv\n\n**MeTube** - YouTube downloader with Python uv + Node.js + Deno\n| File | Link |\n| ----------------- | ------------------------------------------------------------ |\n| CT (update logic) | [ct/metube.sh](../../ct/metube.sh) |\n| Install | [install/metube-install.sh](../../install/metube-install.sh) |\n\n**Endurain** - Fitness tracker with Python uv + PostgreSQL/PostGIS\n| File | Link |\n| ----------------- | ---------------------------------------------------------------- |\n| CT (update logic) | [ct/endurain.sh](../../ct/endurain.sh) |\n| Install | [install/endurain-install.sh](../../install/endurain-install.sh) |\n\n### Java + Gradle\n\n**BookLore** - Book management with Java 21 + Gradle + MariaDB + Nginx\n| File | Link |\n| ----------------- | -------------------------------------------------------------- |\n| CT (update logic) | [ct/booklore.sh](../../ct/booklore.sh) |\n| Install | [install/booklore-install.sh](../../install/booklore-install.sh) |\n\n### Pnpm + Meilisearch\n\n**KaraKeep** - Bookmark manager with Pnpm + Meilisearch + Puppeteer\n| File | Link |\n| ----------------- | -------------------------------------------------------------- |\n| CT (update logic) | [ct/karakeep.sh](../../ct/karakeep.sh) |\n| Install | [install/karakeep-install.sh](../../install/karakeep-install.sh) |\n\n### PHP + MariaDB/MySQL\n\n**Wallabag** - Read-it-later with PHP + MariaDB + Redis + Nginx\n| File | Link |\n| ----------------- | ---------------------------------------------------------------- |\n| CT (update logic) | [ct/wallabag.sh](../../ct/wallabag.sh) |\n| Install | [install/wallabag-install.sh](../../install/wallabag-install.sh) |\n\n**InvoiceNinja** - Invoicing with PHP + MariaDB + Supervisor\n| File | Link |\n| ----------------- | ------------------------------------------------------------------------ |\n| CT (update logic) | [ct/invoiceninja.sh](../../ct/invoiceninja.sh) |\n| Install | [install/invoiceninja-install.sh](../../install/invoiceninja-install.sh) |\n\n**BookStack** - Wiki/Docs with PHP + MariaDB + Apache\n| File | Link |\n| ----------------- | ------------------------------------------------------------------ |\n| CT (update logic) | [ct/bookstack.sh](../../ct/bookstack.sh) |\n| Install | [install/bookstack-install.sh](../../install/bookstack-install.sh) |\n\n### PHP + SQLite (Simple)\n\n**Speedtest Tracker** - Speedtest with PHP + SQLite + Nginx\n| File | Link |\n| ----------------- | ---------------------------------------------------------------------------------- |\n| CT (update logic) | [ct/speedtest-tracker.sh](../../ct/speedtest-tracker.sh) |\n| Install | [install/speedtest-tracker-install.sh](../../install/speedtest-tracker-install.sh) |\n\n---\n\n## Runtime & Language Setup\n\n### `setup_nodejs`\n\nInstall Node.js from NodeSource repository.\n\n```bash\n# Default (Node.js 24)\nsetup_nodejs\n\n# Specific version\nNODE_VERSION=\"20\" setup_nodejs\nNODE_VERSION=\"22\" setup_nodejs\nNODE_VERSION=\"24\" setup_nodejs\n```\n\n### `setup_go`\n\nInstall Go programming language (latest stable).\n\n```bash\nsetup_go\n\n# Use in script\nsetup_go\ncd /opt/myapp\n$STD go build -o myapp .\n```\n\n### `setup_rust`\n\nInstall Rust via rustup.\n\n```bash\nsetup_rust\n\n# Use in script\nsetup_rust\nsource \"$HOME/.cargo/env\"\n$STD cargo build --release\n```\n\n### `setup_uv`\n\nInstall Python uv package manager (fast pip/venv replacement).\n\n```bash\n# Default\nsetup_uv\n\n# Install a specific Python version\nPYTHON_VERSION=\"3.12\" setup_uv\n\n# Use in script\nsetup_uv\ncd /opt/myapp\n$STD uv sync --locked\n```\n\n### `setup_ruby`\n\nInstall Ruby from official repositories.\n\n```bash\nsetup_ruby\n```\n\n### `setup_php`\n\nInstall PHP with configurable modules and FPM/Apache support.\n\n```bash\n# Basic PHP\nsetup_php\n\n# Full configuration\nPHP_VERSION=\"8.4\" \\\nPHP_MODULE=\"mysqli,gd,curl,mbstring,xml,zip,ldap\" \\\nPHP_FPM=\"YES\" \\\nPHP_APACHE=\"YES\" \\\nsetup_php\n```\n\n**Environment Variables:**\n| Variable | Default | Description |\n| ------------- | ------- | ------------------------------- |\n| `PHP_VERSION` | `8.4` | PHP version to install |\n| `PHP_MODULE` | `\"\"` | Comma-separated list of modules |\n| `PHP_FPM` | `NO` | Install PHP-FPM |\n| `PHP_APACHE` | `NO` | Install Apache module |\n\n### `setup_composer`\n\nInstall PHP Composer package manager.\n\n```bash\nsetup_php\nsetup_composer\n\n# Use in script\n$STD composer install --no-dev\n```\n\n### `setup_java`\n\nInstall Java (OpenJDK).\n\n```bash\n# Default (Java 21)\nsetup_java\n\n# Specific version\nJAVA_VERSION=\"17\" setup_java\nJAVA_VERSION=\"21\" setup_java\n```\n\n---\n\n## Database Setup\n\n### `setup_mariadb`\n\nInstall MariaDB server.\n\n```bash\nsetup_mariadb\n```\n\n### `setup_mariadb_db`\n\nCreate a MariaDB database and user. Sets `$MARIADB_DB_PASS` with the generated password.\n\n```bash\nsetup_mariadb\nMARIADB_DB_NAME=\"myapp_db\" MARIADB_DB_USER=\"myapp_user\" setup_mariadb_db\n\n# After calling, these variables are available:\n# $MARIADB_DB_NAME - Database name\n# $MARIADB_DB_USER - Database user\n# $MARIADB_DB_PASS - Generated password (saved to ~/[appname].creds)\n```\n\n### `setup_mysql`\n\nInstall MySQL server.\n\n```bash\nsetup_mysql\n```\n\n### `setup_postgresql`\n\nInstall PostgreSQL server.\n\n```bash\n# Default (PostgreSQL 16)\nsetup_postgresql\n\n# Specific version\nPG_VERSION=\"16\" setup_postgresql\nPG_VERSION=\"16\" setup_postgresql\n```\n\n### `setup_postgresql_db`\n\nCreate a PostgreSQL database and user. Sets `$PG_DB_PASS` with the generated password.\n\n```bash\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"myapp_db\" PG_DB_USER=\"myapp_user\" setup_postgresql_db\n\n# After calling, these variables are available:\n# $PG_DB_NAME - Database name\n# $PG_DB_USER - Database user\n# $PG_DB_PASS - Generated password (saved to ~/[appname].creds)\n```\n\n### `setup_mongodb`\n\nInstall MongoDB server.\n\n```bash\nsetup_mongodb\n```\n\n### `setup_clickhouse`\n\nInstall ClickHouse analytics database.\n\n```bash\nsetup_clickhouse\n```\n\n---\n\n## Advanced Repository Management\n\n### `setup_deb822_repo`\n\nThe modern standard (Debian 12+) for adding external repositories. Automatically handles GPG keys and sources.\n\n```bash\nsetup_deb822_repo \\\n  \"nodejs\" \\\n  \"https://deb.nodesource.com/gpgkey/nodesource.gpg.key\" \\\n  \"https://deb.nodesource.com/node_22.x\" \\\n  \"bookworm\" \\\n  \"main\"\n```\n\n### `prepare_repository_setup`\n\nA high-level helper that performs three critical tasks before adding a new repo:\n1. Cleans up old repo files matching the names provided.\n2. Removes old GPG keyrings from all standard locations.\n3. Ensures APT is in a working state (fixes locks, runs update).\n\n```bash\n# Clean up old mysql/mariadb artifacts before setup\nprepare_repository_setup \"mariadb\" \"mysql\"\n```\n\n### `cleanup_tool_keyrings`\n\nForce-removes GPG keys for specific tools from `/usr/share/keyrings/`, `/etc/apt/keyrings/`, and `/etc/apt/trusted.gpg.d/`.\n\n```bash\ncleanup_tool_keyrings \"docker\" \"kubernetes\"\n```\n\n---\n\n## GitHub Release Helpers\n\n> **Note**: `fetch_and_deploy_gh_release` is the **preferred method** for downloading GitHub releases. It handles version tracking automatically. Only use `get_latest_github_release` if you need the version number separately.\n\n### `fetch_and_deploy_gh_release`\n\n**Primary method** for downloading and extracting GitHub releases. Handles version tracking automatically.\n\n```bash\n# Basic usage - downloads tarball to /opt/appname\nfetch_and_deploy_gh_release \"appname\" \"owner/repo\"\n\n# With explicit parameters\nfetch_and_deploy_gh_release \"appname\" \"owner/repo\" \"tarball\" \"latest\" \"/opt/appname\"\n\n# Pre-built release with specific asset pattern\nfetch_and_deploy_gh_release \"koel\" \"koel/koel\" \"prebuild\" \"latest\" \"/opt/koel\" \"koel-*.tar.gz\"\n\n# Clean install (removes old directory first) - used in update_script\nCLEAN_INSTALL=1 fetch_and_deploy_gh_release \"appname\" \"owner/repo\" \"tarball\" \"latest\" \"/opt/appname\"\n```\n\n**Parameters:**\n| Parameter | Default | Description |\n| --------------- | ------------- | ----------------------------------------------------------------- |\n| `name` | required | App name (for version tracking) |\n| `repo` | required | GitHub repo (`owner/repo`) |\n| `type` | `tarball` | Release type: `tarball`, `zipball`, `prebuild`, `binary` |\n| `version` | `latest` | Version tag or `latest` |\n| `dest` | `/opt/[name]` | Destination directory |\n| `asset_pattern` | `\"\"` | For `prebuild`: glob pattern to match asset (e.g. `app-*.tar.gz`) |\n\n**Environment Variables:**\n| Variable | Description |\n| ----------------- | ------------------------------------------------------------ |\n| `CLEAN_INSTALL=1` | Remove destination directory before extracting (for updates) |\n\n### `check_for_gh_release`\n\nCheck if a newer version is available. Returns 0 if update needed, 1 if already at latest. **Use in `update_script()` function.**\n\n```bash\n# In update_script() function in ct/appname.sh\nif check_for_gh_release \"appname\" \"owner/repo\"; then\n  msg_info \"Updating...\"\n  # Stop services, backup, update, restore, start\n  CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"appname\" \"owner/repo\"\n  msg_ok \"Updated successfully!\"\nfi\n```\n\n### `get_latest_github_release`\n\nGet the latest release version from a GitHub repository. **Only use if you need the version number separately** (e.g., for manual download or display).\n\n```bash\nRELEASE=$(get_latest_github_release \"owner/repo\")\necho \"Latest version: $RELEASE\"\n```\n\n---\n\n## Tools & Utilities\n\n### `setup_meilisearch`\n\nInstall Meilisearch, a lightning-fast search engine.\n\n```bash\nsetup_meilisearch\n\n# Use in script\n$STD php artisan scout:sync-index-settings\n```\n\n### `setup_yq`\n\nInstall yq YAML processor.\n\n```bash\nsetup_yq\n\n# Use in script\nyq '.server.port = 8080' -i config.yaml\n````\n\n### `setup_ffmpeg`\n\nInstall FFmpeg with common codecs.\n\n```bash\nsetup_ffmpeg\n```\n\n### `setup_hwaccel`\n\nSetup GPU hardware acceleration (Intel/AMD/NVIDIA).\n\n```bash\n# Only runs if GPU passthrough is detected (/dev/dri, /dev/nvidia0, /dev/kfd)\nsetup_hwaccel\n```\n\n### `setup_imagemagick`\n\nInstall ImageMagick 7 from source.\n\n```bash\nsetup_imagemagick\n```\n\n### `setup_docker`\n\nInstall Docker Engine.\n\n```bash\nsetup_docker\n```\n\n### `setup_adminer`\n\nInstall Adminer for database management.\n\n```bash\nsetup_mariadb\nsetup_adminer\n\n# Access at http://IP/adminer\n```\n\n---\n\n## SSL/TLS\n\n### `create_self_signed_cert`\n\nCreate a self-signed SSL certificate.\n\n```bash\ncreate_self_signed_cert\n\n# Creates files at:\n# /etc/ssl/[appname]/[appname].key\n# /etc/ssl/[appname]/[appname].crt\n```\n\n---\n\n## Utility Functions\n\n### `verify_tool_version`\n\nValidate that the installed major version matches the expected version. Useful during upgrades or troubleshooting.\n\n```bash\n# Verify Node.js is version 22\nverify_tool_version \"nodejs\" \"22\" \"$(node -v | grep -oP '^v\\K[0-9]+')\"\n```\n\n### `get_lxc_ip`\n\nSet the `$LOCAL_IP` variable with the container's IP address.\n\n```bash\nget_lxc_ip\necho \"Container IP: $LOCAL_IP\"\n\n# Use in config files\nsed -i \"s/localhost/$LOCAL_IP/g\" /opt/myapp/config.yaml\n```\n\n### `ensure_dependencies`\n\nEnsure packages are installed (installs if missing).\n\n```bash\nensure_dependencies \"jq\" \"unzip\" \"curl\"\n```\n\n### `msg_info` / `msg_ok` / `msg_error` / `msg_warn`\n\nDisplay formatted messages.\n\n```bash\nmsg_info \"Installing application...\"\n# ... do work ...\nmsg_ok \"Installation complete\"\n\nmsg_warn \"Optional feature not available\"\nmsg_error \"Installation failed\"\n```\n\n---\n\n## Package Management\n\n### `cleanup_lxc`\n\nFinal cleanup function - call at end of install script.\n\n```bash\n# At the end of your install script\nmotd_ssh\ncustomize\ncleanup_lxc  # Handles autoremove, autoclean, cache cleanup\n```\n\n### `install_packages_with_retry`\n\nInstall packages with automatic retry on failure.\n\n```bash\ninstall_packages_with_retry \"package1\" \"package2\" \"package3\"\n```\n\n### `prepare_repository_setup`\n\nPrepare system for adding new repositories (cleanup old repos, keyrings).\n\n```bash\nprepare_repository_setup \"mariadb\" \"mysql\"\n```\n\n---\n\n## Complete Examples\n\n### Example 1: Node.js App with PostgreSQL (install script)\n\n```bash\n#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: YourUsername\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/example/myapp\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\n# Setup runtimes and databases FIRST\nNODE_VERSION=\"22\" setup_nodejs\nPG_VERSION=\"16\" setup_postgresql\nPG_DB_NAME=\"myapp\" PG_DB_USER=\"myapp\" setup_postgresql_db\nget_lxc_ip\n\n# Download app using fetch_and_deploy (handles version tracking)\nfetch_and_deploy_gh_release \"myapp\" \"example/myapp\" \"tarball\" \"latest\" \"/opt/myapp\"\n\nmsg_info \"Setting up MyApp\"\ncd /opt/myapp\n$STD npm ci --production\nmsg_ok \"Setup MyApp\"\n\nmsg_info \"Configuring MyApp\"\ncat <<EOF >/opt/myapp/.env\nDATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost/${PG_DB_NAME}\nHOST=${LOCAL_IP}\nPORT=3000\nEOF\nmsg_ok \"Configured MyApp\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/myapp.service\n[Unit]\nDescription=MyApp\nAfter=network.target postgresql.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/myapp\nExecStart=/usr/bin/node /opt/myapp/server.js\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now myapp\nmsg_ok \"Created Service\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n### Example 2: Matching Container Script (ct script)\n\n```bash\n#!/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: YourUsername\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/example/myapp\n\nAPP=\"MyApp\"\nvar_tags=\"${var_tags:-webapp}\"\nvar_cpu=\"${var_cpu:-2}\"\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\n  if [[ ! -d /opt/myapp ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  # check_for_gh_release returns true if update available\n  if check_for_gh_release \"myapp\" \"example/myapp\"; then\n    msg_info \"Stopping Service\"\n    systemctl stop myapp\n    msg_ok \"Stopped Service\"\n\n    msg_info \"Creating Backup\"\n    cp /opt/myapp/.env /tmp/myapp_env.bak\n    msg_ok \"Created Backup\"\n\n    # CLEAN_INSTALL=1 removes old dir before extracting\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"myapp\" \"example/myapp\" \"tarball\" \"latest\" \"/opt/myapp\"\n\n    msg_info \"Restoring Config & Rebuilding\"\n    cp /tmp/myapp_env.bak /opt/myapp/.env\n    rm /tmp/myapp_env.bak\n    cd /opt/myapp\n    $STD npm ci --production\n    msg_ok \"Restored Config & Rebuilt\"\n\n    msg_info \"Starting Service\"\n    systemctl start myapp\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}:3000${CL}\"\n```\n\n### Example 3: PHP App with MariaDB (install script)\n\n```bash\n#!/usr/bin/env bash\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\n# PHP with FPM and common modules\nPHP_VERSION=\"8.4\" PHP_FPM=\"YES\" PHP_MODULE=\"bcmath,curl,gd,intl,mbstring,mysql,xml,zip\" setup_php\nsetup_composer\nsetup_mariadb\nMARIADB_DB_NAME=\"myapp\" MARIADB_DB_USER=\"myapp\" setup_mariadb_db\nget_lxc_ip\n\n# Download pre-built release (with asset pattern)\nfetch_and_deploy_gh_release \"myapp\" \"example/myapp\" \"prebuild\" \"latest\" \"/opt/myapp\" \"myapp-*.tar.gz\"\n\nmsg_info \"Configuring MyApp\"\ncd /opt/myapp\ncp .env.example .env\nsed -i \"s|APP_URL=.*|APP_URL=http://${LOCAL_IP}|\" .env\nsed -i \"s|DB_DATABASE=.*|DB_DATABASE=${MARIADB_DB_NAME}|\" .env\nsed -i \"s|DB_USERNAME=.*|DB_USERNAME=${MARIADB_DB_USER}|\" .env\nsed -i \"s|DB_PASSWORD=.*|DB_PASSWORD=${MARIADB_DB_PASS}|\" .env\n$STD composer install --no-dev --no-interaction\n$STD php artisan key:generate --force\n$STD php artisan migrate --force\nchown -R www-data:www-data /opt/myapp\nmsg_ok \"Configured MyApp\"\n\n# ... nginx config, service creation ...\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n"
  },
  {
    "path": "docs/contribution/README.md",
    "content": "# 🤝 Contributing to ProxmoxVE\n\nComplete guide to contributing to the ProxmoxVE project - from your first fork to submitting your pull request.\n\n---\n\n## 📋 Table of Contents\n\n- [Quick Start](#quick-start)\n- [Setting Up Your Fork](#setting-up-your-fork)\n- [Coding Standards](#coding-standards)\n- [Code Audit](#code-audit)\n- [Guides & Resources](#guides--resources)\n- [FAQ](#faq)\n\n---\n\n## 🚀 Quick Start\n\n### 60 Seconds to Contributing (Development)\n\nWhen developing and testing **in your fork**:\n\n```bash\n# 1. Fork on GitHub\n# Visit: https://github.com/community-scripts/ProxmoxVE → Fork (top right)\n\n# 2. Clone your fork\ngit clone https://github.com/YOUR_USERNAME/ProxmoxVE.git\ncd ProxmoxVE\n\n# 3. Auto-configure your fork (IMPORTANT - updates all links!)\nbash docs/contribution/setup-fork.sh --full\n\n# 4. Create a feature branch\ngit checkout -b feature/my-awesome-app\n\n# 5. Read the guides\ncat docs/README.md              # Documentation overview\ncat docs/ct/DETAILED_GUIDE.md   # For container scripts\ncat docs/install/DETAILED_GUIDE.md  # For install scripts\n\n# 6. Create your contribution\ncp docs/contribution/templates_ct/AppName.sh ct/myapp.sh\ncp docs/contribution/templates_install/AppName-install.sh install/myapp-install.sh\n# ... edit files ...\n\n# 7. Push to your fork and test via GitHub\ngit push origin feature/my-awesome-app\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/myapp.sh)\"\n# ⏱️ GitHub may take 10-30 seconds to update files - be patient!\n\n# 8. Request website metadata via the website\n# Go to the script's page on the website, use the \"Report issue\" button — it will guide you.\n\n# 9. No direct install-script test\n# Install scripts are executed by the CT script inside the container\n\n# 10. Commit ONLY your new files (see Cherry-Pick section below!)\ngit add ct/myapp.sh install/myapp-install.sh\ngit commit -m \"feat: add MyApp container and install scripts\"\ngit push origin feature/my-awesome-app\n\n# 11. Create Pull Request on GitHub\n```\n\n⚠️ **IMPORTANT: After setup-fork.sh, many files are modified!**\n\nSee the **Cherry-Pick: Submitting Only Your Changes** section below to learn how to push ONLY your 2 files instead of 600+ modified files!\n\n### How Users Run Scripts (After Merged)\n\nOnce your script is merged to the main repository, users download and run it from GitHub like this:\n\n```bash\n# ✅ Users run from GitHub (normal usage after PR merged)\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/myapp.sh)\"\n\n# Install scripts are called by the CT script and are not run directly by users\n```\n\n### Development vs. Production Execution\n\n**During Development (you, in your fork):**\n\n```bash\n# You MUST test via curl from your GitHub fork (not local files!)\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/myapp.sh)\"\n\n# The script's curl commands are updated by setup-fork.sh to point to YOUR fork\n# This ensures you're testing your actual changes\n# ⏱️ Wait 10-30 seconds after pushing - GitHub updates slowly\n```\n\n**After Merge (users, from GitHub):**\n\n```bash\n# Users download the script from upstream via curl\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/myapp.sh)\"\n\n# The script's curl commands now point back to upstream (community-scripts)\n# This is the stable, tested version\n```\n\n**Summary:**\n\n- **Development**: Push to fork, test via curl → setup-fork.sh changes curl URLs to your fork\n- **Production**: curl | bash from upstream → curl URLs point to community-scripts repo\n\n---\n\n## 🍴 Setting Up Your Fork\n\n### Automatic Setup (Recommended)\n\nWhen you clone your fork, run the setup script to automatically configure everything:\n\n```bash\nbash docs/contribution/setup-fork.sh --full\n```\n\n**What it does:**\n\n- Auto-detects your GitHub username from git config\n- Auto-detects your fork repository name\n- Updates **ALL** hardcoded links to point to your fork instead of the main repo (`--full`)\n- Creates `.git-setup-info` with your configuration\n- Allows you to develop and test independently in your fork\n\n**Why this matters:**\n\nWithout running this script, all links in your fork will still point to the upstream repository (community-scripts). This is a problem when testing because:\n\n- Installation links will pull from upstream, not your fork\n- Updates will target the wrong repository\n- Your contributions won't be properly tested\n\n**After running setup-fork.sh:**\n\nYour fork is fully configured and ready to develop. You can:\n\n- Push changes to your fork\n- Test via curl: `bash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/myapp.sh)\"`\n- All links will reference your fork for development\n- ⏱️ Wait 10-30 seconds after pushing - GitHub takes time to update\n- Commit and push with confidence\n- Create a PR to merge into upstream\n\n**See**: [FORK_SETUP.md](FORK_SETUP.md) for detailed instructions\n\n### Manual Setup\n\nIf the script doesn't work, manually configure:\n\n```bash\n# Set git user\ngit config user.name \"Your Name\"\ngit config user.email \"your.email@example.com\"\n\n# Add upstream remote for syncing with main repo\ngit remote add upstream https://github.com/community-scripts/ProxmoxVE.git\n\n# Verify remotes\ngit remote -v\n# Should show: origin (your fork) and upstream (main repo)\n```\n\n---\n\n## 📖 Coding Standards\n\nAll scripts and configurations must follow our coding standards to ensure consistency and quality.\n\n### Available Guides\n\n- **[CONTRIBUTING.md](CONTRIBUTING.md)** - Essential coding standards and best practices\n- **[CODE-AUDIT.md](CODE-AUDIT.md)** - Code review checklist and audit procedures\n- **[GUIDE.md](GUIDE.md)** - Comprehensive contribution guide\n- **[HELPER_FUNCTIONS.md](HELPER_FUNCTIONS.md)** - Reference for all tools.func helper functions\n- **Container Scripts** - `/ct/` templates and guidelines\n- **Install Scripts** - `/install/` templates and guidelines\n- **Website metadata** – Request via the website (Report issue on the script page); see [templates_json/AppName.md](templates_json/AppName.md)\n\n### Quick Checklist\n\n- ✅ Use `/ct/example.sh` as template for container scripts\n- ✅ Use `/install/example-install.sh` as template for install scripts\n- ✅ Follow naming conventions: `appname.sh` and `appname-install.sh`\n- ✅ Include proper shebang: `#!/usr/bin/env bash`\n- ✅ Add copyright header with author\n- ✅ Handle errors properly with `msg_error`, `msg_ok`, etc.\n- ✅ Test before submitting PR (via curl from your fork, not local bash)\n- ✅ Update documentation if needed\n\n---\n\n## 🔍 Code Audit\n\nBefore submitting a pull request, ensure your code passes our audit:\n\n**See**: [CODE_AUDIT.md](CODE_AUDIT.md) for complete audit checklist\n\nKey points:\n\n- Code consistency with existing scripts\n- Proper error handling\n- Correct variable naming\n- Adequate comments and documentation\n- Security best practices\n\n---\n\n## 🍒 Cherry-Pick: Submitting Only Your Changes\n\n**Problem**: `setup-fork.sh` modifies 600+ files to update links. You don't want to submit all of those changes - only your new 2 files!\n\n**Solution**: Use git cherry-pick to select only YOUR files.\n\n### Step-by-Step Cherry-Pick Guide\n\n#### 1. Check what changed\n\n```bash\n# See all modified files\ngit status\n\n# Verify your files are there\ngit status | grep -E \"ct/myapp|install/myapp\"\n```\n\n#### 2. Create a clean feature branch for submission\n\n```bash\n# Go back to upstream main (clean slate)\ngit fetch upstream\ngit checkout -b submit/myapp upstream/main\n\n# Don't use your modified main branch!\n```\n\n#### 3. Cherry-pick ONLY your files\n\nCherry-picking extracts specific changes from commits:\n\n```bash\n# Option A: Cherry-pick commits that added your files\n# (if you committed your files separately)\ngit cherry-pick <commit-hash-of-your-files>\n\n# Option B: Manually copy and commit only your files\n# From your work branch, get the file contents\ngit show feature/my-awesome-app:ct/myapp.sh > /tmp/myapp.sh\ngit show feature/my-awesome-app:install/myapp-install.sh > /tmp/myapp-install.sh\n\n# Add them to the clean branch\ncp /tmp/myapp.sh ct/myapp.sh\ncp /tmp/myapp-install.sh install/myapp-install.sh\n\n# Commit\ngit add ct/myapp.sh install/myapp-install.sh\ngit commit -m \"feat: add MyApp\"\n```\n\n#### 4. Verify only your files are in the PR\n\n```bash\n# Check git diff against upstream\ngit diff upstream/main --name-only\n# Should show ONLY:\n#   ct/myapp.sh\n#   install/myapp-install.sh\n```\n\n#### 5. Push and create PR\n\n```bash\n# Push your clean submission branch\ngit push origin submit/myapp\n\n# Create PR on GitHub from: submit/myapp → main\n```\n\n### Why This Matters\n\n- ✅ Clean PR with only your changes\n- ✅ Easier for maintainers to review\n- ✅ Faster merge without conflicts\n- ❌ Without cherry-pick: PR has 600+ file changes (won't merge!)\n\n### If You Made a Mistake\n\n```bash\n# Delete the messy branch\ngit branch -D submit/myapp\n\n# Go back to clean branch\ngit checkout -b submit/myapp upstream/main\n\n# Try cherry-picking again\n```\n\n---\n\nIf you're using **Visual Studio Code** with an AI assistant, you can leverage our detailed guidelines to generate high-quality contributions automatically.\n\n### How to Use AI Assistance\n\n1. **Open the AI Guidelines**\n\n   ```\n   docs/contribution/AI.md\n   ```\n\n   This file contains all requirements, patterns, and examples for writing proper scripts.\n\n2. **Prepare Your Information**\n\n   Before asking the AI to generate code, gather:\n   - **Repository URL**: e.g., `https://github.com/owner/myapp`\n   - **Dockerfile/Script**: Paste the app's installation instructions (if available)\n   - **Dependencies**: What packages does it need? (Node, Python, Java, PostgreSQL, etc.)\n   - **Ports**: What port does it listen on? (e.g., 3000, 8080, 5000)\n   - **Configuration**: Any environment variables or config files?\n\n3. **Tell the AI Assistant**\n\n   Share with the AI:\n   - The repository URL\n   - The Dockerfile or install instructions\n   - Link to [docs/contribution/AI.md](AI.md) with instructions to follow\n\n   **Example prompt:**\n\n   ```\n   I want to contribute a container script for MyApp to ProxmoxVE.\n   Repository: https://github.com/owner/myapp\n\n   Here's the Dockerfile:\n   [paste Dockerfile content]\n\n   Please follow the guidelines in docs/contribution/AI.md to create:\n   1. ct/myapp.sh (container script)\n   2. install/myapp-install.sh (installation script)\n\n   Website listing/metadata is requested separately via the website (Report issue on the script page).\n   ```\n\n4. **AI Will Generate**\n\n   The AI will produce scripts that:\n   - Follow all ProxmoxVE patterns and conventions\n   - Use helper functions from `tools.func` correctly\n   - Include proper error handling and messages\n   - Have correct update mechanisms\n   - Are ready to submit as a PR\n\n   Website listing/metadata is requested separately via the website (Report issue on the script page).\n\n### Key Points for AI Assistants\n\n- **Templates Location**: `docs/contribution/templates_ct/AppName.sh`, `templates_install/`, `templates_json/`\n- **Guidelines**: Must follow `docs/contribution/AI.md` exactly\n- **Helper Functions**: Use only functions from `misc/tools.func` - never write custom ones\n- **Testing**: Always test before submission via curl from your fork\n  ```bash\n  bash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/myapp.sh)\"\n  # Wait 10-30 seconds after pushing changes\n  ```\n- **No Docker**: Container scripts must be bare-metal, not Docker-based\n\n### Benefits\n\n- **Speed**: AI generates boilerplate in seconds\n- **Consistency**: Follows same patterns as 200+ existing scripts\n- **Quality**: Less bugs and more maintainable code\n- **Learning**: See how your app should be structured\n\n---\n\n### Documentation\n\n- **[docs/README.md](../README.md)** - Main documentation hub\n- **[docs/ct/README.md](../ct/README.md)** - Container scripts overview\n- **[docs/install/README.md](../install/README.md)** - Installation scripts overview\n- **[docs/ct/DETAILED_GUIDE.md](../ct/DETAILED_GUIDE.md)** - Complete ct/ script reference\n- **[docs/install/DETAILED_GUIDE.md](../install/DETAILED_GUIDE.md)** - Complete install/ script reference\n- **[docs/TECHNICAL_REFERENCE.md](../TECHNICAL_REFERENCE.md)** - Architecture deep-dive\n- **[docs/EXIT_CODES.md](../EXIT_CODES.md)** - Exit codes reference\n- **[docs/DEV_MODE.md](../DEV_MODE.md)** - Debugging guide\n\n### Community Guides\n\nSee [USER_SUBMITTED_GUIDES.md](USER_SUBMITTED_GUIDES.md) for excellent community-written guides:\n\n- Home Assistant installation and configuration\n- Frigate setup on Proxmox\n- Docker and Portainer installation\n- Database setup and optimization\n- And many more!\n\n### Templates\n\nUse these templates when creating new scripts:\n\n```bash\n# Container script template\ncp docs/contribution/templates_ct/AppName.sh ct/my-app.sh\n\n# Installation script template\ncp docs/contribution/templates_install/AppName-install.sh install/my-app-install.sh\n```\n\nFor website metadata (description, logo, etc.), use the Report issue button on the script's page on the website.\n\n**Template Features:**\n\n- Updated to match current codebase patterns\n- Includes all available helper functions from `tools.func`\n- Examples for Node.js, Python, PHP, Go applications\n- Database setup examples (MariaDB, PostgreSQL)\n- Proper service creation and cleanup\n\n---\n\n## 🔄 Git Workflow\n\n### Keep Your Fork Updated\n\n```bash\n# Fetch latest from upstream\ngit fetch upstream\n\n# Rebase your work on latest main\ngit rebase upstream/main\n\n# Push to your fork\ngit push -f origin main\n```\n\n### Create Feature Branch\n\n```bash\n# Create and switch to new branch\ngit checkout -b feature/my-feature\n\n# Make changes...\ngit add .\ngit commit -m \"feat: description of changes\"\n\n# Push to your fork\ngit push origin feature/my-feature\n\n# Create Pull Request on GitHub\n```\n\n### Before Submitting PR\n\n1. **Sync with upstream**\n\n   ```bash\n   git fetch upstream\n   git rebase upstream/main\n   ```\n\n2. **Test your changes** (via curl from your fork)\n\n   ```bash\n   bash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/my-app.sh)\"\n   # Follow prompts and test the container\n   # ⏱️ Wait 10-30 seconds after pushing - GitHub takes time to update\n   ```\n\n3. **Check code standards**\n   - [ ] Follows template structure\n   - [ ] Proper error handling\n   - [ ] Documentation updated (if needed)\n   - [ ] No hardcoded values\n   - [ ] Version tracking implemented\n\n4. **Push final changes**\n   ```bash\n   git push origin feature/my-feature\n   ```\n\n---\n\n## 📋 Pull Request Checklist\n\nBefore opening a PR:\n\n- [ ] Code follows coding standards (see CONTRIBUTING.md)\n- [ ] All templates used correctly\n- [ ] Tested on Proxmox VE\n- [ ] Error handling implemented\n- [ ] Documentation updated (if applicable)\n- [ ] No merge conflicts\n- [ ] Synced with upstream/main\n- [ ] Clear PR title and description\n\n---\n\n## ❓ FAQ\n\n### ❌ Why can't I test with `bash ct/myapp.sh` locally?\n\nYou might try:\n\n```bash\n# ❌ WRONG - This won't test your actual changes!\nbash ct/myapp.sh\n./ct/myapp.sh\nsh ct/myapp.sh\n```\n\n**Why this fails:**\n\n- `bash ct/myapp.sh` uses the LOCAL clone file\n- The LOCAL file doesn't execute the curl commands - it's already on disk\n- The curl URLs INSIDE the script are modified by setup-fork.sh, but they're not executed\n- So you can't verify if your curl URLs actually work\n- Users will get the curl URL version (which may be broken)\n\n**Solution:** Always test via curl from GitHub:\n\n```bash\n# ✅ CORRECT - Tests the actual GitHub URLs\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/myapp.sh)\"\n```\n\n### ❓ How do I test my changes?\n\nYou **cannot** test locally with `bash ct/myapp.sh` from your cloned directory!\n\nYou **must** push to GitHub and test via curl from your fork:\n\n```bash\n# 1. Push your changes to your fork\ngit push origin feature/my-awesome-app\n\n# 2. Test via curl (this loads the script from GitHub, not local files)\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/my-app.sh)\"\n\n# 3. For verbose/debug output, pass environment variables\nVERBOSE=yes bash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/my-app.sh)\"\nDEV_MODE_LOGS=true bash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/my-app.sh)\"\n```\n\n**Why?**\n\n- Local `bash ct/myapp.sh` uses local files from your clone\n- But the script's INTERNAL curl commands have been modified by setup-fork.sh to point to your fork\n- This discrepancy means you're not actually testing the curl URLs\n- Testing via curl ensures the script downloads from YOUR fork GitHub URLs\n- ⏱️ **Important:** GitHub takes 10-30 seconds to recognize newly pushed files. Wait before testing!\n\n**What if local bash worked?**\n\nYou'd be testing local files only, not the actual GitHub URLs that users will download. This means broken curl links wouldn't be caught during testing.\n\n### What if my PR has conflicts?\n\n```bash\n# Sync with upstream main repository\ngit fetch upstream\ngit rebase upstream/main\n\n# Resolve conflicts in your editor\ngit add .\ngit rebase --continue\ngit push -f origin your-branch\n```\n\n### How do I keep my fork updated?\n\nTwo ways:\n\n**Option 1: Run setup script again**\n\n```bash\nbash docs/contribution/setup-fork.sh --full\n```\n\n**Option 2: Manual sync**\n\n```bash\ngit fetch upstream\ngit rebase upstream/main\ngit push -f origin main\n```\n\n### Where do I ask questions?\n\n- **GitHub Issues**: For bugs and feature requests\n- **GitHub Discussions**: For general questions and ideas\n- **Discord**: Community-scripts server for real-time chat\n\n---\n\n## 🎓 Learning Resources\n\n### For First-Time Contributors\n\n1. Read: [docs/README.md](../README.md) - Documentation overview\n2. Read: [CONTRIBUTING.md](CONTRIBUTING.md) - Essential coding standards\n3. Choose your path:\n   - Containers → [docs/ct/DETAILED_GUIDE.md](../ct/DETAILED_GUIDE.md)\n   - Installation → [docs/install/DETAILED_GUIDE.md](../install/DETAILED_GUIDE.md)\n4. Study existing scripts in same category\n5. Create your contribution\n\n### For Experienced Developers\n\n1. Review [CONTRIBUTING.md](CONTRIBUTING.md) - Coding standards\n2. Review [CODE_AUDIT.md](CODE_AUDIT.md) - Audit checklist\n3. Check templates in `/docs/contribution/templates_*/`\n4. Use AI assistants with [AI.md](AI.md) for code generation\n5. Submit PR with confidence\n\n### For Using AI Assistants\n\nSee \"Using AI Assistants\" section above for:\n\n- How to structure prompts\n- What information to provide\n- How to validate AI output\n\n---\n\n## 🚀 Ready to Contribute?\n\n1. **Fork** the repository\n2. **Clone** your fork and **setup** with `bash docs/contribution/setup-fork.sh --full`\n3. **Choose** your contribution type (container, installation, tools, etc.)\n4. **Read** the appropriate detailed guide\n5. **Create** your feature branch\n6. **Develop** and **test** your changes\n7. **Commit** with clear messages\n8. **Push** to your fork\n9. **Create** Pull Request\n\n---\n\n## 📞 Contact & Support\n\n- **GitHub**: [community-scripts/ProxmoxVE](https://github.com/community-scripts/ProxmoxVE)\n- **Issues**: [GitHub Issues](https://github.com/community-scripts/ProxmoxVE/issues)\n- **Discussions**: [GitHub Discussions](https://github.com/community-scripts/ProxmoxVE/discussions)\n- **Discord**: [Join Server](https://discord.gg/UHrpNWGwkH)\n\n---\n\n**Thank you for contributing to ProxmoxVE!** 🙏\n\nYour efforts help make Proxmox VE automation accessible to everyone. Happy coding! 🚀\n"
  },
  {
    "path": "docs/contribution/setup-fork.sh",
    "content": "#!/bin/bash\n\n################################################################################\n# ProxmoxVE Fork Setup Script\n#\n# Automatically configures documentation and scripts for your fork\n# Detects your GitHub username and repository from git config\n# Updates all hardcoded links to point to your fork\n#\n# Usage:\n#   ./setup-fork.sh                    # Auto-detect from git config (updates misc/ only)\n#   ./setup-fork.sh YOUR_USERNAME      # Specify username (updates misc/ only)\n#   ./setup-fork.sh YOUR_USERNAME REPO_NAME  # Specify both (updates misc/ only)\n#   ./setup-fork.sh --full             # Update all files including ct/, install/, vm/, etc.\n#\n# Examples:\n#   ./setup-fork.sh john               # Uses john/ProxmoxVE, updates misc/ only\n#   ./setup-fork.sh john my-fork       # Uses john/my-fork, updates misc/ only\n#   ./setup-fork.sh --full             # Auto-detect + update all files\n################################################################################\n\nset -e\n\n# Colors for output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m' # No Color\n\n# Default values\nREPO_NAME=\"ProxmoxVE\"\nUSERNAME=\"\"\nAUTO_DETECT=true\nUPDATE_ALL=false\n\n################################################################################\n# FUNCTIONS\n################################################################################\n\nprint_header() {\n  echo -e \"\\n${BLUE}╔════════════════════════════════════════════════════════════╗${NC}\"\n  echo -e \"${BLUE}║${NC} ProxmoxVE Fork Setup Script\"\n  echo -e \"${BLUE}║${NC} Configuring for your fork...\"\n  echo -e \"${BLUE}╚════════════════════════════════════════════════════════════╝${NC}\\n\"\n}\n\nprint_info() {\n  echo -e \"${BLUE}ℹ${NC}  $1\"\n}\n\nprint_success() {\n  echo -e \"${GREEN}✓${NC}  $1\"\n}\n\nprint_warning() {\n  echo -e \"${YELLOW}⚠${NC}  $1\"\n}\n\nprint_error() {\n  echo -e \"${RED}✗${NC}  $1\"\n}\n\n# Detect username from git remote\ndetect_username() {\n  local remote_url\n\n  # Try to get from origin\n  if ! remote_url=$(git config --get remote.origin.url 2>/dev/null); then\n    return 1\n  fi\n\n  # Extract username from SSH or HTTPS URL\n  if [[ $remote_url =~ git@github.com:([^/]+) ]]; then\n    echo \"${BASH_REMATCH[1]}\"\n  elif [[ $remote_url =~ github.com/([^/]+) ]]; then\n    echo \"${BASH_REMATCH[1]}\"\n  else\n    return 1\n  fi\n}\n\n# Detect repo name from git remote\ndetect_repo_name() {\n  local remote_url\n\n  if ! remote_url=$(git config --get remote.origin.url 2>/dev/null); then\n    return 1\n  fi\n\n  # Extract repo name (remove .git if present)\n  if [[ $remote_url =~ /([^/]+?)(.git)?$ ]]; then\n    local repo=\"${BASH_REMATCH[1]}\"\n    echo \"${repo%.git}\"\n  else\n    return 1\n  fi\n}\n\n# Ask user for confirmation\nconfirm() {\n  local prompt=\"$1\"\n  local response\n\n  echo -ne \"${YELLOW}${prompt} (y/n)${NC} \"\n  read -r response\n  [[ $response =~ ^[Yy]$ ]]\n}\n\n# Update links in files\nupdate_links() {\n  local old_repo=\"community-scripts\"\n  local old_name=\"ProxmoxVE\"\n  local new_owner=\"$1\"\n  local new_repo=\"$2\"\n  local files_updated=0\n\n  print_info \"Scanning for hardcoded links...\"\n\n  # Change to repo root\n  local repo_root=$(git rev-parse --show-toplevel 2>/dev/null || pwd)\n\n  # Determine search path\n  local search_path=\"$repo_root/misc\"\n  if [[ \"$UPDATE_ALL\" == \"true\" ]]; then\n    search_path=\"$repo_root\"\n    print_info \"Searching all files (--full mode)\"\n  else\n    print_info \"Searching misc/ directory only (core functions)\"\n  fi\n\n  echo \"\"\n\n  # Find all files containing the old repo reference\n  while IFS= read -r file; do\n    # Count occurrences\n    local count=$(grep -E -c \"(github.com|githubusercontent.com)/$old_repo/$old_name\" \"$file\" 2>/dev/null || echo 0)\n\n    if [[ $count -gt 0 ]]; then\n      # Backup original\n      cp \"$file\" \"$file.backup\"\n\n      # Replace links - use different sed syntax for BSD/macOS vs GNU sed\n      if sed --version &>/dev/null 2>&1; then\n        # GNU sed\n        sed -E -i \"s@(github.com|githubusercontent.com)/$old_repo/$old_name@\\\\1/$new_owner/$new_repo@g\" \"$file\"\n      else\n        # BSD sed (macOS)\n        sed -E -i '' \"s@(github.com|githubusercontent.com)/$old_repo/$old_name@\\\\1/$new_owner/$new_repo@g\" \"$file\"\n      fi\n\n      ((files_updated++))\n      print_success \"Updated $file ($count links)\"\n    fi\n  done < <(find \"$search_path\" -type f \\( -name \"*.md\" -o -name \"*.sh\" -o -name \"*.func\" -o -name \"*.json\" \\) -not -path \"*/.git/*\" 2>/dev/null | xargs grep -E -l \"(github.com|githubusercontent.com)/$old_repo/$old_name\" 2>/dev/null)\n\n  return $files_updated\n}\n\n# Create user git config setup info\ncreate_git_setup_info() {\n  local username=\"$1\"\n\n  cat >.git-setup-info <<'EOF'\n# Git Configuration for ProxmoxVE Development\n\n## Recommended Git Configuration\n\n### Set up remotes for easy syncing with upstream:\n\n```bash\n# View your current remotes\ngit remote -v\n\n# If you don't have 'upstream' configured, add it:\ngit remote add upstream https://github.com/community-scripts/ProxmoxVE.git\n\n# Verify both remotes exist:\ngit remote -v\n# Should show:\n# origin     https://github.com/YOUR_USERNAME/ProxmoxVE.git (fetch)\n# origin     https://github.com/YOUR_USERNAME/ProxmoxVE.git (push)\n# upstream   https://github.com/community-scripts/ProxmoxVE.git (fetch)\n# upstream   https://github.com/community-scripts/ProxmoxVE.git (push)\n```\n\n### Configure Git User (if not done globally)\n\n```bash\ngit config user.name \"Your Name\"\ngit config user.email \"your.email@example.com\"\n\n# Or configure globally:\ngit config --global user.name \"Your Name\"\ngit config --global user.email \"your.email@example.com\"\n```\n\n### Useful Git Workflows\n\n**Keep your fork up-to-date:**\n```bash\ngit fetch upstream\ngit rebase upstream/main\ngit push origin main\n```\n\n**Create feature branch:**\n```bash\ngit checkout -b feature/my-awesome-app\n# Make changes...\ngit commit -m \"feat: add my awesome app\"\ngit push origin feature/my-awesome-app\n```\n\n**Pull latest from upstream:**\n```bash\ngit fetch upstream\ngit merge upstream/main\n```\n\n---\n\nFor more help, see: docs/contribution/README.md\nEOF\n\n  print_success \"Created .git-setup-info file\"\n}\n\n################################################################################\n# MAIN LOGIC\n################################################################################\n\nprint_header\n\n# Parse command line arguments\nif [[ $# -gt 0 ]]; then\n  # Check for --full flag\n  if [[ \"$1\" == \"--full\" ]]; then\n    UPDATE_ALL=true\n    shift # Remove --full from arguments\n  fi\n\n  # Process remaining arguments\n  if [[ $# -gt 0 ]]; then\n    USERNAME=\"$1\"\n    AUTO_DETECT=false\n\n    if [[ $# -gt 1 ]]; then\n      REPO_NAME=\"$2\"\n    fi\n  fi\nfi\n\n# Try auto-detection\nif [[ -z \"$USERNAME\" ]]; then\n  if username=$(detect_username); then\n    USERNAME=\"$username\"\n    print_success \"Detected GitHub username: $USERNAME\"\n  else\n    print_error \"Could not auto-detect GitHub username from git config\"\n    echo -e \"${YELLOW}Please run:${NC}\"\n    echo \"  ./setup-fork.sh YOUR_USERNAME\"\n    exit 1\n  fi\nfi\n\n# Auto-detect repo name if needed\nif repo_name=$(detect_repo_name); then\n  REPO_NAME=\"$repo_name\"\n  if [[ \"$REPO_NAME\" != \"ProxmoxVE\" ]]; then\n    print_info \"Detected custom repo name: $REPO_NAME\"\n  else\n    print_success \"Using default repo name: ProxmoxVE\"\n  fi\nfi\n\n# Validate inputs\nif [[ -z \"$USERNAME\" ]]; then\n  print_error \"Username cannot be empty\"\n  exit 1\nfi\n\nif [[ -z \"$REPO_NAME\" ]]; then\n  print_error \"Repository name cannot be empty\"\n  exit 1\nfi\n\n# Show what we'll do\necho -e \"${BLUE}Configuration Summary:${NC}\"\necho \"  Repository URL: https://github.com/$USERNAME/$REPO_NAME\"\nif [[ \"$UPDATE_ALL\" == \"true\" ]]; then\n  echo \"  Files to update: ALL files (ct/, install/, vm/, misc/, docs/, etc.)\"\nelse\n  echo \"  Files to update: misc/ directory only (core functions)\"\nfi\necho \"\"\n\n# Ask for confirmation\nif ! confirm \"Apply these changes?\"; then\n  print_warning \"Setup cancelled\"\n  exit 0\nfi\n\necho \"\"\n\n# Update all links\nif update_links \"$USERNAME\" \"$REPO_NAME\"; then\n  links_changed=$?\n  print_success \"Updated $links_changed files\"\nelse\n  print_warning \"No links needed updating or some files not found\"\nfi\n\n# Create git setup info file\ncreate_git_setup_info \"$USERNAME\"\n\n# Final summary\necho \"\"\necho -e \"${GREEN}╔════════════════════════════════════════════════════════════╗${NC}\"\necho -e \"${GREEN}║${NC} Fork Setup Complete!                                    ${GREEN}║${NC}\"\necho -e \"${GREEN}╚════════════════════════════════════════════════════════════╝${NC}\"\necho \"\"\n\nprint_success \"All documentation links updated to point to your fork\"\nprint_info \"Your fork: https://github.com/$USERNAME/$REPO_NAME\"\nprint_info \"Upstream: https://github.com/community-scripts/ProxmoxVE\"\necho \"\"\n\necho -e \"${BLUE}Next Steps:${NC}\"\necho \"  1. Review the changes: git diff\"\necho \"  2. Check .git-setup-info for recommended git workflow\"\necho \"  3. Start developing: git checkout -b feature/my-app\"\necho \"  4. Read: docs/contribution/README.md\"\necho \"\"\n\nprint_success \"Happy contributing! 🚀\"\n"
  },
  {
    "path": "docs/contribution/templates_ct/AppName.md",
    "content": "# CT Container Scripts - Quick Reference\n\n> [!WARNING]\n> **This is legacy documentation.** Refer to the **modern template** at [templates_ct/AppName.sh](AppName.sh) for best practices.\n>\n> Current templates use:\n>\n> - `tools.func` helpers instead of manual patterns\n> - `check_for_gh_release` and `fetch_and_deploy_gh_release` from build.func\n> - Automatic setup-fork.sh configuration\n\n---\n\n## Before Creating a Script\n\n1. **Fork & Clone:**\n\n   ```bash\n   git clone https://github.com/YOUR_USERNAME/ProxmoxVE.git\n   cd ProxmoxVE\n   ```\n\n2. **Run setup-fork.sh** (updates all curl URLs to your fork):\n\n   ```bash\n   bash docs/contribution/setup-fork.sh\n   ```\n\n3. **Copy the Modern Template:**\n\n   ```bash\n   cp templates_ct/AppName.sh ct/MyApp.sh\n   # Edit ct/MyApp.sh with your app details\n   ```\n\n4. **Test Your Script (via GitHub):**\n\n   ⚠️ **Important:** You must push to GitHub and test via curl, not `bash ct/MyApp.sh`!\n\n   ```bash\n   # Push your changes to your fork first\n   git push origin feature/my-awesome-app\n\n   # Then test via curl (this loads from YOUR fork, not local files)\n   bash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/MyApp.sh)\"\n   ```\n\n   > 💡 **Why?** The script's curl commands are modified by setup-fork.sh, but local execution uses local files, not the updated GitHub URLs. Testing via curl ensures your script actually works.\n   >\n   > ⏱️ **Note:** GitHub sometimes takes 10-30 seconds to update files. If you don't see your changes, wait and try again.\n\n5. **Cherry-Pick for PR** (submit ONLY your 3-4 files):\n   - See [Cherry-Pick Guide](../README.md) for step-by-step git commands\n\n---\n\n## Template Structure\n\nThe modern template includes:\n\n### Header\n\n```bash\n#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)\n# (Note: setup-fork.sh changes this URL to point to YOUR fork during development)\n```\n\n### Metadata\n\n```bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: YourUsername\n# License: MIT\nAPP=\"MyApp\"\nvar_tags=\"app-category;foss\"\nvar_cpu=\"2\"\nvar_ram=\"2048\"\nvar_disk=\"4\"\nvar_os=\"alpine\"\nvar_version=\"3.20\"\nvar_unprivileged=\"1\"\n```\n\n### Core Setup\n\n```bash\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n```\n\n### Update Function\n\nThe modern template provides a standard update pattern:\n\n```bash\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  # Use tools.func helpers:\n  check_for_gh_release \"myapp\" \"owner/repo\"\n  fetch_and_deploy_gh_release \"myapp\" \"owner/repo\" \"tarball\" \"latest\" \"/opt/myapp\"\n}\n```\n\n---\n\n## Key Patterns\n\n### Check for Updates (App Repository)\n\nUse `check_for_gh_release` with the **app repo**:\n\n```bash\ncheck_for_gh_release \"myapp\" \"owner/repo\"\n```\n\n### Deploy External App\n\nUse `fetch_and_deploy_gh_release` with the **app repo**:\n\n```bash\nfetch_and_deploy_gh_release \"myapp\" \"owner/repo\"\n```\n\n### Avoid Manual Version Checking\n\n❌ OLD (manual):\n\n```bash\nRELEASE=$(curl -fsSL https://api.github.com/repos/myapp/myapp/releases/latest | grep tag_name)\n```\n\n✅ NEW (use tools.func):\n\n```bash\nfetch_and_deploy_gh_release \"myapp\" \"owner/repo\"\n```\n\n---\n\n## Best Practices\n\n1. **Use tools.func helpers** - Don't manually curl for versions\n2. **Only add app-specific dependencies** - Don't add ca-certificates, curl, gnupg (handled by build.func)\n3. **Test via curl from your fork** - Push first, then: `bash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/MyApp.sh)\"`\n4. **Wait for GitHub to update** - Takes 10-30 seconds after git push\n5. **Cherry-pick only YOUR files** - Submit only ct/MyApp.sh, install/MyApp-install.sh (2 files). Website metadata: use Report issue on the script's page on the website.\n6. **Verify before PR** - Run `git diff upstream/main --name-only` to confirm only your files changed\n\n---\n\n## Common Update Patterns\n\nSee the [modern template](AppName.sh) and [AI.md](../AI.md) for complete working examples.\n\nRecent reference scripts with good update functions:\n\n- [Trip](https://github.com/community-scripts/ProxmoxVE/blob/main/ct/trip.sh)\n- [Thingsboard](https://github.com/community-scripts/ProxmoxVE/blob/main/ct/thingsboard.sh)\n- [UniFi](https://github.com/community-scripts/ProxmoxVE/blob/main/ct/unifi.sh)\n\n---\n\n## Need Help?\n\n- **[README.md](../README.md)** - Full contribution workflow\n- **[AI.md](../AI.md)** - AI-generated script guidelines\n- **[FORK_SETUP.md](../FORK_SETUP.md)** - Why setup-fork.sh is important\n- **[Slack Community](https://discord.gg/your-link)** - Ask questions\n\n````\n\n### 3.4 **Verbosity**\n\n- Use the appropriate flag (**-q** in the examples) for a command to suppress its output.\n  Example:\n\n```bash\ncurl -fsSL\nunzip -q\n````\n\n- If a command does not come with this functionality use `$STD` to suppress it's output.\n\nExample:\n\n```bash\n$STD php artisan migrate --force\n$STD php artisan config:clear\n```\n\n### 3.5 **Backups**\n\n- Backup user data if necessary.\n- Move all user data back in the directory when the update is finished.\n\n> [!NOTE]\n> This is not meant to be a permanent backup\n\nExample backup:\n\n```bash\n  mv /opt/snipe-it /opt/snipe-it-backup\n```\n\nExample config restore:\n\n```bash\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```\n\n### 3.6 **Cleanup**\n\n- Do not forget to remove any temporary files/folders such as zip-files or temporary backups.\n  Example:\n\n```bash\n  rm -rf /opt/v${RELEASE}.zip\n  rm -rf /opt/snipe-it-backup\n```\n\n### 3.7 **No update function**\n\n- In case you can not provide an update function use the following code to provide user feedback.\n\n```bash\nfunction update_script() {\n    header_info\n    check_container_storage\n    check_container_resources\n    if [[ ! -d /opt/snipeit ]]; 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```\n\n---\n\n## 4 **End of the script**\n\n- `start`: Launches Whiptail dialogue\n- `build_container`: Collects and integrates user settings\n- `description`: Sets LXC container description\n- With `echo -e \"${TAB}${GATEWAY}${BGN}http://${IP}${CL}\"` you can point the user to the IP:PORT/folder needed to access the app.\n\n```bash\nstart\nbuild_container\ndescription\n\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} 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```\n\n---\n\n## 5. **Contribution checklist**\n\n- [ ] Shebang is correctly set (`#!/usr/bin/env bash`).\n- [ ] Correct link to _build.func_\n- [ ] Metadata (author, license) is included at the top.\n- [ ] Variables follow naming conventions.\n- [ ] Update function exists.\n- [ ] Update functions checks if app is installed and for new version.\n- [ ] Update function cleans up temporary files.\n- [ ] Script ends with a helpful message for the user to reach the application.\n"
  },
  {
    "path": "docs/contribution/templates_ct/AppName.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: [YourGitHubUsername]\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: [SOURCE_URL e.g. https://github.com/example/app]\n\n# ============================================================================\n# APP CONFIGURATION\n# ============================================================================\n# These values are sent to build.func and define default container resources.\n# Users can customize these during installation via the interactive prompts.\n# ============================================================================\n\nAPP=\"[AppName]\"\nvar_tags=\"${var_tags:-[category1];[category2]}\" # Max 2 tags, semicolon-separated\nvar_cpu=\"${var_cpu:-2}\"                         # CPU cores: 1-4 typical\nvar_ram=\"${var_ram:-2048}\"                      # RAM in MB: 512, 1024, 2048, etc.\nvar_disk=\"${var_disk:-8}\"                       # Disk in GB: 6, 8, 10, 20 typical\nvar_os=\"${var_os:-debian}\"                      # OS: debian, ubuntu, alpine\nvar_version=\"${var_version:-13}\"                # OS Version: 13 (Debian), 24.04 (Ubuntu), 3.21 (Alpine)\nvar_unprivileged=\"${var_unprivileged:-1}\"       # 1=unprivileged (secure), 0=privileged (for Docker/Podman)\n\n# ============================================================================\n# INITIALIZATION - These are required in all CT scripts\n# ============================================================================\nheader_info \"$APP\" # Display app name and setup header\nvariables          # Initialize build.func variables\ncolor              # Load color variables for output\ncatch_errors       # Enable error handling with automatic exit on failure\n\n# ============================================================================\n# UPDATE SCRIPT - Called when user selects \"Update\" from web interface\n# ============================================================================\n# This function is triggered by the web interface to update the application.\n# It should:\n#   1. Check if installation exists\n#   2. Check for new GitHub releases\n#   3. Stop running services\n#   4. Backup critical data\n#   5. Deploy new version\n#   6. Run post-update commands (migrations, config updates, etc.)\n#   7. Restore data if needed\n#   8. Start services\n#\n# Exit with `exit` at the end to prevent container restart.\n# ============================================================================\n\nfunction update_script() {\n  header_info\n  check_container_storage\n  check_container_resources\n\n  # Step 1: Verify installation exists\n  if [[ ! -d /opt/[appname] ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  # Step 2: Check if update is available\n  if check_for_gh_release \"[appname]\" \"YourUsername/YourRepo\"; then\n\n    # Step 3: Stop services before update\n    msg_info \"Stopping Service\"\n    systemctl stop [appname]\n    msg_ok \"Stopped Service\"\n\n    # Step 4: Backup critical data before overwriting\n    msg_info \"Backing up Data\"\n    cp -r /opt/[appname]/data /opt/[appname]_data_backup 2>/dev/null || true\n    msg_ok \"Backed up Data\"\n\n    # Step 5: Download and deploy new version\n    # CLEAN_INSTALL=1 removes old directory before extracting\n    CLEAN_INSTALL=1 fetch_and_deploy_gh_release \"[appname]\" \"owner/repo\" \"tarball\" \"latest\" \"/opt/[appname]\"\n\n    # Step 6: Run post-update commands (uncomment as needed)\n    # These examples show common patterns - use what applies to your app:\n    #\n    # For Node.js apps:\n    # msg_info \"Installing Dependencies\"\n    # cd /opt/[appname]\n    # $STD npm ci --production\n    # msg_ok \"Installed Dependencies\"\n    #\n    # For Python apps:\n    # msg_info \"Installing Dependencies\"\n    # cd /opt/[appname]\n    # $STD uv sync --frozen\n    # msg_ok \"Installed Dependencies\"\n    #\n    # For database migrations:\n    # msg_info \"Running Database Migrations\"\n    # cd /opt/[appname]\n    # $STD npm run migrate\n    # msg_ok \"Ran Database Migrations\"\n    #\n    # For PHP apps:\n    # msg_info \"Installing Dependencies\"\n    # cd /opt/[appname]\n    # $STD composer install --no-dev\n    # msg_ok \"Installed Dependencies\"\n\n    # Step 7: Restore data from backup\n    msg_info \"Restoring Data\"\n    cp -r /opt/[appname]_data_backup/. /opt/[appname]/data/ 2>/dev/null || true\n    rm -rf /opt/[appname]_data_backup\n    msg_ok \"Restored Data\"\n\n    # Step 8: Restart service with new version\n    msg_info \"Starting Service\"\n    systemctl start [appname]\n    msg_ok \"Started Service\"\n    msg_ok \"Updated successfully!\"\n  fi\n  exit\n}\n\n# ============================================================================\n# MAIN EXECUTION - Container creation flow\n# ============================================================================\n# These are called by build.func and handle the full installation process:\n#   1. start              - Initialize container creation\n#   2. build_container    - Execute the install script inside container\n#   3. description        - Display completion info and access details\n# ============================================================================\n\nstart\nbuild_container\ndescription\n\n# ============================================================================\n# COMPLETION MESSAGE\n# ============================================================================\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} setup has been successfully initialized!${CL}\"\necho -e \"${INFO}${YW} Access it using the following URL:${CL}\"\necho -e \"${TAB}${GATEWAY}${BGN}http://${IP}:[PORT]${CL}\"\n"
  },
  {
    "path": "docs/contribution/templates_install/AppName-install.md",
    "content": "# Install Scripts - Quick Reference\n\n> [!WARNING]\n> **This is legacy documentation.** Refer to the **modern template** at [templates_install/AppName-install.sh](AppName-install.sh) for best practices.\n>\n> Current templates use:\n>\n> - `tools.func` helpers (setup_nodejs, setup_uv, setup_postgresql_db, etc.)\n> - Automatic dependency installation via build.func\n> - Standardized environment variable patterns\n\n---\n\n## Before Creating a Script\n\n1. **Copy the Modern Template:**\n\n   ```bash\n   cp templates_install/AppName-install.sh install/MyApp-install.sh\n   # Edit install/MyApp-install.sh\n   ```\n\n2. **Key Pattern:**\n   - CT scripts source build.func and call the install script\n   - Install scripts use sourced FUNCTIONS_FILE_PATH (via build.func)\n   - Both scripts work together in the container\n\n3. **Test via GitHub:**\n\n   ```bash\n   # Push your changes to your fork first\n   git push origin feature/my-awesome-app\n\n   # Test the CT script via curl (it will call the install script)\n   bash -c \"$(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/ProxmoxVE/main/ct/MyApp.sh)\"\n   # ⏱️ Wait 10-30 seconds after pushing - GitHub takes time to update\n   ```\n\n---\n\n## Template Structure\n\n### Header\n\n```bash\n#!/usr/bin/env bash\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func)\n# (setup-fork.sh modifies this URL to point to YOUR fork during development)\n```\n\n### Dependencies (App-Specific Only)\n\n```bash\n# Don't add: ca-certificates, curl, gnupg, wget, git, jq\n# These are handled by build.func\nmsg_info \"Installing dependencies\"\n$STD apt-get install -y app-specific-deps\nmsg_ok \"Installed dependencies\"\n```\n\n### Runtime Setup\n\nUse tools.func helpers instead of manual installation:\n\n```bash\n# ✅ NEW (use tools.func):\nNODE_VERSION=\"20\"\nsetup_nodejs\n# OR\nPYTHON_VERSION=\"3.12\"\nsetup_uv\n# OR\nPG_DB_NAME=\"myapp_db\"\nPG_DB_USER=\"myapp\"\nsetup_postgresql_db\n```\n\n### Service Configuration\n\n```bash\n# Create .env file\nmsg_info \"Configuring MyApp\"\ncat << EOF > /opt/myapp/.env\nDEBUG=false\nPORT=8080\nDATABASE_URL=postgresql://...\nEOF\nmsg_ok \"Configuration complete\"\n\n# Create systemd service\nmsg_info \"Creating systemd service\"\ncat << EOF > /etc/systemd/system/myapp.service\n[Unit]\nDescription=MyApp\n[Service]\nExecStart=/usr/bin/node /opt/myapp/app.js\n[Install]\nWantedBy=multi-user.target\nEOF\nmsg_ok \"Service created\"\n```\n\n### Finalization\n\n```bash\nmsg_info \"Finalizing MyApp installation\"\nsystemctl enable --now myapp\nmotd_ssh\ncustomize\nmsg_ok \"MyApp installation complete\"\ncleanup_lxc\n```\n\n---\n\n## Key Patterns\n\n### Avoid Manual Version Checking\n\n❌ OLD (manual):\n\n```bash\nRELEASE=$(curl -fsSL https://api.github.com/repos/app/repo/releases/latest | grep tag_name)\nwget https://github.com/app/repo/releases/download/$RELEASE/app.tar.gz\n```\n\n✅ NEW (use tools.func via CT script's fetch_and_deploy_gh_release):\n\n```bash\n# In CT script, not install script:\nfetch_and_deploy_gh_release \"myapp\" \"app/repo\" \"app.tar.gz\" \"latest\" \"/opt/myapp\"\n```\n\n### Database Setup\n\n```bash\n# Use setup_postgresql_db, setup_mysql_db, etc.\nPG_DB_NAME=\"myapp\"\nPG_DB_USER=\"myapp\"\nsetup_postgresql_db\n```\n\n### Node.js Setup\n\n```bash\nNODE_VERSION=\"20\"\nsetup_nodejs\nnpm install --no-save\n```\n\n---\n\n## Best Practices\n\n1. **Only add app-specific dependencies**\n   - Don't add: ca-certificates, curl, gnupg, wget, git, jq\n   - These are handled by build.func\n\n2. **Use tools.func helpers**\n   - setup_nodejs, setup_python, setup_uv, setup_postgresql_db, setup_mysql_db, etc.\n\n3. **Don't do version checks in install script**\n   - Version checking happens in CT script's update_script()\n   - Install script just installs the latest\n\n4. **Structure:**\n   - Dependencies\n   - Runtime setup (tools.func)\n   - Deployment (fetch from CT script)\n   - Configuration files\n   - Systemd service\n   - Finalization\n\n---\n\n## Reference Scripts\n\nSee working examples:\n\n- [Trip](https://github.com/community-scripts/ProxmoxVE/blob/main/install/trip-install.sh)\n- [Thingsboard](https://github.com/community-scripts/ProxmoxVE/blob/main/install/thingsboard-install.sh)\n- [UniFi](https://github.com/community-scripts/ProxmoxVE/blob/main/install/unifi-install.sh)\n\n---\n\n## Need Help?\n\n- **[Modern Template](AppName-install.sh)** - Start here\n- **[CT Template](../templates_ct/AppName.sh)** - How CT scripts work\n- **[README.md](../README.md)** - Full contribution workflow\n- **[AI.md](../AI.md)** - AI-generated script guidelines\n\n### 1.2 **Comments**\n\n- Add clear comments for script metadata, including author, copyright, and license information.\n- Use meaningful inline comments to explain complex commands or logic.\n\nExample:\n\n```bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: [YourUserName]\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: [SOURCE_URL]\n```\n\n> [!NOTE]:\n>\n> - Add your username\n> - When updating/reworking scripts, add \"| Co-Author [YourUserName]\"\n\n### 1.3 **Variables and function import**\n\n- This sections adds the support for all needed functions and variables.\n\n```bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n```\n\n---\n\n## 2. **Variable naming and management**\n\n### 2.1 **Naming conventions**\n\n- Use uppercase names for constants and environment variables.\n- Use lowercase names for local script variables.\n\nExample:\n\n```bash\nDB_NAME=snipeit_db    # Environment-like variable (constant)\ndb_user=\"snipeit\"     # Local variable\n```\n\n---\n\n## 3. **Dependencies**\n\n### 3.1 **Install all at once**\n\n- Install all dependencies with a single command if possible\n\nExample:\n\n```bash\n$STD apt-get install -y \\\n  curl \\\n  composer \\\n  git \\\n  sudo \\\n  mc \\\n  nginx\n```\n\n### 3.2 **Collapse dependencies**\n\nCollapse dependencies to keep the code readable.\n\nExample:\nUse\n\n```bash\nphp8.2-{bcmath,common,ctype}\n```\n\ninstead of\n\n```bash\nphp8.2-bcmath php8.2-common php8.2-ctype\n```\n\n---\n\n## 4. **Paths to application files**\n\nIf possible install the app and all necessary files in `/opt/`\n\n---\n\n## 5. **Version management**\n\n### 5.1 **Install the latest release**\n\n- Always try and install the latest release\n- Do not hardcode any version if not absolutely necessary\n\nExample for a git release:\n\n```bash\nRELEASE=$(curl -fsSL https://api.github.com/repos/snipe/snipe-it/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\ncurl -fsSL \"https://github.com/snipe/snipe-it/archive/refs/tags/v${RELEASE}.zip\"\n```\n\n### 5.2 **Save the version for update checks**\n\n- Write the installed version into a file.\n- This is used for the update function in **AppName.sh** to check for if a Update is needed.\n\nExample:\n\n```bash\necho \"${RELEASE}\" >\"/opt/AppName_version.txt\"\n```\n\n---\n\n## 6. **Input and output management**\n\n### 6.1 **User feedback**\n\n- Use standard functions like `msg_info`, `msg_ok` or `msg_error` to print status messages.\n- Each `msg_info` must be followed with a `msg_ok` before any other output is made.\n- Display meaningful progress messages at key stages.\n\nExample:\n\n```bash\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y ...\nmsg_ok \"Installed Dependencies\"\n```\n\n### 6.2 **Verbosity**\n\n- Use the appropiate flag (**-q** in the examples) for a command to suppres its output\n  Example:\n\n```bash\ncurl -fsSL\nunzip -q\n```\n\n- If a command dose not come with such a functionality use `$STD` (a custom standard redirection variable) for managing output verbosity.\n\nExample:\n\n```bash\n$STD apt-get install -y nginx\n```\n\n---\n\n## 7. **String/File Manipulation**\n\n### 7.1 **File Manipulation**\n\n- Use `sed` to replace placeholder values in configuration files.\n\nExample:\n\n```bash\nsed -i -e \"s|^DB_DATABASE=.*|DB_DATABASE=$DB_NAME|\" \\\n       -e \"s|^DB_USERNAME=.*|DB_USERNAME=$DB_USER|\" \\\n       -e \"s|^DB_PASSWORD=.*|DB_PASSWORD=$DB_PASS|\" .env\n```\n\n---\n\n## 8. **Security practices**\n\n### 8.1 **Password generation**\n\n- Use `openssl` to generate random passwords.\n- Use only alphanumeric values to not introduce unknown behaviour.\n\nExample:\n\n```bash\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n```\n\n### 8.2 **File permissions**\n\nExplicitly set secure ownership and permissions for sensitive files.\n\nExample:\n\n```bash\nchown -R www-data: /opt/snipe-it\nchmod -R 755 /opt/snipe-it\n```\n\n---\n\n## 9. **Service Configuration**\n\n### 9.1 **Configuration files**\n\nUse `cat <<EOF` to write configuration files in a clean and readable way.\n\nExample:\n\n```bash\ncat <<EOF >/etc/nginx/conf.d/snipeit.conf\nserver {\n    listen 80;\n    root /opt/snipe-it/public;\n    index index.php;\n}\nEOF\n```\n\n### 9.2 **Credential management**\n\nStore the generated credentials in a file.\n\nExample:\n\n```bash\nUSERNAME=username\nPASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n{\n    echo \"Application-Credentials\"\n    echo \"Username: $USERNAME\"\n    echo \"Password: $PASSWORD\"\n} >> ~/application.creds\n```\n\n### 9.3 **Enviroment files**\n\nUse `cat <<EOF` to write enviromental files in a clean and readable way.\n\nExample:\n\n```bash\ncat <<EOF >/path/to/.env\nVARIABLE=\"value\"\nPORT=3000\nDB_NAME=\"${DB_NAME}\"\nEOF\n```\n\n### 9.4 **Services**\n\nEnable affected services after configuration changes and start them right away.\n\nExample:\n\n```bash\nsystemctl enable -q --now nginx\n```\n\n---\n\n## 10. **Cleanup**\n\n### 10.1 **Remove temporary files**\n\nRemove temporary files and downloads after use.\n\nExample:\n\n```bash\nrm -rf /opt/v${RELEASE}.zip\n```\n\n### 10.2 **Autoremove and autoclean**\n\nRemove unused dependencies to reduce disk space usage.\n\nExample:\n\n```bash\napt-get -y autoremove\napt-get -y autoclean\n```\n\n---\n\n## 11. **Best Practices Checklist**\n\n- [ ] Shebang is correctly set (`#!/usr/bin/env bash`).\n- [ ] Metadata (author, license) is included at the top.\n- [ ] Variables follow naming conventions.\n- [ ] Sensitive values are dynamically generated.\n- [ ] Files and services have proper permissions.\n- [ ] Script cleans up temporary files.\n\n---\n\n### Example: High-Level Script Flow\n\n1. Dependencies installation\n2. Database setup\n3. Download and configure application\n4. Service configuration\n5. Final cleanup\n"
  },
  {
    "path": "docs/contribution/templates_install/AppName-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: [YourGitHubUsername]\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: [SOURCE_URL e.g. https://github.com/example/app]\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\n# =============================================================================\n# DEPENDENCIES - Only add app-specific dependencies here!\n# Don't add: ca-certificates, curl, gnupg, git, build-essential (handled by build.func)\n# =============================================================================\n\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  libharfbuzz0b \\\n  fontconfig\nmsg_ok \"Installed Dependencies\"\n\n# =============================================================================\n# SETUP RUNTIMES & DATABASES (if needed)\n# =============================================================================\n# Examples (uncomment as needed):\n#\n#   NODE_VERSION=\"22\" setup_nodejs\n#   NODE_VERSION=\"22\" NODE_MODULE=\"pnpm\" setup_nodejs  # Installs pnpm\n#   PYTHON_VERSION=\"3.13\" setup_uv\n#   JAVA_VERSION=\"21\" setup_java\n#   GO_VERSION=\"1.22\" setup_go\n#   PHP_VERSION=\"8.4\" PHP_FPM=\"YES\" setup_php\n#   setup_postgresql           # Server only\n#   setup_mariadb              # Server only\n#   setup_meilisearch          # Search engine\n#\n#   Then set up DB and user (sets $[DB]_DB_PASS):\n#   PG_DB_NAME=\"myapp\" PG_DB_USER=\"myapp\" setup_postgresql_db\n#   MARIADB_DB_NAME=\"myapp\" MARIADB_DB_USER=\"myapp\" setup_mariadb_db\n\n# =============================================================================\n# DOWNLOAD & DEPLOY APPLICATION\n# =============================================================================\n# fetch_and_deploy_gh_release modes:\n#   \"tarball\"  - Source tarball (default if omitted)\n#   \"binary\"   - .deb package (auto-detects amd64/arm64)\n#   \"prebuild\" - Pre-built archive (.tar.gz)\n#   \"singlefile\" - Single binary file\n#\n# Examples:\n#   fetch_and_deploy_gh_release \"myapp\" \"YourUsername/myapp\" \"tarball\" \"latest\" \"/opt/myapp\"\n#   fetch_and_deploy_gh_release \"myapp\" \"YourUsername/myapp\" \"binary\" \"latest\" \"/tmp\"\n#   fetch_and_deploy_gh_release \"myapp\" \"YourUsername/myapp\" \"prebuild\" \"latest\" \"/opt/myapp\" \"myapp-*.tar.gz\"\n\nfetch_and_deploy_gh_release \"[appname]\" \"owner/repo\" \"tarball\" \"latest\" \"/opt/[appname]\"\n\n# --- Tools & Utilities ---\n# get_lxc_ip                          # Sets $LOCAL_IP variable (call early!)\n# setup_ffmpeg                             # Install FFmpeg with codecs\n# setup_hwaccel                            # Setup GPU hardware acceleration\n# setup_imagemagick                        # Install ImageMagick 7\n# setup_docker                             # Install Docker Engine\n# setup_adminer                            # Install Adminer for DB management\n# create_self_signed_cert                  # Creates cert in /etc/ssl/[appname]/\n\n# =============================================================================\n# EXAMPLES\n# =============================================================================\n#\n# EXAMPLE 1: Node.js Application with PostgreSQL\n# ---------------------------------------------\n# NODE_VERSION=\"22\" setup_nodejs\n# PG_VERSION=\"17\" setup_postgresql\n# PG_DB_NAME=\"myapp\" PG_DB_USER=\"myapp\" setup_postgresql_db\n# get_lxc_ip\n# fetch_and_deploy_gh_release \"myapp\" \"owner/myapp\" \"tarball\" \"latest\" \"/opt/myapp\"\n#\n# msg_info \"Configuring MyApp\"\n# cd /opt/myapp\n# $STD npm ci\n# cat <<EOF >/opt/myapp/.env\n# DATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost/${PG_DB_NAME}\n# HOST=${LOCAL_IP}\n# PORT=3000\n# EOF\n# msg_ok \"Configured MyApp\"\n#\n# EXAMPLE 2: Python Application with uv\n# ------------------------------------\n# PYTHON_VERSION=\"3.13\" setup_uv\n# get_lxc_ip\n# fetch_and_deploy_gh_release \"myapp\" \"owner/myapp\" \"tarball\" \"latest\" \"/opt/myapp\"\n#\n# msg_info \"Setting up MyApp\"\n# cd /opt/myapp\n# $STD uv sync\n# cat <<EOF >/opt/myapp/.env\n# HOST=${LOCAL_IP}\n# PORT=8000\n# EOF\n# msg_ok \"Setup MyApp\"\n\n# =============================================================================\n# EXAMPLE 3: PHP Application with MariaDB + Nginx\n# =============================================================================\n# PHP_VERSION=\"8.4\" PHP_FPM=\"YES\" PHP_MODULE=\"bcmath,curl,gd,intl,mbstring,mysql,xml,zip\" setup_php\n# setup_composer\n# setup_mariadb\n# MARIADB_DB_NAME=\"myapp\" MARIADB_DB_USER=\"myapp\" setup_mariadb_db\n# get_lxc_ip\n# fetch_and_deploy_gh_release \"myapp\" \"owner/myapp\" \"prebuild\" \"latest\" \"/opt/myapp\" \"myapp-*.tar.gz\"\n#\n# msg_info \"Configuring MyApp\"\n# cd /opt/myapp\n# cp .env.example .env\n# sed -i \"s|APP_URL=.*|APP_URL=http://${LOCAL_IP}|\" .env\n# sed -i \"s|DB_DATABASE=.*|DB_DATABASE=${MARIADB_DB_NAME}|\" .env\n# sed -i \"s|DB_USERNAME=.*|DB_USERNAME=${MARIADB_DB_USER}|\" .env\n# sed -i \"s|DB_PASSWORD=.*|DB_PASSWORD=${MARIADB_DB_PASS}|\" .env\n# $STD composer install --no-dev --no-interaction\n# chown -R www-data:www-data /opt/myapp\n# msg_ok \"Configured MyApp\"\n\n# =============================================================================\n# YOUR APPLICATION INSTALLATION\n# =============================================================================\n# 1. Setup runtimes and databases FIRST\n# 2. Call get_lxc_ip if you need the container IP\n# 3. Use fetch_and_deploy_gh_release to download the app (handles version tracking)\n# 4. Configure the application\n# 5. Create systemd service\n# 6. Finalize with motd_ssh, customize, cleanup_lxc\n\n# --- Setup runtimes/databases ---\nNODE_VERSION=\"22\" setup_nodejs\nget_lxc_ip\n\n# --- Download and install app ---\nfetch_and_deploy_gh_release \"[appname]\" \"[owner/repo]\" \"tarball\" \"latest\" \"/opt/[appname]\"\n\nmsg_info \"Setting up [AppName]\"\ncd /opt/[appname]\n# $STD npm ci\nmsg_ok \"Setup [AppName]\"\n\n# =============================================================================\n# CONFIGURATION\n# =============================================================================\n\nmsg_info \"Configuring [AppName]\"\ncd /opt/[appname]\n\n# Install application dependencies (uncomment as needed):\n# $STD npm ci --production         # Node.js apps\n# $STD uv sync --frozen            # Python apps\n# $STD composer install --no-dev   # PHP apps\n# $STD cargo build --release       # Rust apps\n\n# Create .env file if needed:\ncat <<EOF >/opt/[appname]/.env\n# Use import_local_ip to get container IP, or hardcode if building on Proxmox\nAPP_URL=http://localhost\nPORT=8080\nEOF\n\nmsg_ok \"Configured [AppName]\"\n\n# =============================================================================\n# CREATE SYSTEMD SERVICE\n# =============================================================================\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/[appname].service\n[Unit]\nDescription=[AppName] Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/[appname]\nExecStart=/usr/bin/node /opt/[appname]/server.js\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now [appname]\nmsg_ok \"Created Service\"\n\n# =============================================================================\n# CLEANUP & FINALIZATION\n# =============================================================================\n# These are called automatically, but shown here for clarity:\n#   motd_ssh           - Displays service info on SSH login\n#   customize          - Enables optional customizations\n#   cleanup_lxc        - Removes temp files, bash history, logs\n\nmotd_ssh\ncustomize\ncleanup_lxc\n"
  },
  {
    "path": "docs/contribution/templates_json/AppName.json",
    "content": "{\n  \"name\": \"AppName\",\n  \"slug\": \"appname\",\n  \"categories\": [\n    0\n  ],\n  \"date_created\": \"2026-01-18\",\n  \"type\": \"ct\",\n  \"updateable\": true,\n  \"privileged\": false,\n  \"interface_port\": 3000,\n  \"documentation\": \"https://docs.example.com/\",\n  \"website\": \"https://example.com/\",\n  \"logo\": \"https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/appname.webp\",\n  \"config_path\": \"/opt/appname/.env\",\n  \"description\": \"Short description of what AppName does and its main features.\",\n  \"install_methods\": [\n    {\n      \"type\": \"default\",\n      \"script\": \"ct/appname.sh\",\n      \"resources\": {\n        \"cpu\": 2,\n        \"ram\": 2048,\n        \"hdd\": 8,\n        \"os\": \"Debian\",\n        \"version\": \"13\"\n      }\n    }\n  ],\n  \"default_credentials\": {\n    \"username\": null,\n    \"password\": null\n  },\n  \"notes\": [\n    {\n      \"text\": \"Change the default password after first login!\",\n      \"type\": \"warning\"\n    }\n  ]\n}\n"
  },
  {
    "path": "docs/contribution/templates_json/AppName.md",
    "content": "# Website Metadata - Quick Reference\n\nMetadata (name, slug, description, logo, categories, etc.) controls how your application appears on the website. You do **not** add JSON files to the repo — you request changes via the website.\n\n---\n\n## How to Request or Update Metadata\n\n1. **Go to the script on the website** — Open the [ProxmoxVE website](https://community-scripts.github.io/ProxmoxVE/), find your script (or the script you want to update).\n2. **Press the \"Report issue\" button** on that script’s page.\n3. **Follow the guide** — The flow will walk you through submitting or updating metadata.\n\n---\n\n## Metadata Structure (Reference)\n\nThe following describes the structure of script metadata used by the website. Use it as reference when filling out the form or describing what you need.\n\n```json\n{\n  \"name\": \"MyApp\",\n  \"slug\": \"myapp\",\n  \"categories\": [1],\n  \"date_created\": \"2026-01-18\",\n  \"type\": \"ct\",\n  \"updateable\": true,\n  \"privileged\": false,\n  \"interface_port\": 3000,\n  \"documentation\": \"https://docs.example.com/\",\n  \"website\": \"https://example.com/\",\n  \"logo\": \"https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/myapp.webp\",\n  \"config_path\": \"/opt/myapp/.env\",\n  \"description\": \"Brief description of what MyApp does\",\n  \"install_methods\": [\n    {\n      \"type\": \"default\",\n      \"script\": \"ct/myapp.sh\",\n      \"resources\": {\n        \"cpu\": 2,\n        \"ram\": 2048,\n        \"hdd\": 8,\n        \"os\": \"Debian\",\n        \"version\": \"13\"\n      }\n    }\n  ],\n  \"default_credentials\": {\n    \"username\": null,\n    \"password\": null\n  },\n  \"notes\": [\n    {\n      \"text\": \"Change the default password after first login!\",\n      \"type\": \"warning\"\n    }\n  ]\n}\n```\n\n---\n\n## Field Reference\n\n| Field                 | Required | Example           | Notes                                          |\n| --------------------- | -------- | ----------------- | ---------------------------------------------- |\n| `name`                | Yes      | \"MyApp\"           | Display name                                   |\n| `slug`                | Yes      | \"myapp\"           | URL-friendly identifier (lowercase, no spaces) |\n| `categories`          | Yes      | [1]               | One or more category IDs                       |\n| `date_created`        | Yes      | \"2026-01-18\"      | Format: YYYY-MM-DD                             |\n| `type`                | Yes      | \"ct\"              | Container type: \"ct\" or \"vm\"                   |\n| `interface_port`      | Yes      | 3000              | Default web interface port                     |\n| `logo`                | No       | \"https://...\"     | Logo URL (64px x 64px PNG)                     |\n| `config_path`         | Yes      | \"/opt/myapp/.env\" | Main config file location                      |\n| `description`         | Yes      | \"App description\" | Brief description (100 chars)                  |\n| `install_methods`     | Yes      | See below         | Installation resources (array)                 |\n| `default_credentials` | No       | See below         | Optional default login                         |\n| `notes`               | No       | See below         | Additional notes (array)                       |\n\n---\n\n## Install Methods\n\nEach installation method specifies resource requirements:\n\n```json\n\"install_methods\": [\n  {\n    \"type\": \"default\",\n    \"script\": \"ct/myapp.sh\",\n    \"resources\": {\n      \"cpu\": 2,\n      \"ram\": 2048,\n      \"hdd\": 8,\n      \"os\": \"Debian\",\n      \"version\": \"13\"\n    }\n  }\n]\n```\n\n**Resource Defaults:**\n\n- CPU: Cores (1-8)\n- RAM: Megabytes (256-4096)\n- Disk: Gigabytes (4-50)\n\n---\n\n## Common Categories\n\n- `0` Miscellaneous\n- `1` Proxmox & Virtualization\n- `2` Operating Systems\n- `3` Containers & Docker\n- `4` Network & Firewall\n- `5` Adblock & DNS\n- `6` Authentication & Security\n- `7` Backup & Recovery\n- `8` Databases\n- `9` Monitoring & Analytics\n- `10` Dashboards & Frontends\n- `11` Files & Downloads\n- `12` Documents & Notes\n- `13` Media & Streaming\n- `14` \\*Arr Suite\n- `15` NVR & Cameras\n- `16` IoT & Smart Home\n- `17` ZigBee, Z-Wave & Matter\n- `18` MQTT & Messaging\n- `19` Automation & Scheduling\n- `20` AI / Coding & Dev-Tools\n- `21` Webservers & Proxies\n- `22` Bots & ChatOps\n- `23` Finance & Budgeting\n- `24` Gaming & Leisure\n- `25` Business & ERP\n\n---\n\n## Best Practices\n\n1. **Use the JSON Generator** - It validates structure\n2. **Keep descriptions short** - 100 characters max\n3. **Use real resource requirements** - Based on your testing\n4. **Include sensible defaults** - Pre-filled in install_methods\n5. **Slug must be lowercase** - No spaces, use hyphens\n\n---\n\n## See Examples on the Website\n\nView script pages on the [ProxmoxVE website](https://community-scripts.github.io/ProxmoxVE/) to see how metadata is displayed for existing scripts.\n\n---\n\n## Need Help?\n\n- **Request metadata** — Use the Report issue button on the script’s page on the website (see [How to Request or Update Metadata](#how-to-request-or-update-metadata) above).\n- **[JSON Generator](https://community-scripts.github.io/ProxmoxVE/json-editor)** - Reference only; structure validation\n- **[README.md](../README.md)** - Full contribution workflow\n"
  },
  {
    "path": "docs/ct/DETAILED_GUIDE.md",
    "content": "# 🚀 **Application Container Scripts (ct/AppName.sh)**\n\n**Modern Guide to Creating LXC Container Installation Scripts**\n\n> **Updated**: December 2025\n> **Context**: Fully integrated with build.func, advanced_settings wizard, and defaults system\n> **Example Used**: `/ct/pihole.sh`, `/ct/docker.sh`\n\n---\n\n## 📋 Table of Contents\n\n- [Overview](#overview)\n- [Architecture & Flow](#architecture--flow)\n- [File Structure](#file-structure)\n- [Complete Script Template](#complete-script-template)\n- [Function Reference](#function-reference)\n- [Advanced Features](#advanced-features)\n- [Real Examples](#real-examples)\n- [Troubleshooting](#troubleshooting)\n- [Contribution Checklist](#contribution-checklist)\n\n---\n\n## Overview\n\n### Purpose\n\nContainer scripts (`ct/AppName.sh`) are **entry points for creating LXC containers** with specific applications pre-installed. They:\n\n1. Define container defaults (CPU, RAM, disk, OS)\n2. Call the main build orchestrator (`build.func`)\n3. Implement application-specific update mechanisms\n4. Provide user-facing success messages\n\n### Execution Context\n\n```\nProxmox Host\n    ↓\nct/AppName.sh sourced (runs as root on host)\n    ↓\nbuild.func: Creates LXC container + runs install script inside\n    ↓\ninstall/AppName-install.sh (runs inside container)\n    ↓\nContainer ready with app installed\n```\n\n### Key Integration Points\n\n- **build.func** - Main orchestrator (container creation, storage, variable management)\n- **install.func** - Container-specific setup (OS update, package management)\n- **tools.func** - Tool installation helpers (repositories, GitHub releases)\n- **core.func** - UI/messaging functions (colors, spinners, validation)\n- **error_handler.func** - Error handling and signal management\n\n---\n\n## Architecture & Flow\n\n### Container Creation Flow\n\n```\nSTART: bash ct/pihole.sh\n  ↓\n[1] Set APP, var_*, defaults\n  ↓\n[2] header_info() → Display ASCII art\n  ↓\n[3] variables() → Parse arguments & load build.func\n  ↓\n[4] color() → Setup ANSI codes\n  ↓\n[5] catch_errors() → Setup trap handlers\n  ↓\n[6] install_script() → Show mode menu (5 options)\n  ↓\n  ├─ INSTALL_MODE=\"0\" (Default)\n  ├─ INSTALL_MODE=\"1\" (Advanced - 19-step wizard)\n  ├─ INSTALL_MODE=\"2\" (User Defaults)\n  ├─ INSTALL_MODE=\"3\" (App Defaults)\n  └─ INSTALL_MODE=\"4\" (Settings Menu)\n  ↓\n[7] advanced_settings() → Collect user configuration (if mode=1)\n  ↓\n[8] start() → Confirm or re-edit settings\n  ↓\n[9] build_container() → Create LXC + execute install script\n  ↓\n[10] description() → Set container description\n  ↓\n[11] SUCCESS → Display access URL\n  ↓\nEND\n```\n\n### Default Values Precedence\n\n```\nPriority 1 (Highest): Environment Variables (var_cpu, var_ram, etc.)\nPriority 2: App-Specific Defaults (/defaults/AppName.vars)\nPriority 3: User Global Defaults (/default.vars)\nPriority 4 (Lowest): Built-in Defaults (in build.func)\n```\n\n---\n\n## File Structure\n\n### Minimal ct/AppName.sh Template\n\n```\n#!/usr/bin/env bash                          # [1] Shebang\n                                             # [2] Copyright/License\nsource <(curl -s .../misc/build.func)        # [3] Import functions\n                                             # [4] APP metadata\nAPP=\"AppName\"                                # [5] Default values\nvar_tags=\"tag1;tag2\"\nvar_cpu=\"2\"\nvar_ram=\"2048\"\n...\n\nheader_info \"$APP\"                           # [6] Display header\nvariables                                    # [7] Process arguments\ncolor                                        # [8] Setup colors\ncatch_errors                                 # [9] Setup error handling\n\nfunction update_script() { ... }             # [10] Update function (optional)\n\nstart                                        # [11] Launch container creation\nbuild_container\ndescription\nmsg_ok \"Completed successfully!\\n\"\n```\n\n---\n\n## Complete Script Template\n\n### 1. File Header & Imports\n\n```bash\n#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: YourUsername\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/example/project\n\n# Import main orchestrator\nsource <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/build.func)\n```\n\n> **⚠️ IMPORTANT**: Before opening a PR, change URL to `community-scripts` repo!\n\n### 2. Application Metadata\n\n```bash\n# Application Configuration\nAPP=\"ApplicationName\"\nvar_tags=\"tag1;tag2;tag3\"      # Max 3-4 tags, no spaces, semicolon-separated\n\n# Container Resources\nvar_cpu=\"2\"                    # CPU cores\nvar_ram=\"2048\"                 # RAM in MB\nvar_disk=\"10\"                  # Disk in GB\n\n# Container Type & OS\nvar_os=\"debian\"                # Options: alpine, debian, ubuntu\nvar_version=\"12\"               # Alpine: 3.20+, Debian: 11-13, Ubuntu: 20.04+\nvar_unprivileged=\"1\"           # 1=unprivileged (secure), 0=privileged (rarely needed)\n```\n\n**Variable Naming Convention**:\n- Variables exposed to user: `var_*` (e.g., `var_cpu`, `var_hostname`, `var_ssh`)\n- Internal variables: lowercase (e.g., `container_id`, `app_version`)\n\n### 3. Display & Initialization\n\n```bash\n# Display header ASCII art\nheader_info \"$APP\"\n\n# Process command-line arguments and load configuration\nvariables\n\n# Setup ANSI color codes and formatting\ncolor\n\n# Initialize error handling (trap ERR, EXIT, INT, TERM)\ncatch_errors\n```\n\n### 4. Update Function (Highly Recommended)\n\n```bash\nfunction update_script() {\n  header_info\n\n  # Always start with these checks\n  check_container_storage\n  check_container_resources\n\n  # Verify app is installed\n  if [[ ! -d /opt/appname ]]; then\n    msg_error \"No ${APP} Installation Found!\"\n    exit\n  fi\n\n  # Get latest version from GitHub\n  RELEASE=$(curl -fsSL https://api.github.com/repos/user/repo/releases/latest | \\\n    grep \"tag_name\" | awk '{print substr($2, 2, length($2)-3)}')\n\n  # Compare with saved version\n  if [[ ! -f /opt/${APP}_version.txt ]] || [[ \"${RELEASE}\" != \"$(cat /opt/${APP}_version.txt)\" ]]; then\n    msg_info \"Updating ${APP} to v${RELEASE}\"\n\n    # Backup user data\n    cp -r /opt/appname /opt/appname-backup\n\n    # Perform update\n    cd /opt\n    wget -q \"https://github.com/user/repo/releases/download/v${RELEASE}/app-${RELEASE}.tar.gz\"\n    tar -xzf app-${RELEASE}.tar.gz\n\n    # Restore user data\n    cp /opt/appname-backup/config/* /opt/appname/config/\n\n    # Cleanup\n    rm -rf app-${RELEASE}.tar.gz /opt/appname-backup\n\n    # Save new version\n    echo \"${RELEASE}\" > /opt/${APP}_version.txt\n\n    msg_ok \"Updated ${APP} to v${RELEASE}\"\n  else\n    msg_ok \"No update required. ${APP} is already at v${RELEASE}.\"\n  fi\n\n  exit\n}\n```\n\n### 5. Script Launch\n\n```bash\n# Start the container creation workflow\nstart\n\n# Build the container with selected configuration\nbuild_container\n\n# Set container description/notes in Proxmox UI\ndescription\n\n# Display success message\nmsg_ok \"Completed successfully!\\n\"\necho -e \"${CREATING}${GN}${APP} 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\n---\n\n## Function Reference\n\n### Core Functions (From build.func)\n\n#### `variables()`\n\n**Purpose**: Initialize container variables, load user arguments, setup orchestration\n\n**Triggered by**: Called automatically at script start\n\n**Behavior**:\n1. Parse command-line arguments (if any)\n2. Generate random UUID for session tracking\n3. Load container storage from Proxmox\n4. Initialize application-specific defaults\n5. Setup SSH/environment configuration\n\n#### `start()`\n\n**Purpose**: Launch the container creation menu with 5 installation modes\n\n**Menu Options**:\n```\n1. Default Installation (Quick setup, predefined settings)\n2. Advanced Installation (19-step wizard with full control)\n3. User Defaults (Load ~/.community-scripts/default.vars)\n4. App Defaults (Load /defaults/AppName.vars)\n5. Settings Menu (Interactive mode selection)\n```\n\n#### `build_container()`\n\n**Purpose**: Main orchestrator for LXC container creation\n\n**Operations**:\n1. Validates all variables\n2. Creates LXC container via `pct create`\n3. Executes `install/AppName-install.sh` inside container\n4. Monitors installation progress\n5. Handles errors and rollback on failure\n\n#### `description()`\n\n**Purpose**: Set container description/notes visible in Proxmox UI\n\n---\n\n## Advanced Features\n\n### 1. Custom Configuration Menus\n\nIf your app has additional setup beyond standard vars:\n\n```bash\ncustom_app_settings() {\n  CONFIGURE_DB=$(whiptail --title \"Database Setup\" \\\n    --yesno \"Would you like to configure a custom database?\" 8 60)\n\n  if [[ $? -eq 0 ]]; then\n    DB_HOST=$(whiptail --inputbox \"Database Host:\" 8 60 3>&1 1>&2 2>&3)\n    DB_PORT=$(whiptail --inputbox \"Database Port:\" 8 60 \"3306\" 3>&1 1>&2 2>&3)\n  fi\n}\n\ncustom_app_settings\n```\n\n### 2. Update Function Patterns\n\nSave installed version for update checks\n\n### 3. Health Check Functions\n\nAdd custom validation:\n\n```bash\nfunction health_check() {\n  header_info\n\n  if [[ ! -d /opt/appname ]]; then\n    msg_error \"Application not found!\"\n    exit 1\n  fi\n\n  if ! systemctl is-active --quiet appname; then\n    msg_error \"Application service not running\"\n    exit 1\n  fi\n\n  msg_ok \"Health check passed\"\n}\n```\n\n---\n\n## Real Examples\n\n### Example 1: Simple Web App (Debian-based)\n\n```bash\n#!/usr/bin/env bash\nsource <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/build.func)\n\nAPP=\"Homarr\"\nvar_tags=\"dashboard;homepage\"\nvar_cpu=\"2\"\nvar_ram=\"1024\"\nvar_disk=\"5\"\nvar_os=\"debian\"\nvar_version=\"12\"\nvar_unprivileged=\"1\"\n\nheader_info \"$APP\"\nvariables\ncolor\ncatch_errors\n\nfunction update_script() {\n  # Update logic here\n}\n\nstart\nbuild_container\ndescription\nmsg_ok \"Completed successfully!\\n\"\n```\n\n---\n\n## Troubleshooting\n\n### Container Creation Fails\n\n**Symptom**: `pct create` exits with error code 209\n\n**Solution**:\n```bash\n# Check existing containers\npct list | grep CTID\n\n# Remove conflicting container\npct destroy CTID\n\n# Retry ct/AppName.sh\n```\n\n### Update Function Doesn't Detect New Version\n\n**Debug**:\n```bash\n# Check version file\ncat /opt/AppName_version.txt\n\n# Test GitHub API\ncurl -fsSL https://api.github.com/repos/user/repo/releases/latest | grep tag_name\n```\n\n---\n\n## Contribution Checklist\n\nBefore submitting a PR:\n\n### Script Structure\n- [ ] Shebang is `#!/usr/bin/env bash`\n- [ ] Imports `build.func` from community-scripts repo\n- [ ] Copyright header with author and source URL\n- [ ] APP variable matches filename\n- [ ] `var_tags` are semicolon-separated (no spaces)\n\n### Default Values\n- [ ] `var_cpu` set appropriately (2-4 for most apps)\n- [ ] `var_ram` set appropriately (1024-4096 MB minimum)\n- [ ] `var_disk` sufficient for app + data (5-20 GB)\n- [ ] `var_os` is realistic\n\n### Functions\n- [ ] `update_script()` implemented\n- [ ] Update function checks if app installed\n- [ ] Proper error handling with `msg_error`\n\n### Testing\n- [ ] Script tested with default installation\n- [ ] Script tested with advanced (19-step) installation\n- [ ] Update function tested on existing installation\n\n---\n\n## Best Practices\n\n### ✅ DO:\n\n1. **Use meaningful defaults**\n2. **Implement version tracking**\n3. **Handle edge cases**\n4. **Use proper messaging with msg_info/msg_ok/msg_error**\n\n### ❌ DON'T:\n\n1. **Hardcode versions**\n2. **Use custom color codes** (use built-in variables)\n3. **Forget error handling**\n4. **Leave temporary files**\n\n---\n\n**Last Updated**: December 2025\n**Compatibility**: ProxmoxVE with build.func v3+\n"
  },
  {
    "path": "docs/ct/README.md",
    "content": "# Container Scripts Documentation (/ct)\n\nThis directory contains comprehensive documentation for container creation scripts in the `/ct` directory.\n\n## Overview\n\nContainer scripts (`ct/*.sh`) are the entry points for creating LXC containers in Proxmox VE. They run on the host and orchestrate the entire container creation process.\n\n## Documentation Structure\n\nEach script has standardized documentation following the project pattern.\n\n## Key Resources\n\n- **[DETAILED_GUIDE.md](DETAILED_GUIDE.md)** - Complete reference for creating ct scripts\n- **[../contribution/README.md](../contribution/README.md)** - How to contribute\n- **[../misc/build.func/](../misc/build.func/)** - Core orchestrator documentation\n\n## Container Creation Flow\n\n```\nct/AppName.sh (host-side)\n    │\n    ├─ Calls: build.func (orchestrator)\n    │\n    ├─ Variables: var_cpu, var_ram, var_disk, var_os\n    │\n    └─ Creates: LXC Container\n                │\n                └─ Runs: install/appname-install.sh (inside)\n```\n\n## Available Scripts\n\nSee `/ct` directory for all container creation scripts. Common examples:\n\n- `pihole.sh` - Pi-hole DNS/DHCP server\n- `docker.sh` - Docker container runtime\n- `wallabag.sh` - Article reading & archiving\n- `nextcloud.sh` - Private cloud storage\n- `debian.sh` - Basic Debian container\n- And 30+ more...\n\n## Quick Start\n\nTo understand how to create a container script:\n\n1. Read: [UPDATED_APP-ct.md](../UPDATED_APP-ct.md)\n2. Study: A similar existing script in `/ct`\n3. Copy template and customize\n4. Test locally\n5. Submit PR\n\n## Contributing a New Container\n\n1. Create `ct/myapp.sh`\n2. Create `install/myapp-install.sh`\n3. Follow template in [UPDATED_APP-ct.md](../UPDATED_APP-ct.md)\n4. Test thoroughly\n5. Submit PR with both files\n\n## Common Tasks\n\n- **Add new container application** → [contribution/README.md](../contribution/README.md)\n- **Debug container creation** → [EXIT_CODES.md](../EXIT_CODES.md)\n- **Understand build.func** → [misc/build.func/](../misc/build.func/)\n- **Development mode debugging** → [DEV_MODE.md](../DEV_MODE.md)\n\n---\n\n**Last Updated**: December 2025\n**Maintainers**: community-scripts team\n"
  },
  {
    "path": "docs/guides/CONFIGURATION_REFERENCE.md",
    "content": "# Configuration Reference\n\n**Complete reference for all configuration variables and options in community-scripts for Proxmox VE.**\n\n---\n\n## Table of Contents\n\n1. [Variable Naming Convention](#variable-naming-convention)\n2. [Complete Variable Reference](#complete-variable-reference)\n3. [Resource Configuration](#resource-configuration)\n4. [Network Configuration](#network-configuration)\n5. [IPv6 Configuration](#ipv6-configuration)\n6. [SSH Configuration](#ssh-configuration)\n7. [Container Features](#container-features)\n8. [Storage Configuration](#storage-configuration)\n9. [Security Settings](#security-settings)\n10. [Advanced Options](#advanced-options)\n11. [Quick Reference Table](#quick-reference-table)\n\n---\n\n## Variable Naming Convention\n\nAll configuration variables follow a consistent pattern:\n\n```\nvar_<setting>=<value>\n```\n\n**Rules:**\n- ✅ Always starts with `var_`\n- ✅ Lowercase letters only\n- ✅ Underscores for word separation\n- ✅ No spaces around `=`\n- ✅ Values can be quoted if needed\n\n**Examples:**\n```bash\n# ✓ Correct\nvar_cpu=4\nvar_hostname=myserver\nvar_ssh_authorized_key=ssh-rsa AAAA...\n\n# ✗ Wrong\nCPU=4                    # Missing var_ prefix\nvar_CPU=4                # Uppercase not allowed\nvar_cpu = 4              # Spaces around =\nvar-cpu=4                # Hyphens not allowed\n```\n\n---\n\n## Complete Variable Reference\n\n### var_unprivileged\n\n**Type:** Boolean (0 or 1)\n**Default:** `1` (unprivileged)\n**Description:** Determines if container runs unprivileged (recommended) or privileged.\n\n```bash\nvar_unprivileged=1    # Unprivileged (safer, recommended)\nvar_unprivileged=0    # Privileged (less secure, more features)\n```\n\n**When to use privileged (0):**\n- Hardware access required\n- Certain kernel modules needed\n- Legacy applications\n- Nested virtualization with full features\n\n**Security Impact:**\n- Unprivileged: Container root is mapped to unprivileged user on host\n- Privileged: Container root = host root (security risk)\n\n---\n\n### var_cpu\n\n**Type:** Integer\n**Default:** Varies by app (usually 1-4)\n**Range:** 1 to host CPU count\n**Description:** Number of CPU cores allocated to container.\n\n```bash\nvar_cpu=1     # Single core (minimal)\nvar_cpu=2     # Dual core (typical)\nvar_cpu=4     # Quad core (recommended for apps)\nvar_cpu=8     # High performance\n```\n\n**Best Practices:**\n- Start with 2 cores for most applications\n- Monitor usage with `pct exec <id> -- htop`\n- Can be changed after creation\n- Consider host CPU count (don't over-allocate)\n\n---\n\n### var_ram\n\n**Type:** Integer (MB)\n**Default:** Varies by app (usually 512-2048)\n**Range:** 512 MB to host RAM\n**Description:** Amount of RAM in megabytes.\n\n```bash\nvar_ram=512      # 512 MB (minimal)\nvar_ram=1024     # 1 GB (typical)\nvar_ram=2048     # 2 GB (comfortable)\nvar_ram=4096     # 4 GB (recommended for databases)\nvar_ram=8192     # 8 GB (high memory apps)\n```\n\n**Conversion Guide:**\n```\n512 MB   = 0.5 GB\n1024 MB  = 1 GB\n2048 MB  = 2 GB\n4096 MB  = 4 GB\n8192 MB  = 8 GB\n16384 MB = 16 GB\n```\n\n**Best Practices:**\n- Minimum 512 MB for basic Linux\n- 1 GB for typical applications\n- 2-4 GB for web servers, databases\n- Monitor with `free -h` inside container\n\n---\n\n### var_disk\n\n**Type:** Integer (GB)\n**Default:** Varies by app (usually 2-8)\n**Range:** 0.001 GB to storage capacity\n**Description:** Root disk size in gigabytes.\n\n```bash\nvar_disk=2      # 2 GB (minimal OS only)\nvar_disk=4      # 4 GB (typical)\nvar_disk=8      # 8 GB (comfortable)\nvar_disk=20     # 20 GB (recommended for apps)\nvar_disk=50     # 50 GB (large applications)\nvar_disk=100    # 100 GB (databases, media)\n```\n\n**Important Notes:**\n- Can be expanded after creation (not reduced)\n- Actual space depends on storage type\n- Thin provisioning supported on most storage\n- Plan for logs, data, updates\n\n**Recommended Sizes by Use Case:**\n```\nBasic Linux container:     4 GB\nWeb server (Nginx/Apache): 8 GB\nApplication server:        10-20 GB\nDatabase server:          20-50 GB\nDocker host:              30-100 GB\nMedia server:             100+ GB\n```\n\n---\n\n### var_hostname\n\n**Type:** String\n**Default:** Application name\n**Max Length:** 63 characters\n**Description:** Container hostname (FQDN format allowed).\n\n```bash\nvar_hostname=myserver\nvar_hostname=pihole\nvar_hostname=docker-01\nvar_hostname=web.example.com\n```\n\n**Rules:**\n- Lowercase letters, numbers, hyphens\n- Cannot start or end with hyphen\n- No underscores allowed\n- No spaces\n\n**Best Practices:**\n```bash\n# ✓ Good\nvar_hostname=web-server\nvar_hostname=db-primary\nvar_hostname=app.domain.com\n\n# ✗ Avoid\nvar_hostname=Web_Server    # Uppercase, underscore\nvar_hostname=-server       # Starts with hyphen\nvar_hostname=my server     # Contains space\n```\n\n---\n\n### var_brg\n\n**Type:** String\n**Default:** `vmbr0`\n**Description:** Network bridge interface.\n\n```bash\nvar_brg=vmbr0    # Default Proxmox bridge\nvar_brg=vmbr1    # Custom bridge\nvar_brg=vmbr2    # Isolated network\n```\n\n**Common Setups:**\n```\nvmbr0 → Main network (LAN)\nvmbr1 → Guest network\nvmbr2 → DMZ\nvmbr3 → Management\nvmbr4 → Storage network\n```\n\n**Check available bridges:**\n```bash\nip link show | grep vmbr\n# or\nbrctl show\n```\n\n---\n\n### var_net\n\n**Type:** String\n**Options:** `dhcp` or `static`\n**Default:** `dhcp`\n**Description:** IPv4 network configuration method.\n\n```bash\nvar_net=dhcp     # Automatic IP via DHCP\nvar_net=static   # Manual IP configuration\n```\n\n**DHCP Mode:**\n- Automatic IP assignment\n- Easy setup\n- Good for development\n- Requires DHCP server on network\n\n**Static Mode:**\n- Fixed IP address\n- Requires gateway configuration\n- Better for servers\n- Configure via advanced settings or after creation\n\n---\n\n### var_gateway\n\n**Type:** IPv4 Address\n**Default:** Auto-detected from host\n**Description:** Network gateway IP address.\n\n```bash\nvar_gateway=192.168.1.1\nvar_gateway=10.0.0.1\nvar_gateway=172.16.0.1\n```\n\n**Auto-detection:**\nIf not specified, system detects gateway from host:\n```bash\nip route | grep default\n```\n\n**When to specify:**\n- Multiple gateways available\n- Custom routing setup\n- Different network segment\n\n---\n\n### var_vlan\n\n**Type:** Integer\n**Range:** 1-4094\n**Default:** None\n**Description:** VLAN tag for network isolation.\n\n```bash\nvar_vlan=10      # VLAN 10\nvar_vlan=100     # VLAN 100\nvar_vlan=200     # VLAN 200\n```\n\n**Common VLAN Schemes:**\n```\nVLAN 10  → Management\nVLAN 20  → Servers\nVLAN 30  → Desktops\nVLAN 40  → Guest WiFi\nVLAN 50  → IoT devices\nVLAN 99  → DMZ\n```\n\n**Requirements:**\n- Switch must support VLANs\n- Proxmox bridge configured for VLAN aware\n- Gateway on same VLAN\n\n---\n\n### var_mtu\n\n**Type:** Integer\n**Default:** `1500`\n**Range:** 68-9000\n**Description:** Maximum Transmission Unit size.\n\n```bash\nvar_mtu=1500     # Standard Ethernet\nvar_mtu=1492     # PPPoE\nvar_mtu=9000     # Jumbo frames\n```\n\n**Common Values:**\n```\n1500 → Standard Ethernet (default)\n1492 → PPPoE connections\n1400 → Some VPN setups\n9000 → Jumbo frames (10GbE networks)\n```\n\n**When to change:**\n- Jumbo frames for performance on 10GbE\n- PPPoE internet connections\n- VPN tunnels with overhead\n- Specific network requirements\n\n---\n\n### var_mac\n\n**Type:** MAC Address\n**Format:** `XX:XX:XX:XX:XX:XX`\n**Default:** Auto-generated\n**Description:** Container MAC address.\n\n```bash\nvar_mac=02:00:00:00:00:01\nvar_mac=DE:AD:BE:EF:00:01\n```\n\n**When to specify:**\n- MAC-based licensing\n- Static DHCP reservations\n- Network access control\n- Cloning configurations\n\n**Best Practices:**\n- Use locally administered addresses (2nd bit set)\n- Start with `02:`, `06:`, `0A:`, `0E:`\n- Avoid vendor OUIs\n- Document custom MACs\n\n---\n\n### var_ipv6_method\n\n**Type:** String\n**Options:** `auto`, `dhcp`, `static`, `none`, `disable`\n**Default:** `none`\n**Description:** IPv6 configuration method.\n\n```bash\nvar_ipv6_method=auto      # SLAAC (auto-configuration)\nvar_ipv6_method=dhcp      # DHCPv6\nvar_ipv6_method=static    # Manual configuration\nvar_ipv6_method=none      # IPv6 enabled but not configured\nvar_ipv6_method=disable   # IPv6 completely disabled\n```\n\n**Detailed Options:**\n\n**auto (SLAAC)**\n- Stateless Address Auto-Configuration\n- Router advertisements\n- No DHCPv6 server needed\n- Recommended for most cases\n\n**dhcp (DHCPv6)**\n- Stateful configuration\n- Requires DHCPv6 server\n- More control over addressing\n\n**static**\n- Manual IPv6 address\n- Manual gateway\n- Full control\n\n**none**\n- IPv6 stack active\n- No address configured\n- Can configure later\n\n**disable**\n- IPv6 completely disabled at kernel level\n- Use when IPv6 causes issues\n- Sets `net.ipv6.conf.all.disable_ipv6=1`\n\n---\n\n### var_ns\n\n**Type:** IP Address\n**Default:** Auto (from host)\n**Description:** DNS nameserver IP.\n\n```bash\nvar_ns=8.8.8.8           # Google DNS\nvar_ns=1.1.1.1           # Cloudflare DNS\nvar_ns=9.9.9.9           # Quad9 DNS\nvar_ns=192.168.1.1       # Local DNS\n```\n\n**Common DNS Servers:**\n```\n8.8.8.8, 8.8.4.4         → Google Public DNS\n1.1.1.1, 1.0.0.1         → Cloudflare DNS\n9.9.9.9, 149.112.112.112 → Quad9 DNS\n208.67.222.222           → OpenDNS\n192.168.1.1              → Local router/Pi-hole\n```\n\n---\n\n### var_ssh\n\n**Type:** Boolean\n**Options:** `yes` or `no`\n**Default:** `no`\n**Description:** Enable SSH server in container.\n\n```bash\nvar_ssh=yes      # SSH server enabled\nvar_ssh=no       # SSH server disabled (console only)\n```\n\n**When enabled:**\n- OpenSSH server installed\n- Started on boot\n- Port 22 open\n- Root login allowed\n\n**Security Considerations:**\n- Disable if not needed\n- Use SSH keys instead of passwords\n- Consider non-standard port\n- Firewall rules recommended\n\n---\n\n### var_ssh_authorized_key\n\n**Type:** String (SSH public key)\n**Default:** None\n**Description:** SSH public key for root user.\n\n```bash\nvar_ssh_authorized_key=ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC... user@host\nvar_ssh_authorized_key=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host\n```\n\n**Supported Key Types:**\n- RSA (2048-4096 bits)\n- Ed25519 (recommended)\n- ECDSA\n- DSA (deprecated)\n\n**How to get your public key:**\n```bash\ncat ~/.ssh/id_rsa.pub\n# or\ncat ~/.ssh/id_ed25519.pub\n```\n\n**Multiple keys:**\nSeparate with newlines (in file) or use multiple deployments.\n\n---\n\n### var_pw\n\n**Type:** String\n**Default:** Empty (auto-login)\n**Description:** Root password.\n\n```bash\nvar_pw=SecurePassword123!    # Set password\nvar_pw=                      # Auto-login (empty)\n```\n\n**Auto-login behavior:**\n- No password required for console\n- Automatic login on console access\n- SSH still requires key if enabled\n- Suitable for development\n\n**Password best practices:**\n- Minimum 12 characters\n- Mix upper/lower/numbers/symbols\n- Use password manager\n- Rotate regularly\n\n---\n\n### var_nesting\n\n**Type:** Boolean (0 or 1)\n**Default:** `1`\n**Description:** Allow nested containers (required for Docker).\n\n```bash\nvar_nesting=1    # Nested containers allowed\nvar_nesting=0    # Nested containers disabled\n```\n\n**Required for:**\n- Docker\n- LXC inside LXC\n- Systemd features\n- Container orchestration\n\n**Security Impact:**\n- Slightly reduced isolation\n- Required for container platforms\n- Generally safe when unprivileged\n\n---\n\n### var_diagnostics\n\n**Type:** Boolean (yes or no)\n**Default:** `yes`\n**Description:** Determines if anonymous telemetry and diagnostic data is sent to Community-Scripts API.\n\n```bash\nvar_diagnostics=yes      # Allow telemetry (helps us improve scripts)\nvar_diagnostics=no       # Disable all telemetry\n```\n\n**Privacy & Usage:**\n- Data is strictly anonymous (random session ID)\n- Reports success/failure of installations\n- Maps error codes (e.g., APT lock, out of RAM)\n- No user-specific data, hostnames, or secret keys are ever sent\n\n---\n\n### var_gpu\n\n**Type:** Boolean/Toggle\n**Options:** `yes` or `no`\n**Default:** `no`\n**Description:** Enable GPU passthrough for the container.\n\n```bash\nvar_gpu=yes      # Enable GPU passthrough (auto-detect)\nvar_gpu=no       # Disable GPU passthrough (default)\n```\n\n**Features enabled:**\n- Auto-detects Intel (QuickSync), NVIDIA, and AMD GPUs\n- Passes through `/dev/dri` and render nodes\n- Configures appropriate container permissions\n- Crucial for media servers (Plex, Jellyfin, Immich)\n\n**Prerequisites:**\n- Host drivers installed correctly\n- Hardware present and visible to Proxmox\n- IOMMU enabled (for some configurations)\n\n---\n\n### var_tun\n\n**Type:** Boolean/Toggle\n**Options:** `yes` or `no`\n**Default:** `no`\n**Description:** Enable TUN/TAP device support.\n\n```bash\nvar_tun=yes      # Enable TUN/TAP support\nvar_tun=no       # Disable TUN/TAP support (default)\n```\n\n**Required for:**\n- VPN software (WireGuard, OpenVPN)\n- Network tunneling (Tailscale, ZeroTier)\n- Custom network bridges\n\n---\n\n### var_keyctl\n\n**Type:** Boolean (0 or 1)\n**Default:** `0`\n**Description:** Enable keyctl system call.\n\n```bash\nvar_keyctl=1     # Keyctl enabled\nvar_keyctl=0     # Keyctl disabled\n```\n\n**Required for:**\n- Docker in some configurations\n- Systemd keyring features\n- Encryption key management\n- Some authentication systems\n\n---\n\n### var_fuse\n\n**Type:** Boolean/Toggle\n**Options:** `yes` or `no`\n**Default:** `no`\n**Description:** Enable FUSE filesystem support.\n\n```bash\nvar_fuse=yes     # FUSE enabled\nvar_fuse=no      # FUSE disabled\n```\n\n**Required for:**\n- sshfs\n- AppImage\n- Some backup tools\n- User-space filesystems\n\n---\n\n### var_mknod\n\n**Type:** Boolean (0 or 1)\n**Default:** `0`\n**Description:** Allow device node creation.\n\n```bash\nvar_mknod=1      # Device nodes allowed\nvar_mknod=0      # Device nodes disabled\n```\n\n**Requires:**\n- Kernel 5.3+\n- Experimental feature\n- Use with caution\n\n---\n\n### var_mount_fs\n\n**Type:** String (comma-separated)\n**Default:** Empty\n**Description:** Allowed mountable filesystems.\n\n```bash\nvar_mount_fs=nfs\nvar_mount_fs=nfs,cifs\nvar_mount_fs=ext4,xfs,nfs\n```\n\n**Common Options:**\n```\nnfs      → NFS network shares\ncifs     → SMB/CIFS shares\next4     → Ext4 filesystems\nxfs      → XFS filesystems\nbtrfs    → Btrfs filesystems\n```\n\n---\n\n### var_protection\n\n**Type:** Boolean\n**Options:** `yes` or `no`\n**Default:** `no`\n**Description:** Prevent accidental deletion.\n\n```bash\nvar_protection=yes    # Protected from deletion\nvar_protection=no     # Can be deleted normally\n```\n\n**When protected:**\n- Cannot delete via GUI\n- Cannot delete via `pct destroy`\n- Must disable protection first\n- Good for production containers\n\n---\n\n### var_tags\n\n**Type:** String (comma-separated)\n**Default:** `community-script`\n**Description:** Container tags for organization.\n\n```bash\nvar_tags=production\nvar_tags=production,webserver\nvar_tags=dev,testing,temporary\n```\n\n**Best Practices:**\n```bash\n# Environment tags\nvar_tags=production\nvar_tags=development\nvar_tags=staging\n\n# Function tags\nvar_tags=webserver,nginx\nvar_tags=database,postgresql\nvar_tags=cache,redis\n\n# Project tags\nvar_tags=project-alpha,frontend\nvar_tags=customer-xyz,billing\n\n# Combined\nvar_tags=production,webserver,project-alpha\n```\n\n---\n\n### var_timezone\n\n**Type:** String (TZ database format)\n**Default:** Host timezone\n**Description:** Container timezone.\n\n```bash\nvar_timezone=Europe/Berlin\nvar_timezone=America/New_York\nvar_timezone=Asia/Tokyo\n```\n\n**Common Timezones:**\n```\nEurope/London\nEurope/Berlin\nEurope/Paris\nAmerica/New_York\nAmerica/Chicago\nAmerica/Los_Angeles\nAsia/Tokyo\nAsia/Singapore\nAustralia/Sydney\nUTC\n```\n\n**List all timezones:**\n```bash\ntimedatectl list-timezones\n```\n\n---\n\n### var_verbose\n\n**Type:** Boolean\n**Options:** `yes` or `no`\n**Default:** `no`\n**Description:** Enable verbose output.\n\n```bash\nvar_verbose=yes    # Show all commands\nvar_verbose=no     # Silent mode\n```\n\n**When enabled:**\n- Shows all executed commands\n- Displays detailed progress\n- Useful for debugging\n- More log output\n\n---\n\n### var_apt_cacher\n\n**Type:** Boolean\n**Options:** `yes` or `no`\n**Default:** `no`\n**Description:** Use APT caching proxy.\n\n```bash\nvar_apt_cacher=yes\nvar_apt_cacher=no\n```\n\n**Benefits:**\n- Faster package installs\n- Reduced bandwidth\n- Offline package cache\n- Speeds up multiple containers\n\n---\n\n### var_apt_cacher_ip\n\n**Type:** IP Address\n**Default:** None\n**Description:** APT cacher proxy IP.\n\n```bash\nvar_apt_cacher=yes\nvar_apt_cacher_ip=192.168.1.100\n```\n\n**Setup apt-cacher-ng:**\n```bash\napt install apt-cacher-ng\n# Runs on port 3142\n```\n\n---\n\n### var_container_storage\n\n**Type:** String\n**Default:** Auto-detected\n**Description:** Storage for container.\n\n```bash\nvar_container_storage=local\nvar_container_storage=local-zfs\nvar_container_storage=pve-storage\n```\n\n**List available storage:**\n```bash\npvesm status\n```\n\n---\n\n### var_template_storage\n\n**Type:** String\n**Default:** Auto-detected\n**Description:** Storage for templates.\n\n```bash\nvar_template_storage=local\nvar_template_storage=nfs-templates\n```\n\n---\n\n## Quick Reference Table\n\n| Variable | Type | Default | Example |\n|----------|------|---------|---------|\n| `var_unprivileged` | 0/1 | 1 | `var_unprivileged=1` |\n| `var_cpu` | int | varies | `var_cpu=4` |\n| `var_ram` | int (MB) | varies | `var_ram=4096` |\n| `var_disk` | int (GB) | varies | `var_disk=20` |\n| `var_hostname` | string | app name | `var_hostname=server` |\n| `var_brg` | string | vmbr0 | `var_brg=vmbr1` |\n| `var_net` | dhcp/static | dhcp | `var_net=dhcp` |\n| `var_gateway` | IP | auto | `var_gateway=192.168.1.1` |\n| `var_ipv6_method` | string | none | `var_ipv6_method=disable` |\n| `var_vlan` | int | - | `var_vlan=100` |\n| `var_mtu` | int | 1500 | `var_mtu=9000` |\n| `var_mac` | MAC | auto | `var_mac=02:00:00:00:00:01` |\n| `var_ns` | IP | auto | `var_ns=8.8.8.8` |\n| `var_ssh` | yes/no | no | `var_ssh=yes` |\n| `var_ssh_authorized_key` | string | - | `var_ssh_authorized_key=ssh-rsa...` |\n| `var_pw` | string | empty | `var_pw=password` |\n| `var_nesting` | 0/1 | 1 | `var_nesting=1` |\n| `var_keyctl` | 0/1 | 0 | `var_keyctl=1` |\n| `var_fuse` | 0/1 | 0 | `var_fuse=1` |\n| `var_mknod` | 0/1 | 0 | `var_mknod=1` |\n| `var_mount_fs` | string | - | `var_mount_fs=nfs,cifs` |\n| `var_protection` | yes/no | no | `var_protection=yes` |\n| `var_tags` | string | community-script | `var_tags=prod,web` |\n| `var_timezone` | string | host TZ | `var_timezone=Europe/Berlin` |\n| `var_verbose` | yes/no | no | `var_verbose=yes` |\n| `var_apt_cacher` | yes/no | no | `var_apt_cacher=yes` |\n| `var_apt_cacher_ip` | IP | - | `var_apt_cacher_ip=192.168.1.10` |\n| `var_container_storage` | string | auto | `var_container_storage=local-zfs` |\n| `var_template_storage` | string | auto | `var_template_storage=local` |\n\n---\n\n## See Also\n\n- [Defaults System Guide](DEFAULTS_GUIDE.md)\n- [Unattended Deployments](UNATTENDED_DEPLOYMENTS.md)\n- [Security Best Practices](SECURITY_GUIDE.md)\n- [Network Configuration](NETWORK_GUIDE.md)\n"
  },
  {
    "path": "docs/guides/DEFAULTS_SYSTEM_GUIDE.md",
    "content": "# Configuration & Defaults System - User Guide\n\n> **Complete Guide to App Defaults and User Defaults**\n> \n> *Learn how to configure, save, and reuse your installation settings*\n\n---\n\n## Table of Contents\n\n1. [Quick Start](#quick-start)\n2. [Understanding the Defaults System](#understanding-the-defaults-system)\n3. [Installation Modes](#installation-modes)\n4. [How to Save Defaults](#how-to-save-defaults)\n5. [How to Use Saved Defaults](#how-to-use-saved-defaults)\n6. [Managing Your Defaults](#managing-your-defaults)\n7. [Advanced Configuration](#advanced-configuration)\n8. [Troubleshooting](#troubleshooting)\n\n---\n\n## Quick Start\n\n### 30-Second Setup\n\n```bash\n# 1. Run any container installation script\nbash pihole-install.sh\n\n# 2. When prompted, select: \"Advanced Settings\"\n#    (This allows you to customize everything)\n\n# 3. Answer all configuration questions\n\n# 4. At the end, when asked \"Save as App Defaults?\"\n#    Select: YES\n\n# 5. Done! Your settings are now saved\n```\n\n**Next Time**: Run the same script again, select **\"App Defaults\"** and your settings will be applied automatically!\n\n---\n\n## Understanding the Defaults System\n\n### The Three-Tier System\n\nYour installation settings are managed through three layers:\n\n#### 🔷 **Tier 1: Built-in Defaults** (Fallback)\n```\nThese are hardcoded in the scripts\nProvide sensible defaults for each application\nExample: PiHole uses 2 CPU cores by default\n```\n\n#### 🔶 **Tier 2: User Defaults** (Global)\n```\nYour personal global defaults\nApplied to ALL container installations\nLocation: /usr/local/community-scripts/default.vars\nExample: \"I always want 4 CPU cores and 2GB RAM\"\n```\n\n#### 🔴 **Tier 3: App Defaults** (Specific)\n```\nApplication-specific saved settings\nOnly applied when installing that specific app\nLocation: /usr/local/community-scripts/defaults/<appname>.vars\nExample: \"Whenever I install PiHole, use these exact settings\"\n```\n\n### Priority System\n\nWhen installing a container, settings are applied in this order:\n\n```\n┌─────────────────────────────────────┐\n│ 1. Environment Variables (HIGHEST)  │  Set in shell: export var_cpu=8\n│    (these override everything)      │\n├─────────────────────────────────────┤\n│ 2. App Defaults                     │  From: defaults/pihole.vars\n│    (app-specific saved settings)    │\n├─────────────────────────────────────┤\n│ 3. User Defaults                    │  From: default.vars\n│    (your global defaults)           │\n├─────────────────────────────────────┤\n│ 4. Built-in Defaults (LOWEST)       │  Hardcoded in script\n│    (failsafe, always available)     │\n└─────────────────────────────────────┘\n```\n\n**In Plain English**: \n- If you set an environment variable → it wins\n- Otherwise, if you have app-specific defaults → use those\n- Otherwise, if you have user defaults → use those\n- Otherwise, use the hardcoded defaults\n\n---\n\n## Installation Modes\n\nWhen you run any installation script, you'll be presented with a menu:\n\n### Option 1️⃣ : **Default Settings**\n\n```\nQuick installation with standard settings\n├─ Best for: First-time users, quick deployments\n├─ What happens:\n│  1. Script uses built-in defaults\n│  2. Container created immediately\n│  3. No questions asked\n└─ Time: ~2 minutes\n```\n\n**When to use**: You want a standard installation, don't need customization\n\n---\n\n### Option 2️⃣ : **Advanced Settings**\n\n```\nFull customization with 19 configuration steps\n├─ Best for: Power users, custom requirements\n├─ What happens:\n│  1. Script asks for EVERY setting\n│  2. You control: CPU, RAM, Disk, Network, SSH, etc.\n│  3. Shows summary before creating\n│  4. Offers to save as App Defaults\n└─ Time: ~5-10 minutes\n```\n\n**When to use**: You want full control over the configuration\n\n**Available Settings**:\n- CPU cores, RAM amount, Disk size\n- Container name, network settings\n- SSH access, API access, Features\n- Password, SSH keys, Tags\n\n---\n\n### Option 3️⃣ : **User Defaults**\n\n```\nUse your saved global defaults\n├─ Best for: Consistent deployments across many containers\n├─ Requires: You've previously saved User Defaults\n├─ What happens:\n│  1. Loads settings from: /usr/local/community-scripts/default.vars\n│  2. Shows you the loaded settings\n│  3. Creates container immediately\n└─ Time: ~2 minutes\n```\n\n**When to use**: You have preferred defaults you want to use for every app\n\n---\n\n### Option 4️⃣ : **App Defaults** (if available)\n\n```\nUse previously saved app-specific defaults\n├─ Best for: Repeating the same configuration multiple times\n├─ Requires: You've previously saved App Defaults for this app\n├─ What happens:\n│  1. Loads settings from: /usr/local/community-scripts/defaults/<app>.vars\n│  2. Shows you the loaded settings\n│  3. Creates container immediately\n└─ Time: ~2 minutes\n```\n\n**When to use**: You've installed this app before and want identical settings\n\n---\n\n### Option 5️⃣ : **Settings Menu**\n\n```\nManage your saved configurations\n├─ Functions:\n│  • View current settings\n│  • Edit storage selections\n│  • Manage defaults location\n│  • See what's currently configured\n└─ Time: ~1 minute\n```\n\n**When to use**: You want to review or modify saved settings\n\n---\n\n## How to Save Defaults\n\n### Method 1: Save While Installing\n\nThis is the easiest way:\n\n#### Step-by-Step: Create App Defaults\n\n```bash\n# 1. Run the installation script\nbash pihole-install.sh\n\n# 2. Choose installation mode\n#    ┌─────────────────────────┐\n#    │ Select installation mode:│\n#    │ 1) Default Settings     │\n#    │ 2) Advanced Settings    │\n#    │ 3) User Defaults        │\n#    │ 4) App Defaults         │\n#    │ 5) Settings Menu        │\n#    └─────────────────────────┘\n#\n#    Enter: 2 (Advanced Settings)\n\n# 3. Answer all configuration questions\n#    • Container name? → my-pihole\n#    • CPU cores? → 4\n#    • RAM amount? → 2048\n#    • Disk size? → 20\n#    • SSH access? → yes\n#    ... (more options)\n\n# 4. Review summary (shown before creation)\n#    ✓ Confirm to proceed\n\n# 5. After creation completes, you'll see:\n#    ┌──────────────────────────────────┐\n#    │ Save as App Defaults for PiHole? │\n#    │ (Yes/No)                         │\n#    └──────────────────────────────────┘\n#\n#    Select: Yes\n\n# 6. Done! Settings saved to:\n#    /usr/local/community-scripts/defaults/pihole.vars\n```\n\n#### Step-by-Step: Create User Defaults\n\n```bash\n# Same as App Defaults, but:\n# When you select \"Advanced Settings\"\n# FIRST app you run with this selection will offer\n# to save as \"User Defaults\" additionally\n\n# This saves to: /usr/local/community-scripts/default.vars\n```\n\n---\n\n### Method 2: Manual File Creation\n\nFor advanced users who want to create defaults without running installation:\n\n```bash\n# Create User Defaults manually\nsudo tee /usr/local/community-scripts/default.vars > /dev/null << 'EOF'\n# Global User Defaults\nvar_cpu=4\nvar_ram=2048\nvar_disk=20\nvar_unprivileged=1\nvar_brg=vmbr0\nvar_gateway=192.168.1.1\nvar_timezone=Europe/Berlin\nvar_ssh=yes\nvar_container_storage=local\nvar_template_storage=local\nEOF\n\n# Create App Defaults manually\nsudo tee /usr/local/community-scripts/defaults/pihole.vars > /dev/null << 'EOF'\n# App-specific defaults for PiHole\nvar_unprivileged=1\nvar_cpu=2\nvar_ram=1024\nvar_disk=10\nvar_brg=vmbr0\nvar_gateway=192.168.1.1\nvar_hostname=pihole\nvar_container_storage=local\nvar_template_storage=local\nEOF\n```\n\n---\n\n### Method 3: Using Environment Variables\n\nSet defaults via environment before running:\n\n```bash\n# Set as environment variables\nexport var_cpu=4\nexport var_ram=2048\nexport var_disk=20\nexport var_hostname=my-container\n\n# Run installation\nbash pihole-install.sh\n\n# These settings will be used\n# (Can still be overridden by saved defaults)\n```\n\n---\n\n## How to Use Saved Defaults\n\n### Using User Defaults\n\n```bash\n# 1. Run any installation script\nbash pihole-install.sh\n\n# 2. When asked for mode, select:\n#    Option: 3 (User Defaults)\n\n# 3. Your settings from default.vars are applied\n# 4. Container created with your saved settings\n```\n\n### Using App Defaults\n\n```bash\n# 1. Run the app you configured before\nbash pihole-install.sh\n\n# 2. When asked for mode, select:\n#    Option: 4 (App Defaults)\n\n# 3. Your settings from defaults/pihole.vars are applied\n# 4. Container created with exact same settings\n```\n\n### Overriding Saved Defaults\n\n```bash\n# Even if you have defaults saved,\n# you can override them with environment variables\n\nexport var_cpu=8  # Override saved defaults\nexport var_hostname=custom-name\n\nbash pihole-install.sh\n# Installation will use these values instead of saved defaults\n```\n\n---\n\n## Managing Your Defaults\n\n### View Your Settings\n\n#### View User Defaults\n```bash\ncat /usr/local/community-scripts/default.vars\n```\n\n#### View App Defaults\n```bash\ncat /usr/local/community-scripts/defaults/pihole.vars\n```\n\n#### List All Saved App Defaults\n```bash\nls -la /usr/local/community-scripts/defaults/\n```\n\n### Edit Your Settings\n\n#### Edit User Defaults\n```bash\nsudo nano /usr/local/community-scripts/default.vars\n```\n\n#### Edit App Defaults\n```bash\nsudo nano /usr/local/community-scripts/defaults/pihole.vars\n```\n\n### Update Existing Defaults\n\n```bash\n# Run installation again with your app\nbash pihole-install.sh\n\n# Select: Advanced Settings\n# Make desired changes\n# At end, when asked to save:\n#   \"Defaults already exist, Update?\"\n#   Select: Yes\n\n# Your saved defaults are updated\n```\n\n### Delete Defaults\n\n#### Delete User Defaults\n```bash\nsudo rm /usr/local/community-scripts/default.vars\n```\n\n#### Delete App Defaults\n```bash\nsudo rm /usr/local/community-scripts/defaults/pihole.vars\n```\n\n#### Delete All App Defaults\n```bash\nsudo rm /usr/local/community-scripts/defaults/*\n```\n\n---\n\n## Advanced Configuration\n\n### Available Variables\n\nAll configurable variables start with `var_`:\n\n#### Resource Allocation\n```bash\nvar_cpu=4              # CPU cores\nvar_ram=2048           # RAM in MB\nvar_disk=20            # Disk in GB\nvar_unprivileged=1     # 0=privileged, 1=unprivileged\n```\n\n#### Network\n```bash\nvar_brg=vmbr0          # Bridge interface\nvar_net=dhcp           # dhcp, static IP/CIDR, or IP range (see below)\nvar_gateway=192.168.1.1  # Default gateway (required for static IP)\nvar_mtu=1500           # MTU size\nvar_vlan=100           # VLAN ID\n```\n\n#### IP Range Scanning\n\nYou can specify an IP range instead of a static IP. The system will ping each IP in the range and automatically assign the first free IP:\n\n```bash\n# Format: START_IP/CIDR-END_IP/CIDR\nvar_net=192.168.1.100/24-192.168.1.200/24\nvar_gateway=192.168.1.1\n```\n\nThis is useful for automated deployments where you want static IPs but don't want to track which IPs are already in use.\n\n#### System\n```bash\nvar_hostname=pihole    # Container name\nvar_timezone=Europe/Berlin  # Timezone\nvar_pw=SecurePass123   # Root password\nvar_tags=dns,pihole    # Tags for organization\nvar_verbose=yes        # Enable verbose output\n```\n\n#### Security & Access\n```bash\nvar_ssh=yes            # Enable SSH\nvar_ssh_authorized_key=\"ssh-rsa AA...\" # SSH public key\nvar_protection=1       # Enable protection flag\n```\n\n#### Features\n```bash\nvar_fuse=1             # FUSE filesystem support\nvar_tun=1              # TUN device support\nvar_nesting=1          # Nesting (Docker in LXC)\nvar_keyctl=1           # Keyctl syscall\nvar_mknod=1            # Device node creation\n```\n\n#### Storage\n```bash\nvar_container_storage=local    # Where to store container\nvar_template_storage=local     # Where to store templates\n```\n\n### Example Configuration Files\n\n#### Gaming Server Defaults\n```bash\n# High performance for gaming containers\nvar_cpu=8\nvar_ram=4096\nvar_disk=50\nvar_unprivileged=0\nvar_fuse=1\nvar_nesting=1\nvar_tags=gaming\n```\n\n#### Development Server\n```bash\n# Development with Docker support\nvar_cpu=4\nvar_ram=2048\nvar_disk=30\nvar_unprivileged=1\nvar_nesting=1\nvar_ssh=yes\nvar_tags=development\n```\n\n#### IoT/Monitoring\n```bash\n# Low-resource, always-on containers\nvar_cpu=2\nvar_ram=512\nvar_disk=10\nvar_unprivileged=1\nvar_nesting=0\nvar_fuse=0\nvar_tun=0\nvar_tags=iot,monitoring\n```\n\n---\n\n## Troubleshooting\n\n### \"App Defaults not available\" Message\n\n**Problem**: You want to use App Defaults, but option says they're not available\n\n**Solution**:\n1. You haven't created App Defaults yet for this app\n2. Run the app with \"Advanced Settings\"\n3. When finished, save as App Defaults\n4. Next time, App Defaults will be available\n\n---\n\n### \"Settings not being applied\"\n\n**Problem**: You saved defaults, but they're not being used\n\n**Checklist**:\n```bash\n# 1. Verify files exist\nls -la /usr/local/community-scripts/default.vars\nls -la /usr/local/community-scripts/defaults/<app>.vars\n\n# 2. Check file permissions (should be readable)\nstat /usr/local/community-scripts/default.vars\n\n# 3. Verify correct mode selected\n#    (Make sure you selected \"User Defaults\" or \"App Defaults\")\n\n# 4. Check for environment variable override\nenv | grep var_\n#    If you have var_* set in environment,\n#    those override your saved defaults\n```\n\n---\n\n### \"Cannot write to defaults location\"\n\n**Problem**: Permission denied when saving defaults\n\n**Solution**:\n```bash\n# Create the defaults directory if missing\nsudo mkdir -p /usr/local/community-scripts/defaults\n\n# Fix permissions\nsudo chmod 755 /usr/local/community-scripts\nsudo chmod 755 /usr/local/community-scripts/defaults\n\n# Make sure you're running as root\nsudo bash pihole-install.sh\n```\n\n---\n\n### \"Defaults directory doesn't exist\"\n\n**Problem**: Script can't find where to save defaults\n\n**Solution**:\n```bash\n# Create the directory\nsudo mkdir -p /usr/local/community-scripts/defaults\n\n# Verify\nls -la /usr/local/community-scripts/\n```\n\n---\n\n### Settings seem random or wrong\n\n**Problem**: Container gets different settings than expected\n\n**Possible Causes & Solutions**:\n\n```bash\n# 1. Check if environment variables are set\nenv | grep var_\n# If you see var_* entries, those override your defaults\n# Clear them: unset var_cpu var_ram (etc)\n\n# 2. Verify correct defaults are in files\ncat /usr/local/community-scripts/default.vars\ncat /usr/local/community-scripts/defaults/pihole.vars\n\n# 3. Check which mode you actually selected\n# (Script output shows which defaults were applied)\n\n# 4. Check Proxmox logs for errors\nsudo journalctl -u pve-daemon -n 50\n```\n\n---\n\n### \"Variable not recognized\"\n\n**Problem**: You set a variable that doesn't work\n\n**Solution**:\nOnly certain variables are allowed (security whitelist):\n\n```\nAllowed variables (starting with var_):\n✓ var_cpu, var_ram, var_disk, var_unprivileged\n✓ var_brg, var_gateway, var_mtu, var_vlan, var_net\n✓ var_hostname, var_pw, var_timezone\n✓ var_ssh, var_ssh_authorized_key\n✓ var_fuse, var_tun, var_nesting, var_keyctl\n✓ var_container_storage, var_template_storage\n✓ var_tags, var_verbose\n✓ var_apt_cacher, var_apt_cacher_ip\n✓ var_protection, var_mount_fs\n\n✗ Other variables are NOT supported\n```\n\n---\n\n## Best Practices\n\n### ✅ Do's\n\n✓ Use **App Defaults** when you want app-specific settings\n✓ Use **User Defaults** for your global preferences\n✓ Edit defaults files directly with `nano` (safe)\n✓ Keep separate App Defaults for each app\n✓ Back up your defaults regularly\n✓ Use environment variables for temporary overrides\n\n### ❌ Don'ts\n\n✗ Don't use `source` on defaults files (security risk)\n✗ Don't put sensitive passwords in defaults (use SSH keys)\n✗ Don't modify defaults while installation is running\n✗ Don't delete defaults.d while containers are being created\n✗ Don't use special characters without escaping\n\n---\n\n## Quick Reference\n\n### Defaults Locations\n\n| Type | Location | Example |\n|------|----------|---------|\n| User Defaults | `/usr/local/community-scripts/default.vars` | Global settings |\n| App Defaults | `/usr/local/community-scripts/defaults/<app>.vars` | PiHole-specific |\n| Backup Dir | `/usr/local/community-scripts/defaults/` | All app defaults |\n\n### File Format\n\n```bash\n# Comments start with #\nvar_name=value\n\n# No spaces around =\n✓ var_cpu=4\n✗ var_cpu = 4\n\n# String values don't need quotes\n✓ var_hostname=mycontainer\n✓ var_hostname='mycontainer'\n\n# Values with spaces need quotes\n✓ var_tags=\"docker,production,testing\"\n✗ var_tags=docker,production,testing\n```\n\n### Command Reference\n\n```bash\n# View defaults\ncat /usr/local/community-scripts/default.vars\n\n# Edit defaults\nsudo nano /usr/local/community-scripts/default.vars\n\n# List all app defaults\nls /usr/local/community-scripts/defaults/\n\n# Backup your defaults\ncp -r /usr/local/community-scripts/defaults/ ~/defaults-backup/\n\n# Set temporary override\nexport var_cpu=8\nbash pihole-install.sh\n\n# Create custom defaults\nsudo tee /usr/local/community-scripts/defaults/custom.vars << 'EOF'\nvar_cpu=4\nvar_ram=2048\nEOF\n```\n\n---\n\n## Getting Help\n\n### Need More Information?\n\n- 📖 [Main Documentation](../../docs/)\n- 🐛 [Report Issues](https://github.com/community-scripts/ProxmoxVE/issues)\n- 💬 [Discussions](https://github.com/community-scripts/ProxmoxVE/discussions)\n\n### Useful Commands\n\n```bash\n# Check what variables are available\ngrep \"var_\" /path/to/app-install.sh | head -20\n\n# Verify defaults syntax\ncat /usr/local/community-scripts/default.vars\n\n# Monitor installation with defaults\nbash pihole-install.sh 2>&1 | tee installation.log\n```\n\n---\n\n## Document Information\n\n| Field | Value |\n|-------|-------|\n| Version | 1.0 |\n| Last Updated | November 28, 2025 |\n| Status | Current |\n| License | MIT |\n\n---\n\n**Happy configuring! 🚀**\n"
  },
  {
    "path": "docs/guides/README.md",
    "content": "# Configuration & Deployment Guides\n\nThis directory contains comprehensive guides for configuring and deploying Proxmox VE containers using community-scripts.\n\n## 📚 Available Guides\n\n### [Configuration Reference](CONFIGURATION_REFERENCE.md)\n\nComplete reference for all configuration options, environment variables, and advanced settings available in the build system.\n\n**Topics covered:**\n\n- Container specifications (CPU, RAM, Disk)\n- Network configuration (IPv4/IPv6, VLAN, MTU)\n- Storage selection and management\n- Privilege modes and features\n- OS selection and versions\n\n### [Defaults System Guide](DEFAULTS_SYSTEM_GUIDE.md)\n\nUnderstanding and customizing default settings for container deployments.\n\n**Topics covered:**\n\n- Default system settings\n- Per-script overrides\n- Custom defaults configuration\n- Environment variable precedence\n\n### [Unattended Deployments](UNATTENDED_DEPLOYMENTS.md)\n\nAutomating container deployments without user interaction.\n\n**Topics covered:**\n\n- Environment variable configuration\n- Batch deployments\n- CI/CD integration\n- Scripted installations\n- Pre-configured templates\n\n## 🔗 Related Documentation\n\n- **[CT Scripts Guide](../ct/)** - Container script structure and usage\n- **[Install Scripts Guide](../install/)** - Installation script internals\n- **[API Documentation](../api/)** - API integration and endpoints\n- **[Build Functions](../misc/build.func/)** - Build system functions reference\n- **[Tools Functions](../misc/tools.func/)** - Utility functions reference\n\n## 💡 Quick Start\n\nFor most users, start with the **Unattended Deployments** guide to learn how to automate your container setups.\n\nFor advanced configuration options, refer to the **Configuration Reference**.\n\n## 🤝 Contributing\n\nIf you'd like to improve these guides or add new ones, please see our [Contribution Guide](../contribution/).\n"
  },
  {
    "path": "docs/guides/UNATTENDED_DEPLOYMENTS.md",
    "content": "# Unattended Deployments Guide\n\nComplete guide for automated, zero-interaction container deployments using community-scripts for Proxmox VE.\n\n---\n\n## 🎯 What You'll Learn\n\nThis comprehensive guide covers:\n- ✅ Complete automation of container deployments\n- ✅ Zero-interaction installations\n- ✅ Batch deployments (multiple containers)\n- ✅ Infrastructure as Code (Ansible, Terraform)\n- ✅ CI/CD pipeline integration\n- ✅ Error handling and rollback strategies\n- ✅ Production-ready deployment scripts\n- ✅ Security best practices\n\n---\n\n## Table of Contents\n\n1. [Overview](#overview)\n2. [Prerequisites](#prerequisites)\n3. [Deployment Methods](#deployment-methods)\n4. [Single Container Deployment](#single-container-deployment)\n5. [Batch Deployments](#batch-deployments)\n6. [Infrastructure as Code](#infrastructure-as-code)\n7. [CI/CD Integration](#cicd-integration)\n8. [Error Handling](#error-handling)\n9. [Security Considerations](#security-considerations)\n\n---\n\n## Overview\n\nUnattended deployments allow you to:\n- ✅ Deploy containers without manual interaction\n- ✅ Automate infrastructure provisioning\n- ✅ Integrate with CI/CD pipelines\n- ✅ Maintain consistent configurations\n- ✅ Scale deployments across multiple nodes\n\n---\n\n## Prerequisites\n\n### 1. Proxmox VE Access\n```bash\n# Verify you have root access\nwhoami  # Should return: root\n\n# Check Proxmox version (8.0+ or 9.0-9.1 required)\npveversion\n```\n\n### 2. Network Connectivity\n```bash\n# Test GitHub access\ncurl -I https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh\n\n# Test internet connectivity\nping -c 1 1.1.1.1\n```\n\n### 3. Storage Available\n```bash\n# List available storage\npvesm status\n\n# Check free space\ndf -h\n```\n\n---\n\n## Deployment Methods\n\n### Method Comparison\n\n| Method | Use Case | Complexity | Flexibility |\n|--------|----------|------------|-------------|\n| **Environment Variables** | Quick one-offs | Low | High |\n| **App Defaults** | Repeat deployments | Low | Medium |\n| **Shell Scripts** | Batch operations | Medium | High |\n| **Ansible** | Infrastructure as Code | High | Very High |\n| **Terraform** | Cloud-native IaC | High | Very High |\n\n---\n\n## Single Container Deployment\n\n### Basic Unattended Deployment\n\n**Simplest form:**\n```bash\nvar_hostname=myserver bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)\"\n```\n\n### Complete Configuration Example\n\n```bash\n#!/bin/bash\n# deploy-single.sh - Deploy a single container with full configuration\n\nvar_unprivileged=1 \\\nvar_cpu=4 \\\nvar_ram=4096 \\\nvar_disk=30 \\\nvar_hostname=production-app \\\nvar_os=debian \\\nvar_version=13 \\\nvar_brg=vmbr0 \\\nvar_net=dhcp \\\nvar_ipv6_method=none \\\nvar_ssh=yes \\\nvar_ssh_authorized_key=\"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... admin@workstation\" \\\nvar_nesting=1 \\\nvar_tags=production,automated \\\nvar_protection=yes \\\nvar_verbose=no \\\n  bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)\"\n\necho \"✓ Container deployed successfully\"\n```\n\n### Using IP Range Scan for Automatic IP Assignment\n\nInstead of manually specifying static IPs, you can define an IP range. The system will automatically ping each IP and assign the first free one:\n\n```bash\n#!/bin/bash\n# deploy-with-ip-scan.sh - Auto-assign first free IP from range\n\nvar_unprivileged=1 \\\nvar_cpu=4 \\\nvar_ram=4096 \\\nvar_hostname=web-server \\\nvar_net=192.168.1.100/24-192.168.1.150/24 \\\nvar_gateway=192.168.1.1 \\\n  bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)\"\n\n# The script will:\n# 1. Ping 192.168.1.100 - if responds, skip\n# 2. Ping 192.168.1.101 - if responds, skip\n# 3. Continue until first IP that doesn't respond\n# 4. Assign that IP to the container\n```\n\n> **Note**: IP range format is `START_IP/CIDR-END_IP/CIDR`. Both sides must include the same CIDR notation.\n\n### Using App Defaults\n\n**Step 1: Create defaults once (interactive)**\n```bash\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/pihole.sh)\"\n# Select \"Advanced Settings\" → Configure → Save as \"App Defaults\"\n```\n\n**Step 2: Deploy unattended (uses saved defaults)**\n```bash\n#!/bin/bash\n# deploy-with-defaults.sh\n\n# App defaults are loaded automatically\nbash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/pihole.sh)\"\n# Script will use /usr/local/community-scripts/defaults/pihole.vars\n```\n\n### Advanced Configuration Variables\n\nBeyond the basic resource settings, you can control advanced container features:\n\n| Variable | Description | Options |\n|----------|-------------|---------|\n| `var_os` | Operating system template | `debian`, `ubuntu`, `alpine` |\n| `var_version` | OS version | `12`, `13` (Debian), `22.04`, `24.04` (Ubuntu) |\n| `var_gpu` | Enable GPU passthrough | `yes`, `no` (Default: `no`) |\n| `var_tun` | Enable TUN/TAP device | `yes`, `no` (Default: `no`) |\n| `var_nesting` | Enable nesting | `1`, `0` (Default: `1`) |\n\n**Example with GPU and TUN:**\n```bash\nvar_gpu=yes \\\nvar_tun=yes \\\nvar_hostname=transcoder \\\n  bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/plex.sh)\"\n```\n\n---\n\n## Batch Deployments\n\n### Deploy Multiple Containers\n\n#### Simple Loop\n\n```bash\n#!/bin/bash\n# batch-deploy-simple.sh\n\napps=(\"thingsboard\" \"qui\" \"flatnotes\")\n\nfor app in \"${apps[@]}\"; do\n  echo \"Deploying $app...\"\n  var_hostname=\"$app-server\" \\\n  var_cpu=2 \\\n  var_ram=2048 \\\n    bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"\n\n  echo \"✓ $app deployed\"\n  sleep 5  # Wait between deployments\ndone\n```\n\n#### Advanced with Configuration Array\n\n```bash\n#!/bin/bash\n# batch-deploy-advanced.sh - Deploy multiple containers with individual configs\n\ndeclare -A CONTAINERS=(\n  [\"beszel\"]=\"1:512:8:vmbr0:monitoring\"\n  [\"qui\"]=\"2:1024:10:vmbr0:torrent,ui\"\n  [\"thingsboard\"]=\"6:8192:50:vmbr1:iot,industrial\"\n  [\"dockge\"]=\"2:2048:10:vmbr0:docker,management\"\n)\n\nfor app in \"${!CONTAINERS[@]}\"; do\n  # Parse configuration\n  IFS=':' read -r cpu ram disk bridge tags <<< \"${CONTAINERS[$app]}\"\n\n  echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n  echo \"Deploying: $app\"\n  echo \"  CPU: $cpu cores\"\n  echo \"  RAM: $ram MB\"\n  echo \"  Disk: $disk GB\"\n  echo \"  Bridge: $bridge\"\n  echo \"  Tags: $tags\"\n  echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n\n  # Deploy container\n  var_unprivileged=1 \\\n  var_cpu=\"$cpu\" \\\n  var_ram=\"$ram\" \\\n  var_disk=\"$disk\" \\\n  var_hostname=\"$app\" \\\n  var_brg=\"$bridge\" \\\n  var_net=dhcp \\\n  var_ipv6_method=none \\\n  var_ssh=yes \\\n  var_tags=\"$tags,automated\" \\\n    bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\" 2>&1 | tee \"deploy-${app}.log\"\n\n  if [ $? -eq 0 ]; then\n    echo \"✓ $app deployed successfully\"\n  else\n    echo \"✗ $app deployment failed - check deploy-${app}.log\"\n  fi\n\n  sleep 5\ndone\n\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \"Batch deployment complete!\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n```\n\n#### Parallel Deployment\n\n```bash\n#!/bin/bash\n# parallel-deploy.sh - Deploy multiple containers in parallel\n\ndeploy_container() {\n  local app=\"$1\"\n  local cpu=\"$2\"\n  local ram=\"$3\"\n  local disk=\"$4\"\n\n  echo \"[$app] Starting deployment...\"\n  var_cpu=\"$cpu\" \\\n  var_ram=\"$ram\" \\\n  var_disk=\"$disk\" \\\n  var_hostname=\"$app\" \\\n  var_net=dhcp \\\n    bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\" \\\n    &> \"deploy-${app}.log\"\n\n  echo \"[$app] ✓ Completed\"\n}\n\n# Export function for parallel execution\nexport -f deploy_container\n\n# Deploy in parallel (max 3 at a time)\nparallel -j 3 deploy_container ::: \\\n  \"debian 2 2048 10\" \\\n  \"ubuntu 2 2048 10\" \\\n  \"alpine 1 1024 5\" \\\n  \"pihole 2 1024 8\" \\\n  \"docker 4 4096 30\"\n\necho \"All deployments complete!\"\n```\n\n---\n\n## Infrastructure as Code\n\n### Ansible Playbook\n\n#### Basic Playbook\n\n```yaml\n---\n# playbook-proxmox.yml\n- name: Deploy ProxmoxVE Containers\n  hosts: proxmox_hosts\n  become: yes\n  tasks:\n    - name: Deploy Debian Container\n      shell: |\n        var_unprivileged=1 \\\n        var_cpu=2 \\\n        var_ram=2048 \\\n        var_disk=10 \\\n        var_hostname=debian-{{ inventory_hostname }} \\\n        var_net=dhcp \\\n        var_ssh=yes \\\n        var_tags=ansible,automated \\\n        bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)\"\n      args:\n        executable: /bin/bash\n      register: deploy_result\n\n    - name: Display deployment result\n      debug:\n        var: deploy_result.stdout_lines\n```\n\n#### Advanced Playbook with Variables\n\n```yaml\n---\n# advanced-playbook.yml\n- name: Deploy Multiple Container Types\n  hosts: proxmox\n  vars:\n    containers:\n      - name: pihole\n        cpu: 2\n        ram: 1024\n        disk: 8\n        tags: \"dns,network\"\n      - name: homeassistant\n        cpu: 4\n        ram: 4096\n        disk: 20\n        tags: \"automation,ha\"\n      - name: docker\n        cpu: 6\n        ram: 8192\n        disk: 50\n        tags: \"containers,docker\"\n\n    ssh_key: \"{{ lookup('file', '~/.ssh/id_rsa.pub') }}\"\n\n  tasks:\n    - name: Ensure community-scripts directory exists\n      file:\n        path: /usr/local/community-scripts/defaults\n        state: directory\n        mode: '0755'\n\n    - name: Deploy containers\n      shell: |\n        var_unprivileged=1 \\\n        var_cpu={{ item.cpu }} \\\n        var_ram={{ item.ram }} \\\n        var_disk={{ item.disk }} \\\n        var_hostname={{ item.name }} \\\n        var_brg=vmbr0 \\\n        var_net=dhcp \\\n        var_ipv6_method=none \\\n        var_ssh=yes \\\n        var_ssh_authorized_key=\"{{ ssh_key }}\" \\\n        var_tags=\"{{ item.tags }},ansible\" \\\n        bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/{{ item.name }}.sh)\"\n      args:\n        executable: /bin/bash\n      loop: \"{{ containers }}\"\n      register: deployment_results\n\n    - name: Wait for containers to be ready\n      wait_for:\n        timeout: 60\n\n    - name: Report deployment status\n      debug:\n        msg: \"Deployed {{ item.item.name }} - Status: {{ 'Success' if item.rc == 0 else 'Failed' }}\"\n      loop: \"{{ deployment_results.results }}\"\n```\n\nRun with:\n```bash\nansible-playbook -i inventory.ini advanced-playbook.yml\n```\n\n### Terraform Integration\n\n```hcl\n# main.tf - Deploy containers via Terraform\n\nterraform {\n  required_providers {\n    proxmox = {\n      source = \"telmate/proxmox\"\n      version = \"2.9.14\"\n    }\n  }\n}\n\nprovider \"proxmox\" {\n  pm_api_url = \"https://proxmox.example.com:8006/api2/json\"\n  pm_api_token_id = \"terraform@pam!terraform\"\n  pm_api_token_secret = var.proxmox_token\n}\n\nresource \"null_resource\" \"deploy_container\" {\n  for_each = var.containers\n\n  provisioner \"remote-exec\" {\n    inline = [\n      \"var_unprivileged=1\",\n      \"var_cpu=${each.value.cpu}\",\n      \"var_ram=${each.value.ram}\",\n      \"var_disk=${each.value.disk}\",\n      \"var_hostname=${each.key}\",\n      \"var_net=dhcp\",\n      \"bash -c \\\"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${each.value.template}.sh)\\\"\"\n    ]\n\n    connection {\n      type = \"ssh\"\n      host = var.proxmox_host\n      user = \"root\"\n      private_key = file(\"~/.ssh/id_rsa\")\n    }\n  }\n}\n\nvariable \"containers\" {\n  type = map(object({\n    template = string\n    cpu = number\n    ram = number\n    disk = number\n  }))\n\n  default = {\n    \"pihole\" = {\n      template = \"pihole\"\n      cpu = 2\n      ram = 1024\n      disk = 8\n    }\n    \"homeassistant\" = {\n      template = \"homeassistant\"\n      cpu = 4\n      ram = 4096\n      disk = 20\n    }\n  }\n}\n```\n\n---\n\n## CI/CD Integration\n\n### GitHub Actions\n\n```yaml\n# .github/workflows/deploy-container.yml\nname: Deploy Container to Proxmox\n\non:\n  push:\n    branches: [main]\n  workflow_dispatch:\n    inputs:\n      container_type:\n        description: 'Container type to deploy'\n        required: true\n        type: choice\n        options:\n          - debian\n          - ubuntu\n          - docker\n          - pihole\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Deploy to Proxmox\n        uses: appleboy/ssh-action@v0.1.10\n        with:\n          host: ${{ secrets.PROXMOX_HOST }}\n          username: root\n          key: ${{ secrets.SSH_PRIVATE_KEY }}\n          script: |\n            var_unprivileged=1 \\\n            var_cpu=4 \\\n            var_ram=4096 \\\n            var_disk=30 \\\n            var_hostname=${{ github.event.inputs.container_type }}-ci \\\n            var_net=dhcp \\\n            var_ssh=yes \\\n            var_tags=ci-cd,automated \\\n            bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${{ github.event.inputs.container_type }}.sh)\"\n\n      - name: Notify deployment status\n        if: success()\n        run: echo \"✓ Container deployed successfully\"\n```\n\n### GitLab CI\n\n```yaml\n# .gitlab-ci.yml\nstages:\n  - deploy\n\ndeploy_container:\n  stage: deploy\n  image: alpine:latest\n  before_script:\n    - apk add --no-cache openssh-client curl bash\n    - eval $(ssh-agent -s)\n    - echo \"$SSH_PRIVATE_KEY\" | tr -d '\\r' | ssh-add -\n    - mkdir -p ~/.ssh\n    - chmod 700 ~/.ssh\n    - ssh-keyscan $PROXMOX_HOST >> ~/.ssh/known_hosts\n  script:\n    - |\n      ssh root@$PROXMOX_HOST << 'EOF'\n        var_unprivileged=1 \\\n        var_cpu=4 \\\n        var_ram=4096 \\\n        var_disk=30 \\\n        var_hostname=gitlab-ci-container \\\n        var_net=dhcp \\\n        var_tags=gitlab-ci,automated \\\n        bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)\"\n      EOF\n  only:\n    - main\n  when: manual\n```\n\n---\n\n## Error Handling\n\n### Deployment Verification Script\n\n```bash\n#!/bin/bash\n# deploy-with-verification.sh\n\nAPP=\"debian\"\nHOSTNAME=\"production-server\"\nMAX_RETRIES=3\nRETRY_COUNT=0\n\ndeploy_container() {\n  echo \"Attempting deployment (Try $((RETRY_COUNT + 1))/$MAX_RETRIES)...\"\n\n  var_unprivileged=1 \\\n  var_cpu=4 \\\n  var_ram=4096 \\\n  var_disk=30 \\\n  var_hostname=\"$HOSTNAME\" \\\n  var_net=dhcp \\\n  var_ssh=yes \\\n    bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${APP}.sh)\" 2>&1 | tee deploy.log\n\n  return ${PIPESTATUS[0]}\n}\n\nverify_deployment() {\n  echo \"Verifying deployment...\"\n\n  # Check if container exists\n  if ! pct list | grep -q \"$HOSTNAME\"; then\n    echo \"✗ Container not found in pct list\"\n    return 1\n  fi\n\n  # Check if container is running\n  CTID=$(pct list | grep \"$HOSTNAME\" | awk '{print $1}')\n  STATUS=$(pct status \"$CTID\" | awk '{print $2}')\n\n  if [ \"$STATUS\" != \"running\" ]; then\n    echo \"✗ Container not running (Status: $STATUS)\"\n    return 1\n  fi\n\n  # Check network connectivity\n  if ! pct exec \"$CTID\" -- ping -c 1 1.1.1.1 &>/dev/null; then\n    echo \"⚠ Warning: No internet connectivity\"\n  fi\n\n  echo \"✓ Deployment verified successfully\"\n  echo \"  Container ID: $CTID\"\n  echo \"  Status: $STATUS\"\n  echo \"  IP: $(pct exec \"$CTID\" -- hostname -I)\"\n\n  return 0\n}\n\n# Main deployment loop with retry\nwhile [ $RETRY_COUNT -lt $MAX_RETRIES ]; do\n  if deploy_container; then\n    if verify_deployment; then\n      echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n      echo \"✓ Deployment successful!\"\n      echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n      exit 0\n    else\n      echo \"✗ Deployment verification failed\"\n    fi\n  else\n    echo \"✗ Deployment failed\"\n  fi\n\n  RETRY_COUNT=$((RETRY_COUNT + 1))\n\n  if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then\n    echo \"Retrying in 10 seconds...\"\n    sleep 10\n  fi\ndone\n\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \"✗ Deployment failed after $MAX_RETRIES attempts\"\necho \"Check deploy.log for details\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\nexit 1\n```\n\n### Rollback on Failure\n\n```bash\n#!/bin/bash\n# deploy-with-rollback.sh\n\nAPP=\"debian\"\nHOSTNAME=\"test-server\"\nSNAPSHOT_NAME=\"pre-deployment\"\n\n# Take snapshot of existing container (if exists)\nbackup_existing() {\n  EXISTING_CTID=$(pct list | grep \"$HOSTNAME\" | awk '{print $1}')\n  if [ -n \"$EXISTING_CTID\" ]; then\n    echo \"Creating snapshot of existing container...\"\n    pct snapshot \"$EXISTING_CTID\" \"$SNAPSHOT_NAME\" --description \"Pre-deployment backup\"\n    return 0\n  fi\n  return 1\n}\n\n# Deploy new container\ndeploy() {\n  var_hostname=\"$HOSTNAME\" \\\n  var_cpu=4 \\\n  var_ram=4096 \\\n    bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${APP}.sh)\"\n  return $?\n}\n\n# Rollback to snapshot\nrollback() {\n  local ctid=\"$1\"\n  echo \"Rolling back to snapshot...\"\n  pct rollback \"$ctid\" \"$SNAPSHOT_NAME\"\n  pct delsnapshot \"$ctid\" \"$SNAPSHOT_NAME\"\n}\n\n# Main execution\nbackup_existing\nHAD_BACKUP=$?\n\nif deploy; then\n  echo \"✓ Deployment successful\"\n  [ $HAD_BACKUP -eq 0 ] && echo \"You can remove the snapshot with: pct delsnapshot <CTID> $SNAPSHOT_NAME\"\nelse\n  echo \"✗ Deployment failed\"\n  if [ $HAD_BACKUP -eq 0 ]; then\n    read -p \"Rollback to previous version? (y/N) \" -n 1 -r\n    echo\n    if [[ $REPLY =~ ^[Yy]$ ]]; then\n      rollback \"$EXISTING_CTID\"\n      echo \"✓ Rolled back successfully\"\n    fi\n  fi\n  exit 1\nfi\n```\n\n---\n\n## Security Considerations\n\n### Secure Deployment Script\n\n```bash\n#!/bin/bash\n# secure-deploy.sh - Production-ready secure deployment\n\nset -euo pipefail  # Exit on error, undefined vars, pipe failures\n\n# Configuration\nreadonly APP=\"debian\"\nreadonly HOSTNAME=\"secure-server\"\nreadonly SSH_KEY_PATH=\"/root/.ssh/id_rsa.pub\"\nreadonly LOG_FILE=\"/var/log/container-deployments.log\"\n\n# Logging function\nlog() {\n  echo \"[$(date +'%Y-%m-%d %H:%M:%S')] $*\" | tee -a \"$LOG_FILE\"\n}\n\n# Validate prerequisites\nvalidate_environment() {\n  log \"Validating environment...\"\n\n  # Check if running as root\n  if [ \"$EUID\" -ne 0 ]; then\n    log \"ERROR: Must run as root\"\n    exit 1\n  fi\n\n  # Check SSH key exists\n  if [ ! -f \"$SSH_KEY_PATH\" ]; then\n    log \"ERROR: SSH key not found at $SSH_KEY_PATH\"\n    exit 1\n  fi\n\n  # Check internet connectivity\n  if ! curl -s --max-time 5 https://github.com &>/dev/null; then\n    log \"ERROR: No internet connectivity\"\n    exit 1\n  fi\n\n  log \"✓ Environment validated\"\n}\n\n# Secure deployment\ndeploy_secure() {\n  log \"Starting secure deployment for $HOSTNAME...\"\n\n  SSH_KEY=$(cat \"$SSH_KEY_PATH\")\n\n  var_unprivileged=1 \\\n  var_cpu=4 \\\n  var_ram=4096 \\\n  var_disk=30 \\\n  var_hostname=\"$HOSTNAME\" \\\n  var_brg=vmbr0 \\\n  var_net=dhcp \\\n  var_ipv6_method=disable \\\n  var_ssh=yes \\\n  var_ssh_authorized_key=\"$SSH_KEY\" \\\n  var_nesting=0 \\\n  var_keyctl=0 \\\n  var_fuse=0 \\\n  var_protection=yes \\\n  var_tags=production,secure,automated \\\n  var_verbose=no \\\n    bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${APP}.sh)\" 2>&1 | tee -a \"$LOG_FILE\"\n\n  if [ ${PIPESTATUS[0]} -eq 0 ]; then\n    log \"✓ Deployment successful\"\n    return 0\n  else\n    log \"✗ Deployment failed\"\n    return 1\n  fi\n}\n\n# Main execution\nmain() {\n  validate_environment\n\n  if deploy_secure; then\n    log \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n    log \"Secure deployment completed successfully\"\n    log \"Container: $HOSTNAME\"\n    log \"Features: Unprivileged, SSH-only, Protected\"\n    log \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n    exit 0\n  else\n    log \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n    log \"Deployment failed - check logs at $LOG_FILE\"\n    log \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n    exit 1\n  fi\n}\n\nmain \"$@\"\n```\n\n### SSH Key Management\n\n```bash\n#!/bin/bash\n# deploy-with-ssh-keys.sh - Secure SSH key deployment\n\n# Load SSH keys from multiple sources\nload_ssh_keys() {\n  local keys=()\n\n  # Personal key\n  if [ -f ~/.ssh/id_rsa.pub ]; then\n    keys+=(\"$(cat ~/.ssh/id_rsa.pub)\")\n  fi\n\n  # Team keys\n  if [ -f /etc/ssh/authorized_keys.d/team ]; then\n    while IFS= read -r key; do\n      [ -n \"$key\" ] && keys+=(\"$key\")\n    done < /etc/ssh/authorized_keys.d/team\n  fi\n\n  # Join keys with newline\n  printf \"%s\\n\" \"${keys[@]}\"\n}\n\n# Deploy with multiple SSH keys\nSSH_KEYS=$(load_ssh_keys)\n\nvar_ssh=yes \\\nvar_ssh_authorized_key=\"$SSH_KEYS\" \\\nvar_hostname=multi-key-server \\\n  bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)\"\n```\n\n---\n\n## Complete Production Example\n\n```bash\n#!/bin/bash\n# production-deploy.sh - Complete production deployment system\n\nset -euo pipefail\n\n#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n# Configuration\n#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nreadonly SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nreadonly LOG_DIR=\"/var/log/proxmox-deployments\"\nreadonly CONFIG_FILE=\"$SCRIPT_DIR/deployment-config.json\"\n\n#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n# Functions\n#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nsetup_logging() {\n  mkdir -p \"$LOG_DIR\"\n  exec 1> >(tee -a \"$LOG_DIR/deployment-$(date +%Y%m%d-%H%M%S).log\")\n  exec 2>&1\n}\n\nlog_info() { echo \"[INFO] $(date +'%H:%M:%S') - $*\"; }\nlog_error() { echo \"[ERROR] $(date +'%H:%M:%S') - $*\" >&2; }\nlog_success() { echo \"[SUCCESS] $(date +'%H:%M:%S') - $*\"; }\n\nvalidate_prerequisites() {\n  log_info \"Validating prerequisites...\"\n\n  [ \"$EUID\" -eq 0 ] || { log_error \"Must run as root\"; exit 1; }\n  command -v jq >/dev/null 2>&1 || { log_error \"jq not installed\"; exit 1; }\n  command -v curl >/dev/null 2>&1 || { log_error \"curl not installed\"; exit 1; }\n\n  log_success \"Prerequisites validated\"\n}\n\ndeploy_from_config() {\n  local config_file=\"$1\"\n\n  if [ ! -f \"$config_file\" ]; then\n    log_error \"Config file not found: $config_file\"\n    return 1\n  fi\n\n  local container_count\n  container_count=$(jq '.containers | length' \"$config_file\")\n\n  log_info \"Deploying $container_count containers from config...\"\n\n  for i in $(seq 0 $((container_count - 1))); do\n    local name cpu ram disk app tags\n\n    name=$(jq -r \".containers[$i].name\" \"$config_file\")\n    cpu=$(jq -r \".containers[$i].cpu\" \"$config_file\")\n    ram=$(jq -r \".containers[$i].ram\" \"$config_file\")\n    disk=$(jq -r \".containers[$i].disk\" \"$config_file\")\n    app=$(jq -r \".containers[$i].app\" \"$config_file\")\n    tags=$(jq -r \".containers[$i].tags\" \"$config_file\")\n\n    log_info \"Deploying container: $name ($app)\"\n\n    var_unprivileged=1 \\\n    var_cpu=\"$cpu\" \\\n    var_ram=\"$ram\" \\\n    var_disk=\"$disk\" \\\n    var_hostname=\"$name\" \\\n    var_net=dhcp \\\n    var_ssh=yes \\\n    var_tags=\"$tags,automated\" \\\n    var_protection=yes \\\n      bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"\n\n    if [ $? -eq 0 ]; then\n      log_success \"Deployed: $name\"\n    else\n      log_error \"Failed to deploy: $name\"\n    fi\n\n    sleep 5\n  done\n}\n\ngenerate_report() {\n  log_info \"Generating deployment report...\"\n\n  echo \"\"\n  echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n  echo \"DEPLOYMENT REPORT\"\n  echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n  echo \"Time: $(date)\"\n  echo \"\"\n  pct list\n  echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n}\n\n#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n# Main\n#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nmain() {\n  setup_logging\n  log_info \"Starting production deployment system\"\n\n  validate_prerequisites\n  deploy_from_config \"$CONFIG_FILE\"\n  generate_report\n\n  log_success \"Production deployment complete\"\n}\n\nmain \"$@\"\n```\n\n**Example config file (deployment-config.json):**\n```json\n{\n  \"containers\": [\n    {\n      \"name\": \"pihole\",\n      \"app\": \"pihole\",\n      \"cpu\": 2,\n      \"ram\": 1024,\n      \"disk\": 8,\n      \"tags\": \"dns,network,production\"\n    },\n    {\n      \"name\": \"homeassistant\",\n      \"app\": \"homeassistant\",\n      \"cpu\": 4,\n      \"ram\": 4096,\n      \"disk\": 20,\n      \"tags\": \"automation,ha,production\"\n    },\n    {\n      \"name\": \"docker-host\",\n      \"app\": \"docker\",\n      \"cpu\": 8,\n      \"ram\": 16384,\n      \"disk\": 100,\n      \"tags\": \"containers,docker,production\"\n    }\n  ]\n}\n```\n\n---\n\n## See Also\n\n- [Defaults System Guide](DEFAULTS_GUIDE.md)\n- [Configuration Reference](CONFIGURATION_REFERENCE.md)\n- [Security Best Practices](SECURITY_GUIDE.md)\n- [Network Configuration](NETWORK_GUIDE.md)\n"
  },
  {
    "path": "docs/guides/USER_SUBMITTED_GUIDES.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<h2 align=\"center\">User Submitted Guides </h2>\n\n<sub> In order to contribute a guide on installing with Proxmox VE Helper Scripts, you should open a pull request that adds the guide to the `USER_SUBMITTED_GUIDES.md` file. </sub>\n\n[Proxmox Automation with Proxmox Helper Scripts!](https://www.youtube.com/watch?v=kcpu4z5eSEU)\n\n[Installing Home Assistant OS using Proxmox 8](https://community.home-assistant.io/t/installing-home-assistant-os-using-proxmox-8/201835)\n\n[How To Separate Zigbee2MQTT From Home Assistant In Proxmox](https://smarthomescene.com/guides/how-to-separate-zigbee2mqtt-from-home-assistant-in-proxmox/)\n\n[How To Install Home Assistant On Proxmox: The Easy Way](https://smarthomescene.com/guides/how-to-install-home-assistant-on-proxmox-the-easy-way/)\n\n[Home Assistant: Installing InfluxDB (LXC)](https://www.derekseaman.com/2023/04/home-assistant-installing-influxdb-lxc.html)\n\n[Home Assistant: Proxmox Quick Start Guide](https://www.derekseaman.com/2023/10/home-assistant-proxmox-ve-8-0-quick-start-guide-2.html)\n\n[Home Assistant: Installing Grafana (LXC) with Let’s Encrypt SSL](https://www.derekseaman.com/2023/04/home-assistant-installing-grafana-lxc.html)\n\n[Proxmox: Plex LXC with Alder Lake Transcoding](https://www.derekseaman.com/2023/04/proxmox-plex-lxc-with-alder-lake-transcoding.html)\n\n[How To Backup Home Assistant In Proxmox](https://smarthomescene.com/guides/how-to-backup-home-assistant-in-proxmox/)\n\n[Running Frigate on Proxmox](https://www.homeautomationguy.io/blog/running-frigate-on-proxmox)\n\n[Frigate VM on Proxmox with PCIe Coral TPU](https://www.derekseaman.com/2023/06/home-assistant-frigate-vm-on-proxmox-with-pcie-coral-tpu.html)\n\n[Moving Home Assistant’s Database To MariaDB On Proxmox](https://smarthomescene.com/guides/moving-home-assistants-database-to-mariadb-on-proxmox/)\n\n[How-to: Proxmox VE 7.4 to 8.0 Upgrade](https://www.derekseaman.com/2023/06/how-to-proxmox-7-4-to-8-0-upgrade.html)\n\n[iGPU Transcoding In Proxmox with Jellyfin](https://www.youtube.com/watch?v=XAa_qpNmzZs)\n\n[Proxmox + NetData](<https://dbt3ch.com/books/proxmox-netdata-for-better-insights-and-notifications/page/proxmox-netdata-for-better-insights-and-notifications>)\n\n[Proxmox Homelab Series](<https://blog.kye.dev/proxmox-series>)\n\n[The fastest installation of Docker and Portainer on Proxmox VE](https://lavr.site/en-fastest-install-docker-portainer-proxmox/)\n\n[How To Setup Proxmox Backuper Server Using Helper Scripts](<https://youtu.be/6C2JOsrZZZw?si=kkrrcL_nLCDBJkOB>)\n"
  },
  {
    "path": "docs/install/DETAILED_GUIDE.md",
    "content": "# 🛠️ **Application Installation Scripts (install/AppName-install.sh)**\n\n**Modern Guide to Writing In-Container Installation Scripts**\n\n> **Updated**: December 2025\n> **Context**: Integrated with tools.func, error_handler.func, and install.func\n> **Examples Used**: `/install/pihole-install.sh`, `/install/mealie-install.sh`\n\n---\n\n## 📋 Table of Contents\n\n- [Overview](#overview)\n- [Execution Context](#execution-context)\n- [File Structure](#file-structure)\n- [Complete Script Template](#complete-script-template)\n- [Installation Phases](#installation-phases)\n- [Function Reference](#function-reference)\n- [Best Practices](#best-practices)\n- [Real Examples](#real-examples)\n- [Troubleshooting](#troubleshooting)\n- [Contribution Checklist](#contribution-checklist)\n\n---\n\n## Overview\n\n### Purpose\n\nInstallation scripts (`install/AppName-install.sh`) **run inside the LXC container** and are responsible for:\n\n1. Setting up the container OS (updates, packages)\n2. Installing application dependencies\n3. Downloading and configuring the application\n4. Setting up services and systemd units\n5. Creating version tracking files for updates\n6. Generating credentials/configurations\n7. Final cleanup and validation\n\n### Execution Flow\n\n```\nct/AppName.sh (Proxmox Host)\n       ↓\nbuild_container()\n       ↓\npct exec CTID bash -c \"$(cat install/AppName-install.sh)\"\n       ↓\ninstall/AppName-install.sh (Inside Container)\n       ↓\nContainer Ready with App Installed\n```\n\n---\n\n## Execution Context\n\n### Environment Variables Available\n\n```bash\n# From Proxmox/Container\nCTID                    # Container ID (100, 101, etc.)\nPCT_OSTYPE             # OS type (alpine, debian, ubuntu)\nHOSTNAME               # Container hostname\n\n# From build.func\nFUNCTIONS_FILE_PATH    # Bash functions library (core.func + tools.func)\nVERBOSE                # Verbose mode (yes/no)\nSTD                    # Standard redirection variable (silent/empty)\n\n# From install.func\nAPP                    # Application name\nNSAPP                  # Normalized app name (lowercase, no spaces)\nMETHOD                 # Installation method (ct/install)\nRANDOM_UUID            # Session UUID for telemetry\n```\n\n---\n\n## File Structure\n\n### Minimal install/AppName-install.sh Template\n\n```bash\n#!/usr/bin/env bash                          # [1] Shebang\n\n# [2] Copyright/Metadata\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: YourUsername\n# License: MIT\n# Source: https://example.com\n\n# [3] Load functions\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\n# [4] Installation steps\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y package1 package2\nmsg_ok \"Installed Dependencies\"\n\n# [5] Final setup\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n---\n\n## Complete Script Template\n\n### Phase 1: Header & Initialization\n\n```bash\n#!/usr/bin/env bash\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: YourUsername\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/application/repo\n\n# Load all available functions (from core.func + tools.func)\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\n# Initialize environment\ncolor                   # Setup ANSI colors and icons\nverb_ip6                # Configure IPv6 (if needed)\ncatch_errors           # Setup error traps\nsetting_up_container   # Verify OS is ready\nnetwork_check          # Verify internet connectivity\nupdate_os              # Update packages (apk/apt)\n```\n\n### Phase 2: Dependency Installation\n\n```bash\nmsg_info \"Installing Dependencies\"\n$STD apt-get install -y \\\n  curl \\\n  wget \\\n  git \\\n  nano \\\n  build-essential \\\n  libssl-dev \\\n  python3-dev\nmsg_ok \"Installed Dependencies\"\n```\n\n### Phase 3: Tool Setup (Using tools.func)\n\n```bash\n# Setup specific tool versions\nNODE_VERSION=\"22\" setup_nodejs\nPHP_VERSION=\"8.4\" setup_php\nPYTHON_VERSION=\"3.12\" setup_uv\n```\n\n### Phase 4: Application Download & Setup\n\n```bash\n# Download from GitHub\nRELEASE=$(curl -fsSL https://api.github.com/repos/user/repo/releases/latest | \\\n  grep \"tag_name\" | awk '{print substr($2, 2, length($2)-3)}')\n\nwget -q \"https://github.com/user/repo/releases/download/v${RELEASE}/app-${RELEASE}.tar.gz\"\ncd /opt\ntar -xzf app-${RELEASE}.tar.gz\nrm -f app-${RELEASE}.tar.gz\n```\n\n### Phase 5: Configuration Files\n\n```bash\n# Using cat << EOF (multiline)\ncat <<'EOF' >/etc/nginx/sites-available/appname\nserver {\n    listen 80;\n    server_name _;\n    root /opt/appname/public;\n    index index.php index.html;\n}\nEOF\n\n# Using sed for replacements\nsed -i -e \"s|^DB_HOST=.*|DB_HOST=localhost|\" \\\n       -e \"s|^DB_USER=.*|DB_USER=appuser|\" \\\n       /opt/appname/.env\n```\n\n### Phase 6: Database Setup (If Needed)\n\n```bash\nmsg_info \"Setting up Database\"\n\nDB_NAME=\"appname_db\"\nDB_USER=\"appuser\"\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n\n# For MySQL/MariaDB\nmysql -u root <<EOF\nCREATE DATABASE ${DB_NAME};\nCREATE USER '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';\nGRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';\nFLUSH PRIVILEGES;\nEOF\n\nmsg_ok \"Database setup complete\"\n```\n\n### Phase 7: Permission & Ownership\n\n```bash\nmsg_info \"Setting permissions\"\n\n# Web applications typically run as www-data\nchown -R www-data:www-data /opt/appname\nchmod -R 755 /opt/appname\nchmod -R 644 /opt/appname/*\nchmod 755 /opt/appname/*/.*\n\nmsg_ok \"Permissions set\"\n```\n\n### Phase 8: Service Configuration\n\n```bash\n# Enable systemd service\nsystemctl enable -q --now appname\n\n# Or for OpenRC (Alpine)\nrc-service appname start\nrc-update add appname default\n\n# Verify service is running\nif systemctl is-active --quiet appname; then\n  msg_ok \"Service running successfully\"\nelse\n  msg_error \"Service failed to start\"\n  journalctl -u appname -n 20\n  exit 1\nfi\n```\n\n### Phase 9: Version Tracking\n\n```bash\n# Essential for update detection\necho \"${RELEASE}\" > /opt/${APP}_version.txt\n\n# Or with additional metadata\ncat > /opt/${APP}_version.txt <<EOF\nVersion: ${RELEASE}\nInstallDate: $(date)\nInstallMethod: ${METHOD}\nEOF\n```\n\n### Phase 10: Final Setup & Cleanup\n\n```bash\n# Display MOTD and enable autologin\nmotd_ssh\n\n# Final customization\ncustomize\n\n# Clean up package manager cache\nmsg_info \"Cleaning up\"\napt-get -y autoremove\napt-get -y autoclean\nmsg_ok \"Cleaned\"\n\n# Or for Alpine\napk cache clean\nrm -rf /var/cache/apk/*\n\n# System cleanup\ncleanup_lxc\n```\n\n---\n\n## Installation Phases\n\n### Phase 1: Container OS Setup\n- Network interface brought up and configured\n- Internet connectivity verified\n- Package lists updated\n- All OS packages upgraded to latest versions\n\n### Phase 2: Base Dependencies\n```bash\nmsg_info \"Installing Base Dependencies\"\n$STD apt-get install -y \\\n  curl wget git nano build-essential\nmsg_ok \"Installed Base Dependencies\"\n```\n\n### Phase 3: Tool Installation\n```bash\nNODE_VERSION=\"22\" setup_nodejs\nPHP_VERSION=\"8.4\" setup_php\n```\n\n### Phase 4: Application Setup\n```bash\nRELEASE=$(curl -fsSL https://api.github.com/repos/user/repo/releases/latest | \\\n  grep \"tag_name\" | awk '{print substr($2, 2, length($2)-3)}')\nwget -q \"https://github.com/user/repo/releases/download/v${RELEASE}/app.tar.gz\"\n```\n\n### Phase 5: Configuration\nApplication-specific configuration files and environment setup\n\n### Phase 6: Service Registration\nEnable and verify systemd services are running\n\n---\n\n## Function Reference\n\n### Core Messaging Functions\n\n#### `msg_info(message)`\n\nDisplays an info message with spinner animation\n\n```bash\nmsg_info \"Installing application\"\n# Output: ⏳ Installing application (with spinning animation)\n```\n\n#### `msg_ok(message)`\n\nDisplays success message with checkmark\n\n```bash\nmsg_ok \"Installation completed\"\n# Output: ✔️ Installation completed\n```\n\n#### `msg_error(message)`\n\nDisplays error message and exits\n\n```bash\nmsg_error \"Installation failed\"\n# Output: ✖️ Installation failed\n```\n\n### Package Management\n\n#### `$STD` Variable\n\nControls output verbosity\n\n```bash\n# Silent mode (respects VERBOSE setting)\n$STD apt-get install -y nginx\n```\n\n#### `update_os()`\n\nUpdates OS packages\n\n```bash\nupdate_os\n# Runs: apt update && apt upgrade\n```\n\n### Tool Installation Functions\n\n#### `setup_nodejs()`\n\nInstalls Node.js with optional global modules\n\n```bash\nNODE_VERSION=\"22\" setup_nodejs\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn,@vue/cli\" setup_nodejs\n```\n\n#### `setup_php()`\n\nInstalls PHP with optional extensions\n\n```bash\nPHP_VERSION=\"8.4\" PHP_MODULE=\"bcmath,curl,gd,intl,redis\" setup_php\n```\n\n#### Other Tools\n\n```bash\nsetup_mariadb     # MariaDB database\nsetup_mysql       # MySQL database\nsetup_postgresql  # PostgreSQL\nsetup_docker      # Docker Engine\nsetup_composer    # PHP Composer\nsetup_python      # Python 3\nsetup_ruby        # Ruby\nsetup_rust        # Rust\n```\n\n### Cleanup Functions\n\n#### `cleanup_lxc()`\n\nComprehensive container cleanup\n\n- Removes package manager caches\n- Cleans temporary files\n- Clears language package caches\n- Removes systemd journal logs\n\n```bash\ncleanup_lxc\n# Output: ⏳ Cleaning up\n#         ✔️ Cleaned\n```\n\n---\n\n## Best Practices\n\n### ✅ DO:\n\n1. **Always Use $STD for Commands**\n```bash\n# ✅ Good: Respects VERBOSE setting\n$STD apt-get install -y nginx\n```\n\n2. **Generate Random Passwords Safely**\n```bash\n# ✅ Good: Alphanumeric only\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\n```\n\n3. **Check Command Success**\n```bash\n# ✅ Good: Verify success\nif ! wget -q \"https://example.com/file.tar.gz\"; then\n  msg_error \"Download failed\"\n  exit 1\nfi\n```\n\n4. **Set Proper Permissions**\n```bash\n# ✅ Good: Explicit permissions\nchown -R www-data:www-data /opt/appname\nchmod -R 755 /opt/appname\n```\n\n5. **Save Version for Update Checks**\n```bash\n# ✅ Good: Version tracked\necho \"${RELEASE}\" > /opt/${APP}_version.txt\n```\n\n6. **Handle Alpine vs Debian Differences**\n```bash\n# ✅ Good: Detect OS\nif grep -qi 'alpine' /etc/os-release; then\n  apk add package\nelse\n  apt-get install -y package\nfi\n```\n\n### ❌ DON'T:\n\n1. **Hardcode Versions**\n```bash\n# ❌ Bad: Won't auto-update\nwget https://example.com/app-1.2.3.tar.gz\n```\n\n2. **Use Root Without Password**\n```bash\n# ❌ Bad: Security risk\nmysql -u root\n```\n\n3. **Forget Error Handling**\n```bash\n# ❌ Bad: Silent failures\nwget https://example.com/file\ntar -xzf file\n```\n\n4. **Leave Temporary Files**\n```bash\n# ✅ Always cleanup\nrm -rf /opt/app-${RELEASE}.tar.gz\n```\n\n---\n\n## Real Examples\n\n### Example 1: Node.js Application\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\ncolor\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Node.js\"\nNODE_VERSION=\"22\" setup_nodejs\nmsg_ok \"Node.js installed\"\n\nmsg_info \"Installing Application\"\ncd /opt\nRELEASE=$(curl -fsSL https://api.github.com/repos/user/repo/releases/latest | \\\n  grep \"tag_name\" | awk '{print substr($2, 2, length($2)-3)}')\nwget -q \"https://github.com/user/repo/releases/download/v${RELEASE}/app.tar.gz\"\ntar -xzf app.tar.gz\necho \"${RELEASE}\" > /opt/app_version.txt\nmsg_ok \"Application installed\"\n\nsystemctl enable --now app\ncleanup_lxc\n```\n\n### Example 2: PHP Application with Database\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\ncolor\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nPHP_VERSION=\"8.4\" PHP_MODULE=\"bcmath,curl,pdo_mysql\" setup_php\nsetup_mariadb  # Uses distribution packages (recommended)\n# Or for specific version: MARIADB_VERSION=\"11.4\" setup_mariadb\n\n# Database setup\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nmysql -u root <<EOF\nCREATE DATABASE appdb;\nCREATE USER 'appuser'@'localhost' IDENTIFIED BY '${DB_PASS}';\nGRANT ALL ON appdb.* TO 'appuser'@'localhost';\nFLUSH PRIVILEGES;\nEOF\n\n# App installation\ncd /opt\nwget -q https://github.com/user/repo/releases/latest/download/app.tar.gz\ntar -xzf app.tar.gz\n\n# Configuration\ncat > /opt/app/.env <<EOF\nDB_HOST=localhost\nDB_NAME=appdb\nDB_USER=appuser\nDB_PASS=${DB_PASS}\nEOF\n\nchown -R www-data:www-data /opt/app\nsystemctl enable --now php-fpm\ncleanup_lxc\n```\n\n---\n\n## Troubleshooting\n\n### Installation Hangs\n\n**Check internet connectivity**:\n```bash\nping -c 1 8.8.8.8\n```\n\n**Enable verbose mode**:\n```bash\n# In ct/AppName.sh, before running\nVERBOSE=\"yes\" bash install/AppName-install.sh\n```\n\n### Package Not Found\n\n**Update package lists**:\n```bash\napt update\napt-cache search package_name\n```\n\n### Service Won't Start\n\n**Check logs**:\n```bash\njournalctl -u appname -n 50\nsystemctl status appname\n```\n\n---\n\n## Contribution Checklist\n\nBefore submitting a PR:\n\n### Structure\n- [ ] Shebang is `#!/usr/bin/env bash`\n- [ ] Loads functions from `$FUNCTIONS_FILE_PATH`\n- [ ] Copyright header with author\n- [ ] Clear phase comments\n\n### Installation\n- [ ] `setting_up_container` called early\n- [ ] `network_check` before downloads\n- [ ] `update_os` before package installation\n- [ ] All errors checked properly\n\n### Functions\n- [ ] Uses `msg_info/msg_ok/msg_error` for status\n- [ ] Uses `$STD` for command output silencing\n- [ ] Version saved to `/opt/${APP}_version.txt`\n- [ ] Proper permissions set\n\n### Cleanup\n- [ ] `motd_ssh` called for final setup\n- [ ] `customize` called for options\n- [ ] `cleanup_lxc` called at end\n\n### Testing\n- [ ] Tested with default settings\n- [ ] Tested with advanced (19-step) mode\n- [ ] Service starts and runs correctly\n\n---\n\n**Last Updated**: December 2025\n**Compatibility**: ProxmoxVE with install.func v3+\n"
  },
  {
    "path": "docs/install/README.md",
    "content": "# Installation Scripts Documentation (/install)\n\nThis directory contains comprehensive documentation for installation scripts in the `/install` directory.\n\n## Overview\n\nInstallation scripts (`install/*.sh`) run inside LXC containers and handle application-specific setup, configuration, and deployment.\n\n## Documentation Structure\n\nEach installation script category has documentation following the project pattern.\n\n## Key Resources\n\n- **[DETAILED_GUIDE.md](DETAILED_GUIDE.md)** - Complete reference for creating install scripts\n- **[../contribution/README.md](../contribution/README.md)** - How to contribute\n- **[../misc/install.func/](../misc/install.func/)** - Installation workflow documentation\n- **[../misc/tools.func/](../misc/tools.func/)** - Package installation documentation\n\n## Installation Script Flow\n\n```\ninstall/appname-install.sh (container-side)\n    │\n    ├─ Sources: $FUNCTIONS_FILE_PATH\n    │  ├─ core.func (messaging)\n    │  ├─ error_handler.func (error handling)\n    │  ├─ install.func (setup)\n    │  └─ tools.func (packages & tools)\n    │\n    ├─ 10-Phase Installation:\n    │  1. OS Setup\n    │  2. Base Dependencies\n    │  3. Tool Setup\n    │  4. Application Download\n    │  5. Configuration\n    │  6. Database Setup\n    │  7. Permissions\n    │  8. Services\n    │  9. Version Tracking\n    │  10. Final Cleanup\n    │\n    └─ Result: Application ready\n```\n\n## Available Installation Scripts\n\nSee `/install` directory for all installation scripts. Examples:\n\n- `pihole-install.sh` - Pi-hole installation\n- `docker-install.sh` - Docker installation\n- `wallabag-install.sh` - Wallabag setup\n- `nextcloud-install.sh` - Nextcloud deployment\n- `debian-install.sh` - Base Debian setup\n- And 30+ more...\n\n## Quick Start\n\nTo understand how to create an installation script:\n\n1. Read: [UPDATED_APP-install.md](../UPDATED_APP-install.md)\n2. Study: A similar existing script in `/install`\n3. Copy template and customize\n4. Test in container\n5. Submit PR\n\n## 10-Phase Installation Pattern\n\nEvery installation script follows this structure:\n\n### Phase 1: OS Setup\n```bash\nsetting_up_container\nnetwork_check\nupdate_os\n```\n\n### Phase 2: Base Dependencies\n```bash\npkg_update\npkg_install curl wget git\n```\n\n### Phase 3: Tool Setup\n```bash\nsetup_nodejs \"20\"\nsetup_php \"8.3\"\nsetup_mariadb  # Uses distribution packages (recommended)\n# MARIADB_VERSION=\"11.4\" setup_mariadb  # For specific version\n```\n\n### Phase 4: Application Download\n```bash\ngit clone https://github.com/user/app /opt/app\ncd /opt/app\n```\n\n### Phase 5: Configuration\n```bash\n# Create .env files, config files, etc.\ncat > .env <<EOF\nSETTING=value\nEOF\n```\n\n### Phase 6: Database Setup\n```bash\n# Create databases, users, etc.\nmysql -e \"CREATE DATABASE appdb\"\n```\n\n### Phase 7: Permissions\n```bash\nchown -R appuser:appgroup /opt/app\nchmod -R 755 /opt/app\n```\n\n### Phase 8: Services\n```bash\nsystemctl enable app\nsystemctl start app\n```\n\n### Phase 9: Version Tracking\n```bash\necho \"1.0.0\" > /opt/app_version.txt\n```\n\n### Phase 10: Final Cleanup\n```bash\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n## Contributing an Installation Script\n\n1. Create `ct/myapp.sh` (host script)\n2. Create `install/myapp-install.sh` (container script)\n3. Follow 10-phase pattern in [UPDATED_APP-install.md](../UPDATED_APP-install.md)\n4. Test in actual container\n5. Submit PR with both files\n\n## Common Tasks\n\n- **Create new installation script** → [UPDATED_APP-install.md](../UPDATED_APP-install.md)\n- **Install Node.js/PHP/Database** → [misc/tools.func/](../misc/tools.func/)\n- **Setup Alpine container** → [misc/alpine-install.func/](../misc/alpine-install.func/)\n- **Debug installation errors** → [EXIT_CODES.md](../EXIT_CODES.md)\n- **Use dev mode** → [DEV_MODE.md](../DEV_MODE.md)\n\n## Alpine vs Debian\n\n- **Debian-based** → Use `tools.func`, `install.func`, `systemctl`\n- **Alpine-based** → Use `alpine-tools.func`, `alpine-install.func`, `rc-service`\n\n---\n\n**Last Updated**: December 2025\n**Maintainers**: community-scripts team\n"
  },
  {
    "path": "docs/misc/README.md",
    "content": "# Misc Documentation\n\nThis directory contains comprehensive documentation for all function libraries and components of the Proxmox Community Scripts project. Each section is organized as a dedicated subdirectory with detailed references, examples, and integration guides.\n\n---\n\n## 🏗️ **Core Function Libraries**\n\n### 📁 [build.func/](./build.func/)\n**Core LXC Container Orchestration** - Main orchestrator for Proxmox LXC container creation\n\n**Contents:**\n- BUILD_FUNC_FLOWCHART.md - Visual execution flows and decision trees\n- BUILD_FUNC_ARCHITECTURE.md - System architecture and design\n- BUILD_FUNC_ENVIRONMENT_VARIABLES.md - Complete environment variable reference\n- BUILD_FUNC_FUNCTIONS_REFERENCE.md - Alphabetical function reference\n- BUILD_FUNC_EXECUTION_FLOWS.md - Detailed execution flows\n- BUILD_FUNC_USAGE_EXAMPLES.md - Practical usage examples\n- README.md - Overview and quick reference\n\n**Key Functions**: `variables()`, `start()`, `build_container()`, `build_defaults()`, `advanced_settings()`\n\n---\n\n### 📁 [core.func/](./core.func/)\n**System Utilities & Foundation** - Essential utility functions and system checks\n\n**Contents:**\n- CORE_FLOWCHART.md - Visual execution flows\n- CORE_FUNCTIONS_REFERENCE.md - Complete function reference\n- CORE_INTEGRATION.md - Integration points\n- CORE_USAGE_EXAMPLES.md - Practical examples\n- README.md - Overview and quick reference\n\n**Key Functions**: `color()`, `msg_info()`, `msg_ok()`, `msg_error()`, `root_check()`, `pve_check()`, `parse_dev_mode()`\n\n---\n\n### 📁 [error_handler.func/](./error_handler.func/)\n**Error Handling & Signal Management** - Comprehensive error handling and signal trapping\n\n**Contents:**\n- ERROR_HANDLER_FLOWCHART.md - Visual error handling flows\n- ERROR_HANDLER_FUNCTIONS_REFERENCE.md - Function reference\n- ERROR_HANDLER_INTEGRATION.md - Integration with other components\n- ERROR_HANDLER_USAGE_EXAMPLES.md - Practical examples\n- README.md - Overview and quick reference\n\n**Key Functions**: `catch_errors()`, `error_handler()`, `explain_exit_code()`, `signal_handler()`\n\n---\n\n### 📁 [api.func/](./api.func/)\n**Proxmox API Integration** - API communication and diagnostic reporting\n\n**Contents:**\n- API_FLOWCHART.md - API communication flows\n- API_FUNCTIONS_REFERENCE.md - Function reference\n- API_INTEGRATION.md - Integration points\n- API_USAGE_EXAMPLES.md - Practical examples\n- README.md - Overview and quick reference\n\n**Key Functions**: `post_to_api()`, `post_update_to_api()`, `get_error_description()`\n\n---\n\n## 📦 **Installation & Setup Function Libraries**\n\n### 📁 [install.func/](./install.func/)\n**Container Installation Workflow** - Installation orchestration for container-internal setup\n\n**Contents:**\n- INSTALL_FUNC_FLOWCHART.md - Installation workflow diagrams\n- INSTALL_FUNC_FUNCTIONS_REFERENCE.md - Complete function reference\n- INSTALL_FUNC_INTEGRATION.md - Integration with build and tools\n- INSTALL_FUNC_USAGE_EXAMPLES.md - Practical examples\n- README.md - Overview and quick reference\n\n**Key Functions**: `setting_up_container()`, `network_check()`, `update_os()`, `motd_ssh()`, `cleanup_lxc()`\n\n---\n\n### 📁 [tools.func/](./tools.func/)\n**Package & Tool Installation** - Robust package management and 30+ tool installation functions\n\n**Contents:**\n- TOOLS_FUNC_FLOWCHART.md - Package management flows\n- TOOLS_FUNC_FUNCTIONS_REFERENCE.md - 30+ function reference\n- TOOLS_FUNC_INTEGRATION.md - Integration with install workflows\n- TOOLS_FUNC_USAGE_EXAMPLES.md - Practical examples\n- TOOLS_FUNC_ENVIRONMENT_VARIABLES.md - Configuration reference\n- README.md - Overview and quick reference\n\n**Key Functions**: `setup_nodejs()`, `setup_php()`, `setup_mariadb()`, `setup_docker()`, `setup_deb822_repo()`, `pkg_install()`, `pkg_update()`\n\n---\n\n### 📁 [alpine-install.func/](./alpine-install.func/)\n**Alpine Container Setup** - Alpine Linux-specific installation functions\n\n**Contents:**\n- ALPINE_INSTALL_FUNC_FLOWCHART.md - Alpine setup flows\n- ALPINE_INSTALL_FUNC_FUNCTIONS_REFERENCE.md - Function reference\n- ALPINE_INSTALL_FUNC_INTEGRATION.md - Integration points\n- ALPINE_INSTALL_FUNC_USAGE_EXAMPLES.md - Practical examples\n- README.md - Overview and quick reference\n\n**Key Functions**: `update_os()` (apk version), `verb_ip6()`, `motd_ssh()` (Alpine), `customize()`\n\n---\n\n### 📁 [alpine-tools.func/](./alpine-tools.func/)\n**Alpine Tool Installation** - Alpine-specific package and tool installation\n\n**Contents:**\n- ALPINE_TOOLS_FUNC_FLOWCHART.md - Alpine package flows\n- ALPINE_TOOLS_FUNC_FUNCTIONS_REFERENCE.md - Function reference\n- ALPINE_TOOLS_FUNC_INTEGRATION.md - Integration with Alpine workflows\n- ALPINE_TOOLS_FUNC_USAGE_EXAMPLES.md - Practical examples\n- README.md - Overview and quick reference\n\n**Key Functions**: `apk_add()`, `apk_update()`, `apk_del()`, `add_community_repo()`, Alpine tool setup functions\n\n---\n\n### 📁 [cloud-init.func/](./cloud-init.func/)\n**VM Cloud-Init Configuration** - Cloud-init and VM provisioning functions\n\n**Contents:**\n- CLOUD_INIT_FUNC_FLOWCHART.md - Cloud-init flows\n- CLOUD_INIT_FUNC_FUNCTIONS_REFERENCE.md - Function reference\n- CLOUD_INIT_FUNC_INTEGRATION.md - Integration points\n- CLOUD_INIT_FUNC_USAGE_EXAMPLES.md - Practical examples\n- README.md - Overview and quick reference\n\n**Key Functions**: `generate_cloud_init()`, `generate_user_data()`, `setup_ssh_keys()`, `setup_static_ip()`\n\n---\n\n## 🔗 **Function Library Relationships**\n\n```\n┌─────────────────────────────────────────────┐\n│       Container Creation Flow               │\n├─────────────────────────────────────────────┤\n│                                             │\n│  ct/AppName.sh                              │\n│      ↓ (sources)                            │\n│  build.func                                 │\n│      ├─ variables()                         │\n│      ├─ build_container()                   │\n│      └─ advanced_settings()                 │\n│      ↓ (calls pct create with)              │\n│  install/appname-install.sh                 │\n│      ↓ (sources)                            │\n│      ├─ core.func      (colors, messaging)  │\n│      ├─ error_handler.func (error trapping) │\n│      ├─ install.func   (setup/network)      │\n│      └─ tools.func     (packages/tools)     │\n│                                             │\n└─────────────────────────────────────────────┘\n\n┌─────────────────────────────────────────────┐\n│       Alpine Container Flow                 │\n├─────────────────────────────────────────────┤\n│                                             │\n│  install/appname-install.sh (Alpine)        │\n│      ↓ (sources)                            │\n│      ├─ core.func              (colors)     │\n│      ├─ error_handler.func     (errors)     │\n│      ├─ alpine-install.func    (apk setup)  │\n│      └─ alpine-tools.func      (apk tools)  │\n│                                             │\n└─────────────────────────────────────────────┘\n\n┌─────────────────────────────────────────────┐\n│       VM Provisioning Flow                  │\n├─────────────────────────────────────────────┤\n│                                             │\n│  vm/OsName-vm.sh                            │\n│      ↓ (uses)                               │\n│  cloud-init.func                            │\n│      ├─ generate_cloud_init()               │\n│      ├─ setup_ssh_keys()                    │\n│      └─ configure_network()                 │\n│                                             │\n└─────────────────────────────────────────────┘\n```\n\n---\n\n## 📊 **Documentation Quick Stats**\n\n| Library | Files | Functions | Status |\n|---------|:---:|:---:|:---:|\n| build.func | 7 | 50+ | ✅ Complete |\n| core.func | 5 | 20+ | ✅ Complete |\n| error_handler.func | 5 | 10+ | ✅ Complete |\n| api.func | 5 | 5+ | ✅ Complete |\n| install.func | 5 | 8+ | ✅ Complete |\n| tools.func | 6 | 30+ | ✅ Complete |\n| alpine-install.func | 5 | 6+ | ✅ Complete |\n| alpine-tools.func | 5 | 15+ | ✅ Complete |\n| cloud-init.func | 5 | 12+ | ✅ Complete |\n\n**Total**: 9 function libraries, 48 documentation files, 150+ functions\n\n---\n\n## 🚀 **Getting Started**\n\n### For Container Creation Scripts\nStart with: **[build.func/](./build.func/)** → **[tools.func/](./tools.func/)** → **[install.func/](./install.func/)**\n\n### For Alpine Containers\nStart with: **[alpine-install.func/](./alpine-install.func/)** → **[alpine-tools.func/](./alpine-tools.func/)**\n\n### For VM Provisioning\nStart with: **[cloud-init.func/](./cloud-init.func/)**\n\n### For Troubleshooting\nStart with: **[error_handler.func/](./error_handler.func/)** → **[EXIT_CODES.md](../EXIT_CODES.md)**\n\n---\n\n## 📚 **Related Top-Level Documentation**\n\n- **[CONTRIBUTION_GUIDE.md](../CONTRIBUTION_GUIDE.md)** - How to contribute to ProxmoxVE\n- **[UPDATED_APP-ct.md](../UPDATED_APP-ct.md)** - Container script guide\n- **[UPDATED_APP-install.md](../UPDATED_APP-install.md)** - Installation script guide\n- **[DEFAULTS_SYSTEM_GUIDE.md](../DEFAULTS_SYSTEM_GUIDE.md)** - Configuration system\n- **[TECHNICAL_REFERENCE.md](../TECHNICAL_REFERENCE.md)** - Architecture reference\n- **[EXIT_CODES.md](../EXIT_CODES.md)** - Complete exit code reference\n- **[DEV_MODE.md](../DEV_MODE.md)** - Development debugging modes\n- **[CHANGELOG_MISC.md](../CHANGELOG_MISC.md)** - Change history\n\n---\n\n## 🔄 **Standardized Documentation Structure**\n\nEach function library follows the same documentation pattern:\n\n```\nfunction-library/\n├── README.md                          # Quick reference & overview\n├── FUNCTION_LIBRARY_FLOWCHART.md      # Visual execution flows\n├── FUNCTION_LIBRARY_FUNCTIONS_REFERENCE.md  # Alphabetical reference\n├── FUNCTION_LIBRARY_INTEGRATION.md    # Integration points\n├── FUNCTION_LIBRARY_USAGE_EXAMPLES.md # Practical examples\n└── [FUNCTION_LIBRARY_ENVIRONMENT_VARIABLES.md]  # (if applicable)\n```\n\n**Advantages**:\n- ✅ Consistent navigation across all libraries\n- ✅ Quick reference sections in each README\n- ✅ Visual flowcharts for understanding\n- ✅ Complete function references\n- ✅ Real-world usage examples\n- ✅ Integration guides for connecting libraries\n\n---\n\n## 📝 **Documentation Standards**\n\nAll documentation follows these standards:\n\n1. **README.md** - Quick overview, key features, quick reference\n2. **FLOWCHART.md** - ASCII flowcharts and visual diagrams\n3. **FUNCTIONS_REFERENCE.md** - Every function with full details\n4. **INTEGRATION.md** - How this library connects to others\n5. **USAGE_EXAMPLES.md** - Copy-paste ready examples\n6. **ENVIRONMENT_VARIABLES.md** - (if applicable) Configuration reference\n\n---\n\n## ✅ **Last Updated**: December 2025\n**Maintainers**: community-scripts team\n**License**: MIT\n**Status**: All 9 libraries fully documented and standardized\n\n---\n\n*This directory contains specialized documentation for specific components of the Proxmox Community Scripts project.*\n"
  },
  {
    "path": "docs/misc/alpine-install.func/ALPINE_INSTALL_FUNC_FLOWCHART.md",
    "content": "# alpine-install.func Flowchart\n\nAlpine container initialization flow (apk-based, OpenRC init system).\n\n## Alpine Container Setup Flow\n\n```\nAlpine Container Started\n    ↓\nsetting_up_container()\n    ↓\nverb_ip6()              [optional - IPv6]\n    ↓\nupdate_os()             [apk update/upgrade]\n    ↓\nnetwork_check()\n    ↓\nApplication Installation\n    ↓\nmotd_ssh()\n    ↓\ncustomize()\n    ↓\ncleanup_lxc()\n    ↓\nComplete ✓\n```\n\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/alpine-install.func/ALPINE_INSTALL_FUNC_FUNCTIONS_REFERENCE.md",
    "content": "# alpine-install.func Functions Reference\n\nAlpine Linux-specific installation functions (apk-based, OpenRC).\n\n## Core Functions\n\n### setting_up_container()\nInitialize Alpine container setup.\n\n### update_os()\nUpdate Alpine packages via `apk update && apk upgrade`.\n\n### verb_ip6()\nEnable IPv6 on Alpine with persistent configuration.\n\n### network_check()\nVerify network connectivity in Alpine.\n\n### motd_ssh()\nConfigure SSH daemon and MOTD on Alpine.\n\n### customize()\nApply Alpine-specific customizations.\n\n### cleanup_lxc()\nFinal cleanup (Alpine-specific).\n\n---\n\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/alpine-install.func/ALPINE_INSTALL_FUNC_INTEGRATION.md",
    "content": "# alpine-install.func Integration Guide\n\nIntegration of alpine-install.func with Alpine container workflows.\n\n## Alpine-Specific Integration\n\nAlpine containers use:\n- `apk` instead of `apt-get`\n- `OpenRC` instead of `systemd`\n- Alpine-specific package names\n\n---\n\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/alpine-install.func/ALPINE_INSTALL_FUNC_USAGE_EXAMPLES.md",
    "content": "# alpine-install.func Usage Examples\n\nBasic examples for Alpine container installation.\n\n### Example: Basic Alpine Setup\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\nsetting_up_container\nupdate_os\n\n# Install Alpine packages\napk add --no-cache curl wget git\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n---\n\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/alpine-install.func/README.md",
    "content": "# alpine-install.func Documentation\n\n## Overview\n\nThe `alpine-install.func` file provides Alpine Linux-specific installation and configuration functions for LXC containers. It complements the standard `install.func` with Alpine-specific operations using the apk package manager instead of apt.\n\n## Purpose and Use Cases\n\n- **Alpine Container Setup**: Initialize Alpine Linux containers with proper configuration\n- **IPv6 Management**: Enable or disable IPv6 in Alpine with persistent configuration\n- **Network Verification**: Verify connectivity in Alpine environments\n- **SSH Configuration**: Setup SSH daemon on Alpine\n- **Auto-Login Setup**: Configure passwordless root login for Alpine containers\n- **Package Management**: Safe apk operations with error handling\n\n## Quick Reference\n\n### Key Function Groups\n- **Initialization**: `setting_up_container()` - Alpine setup message\n- **Network**: `verb_ip6()`, `network_check()` - IPv6 and connectivity\n- **OS Configuration**: `update_os()` - Alpine package updates\n- **SSH/MOTD**: `motd_ssh()` - SSH and login message setup\n- **Container Customization**: `customize()`, `cleanup_lxc()` - Final setup\n\n### Dependencies\n- **External**: `apk`, `curl`, `wget`, `ping`\n- **Internal**: Uses functions from `core.func`, `error_handler.func`\n\n### Integration Points\n- Used by: Alpine-based install scripts (alpine.sh, alpine-ntfy.sh, etc.)\n- Uses: Environment variables from build.func\n- Provides: Alpine-specific installation and management services\n\n## Documentation Files\n\n### 📊 [ALPINE_INSTALL_FUNC_FLOWCHART.md](./ALPINE_INSTALL_FUNC_FLOWCHART.md)\nVisual execution flows showing Alpine container initialization and setup workflows.\n\n### 📚 [ALPINE_INSTALL_FUNC_FUNCTIONS_REFERENCE.md](./ALPINE_INSTALL_FUNC_FUNCTIONS_REFERENCE.md)\nComplete alphabetical reference of all functions with parameters and usage details.\n\n### 💡 [ALPINE_INSTALL_FUNC_USAGE_EXAMPLES.md](./ALPINE_INSTALL_FUNC_USAGE_EXAMPLES.md)\nPractical examples showing how to use Alpine installation functions.\n\n### 🔗 [ALPINE_INSTALL_FUNC_INTEGRATION.md](./ALPINE_INSTALL_FUNC_INTEGRATION.md)\nHow alpine-install.func integrates with standard install workflows.\n\n## Key Features\n\n### Alpine-Specific Functions\n- **apk Package Manager**: Alpine package operations (instead of apt-get)\n- **OpenRC Support**: Alpine uses OpenRC init instead of systemd\n- **Lightweight Setup**: Minimal dependencies appropriate for Alpine\n- **IPv6 Configuration**: Persistent IPv6 settings via `/etc/network/interfaces`\n\n### Network & Connectivity\n- **IPv6 Toggle**: Enable/disable with persistent configuration\n- **Connectivity Check**: Verify internet access in Alpine\n- **DNS Verification**: Resolve domain names correctly\n- **Retry Logic**: Automatic recovery from transient failures\n\n### SSH & Auto-Login\n- **SSH Daemon**: Setup and start sshd on Alpine\n- **Root Keys**: Configure root SSH access\n- **Auto-Login**: Optional automatic login without password\n- **MOTD**: Custom login message on Alpine\n\n## Function Categories\n\n### 🔹 Core Functions\n- `setting_up_container()` - Alpine container setup message\n- `update_os()` - Update Alpine packages via apk\n- `verb_ip6()` - Enable/disable IPv6 persistently\n- `network_check()` - Verify network connectivity\n\n### 🔹 SSH & Configuration Functions\n- `motd_ssh()` - Configure SSH daemon on Alpine\n- `customize()` - Apply Alpine-specific customizations\n- `cleanup_lxc()` - Final cleanup\n\n### 🔹 Service Management (OpenRC)\n- `rc-update` - Enable/disable services for Alpine\n- `rc-service` - Start/stop services on Alpine\n- Service configuration files in `/etc/init.d/`\n\n## Differences from Debian Install\n\n| Feature | Debian (install.func) | Alpine (alpine-install.func) |\n|---------|:---:|:---:|\n| Package Manager | apt-get | apk |\n| Init System | systemd | OpenRC |\n| SSH Service | systemctl | rc-service |\n| Config Files | /etc/systemd/ | /etc/init.d/ |\n| Network Config | /etc/network/ or Netplan | /etc/network/interfaces |\n| IPv6 Setup | netplan files | /etc/network/interfaces |\n| Auto-Login | getty override | `/etc/inittab` or shell config |\n| Size | ~200MB | ~100MB |\n\n## Execution Flow for Alpine\n\n```\nAlpine Container Started\n    ↓\nsource $FUNCTIONS_FILE_PATH\n    ↓\nsetting_up_container()           ← Alpine setup message\n    ↓\nupdate_os()                      ← apk update\n    ↓\nverb_ip6()                       ← IPv6 configuration (optional)\n    ↓\nnetwork_check()                  ← Verify connectivity\n    ↓\n[Application-Specific Installation]\n    ↓\nmotd_ssh()                       ← Configure SSH/MOTD\ncustomize()                      ← Apply customizations\n    ↓\ncleanup_lxc()                    ← Final cleanup\n    ↓\nAlpine Installation Complete\n```\n\n## Common Usage Patterns\n\n### Basic Alpine Setup\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\nsetting_up_container\nupdate_os\n\n# Install Alpine-specific packages\napk add --no-cache curl wget git\n\n# ... application installation ...\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n### With IPv6 Enabled\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\nsetting_up_container\nverb_ip6\nupdate_os\nnetwork_check\n\n# ... application installation ...\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n### Installing Services\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\nsetting_up_container\nupdate_os\n\n# Install via apk\napk add --no-cache nginx\n\n# Enable and start service on Alpine\nrc-update add nginx\nrc-service nginx start\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n## Best Practices\n\n### ✅ DO\n- Use `apk add --no-cache` to reduce image size\n- Enable IPv6 if application needs it (`verb_ip6`)\n- Use `rc-service` for service management on Alpine\n- Check `/etc/network/interfaces` for IPv6 persistence\n- Test network connectivity before critical operations\n- Use `$STD` for output suppression in production\n\n### ❌ DON'T\n- Use `apt-get` commands (Alpine doesn't have apt)\n- Use `systemctl` (Alpine uses OpenRC, not systemd)\n- Use `service` command (it may not exist on Alpine)\n- Assume systemd exists on Alpine\n- Forget to add `--no-cache` flag to `apk add`\n- Hardcode paths from Debian (different on Alpine)\n\n## Alpine-Specific Considerations\n\n### Package Names\nSome packages have different names on Alpine:\n```bash\n# Debian        → Alpine\n# curl          → curl (same)\n# wget          → wget (same)\n# python3       → python3 (same)\n# libpq5        → postgresql-client\n# libmariadb3   → mariadb-client\n```\n\n### Service Management\n```bash\n# Debian (systemd)      → Alpine (OpenRC)\nsystemctl start nginx   → rc-service nginx start\nsystemctl enable nginx  → rc-update add nginx\nsystemctl status nginx  → rc-service nginx status\n```\n\n### Network Configuration\n```bash\n# Debian (Netplan)                → Alpine (/etc/network/interfaces)\n/etc/netplan/01-*.yaml            → /etc/network/interfaces\nnetplan apply                      → Configure directly in interfaces\n\n# Enable IPv6 persistently on Alpine:\n# Add to /etc/network/interfaces:\n# iface eth0 inet6 static\n#     address <IPv6_ADDRESS>\n```\n\n## Troubleshooting\n\n### \"apk command not found\"\n- This is Alpine Linux, not Debian\n- Install packages with `apk add` instead of `apt-get install`\n- Example: `apk add --no-cache curl wget`\n\n### \"IPv6 not persisting after reboot\"\n- IPv6 must be configured in `/etc/network/interfaces`\n- The `verb_ip6()` function handles this automatically\n- Verify: `cat /etc/network/interfaces`\n\n### \"Service won't start on Alpine\"\n- Alpine uses OpenRC, not systemd\n- Use `rc-service nginx start` instead of `systemctl start nginx`\n- Enable service: `rc-update add nginx`\n- Check logs: `/var/log/` or `rc-service nginx status`\n\n### \"Container too large\"\n- Alpine should be much smaller than Debian\n- Verify using `apk add --no-cache` (removes package cache)\n- Example: `apk add --no-cache nginx` (not `apk add nginx`)\n\n## Related Documentation\n\n- **[alpine-tools.func/](../alpine-tools.func/)** - Alpine tool installation\n- **[install.func/](../install.func/)** - Standard installation functions\n- **[core.func/](../core.func/)** - Utility functions\n- **[error_handler.func/](../error_handler.func/)** - Error handling\n- **[UPDATED_APP-install.md](../../UPDATED_APP-install.md)** - Application script guide\n\n## Recent Updates\n\n### Version 2.0 (Dec 2025)\n- ✅ Enhanced IPv6 persistence configuration\n- ✅ Improved OpenRC service management\n- ✅ Better apk error handling\n- ✅ Added Alpine-specific best practices documentation\n- ✅ Streamlined SSH setup for Alpine\n\n---\n\n**Last Updated**: December 2025\n**Maintainers**: community-scripts team\n**License**: MIT\n"
  },
  {
    "path": "docs/misc/alpine-tools.func/ALPINE_TOOLS_FUNC_FLOWCHART.md",
    "content": "# alpine-tools.func Flowchart\n\nAlpine tool installation and package management flow.\n\n## Tool Installation on Alpine\n\n```\napk_update()\n    ↓\nadd_community_repo()    [optional]\n    ↓\napk_add PACKAGES\n    ↓\nTool Installation\n    ↓\nrc-service start\n    ↓\nrc-update add           [enable at boot]\n    ↓\nComplete ✓\n```\n\n---\n\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/alpine-tools.func/ALPINE_TOOLS_FUNC_FUNCTIONS_REFERENCE.md",
    "content": "# alpine-tools.func Functions Reference\n\nAlpine-specific tool installation functions.\n\n## Core Functions\n\n### apk_update()\nUpdate Alpine package lists.\n\n### apk_add(PACKAGES)\nInstall Alpine packages.\n\n### apk_del(PACKAGES)\nRemove Alpine packages.\n\n### add_community_repo()\nEnable Alpine community repository.\n\n### add_testing_repo()\nEnable Alpine testing repository.\n\n### Alpine Tool Functions\n- `setup_nodejs()` - Alpine Node.js\n- `setup_php()` - Alpine PHP\n- `setup_mariadb()` - Alpine MariaDB\n- `setup_postgresql()` - Alpine PostgreSQL\n- (+ more Alpine-specific setups)\n\n---\n\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/alpine-tools.func/ALPINE_TOOLS_FUNC_INTEGRATION.md",
    "content": "# alpine-tools.func Integration Guide\n\nAlpine tool installation integration with container workflows.\n\n---\n\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/alpine-tools.func/ALPINE_TOOLS_FUNC_USAGE_EXAMPLES.md",
    "content": "# alpine-tools.func Usage Examples\n\nExamples for Alpine tool installation.\n\n### Example: Alpine Setup with Tools\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\napk_update\nsetup_nodejs \"20\"\nsetup_php \"8.3\"\nsetup_mariadb \"11\"\n```\n\n---\n\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/alpine-tools.func/README.md",
    "content": "# alpine-tools.func Documentation\n\n## Overview\n\nThe `alpine-tools.func` file provides Alpine Linux-specific tool installation functions for package and service management within Alpine LXC containers. It complements `tools.func` with Alpine-specific implementations using the apk package manager.\n\n## Purpose and Use Cases\n\n- **Alpine Tool Installation**: Install services and tools using apk on Alpine\n- **Package Management**: Safe apk operations with error handling\n- **Service Setup**: Install and configure common services on Alpine\n- **Dependency Management**: Handle Alpine-specific package dependencies\n- **Repository Management**: Setup and manage Alpine package repositories\n\n## Quick Reference\n\n### Key Function Groups\n- **Package Operations**: Alpine-specific apk commands with error handling\n- **Service Installation**: Install databases, web servers, tools on Alpine\n- **Repository Setup**: Configure Alpine community and testing repositories\n- **Tool Setup**: Install development tools and utilities\n\n### Dependencies\n- **External**: `apk`, `curl`, `wget`\n- **Internal**: Uses functions from `core.func`, `error_handler.func`\n\n### Integration Points\n- Used by: Alpine-based application install scripts\n- Uses: Environment variables from build.func\n- Provides: Alpine package and tool installation services\n\n## Documentation Files\n\n### 📊 [ALPINE_TOOLS_FUNC_FLOWCHART.md](./ALPINE_TOOLS_FUNC_FLOWCHART.md)\nVisual execution flows for package operations and tool installation on Alpine.\n\n### 📚 [ALPINE_TOOLS_FUNC_FUNCTIONS_REFERENCE.md](./ALPINE_TOOLS_FUNC_FUNCTIONS_REFERENCE.md)\nComplete alphabetical reference of all Alpine tool functions.\n\n### 💡 [ALPINE_TOOLS_FUNC_USAGE_EXAMPLES.md](./ALPINE_TOOLS_FUNC_USAGE_EXAMPLES.md)\nPractical examples for common Alpine installation patterns.\n\n### 🔗 [ALPINE_TOOLS_FUNC_INTEGRATION.md](./ALPINE_TOOLS_FUNC_INTEGRATION.md)\nHow alpine-tools.func integrates with Alpine installation workflows.\n\n## Key Features\n\n### Alpine Package Management\n- **apk Add**: Safe package installation with error handling\n- **apk Update**: Update package lists with retry logic\n- **apk Del**: Remove packages and dependencies\n- **Repository Configuration**: Add community and testing repos\n\n### Alpine Tool Coverage\n- **Web Servers**: nginx, lighttpd\n- **Databases**: mariadb, postgresql, sqlite\n- **Development**: gcc, make, git, node.js (via apk)\n- **Services**: sshd, docker, podman\n- **Utilities**: curl, wget, htop, vim\n\n### Error Handling\n- **Retry Logic**: Automatic recovery from transient failures\n- **Dependency Resolution**: Handle missing dependencies\n- **Lock Management**: Wait for apk locks to release\n- **Error Reporting**: Clear error messages\n\n## Function Categories\n\n### 🔹 Package Management\n- `apk_update()` - Update Alpine packages with retry\n- `apk_add()` - Install packages safely\n- `apk_del()` - Remove packages completely\n\n### 🔹 Repository Functions\n- `add_community_repo()` - Enable community repositories\n- `add_testing_repo()` - Enable testing repositories\n- `setup_apk_repo()` - Configure custom apk repositories\n\n### 🔹 Service Installation Functions\n- `setup_nginx()` - Install and configure nginx\n- `setup_mariadb()` - Install MariaDB on Alpine\n- `setup_postgresql()` - Install PostgreSQL\n- `setup_docker()` - Install Docker on Alpine\n- `setup_nodejs()` - Install Node.js from Alpine repos\n\n### 🔹 Development Tools\n- `setup_build_tools()` - Install gcc, make, build-essential\n- `setup_git()` - Install git version control\n- `setup_python()` - Install Python 3 and pip\n\n## Alpine vs Debian Package Differences\n\n| Package | Debian | Alpine |\n|---------|:---:|:---:|\n| nginx | `apt-get install nginx` | `apk add nginx` |\n| mariadb | `apt-get install mariadb-server` | `apk add mariadb` |\n| PostgreSQL | `apt-get install postgresql` | `apk add postgresql` |\n| Node.js | `apt-get install nodejs npm` | `apk add nodejs npm` |\n| Docker | Special setup | `apk add docker` |\n| Python | `apt-get install python3 python3-pip` | `apk add python3 py3-pip` |\n\n## Common Usage Patterns\n\n### Basic Alpine Tool Installation\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\n# Update package lists\napk_update\n\n# Install nginx\napk_add nginx\n\n# Start service\nrc-service nginx start\nrc-update add nginx\n```\n\n### With Community Repository\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\n# Enable community repo for more packages\nadd_community_repo\n\n# Update and install\napk_update\napk_add postgresql postgresql-client\n\n# Start service\nrc-service postgresql start\n```\n\n### Development Environment\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\n# Install build tools\nsetup_build_tools\nsetup_git\nsetup_nodejs \"20\"\n\n# Install application\ngit clone https://example.com/app\ncd app\nnpm install\n```\n\n## Best Practices\n\n### ✅ DO\n- Always use `apk add --no-cache` to keep images small\n- Call `apk_update()` before installing packages\n- Use community repository for more packages (`add_community_repo`)\n- Handle apk locks gracefully with retry logic\n- Use `$STD` variable for output control\n- Check if tool already installed before reinstalling\n\n### ❌ DON'T\n- Use `apt-get` commands (Alpine doesn't have apt)\n- Install packages without `--no-cache` flag\n- Hardcode Alpine-specific paths\n- Mix Alpine and Debian commands\n- Forget to enable services with `rc-update`\n- Use `systemctl` (Alpine has OpenRC, not systemd)\n\n## Alpine Repository Configuration\n\n### Default Repositories\nAlpine comes with main repository enabled by default. Additional repos:\n\n```bash\n# Community repository (apk add php, go, rust, etc.)\nadd_community_repo\n\n# Testing repository (bleeding edge packages)\nadd_testing_repo\n```\n\n### Repository Locations\n```bash\n/etc/apk/repositories      # Main repo list\n/etc/apk/keys/             # GPG keys for repos\n/var/cache/apk/            # Package cache\n```\n\n## Package Size Optimization\n\nAlpine is designed for small container images:\n\n```bash\n# DON'T: Leaves package cache (increases image size)\napk add nginx\n\n# DO: Remove cache to reduce size\napk add --no-cache nginx\n\n# Expected sizes:\n# Alpine base: ~5MB\n# Alpine + nginx: ~10-15MB\n# Debian base: ~75MB\n# Debian + nginx: ~90-95MB\n```\n\n## Service Management on Alpine\n\n### Using OpenRC\n```bash\n# Start service immediately\nrc-service nginx start\n\n# Stop service\nrc-service nginx stop\n\n# Restart service\nrc-service nginx restart\n\n# Enable at boot\nrc-update add nginx\n\n# Disable at boot\nrc-update del nginx\n\n# List enabled services\nrc-update show\n```\n\n## Troubleshooting\n\n### \"apk: lock is held by PID\"\n```bash\n# Alpine apk database is locked (another process using apk)\n# Wait a moment\nsleep 5\napk_update\n\n# Or manually:\nrm /var/lib/apk/lock 2>/dev/null || true\napk update\n```\n\n### \"Package not found\"\n```bash\n# May be in community or testing repository\nadd_community_repo\napk_update\napk_add package-name\n```\n\n### \"Repository not responding\"\n```bash\n# Alpine repo may be slow or unreachable\n# Try updating again with retry logic\napk_update  # Built-in retry logic\n\n# Or manually retry\nsleep 10\napk update\n```\n\n### \"Service fails to start\"\n```bash\n# Check service status on Alpine\nrc-service nginx status\n\n# View logs\ntail /var/log/nginx/error.log\n\n# Verify configuration\nnginx -t\n```\n\n## Related Documentation\n\n- **[alpine-install.func/](../alpine-install.func/)** - Alpine installation functions\n- **[tools.func/](../tools.func/)** - Debian/standard tool installation\n- **[core.func/](../core.func/)** - Utility functions\n- **[error_handler.func/](../error_handler.func/)** - Error handling\n- **[UPDATED_APP-install.md](../../UPDATED_APP-install.md)** - Application script guide\n\n## Recent Updates\n\n### Version 2.0 (Dec 2025)\n- ✅ Enhanced apk error handling and retry logic\n- ✅ Improved repository management\n- ✅ Better service management with OpenRC\n- ✅ Added Alpine-specific optimization guidance\n- ✅ Enhanced package cache management\n\n---\n\n**Last Updated**: December 2025\n**Maintainers**: community-scripts team\n**License**: MIT\n"
  },
  {
    "path": "docs/misc/api.func/API_FLOWCHART.md",
    "content": "# api.func Execution Flowchart\n\n## Main API Communication Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        API Communication Initialization                        │\n│  Entry point when api.func functions are called by installation scripts        │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Prerequisites Check                                      │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Prerequisites Validation                                │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Check curl    │    │   Check         │    │   Check             │ │ │\n│  │  │   Availability  │    │   Diagnostics   │    │   Random UUID       │ │ │\n│  │  │                 │    │   Setting       │    │                     │ │\n│  │  │ • command -v    │    │ • DIAGNOSTICS   │    │ • RANDOM_UUID       │ │\n│  │  │   curl          │    │   = \"yes\"       │    │   not empty         │ │\n│  │  │ • Return if     │    │ • Return if     │    │ • Return if         │ │\n│  │  │   not found     │    │   disabled      │    │   not set          │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Data Collection                                          │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    System Information Gathering                            │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Get PVE       │    │   Collect       │    │   Prepare JSON      │ │ │\n│  │  │   Version        │    │   Environment   │    │   Payload           │ │\n│  │  │                 │    │   Variables     │    │                     │ │\n│  │  │ • pveversion    │    │ • CT_TYPE       │    │ • Create JSON       │ │\n│  │  │   command       │    │ • DISK_SIZE     │    │   structure         │ │\n│  │  │ • Parse version │    │ • CORE_COUNT    │    │ • Include all       │ │\n│  │  │ • Extract       │    │ • RAM_SIZE      │    │   variables         │ │\n│  │  │   major.minor  │    │ • var_os        │    │ • Format for API    │ │\n│  │  │                 │    │ • var_version   │    │                     │ │\n│  │  │                 │    │ • NSAPP        │    │                     │ │\n│  │  │                 │    │ • METHOD       │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        API Request Execution                                   │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    HTTP Request Processing                                 │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Prepare       │    │   Execute       │    │   Handle            │ │ │\n│  │  │   Request        │    │   HTTP Request  │    │   Response          │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • Set API URL   │    │ • curl -s -w    │    │ • Capture HTTP      │ │\n│  │  │ • Set headers   │    │   \"%{http_code}\" │    │   status code      │ │\n│  │  │ • Set payload   │    │ • POST request  │    │ • Store response    │ │\n│  │  │ • Content-Type  │    │ • JSON data     │    │ • Handle errors     │ │\n│  │  │   application/  │    │ • Follow        │    │   gracefully        │ │\n│  │  │   json          │    │   redirects     │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## LXC API Reporting Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        POST_TO_API() Flow                                     │\n│  Send LXC container installation data to API                                  │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        LXC Data Preparation                                   │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    LXC-Specific Data Collection                           │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Set LXC       │    │   Include LXC   │    │   Set Status        │ │ │\n│  │  │   Type           │    │   Variables     │    │   Information        │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • ct_type: 1    │    │ • DISK_SIZE     │    │ • status:           │ │\n│  │  │ • type: \"lxc\"   │    │ • CORE_COUNT    │    │   \"installing\"      │ │\n│  │  │ • Include all   │    │ • RAM_SIZE      │    │ • Include all       │ │\n│  │  │   LXC data      │    │ • var_os        │    │   tracking data     │ │\n│  │  │                 │    │ • var_version   │    │                     │ │\n│  │  │                 │    │ • DISABLEIP6    │    │                     │ │\n│  │  │                 │    │ • NSAPP         │    │                     │ │\n│  │  │                 │    │ • METHOD        │    │                     │ │\n│  │  │                 │    │ • pve_version   │    │                     │ │\n│  │  │                 │    │ • random_id     │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        JSON Payload Creation                                  │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    JSON Structure Generation                               │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Create JSON   │    │   Validate       │    │   Format for        │ │ │\n│  │  │   Structure      │    │   Data           │    │   API Request       │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • Use heredoc   │    │ • Check all     │    │ • Ensure proper     │ │\n│  │  │   syntax        │    │   variables      │    │   JSON format       │ │\n│  │  │ • Include all   │    │   are set       │    │ • Escape special    │ │\n│  │  │   required      │    │ • Validate      │    │   characters        │ │\n│  │  │   fields        │    │   data types    │    │ • Set content       │ │\n│  │  │ • Format        │    │ • Handle        │    │   type              │ │\n│  │  │   properly      │    │   missing       │    │                     │ │\n│  │  │                 │    │   values        │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## VM API Reporting Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        POST_TO_API_VM() Flow                                  │\n│  Send VM installation data to API                                            │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        VM Data Preparation                                    │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    VM-Specific Data Collection                             │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Check         │    │   Set VM        │    │   Process Disk      │ │ │\n│  │  │   Diagnostics   │    │   Type          │    │   Size              │ │\n│  │  │   File          │    │                 │    │                     │ │\n│  │  │                 │    │ • ct_type: 2   │    │ • Remove 'G'        │ │\n│  │  │ • Check file    │    │ • type: \"vm\"    │    │   suffix            │ │\n│  │  │   existence     │    │ • Include all   │    │ • Convert to        │ │\n│  │  │ • Read          │    │   VM data       │    │   numeric value      │ │\n│  │  │   DIAGNOSTICS   │    │                 │    │ • Store in          │ │\n│  │  │   setting       │    │                 │    │   DISK_SIZE_API    │ │\n│  │  │ • Parse value   │    │                 │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        VM JSON Payload Creation                              │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    VM-Specific JSON Structure                              │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Include VM    │    │   Set VM        │    │   Format VM         │ │ │\n│  │  │   Variables      │    │   Status         │    │   Data for API      │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • DISK_SIZE_API │    │ • status:       │    │ • Ensure proper     │ │\n│  │  │ • CORE_COUNT    │    │   \"installing\"  │    │   JSON format       │ │\n│  │  │ • RAM_SIZE      │    │ • Include all   │    │ • Handle VM-        │ │\n│  │  │ • var_os        │    │   tracking      │    │   specific data     │ │\n│  │  │ • var_version   │    │   information   │    │ • Set appropriate   │ │\n│  │  │ • NSAPP         │    │                 │    │   content type      │ │\n│  │  │ • METHOD        │    │                 │    │                     │ │\n│  │  │ • pve_version   │    │                 │    │                     │ │\n│  │  │ • random_id     │    │                 │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Status Update Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        POST_UPDATE_TO_API() Flow                              │\n│  Send installation completion status to API                                  │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Update Prevention Check                                │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Duplicate Update Prevention                             │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Check         │    │   Set Flag      │    │   Return Early      │ │ │\n│  │  │   POST_UPDATE_  │    │   if First      │    │   if Already        │ │\n│  │  │   DONE          │    │   Update        │    │   Updated           │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • Check if      │    │ • Set           │    │ • Return 0          │ │\n│  │  │   already       │    │   POST_UPDATE_  │    │ • Skip API call    │ │\n│  │  │   updated       │    │   DONE=true     │    │ • Prevent          │ │\n│  │  │ • Prevent       │    │ • Continue      │    │   duplicate        │ │\n│  │  │   duplicate     │    │   with update   │    │   requests         │ │\n│  │  │   requests      │    │                 │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Status and Error Processing                            │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Status Determination                                     │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Determine     │    │   Get Error     │    │   Prepare Status    │ │ │\n│  │  │   Status         │    │   Description   │    │   Data              │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • status:       │    │ • Call          │    │ • Include status    │ │\n│  │  │   \"success\" or  │    │   get_error_    │    │ • Include error     │ │\n│  │  │   \"failed\"      │    │   description() │    │   description       │ │\n│  │  │ • Set exit      │    │ • Get human-    │    │ • Include random    │ │\n│  │  │   code based    │    │   readable      │    │   ID for tracking   │ │\n│  │  │   on status     │    │   error message │    │                     │ │\n│  │  │ • Default to    │    │ • Handle        │    │                     │ │\n│  │  │   error if      │    │   unknown       │    │                     │ │\n│  │  │   not set      │    │   errors         │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Status Update API Request                              │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Status Update Payload Creation                          │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Create        │    │   Send Status    │    │   Mark Update       │ │ │\n│  │  │   Status JSON    │    │   Update         │    │   Complete          │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • Include       │    │ • POST to        │    │ • Set              │ │\n│  │  │   status        │    │   updatestatus   │    │   POST_UPDATE_     │ │\n│  │  │ • Include       │    │   endpoint       │    │   DONE=true        │ │\n│  │  │   error         │    │ • Include JSON   │    │ • Prevent further  │ │\n│  │  │   description   │    │   payload        │    │   updates          │ │\n│  │  │ • Include       │    │ • Handle         │    │ • Complete         │ │\n│  │  │   random_id     │    │   response       │    │   process          │ │\n│  │  │                 │    │   gracefully     │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Error Description Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        GET_ERROR_DESCRIPTION() Flow                           │\n│  Convert numeric exit codes to human-readable explanations                    │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Error Code Classification                              │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Error Code Categories                                  │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   General       │    │   Network        │    │   LXC-Specific      │ │ │\n│  │  │   System        │    │   Errors         │    │   Errors            │ │\n│  │  │   Errors        │    │                 │    │                     │ │\n│  │  │                 │    │ • 18: Connection│    │ • 100-101: LXC      │ │\n│  │  │ • 0-9: Basic    │    │   failed         │    │   install errors    │ │\n│  │  │   errors        │    │ • 22: Invalid    │    │ • 200-209: LXC      │ │\n│  │  │ • 126-128:      │    │   argument       │    │   creation errors   │ │\n│  │  │   Command       │    │ • 28: No space   │    │                     │ │\n│  │  │   errors        │    │ • 35: Timeout    │    │                     │ │\n│  │  │ • 129-143:      │    │ • 56: TLS error  │    │                     │ │\n│  │  │   Signal        │    │ • 60: SSL cert   │    │                     │ │\n│  │  │   errors        │    │   error          │    │                     │ │\n│  │  │ • 152: Resource │    │                 │    │                     │ │\n│  │  │   limit         │    │                 │    │                     │ │\n│  │  │ • 255: Unknown  │    │                 │    │                     │ │\n│  │  │   critical      │    │                 │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Error Message Return                                   │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Error Message Formatting                               │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Match Error   │    │   Return        │    │   Default Case       │ │ │\n│  │  │   Code          │    │   Description   │    │                     │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • Use case      │    │ • Return        │    │ • Return \"Unknown   │ │\n│  │  │   statement     │    │   human-        │    │   error code        │ │\n│  │  │ • Match         │    │   readable      │    │   (exit_code)\"      │ │\n│  │  │   specific      │    │   message       │    │ • Handle            │ │\n│  │  │   codes         │    │ • Include       │    │   unrecognized      │ │\n│  │  │ • Handle        │    │   context       │    │   codes             │ │\n│  │  │   ranges        │    │   information   │    │ • Provide fallback  │ │\n│  │  │                 │    │                 │    │   message           │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Integration Points\n\n### With Installation Scripts\n- **build.func**: Sends LXC installation data\n- **vm-core.func**: Sends VM installation data\n- **install.func**: Reports installation status\n- **alpine-install.func**: Reports Alpine installation data\n\n### With Error Handling\n- **error_handler.func**: Provides error explanations\n- **core.func**: Uses error descriptions in silent execution\n- **Diagnostic reporting**: Tracks error patterns\n\n### External Dependencies\n- **curl**: HTTP client for API communication\n- **Community Scripts API**: External API endpoint\n- **Network connectivity**: Required for API communication\n"
  },
  {
    "path": "docs/misc/api.func/API_FUNCTIONS_REFERENCE.md",
    "content": "# api.func Functions Reference\n\n## Overview\n\nThis document provides a comprehensive alphabetical reference of all functions in `api.func`, including parameters, dependencies, usage examples, and error handling.\n\n## Function Categories\n\n### Error Description Functions\n\n#### `get_error_description()`\n**Purpose**: Convert numeric exit codes to human-readable explanations\n**Parameters**:\n- `$1` - Exit code to explain\n**Returns**: Human-readable error explanation string\n**Side Effects**: None\n**Dependencies**: None\n**Environment Variables Used**: None\n\n**Supported Exit Codes**:\n- **General System**: 0-9, 18, 22, 28, 35, 56, 60, 125-128, 129-143, 152, 255\n- **LXC-Specific**: 100-101, 200-209\n- **Docker**: 125\n\n**Usage Example**:\n```bash\nerror_msg=$(get_error_description 127)\necho \"Error 127: $error_msg\"\n# Output: Error 127: Command not found: Incorrect path or missing dependency.\n```\n\n**Error Code Examples**:\n```bash\nget_error_description 0     # \" \" (space)\nget_error_description 1     # \"General error: An unspecified error occurred.\"\nget_error_description 127   # \"Command not found: Incorrect path or missing dependency.\"\nget_error_description 200   # \"LXC creation failed.\"\nget_error_description 255   # \"Unknown critical error, often due to missing permissions or broken scripts.\"\n```\n\n### API Communication Functions\n\n#### `post_to_api()`\n**Purpose**: Send LXC container installation data to community-scripts.org API\n**Parameters**: None (uses environment variables)\n**Returns**: None\n**Side Effects**:\n- Sends HTTP POST request to API\n- Stores response in RESPONSE variable\n- Requires curl command and network connectivity\n**Dependencies**: `curl` command\n**Environment Variables Used**: `DIAGNOSTICS`, `RANDOM_UUID`, `CT_TYPE`, `DISK_SIZE`, `CORE_COUNT`, `RAM_SIZE`, `var_os`, `var_version`, `DISABLEIP6`, `NSAPP`, `METHOD`\n\n**Prerequisites**:\n- `curl` command must be available\n- `DIAGNOSTICS` must be set to \"yes\"\n- `RANDOM_UUID` must be set and not empty\n\n**API Endpoint**: `https://api.community-scripts.org/dev/upload`\n\n**JSON Payload Structure**:\n```json\n{\n    \"ct_type\": 1,\n    \"type\": \"lxc\",\n    \"disk_size\": 8,\n    \"core_count\": 2,\n    \"ram_size\": 2048,\n    \"os_type\": \"debian\",\n    \"os_version\": \"12\",\n    \"disableip6\": \"true\",\n    \"nsapp\": \"plex\",\n    \"method\": \"install\",\n    \"pve_version\": \"8.0\",\n    \"status\": \"installing\",\n    \"random_id\": \"uuid-string\"\n}\n```\n\n**Usage Example**:\n```bash\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"$(uuidgen)\"\nexport CT_TYPE=1\nexport DISK_SIZE=8\nexport CORE_COUNT=2\nexport RAM_SIZE=2048\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport NSAPP=\"plex\"\nexport METHOD=\"install\"\n\npost_to_api\n```\n\n#### `post_to_api_vm()`\n**Purpose**: Send VM installation data to community-scripts.org API\n**Parameters**: None (uses environment variables)\n**Returns**: None\n**Side Effects**:\n- Sends HTTP POST request to API\n- Stores response in RESPONSE variable\n- Requires curl command and network connectivity\n**Dependencies**: `curl` command, diagnostics file\n**Environment Variables Used**: `DIAGNOSTICS`, `RANDOM_UUID`, `DISK_SIZE`, `CORE_COUNT`, `RAM_SIZE`, `var_os`, `var_version`, `NSAPP`, `METHOD`\n\n**Prerequisites**:\n- `/usr/local/community-scripts/diagnostics` file must exist\n- `DIAGNOSTICS` must be set to \"yes\" in diagnostics file\n- `curl` command must be available\n- `RANDOM_UUID` must be set and not empty\n\n**API Endpoint**: `https://api.community-scripts.org/dev/upload`\n\n**JSON Payload Structure**:\n```json\n{\n    \"ct_type\": 2,\n    \"type\": \"vm\",\n    \"disk_size\": 8,\n    \"core_count\": 2,\n    \"ram_size\": 2048,\n    \"os_type\": \"debian\",\n    \"os_version\": \"12\",\n    \"disableip6\": \"\",\n    \"nsapp\": \"plex\",\n    \"method\": \"install\",\n    \"pve_version\": \"8.0\",\n    \"status\": \"installing\",\n    \"random_id\": \"uuid-string\"\n}\n```\n\n**Usage Example**:\n```bash\n# Create diagnostics file\necho \"DIAGNOSTICS=yes\" > /usr/local/community-scripts/diagnostics\n\nexport RANDOM_UUID=\"$(uuidgen)\"\nexport DISK_SIZE=\"8G\"\nexport CORE_COUNT=2\nexport RAM_SIZE=2048\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport NSAPP=\"plex\"\nexport METHOD=\"install\"\n\npost_to_api_vm\n```\n\n#### `post_update_to_api()`\n**Purpose**: Send installation completion status to community-scripts.org API\n**Parameters**:\n- `$1` - Status (\"success\" or \"failed\", default: \"failed\")\n- `$2` - Exit code (default: 1)\n**Returns**: None\n**Side Effects**:\n- Sends HTTP POST request to API\n- Sets POST_UPDATE_DONE=true to prevent duplicates\n- Stores response in RESPONSE variable\n**Dependencies**: `curl` command, `get_error_description()`\n**Environment Variables Used**: `DIAGNOSTICS`, `RANDOM_UUID`\n\n**Prerequisites**:\n- `curl` command must be available\n- `DIAGNOSTICS` must be set to \"yes\"\n- `RANDOM_UUID` must be set and not empty\n- POST_UPDATE_DONE must be false (prevents duplicates)\n\n**API Endpoint**: `https://api.community-scripts.org/dev/upload/updatestatus`\n\n**JSON Payload Structure**:\n```json\n{\n    \"status\": \"success\",\n    \"error\": \"Error description from get_error_description()\",\n    \"random_id\": \"uuid-string\"\n}\n```\n\n**Usage Example**:\n```bash\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"$(uuidgen)\"\n\n# Report successful installation\npost_update_to_api \"success\" 0\n\n# Report failed installation\npost_update_to_api \"failed\" 127\n```\n\n## Function Call Hierarchy\n\n### API Communication Flow\n```\npost_to_api()\n├── Check curl availability\n├── Check DIAGNOSTICS setting\n├── Check RANDOM_UUID\n├── Get PVE version\n├── Create JSON payload\n└── Send HTTP POST request\n\npost_to_api_vm()\n├── Check diagnostics file\n├── Check curl availability\n├── Check DIAGNOSTICS setting\n├── Check RANDOM_UUID\n├── Process disk size\n├── Get PVE version\n├── Create JSON payload\n└── Send HTTP POST request\n\npost_update_to_api()\n├── Check POST_UPDATE_DONE flag\n├── Check curl availability\n├── Check DIAGNOSTICS setting\n├── Check RANDOM_UUID\n├── Determine status and exit code\n├── Get error description\n├── Create JSON payload\n├── Send HTTP POST request\n└── Set POST_UPDATE_DONE=true\n```\n\n### Error Description Flow\n```\nget_error_description()\n├── Match exit code\n├── Return appropriate description\n└── Handle unknown codes\n```\n\n## Error Code Reference\n\n### General System Errors\n| Code | Description |\n|------|-------------|\n| 0 | (space) |\n| 1 | General error: An unspecified error occurred. |\n| 2 | Incorrect shell usage or invalid command arguments. |\n| 3 | Unexecuted function or invalid shell condition. |\n| 4 | Error opening a file or invalid path. |\n| 5 | I/O error: An input/output failure occurred. |\n| 6 | No such device or address. |\n| 7 | Insufficient memory or resource exhaustion. |\n| 8 | Non-executable file or invalid file format. |\n| 9 | Failed child process execution. |\n| 18 | Connection to a remote server failed. |\n| 22 | Invalid argument or faulty network connection. |\n| 28 | No space left on device. |\n| 35 | Timeout while establishing a connection. |\n| 56 | Faulty TLS connection. |\n| 60 | SSL certificate error. |\n\n### Command Execution Errors\n| Code | Description |\n|------|-------------|\n| 125 | Docker error: Container could not start. |\n| 126 | Command not executable: Incorrect permissions or missing dependencies. |\n| 127 | Command not found: Incorrect path or missing dependency. |\n| 128 | Invalid exit signal, e.g., incorrect Git command. |\n\n### Signal Errors\n| Code | Description |\n|------|-------------|\n| 129 | Signal 1 (SIGHUP): Process terminated due to hangup. |\n| 130 | Signal 2 (SIGINT): Manual termination via Ctrl+C. |\n| 132 | Signal 4 (SIGILL): Illegal machine instruction. |\n| 133 | Signal 5 (SIGTRAP): Debugging error or invalid breakpoint signal. |\n| 134 | Signal 6 (SIGABRT): Program aborted itself. |\n| 135 | Signal 7 (SIGBUS): Memory error, invalid memory address. |\n| 137 | Signal 9 (SIGKILL): Process forcibly terminated (OOM-killer or 'kill -9'). |\n| 139 | Signal 11 (SIGSEGV): Segmentation fault, possibly due to invalid pointer access. |\n| 141 | Signal 13 (SIGPIPE): Pipe closed unexpectedly. |\n| 143 | Signal 15 (SIGTERM): Process terminated normally. |\n| 152 | Signal 24 (SIGXCPU): CPU time limit exceeded. |\n\n### LXC-Specific Errors\n| Code | Description |\n|------|-------------|\n| 100 | LXC install error: Unexpected error in create_lxc.sh. |\n| 101 | LXC install error: No network connection detected. |\n| 200 | LXC creation failed. |\n| 201 | LXC error: Invalid Storage class. |\n| 202 | User aborted menu in create_lxc.sh. |\n| 203 | CTID not set in create_lxc.sh. |\n| 204 | PCT_OSTYPE not set in create_lxc.sh. |\n| 205 | CTID cannot be less than 100 in create_lxc.sh. |\n| 206 | CTID already in use in create_lxc.sh. |\n| 207 | Template not found in create_lxc.sh. |\n| 208 | Error downloading template in create_lxc.sh. |\n| 209 | Container creation failed, but template is intact in create_lxc.sh. |\n\n### Other Errors\n| Code | Description |\n|------|-------------|\n| 255 | Unknown critical error, often due to missing permissions or broken scripts. |\n| * | Unknown error code (exit_code). |\n\n## Environment Variable Dependencies\n\n### Required Variables\n- **`DIAGNOSTICS`**: Enable/disable diagnostic reporting (\"yes\"/\"no\")\n- **`RANDOM_UUID`**: Unique identifier for tracking\n\n### Optional Variables\n- **`CT_TYPE`**: Container type (1 for LXC, 2 for VM)\n- **`DISK_SIZE`**: Disk size in GB (or GB with 'G' suffix for VM)\n- **`CORE_COUNT`**: Number of CPU cores\n- **`RAM_SIZE`**: RAM size in MB\n- **`var_os`**: Operating system type\n- **`var_version`**: OS version\n- **`DISABLEIP6`**: IPv6 disable setting\n- **`NSAPP`**: Namespace application name\n- **`METHOD`**: Installation method\n\n### Internal Variables\n- **`POST_UPDATE_DONE`**: Prevents duplicate status updates\n- **`API_URL`**: Community scripts API endpoint\n- **`JSON_PAYLOAD`**: API request payload\n- **`RESPONSE`**: API response\n- **`DISK_SIZE_API`**: Processed disk size for VM API\n\n## Error Handling Patterns\n\n### API Communication Errors\n- All API functions handle curl failures gracefully\n- Network errors don't block installation process\n- Missing prerequisites cause early return\n- Duplicate updates are prevented\n\n### Error Description Errors\n- Unknown error codes return generic message\n- All error codes are handled with case statement\n- Fallback message includes the actual error code\n\n### Prerequisites Validation\n- Check curl availability before API calls\n- Validate DIAGNOSTICS setting\n- Ensure RANDOM_UUID is set\n- Check for duplicate updates\n\n## Integration Examples\n\n### With build.func\n```bash\n#!/usr/bin/env bash\nsource core.func\nsource api.func\nsource build.func\n\n# Set up API reporting\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"$(uuidgen)\"\n\n# Report installation start\npost_to_api\n\n# Container creation...\n# ... build.func code ...\n\n# Report completion\nif [[ $? -eq 0 ]]; then\n    post_update_to_api \"success\" 0\nelse\n    post_update_to_api \"failed\" $?\nfi\n```\n\n### With vm-core.func\n```bash\n#!/usr/bin/env bash\nsource core.func\nsource api.func\nsource vm-core.func\n\n# Set up API reporting\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"$(uuidgen)\"\n\n# Report VM installation start\npost_to_api_vm\n\n# VM creation...\n# ... vm-core.func code ...\n\n# Report completion\npost_update_to_api \"success\" 0\n```\n\n### With error_handler.func\n```bash\n#!/usr/bin/env bash\nsource core.func\nsource error_handler.func\nsource api.func\n\n# Use error descriptions\nerror_code=127\nerror_msg=$(get_error_description $error_code)\necho \"Error $error_code: $error_msg\"\n\n# Report error to API\npost_update_to_api \"failed\" $error_code\n```\n\n## Best Practices\n\n### API Usage\n1. Always check prerequisites before API calls\n2. Use unique identifiers for tracking\n3. Handle API failures gracefully\n4. Don't block installation on API failures\n\n### Error Reporting\n1. Use appropriate error codes\n2. Provide meaningful error descriptions\n3. Report both success and failure cases\n4. Prevent duplicate status updates\n\n### Diagnostic Reporting\n1. Respect user privacy settings\n2. Only send data when diagnostics enabled\n3. Use anonymous tracking identifiers\n4. Include relevant system information\n\n### Error Handling\n1. Handle unknown error codes gracefully\n2. Provide fallback error messages\n3. Include error code in unknown error messages\n4. Use consistent error message format\n"
  },
  {
    "path": "docs/misc/api.func/API_INTEGRATION.md",
    "content": "# api.func Integration Guide\n\n## Overview\n\nThis document describes how `api.func` integrates with other components in the Proxmox Community Scripts project, including dependencies, data flow, and API surface.\n\n## Dependencies\n\n### External Dependencies\n\n#### Required Commands\n- **`curl`**: HTTP client for API communication\n- **`uuidgen`**: Generate unique identifiers (optional, can use other methods)\n\n#### Optional Commands\n- **None**: No other external command dependencies\n\n### Internal Dependencies\n\n#### Environment Variables from Other Scripts\n- **build.func**: Provides container creation variables\n- **vm-core.func**: Provides VM creation variables\n- **core.func**: Provides system information variables\n- **Installation scripts**: Provide application-specific variables\n\n## Integration Points\n\n### With build.func\n\n#### LXC Container Reporting\n```bash\n# build.func uses api.func for container reporting\nsource core.func\nsource api.func\nsource build.func\n\n# Set up API reporting\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"$(uuidgen)\"\n\n# Container creation with API reporting\ncreate_container() {\n    # Set container parameters\n    export CT_TYPE=1\n    export DISK_SIZE=\"$var_disk\"\n    export CORE_COUNT=\"$var_cpu\"\n    export RAM_SIZE=\"$var_ram\"\n    export var_os=\"$var_os\"\n    export var_version=\"$var_version\"\n    export NSAPP=\"$APP\"\n    export METHOD=\"install\"\n\n    # Report installation start\n    post_to_api\n\n    # Container creation using build.func\n    # ... build.func container creation logic ...\n\n    # Report completion\n    if [[ $? -eq 0 ]]; then\n        post_update_to_api \"success\" 0\n    else\n        post_update_to_api \"failed\" $?\n    fi\n}\n```\n\n#### Error Reporting Integration\n```bash\n# build.func uses api.func for error reporting\nhandle_container_error() {\n    local exit_code=$1\n    local error_msg=$(get_error_description $exit_code)\n\n    echo \"Container creation failed: $error_msg\"\n    post_update_to_api \"failed\" $exit_code\n}\n```\n\n### With vm-core.func\n\n#### VM Installation Reporting\n```bash\n# vm-core.func uses api.func for VM reporting\nsource core.func\nsource api.func\nsource vm-core.func\n\n# Set up VM API reporting\nmkdir -p /usr/local/community-scripts\necho \"DIAGNOSTICS=yes\" > /usr/local/community-scripts/diagnostics\n\nexport RANDOM_UUID=\"$(uuidgen)\"\n\n# VM creation with API reporting\ncreate_vm() {\n    # Set VM parameters\n    export DISK_SIZE=\"${var_disk}G\"\n    export CORE_COUNT=\"$var_cpu\"\n    export RAM_SIZE=\"$var_ram\"\n    export var_os=\"$var_os\"\n    export var_version=\"$var_version\"\n    export NSAPP=\"$APP\"\n    export METHOD=\"install\"\n\n    # Report VM installation start\n    post_to_api_vm\n\n    # VM creation using vm-core.func\n    # ... vm-core.func VM creation logic ...\n\n    # Report completion\n    post_update_to_api \"success\" 0\n}\n```\n\n### With core.func\n\n#### System Information Integration\n```bash\n# core.func provides system information for api.func\nsource core.func\nsource api.func\n\n# Get system information for API reporting\nget_system_info_for_api() {\n    # Get PVE version using core.func utilities\n    local pve_version=$(pveversion | awk -F'[/ ]' '{print $2}')\n\n    # Set API parameters\n    export var_os=\"$var_os\"\n    export var_version=\"$var_version\"\n\n    # Use core.func error handling with api.func reporting\n    if silent apt-get update; then\n        post_update_to_api \"success\" 0\n    else\n        post_update_to_api \"failed\" $?\n    fi\n}\n```\n\n### With error_handler.func\n\n#### Error Description Integration\n```bash\n# error_handler.func uses api.func for error descriptions\nsource core.func\nsource error_handler.func\nsource api.func\n\n# Enhanced error handler with API reporting\nenhanced_error_handler() {\n    local exit_code=${1:-$?}\n    local command=${2:-${BASH_COMMAND:-unknown}}\n\n    # Get error description from api.func\n    local error_msg=$(get_error_description $exit_code)\n\n    # Display error information\n    echo \"Error $exit_code: $error_msg\"\n    echo \"Command: $command\"\n\n    # Report error to API\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n    post_update_to_api \"failed\" $exit_code\n\n    # Use standard error handler\n    error_handler $exit_code $command\n}\n```\n\n### With install.func\n\n#### Installation Process Reporting\n```bash\n# install.func uses api.func for installation reporting\nsource core.func\nsource api.func\nsource install.func\n\n# Installation with API reporting\ninstall_package_with_reporting() {\n    local package=\"$1\"\n\n    # Set up API reporting\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n    export NSAPP=\"$package\"\n    export METHOD=\"install\"\n\n    # Report installation start\n    post_to_api\n\n    # Package installation using install.func\n    if install_package \"$package\"; then\n        echo \"$package installed successfully\"\n        post_update_to_api \"success\" 0\n        return 0\n    else\n        local exit_code=$?\n        local error_msg=$(get_error_description $exit_code)\n        echo \"$package installation failed: $error_msg\"\n        post_update_to_api \"failed\" $exit_code\n        return $exit_code\n    fi\n}\n```\n\n### With alpine-install.func\n\n#### Alpine Installation Reporting\n```bash\n# alpine-install.func uses api.func for Alpine reporting\nsource core.func\nsource api.func\nsource alpine-install.func\n\n# Alpine installation with API reporting\ninstall_alpine_with_reporting() {\n    local app=\"$1\"\n\n    # Set up API reporting\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n    export NSAPP=\"$app\"\n    export METHOD=\"install\"\n    export var_os=\"alpine\"\n\n    # Report Alpine installation start\n    post_to_api\n\n    # Alpine installation using alpine-install.func\n    if install_alpine_app \"$app\"; then\n        echo \"Alpine $app installed successfully\"\n        post_update_to_api \"success\" 0\n        return 0\n    else\n        local exit_code=$?\n        local error_msg=$(get_error_description $exit_code)\n        echo \"Alpine $app installation failed: $error_msg\"\n        post_update_to_api \"failed\" $exit_code\n        return $exit_code\n    fi\n}\n```\n\n### With alpine-tools.func\n\n#### Alpine Tools Reporting\n```bash\n# alpine-tools.func uses api.func for Alpine tools reporting\nsource core.func\nsource api.func\nsource alpine-tools.func\n\n# Alpine tools with API reporting\nrun_alpine_tool_with_reporting() {\n    local tool=\"$1\"\n\n    # Set up API reporting\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n    export NSAPP=\"alpine-tools\"\n    export METHOD=\"tool\"\n\n    # Report tool execution start\n    post_to_api\n\n    # Run Alpine tool using alpine-tools.func\n    if run_alpine_tool \"$tool\"; then\n        echo \"Alpine tool $tool executed successfully\"\n        post_update_to_api \"success\" 0\n        return 0\n    else\n        local exit_code=$?\n        local error_msg=$(get_error_description $exit_code)\n        echo \"Alpine tool $tool failed: $error_msg\"\n        post_update_to_api \"failed\" $exit_code\n        return $exit_code\n    fi\n}\n```\n\n### With passthrough.func\n\n#### Hardware Passthrough Reporting\n```bash\n# passthrough.func uses api.func for hardware reporting\nsource core.func\nsource api.func\nsource passthrough.func\n\n# Hardware passthrough with API reporting\nconfigure_passthrough_with_reporting() {\n    local hardware_type=\"$1\"\n\n    # Set up API reporting\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n    export NSAPP=\"passthrough\"\n    export METHOD=\"hardware\"\n\n    # Report passthrough configuration start\n    post_to_api\n\n    # Configure passthrough using passthrough.func\n    if configure_passthrough \"$hardware_type\"; then\n        echo \"Hardware passthrough configured successfully\"\n        post_update_to_api \"success\" 0\n        return 0\n    else\n        local exit_code=$?\n        local error_msg=$(get_error_description $exit_code)\n        echo \"Hardware passthrough failed: $error_msg\"\n        post_update_to_api \"failed\" $exit_code\n        return $exit_code\n    fi\n}\n```\n\n### With tools.func\n\n#### Maintenance Operations Reporting\n```bash\n# tools.func uses api.func for maintenance reporting\nsource core.func\nsource api.func\nsource tools.func\n\n# Maintenance operations with API reporting\nrun_maintenance_with_reporting() {\n    local operation=\"$1\"\n\n    # Set up API reporting\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n    export NSAPP=\"maintenance\"\n    export METHOD=\"tool\"\n\n    # Report maintenance start\n    post_to_api\n\n    # Run maintenance using tools.func\n    if run_maintenance_operation \"$operation\"; then\n        echo \"Maintenance operation $operation completed successfully\"\n        post_update_to_api \"success\" 0\n        return 0\n    else\n        local exit_code=$?\n        local error_msg=$(get_error_description $exit_code)\n        echo \"Maintenance operation $operation failed: $error_msg\"\n        post_update_to_api \"failed\" $exit_code\n        return $exit_code\n    fi\n}\n```\n\n## Data Flow\n\n### Input Data\n\n#### Environment Variables from Other Scripts\n- **`CT_TYPE`**: Container type (1 for LXC, 2 for VM)\n- **`DISK_SIZE`**: Disk size in GB\n- **`CORE_COUNT`**: Number of CPU cores\n- **`RAM_SIZE`**: RAM size in MB\n- **`var_os`**: Operating system type\n- **`var_version`**: OS version\n- **`DISABLEIP6`**: IPv6 disable setting\n- **`NSAPP`**: Namespace application name\n- **`METHOD`**: Installation method\n- **`DIAGNOSTICS`**: Enable/disable diagnostic reporting\n- **`RANDOM_UUID`**: Unique identifier for tracking\n\n#### Function Parameters\n- **Exit codes**: Passed to `get_error_description()` and `post_update_to_api()`\n- **Status information**: Passed to `post_update_to_api()`\n- **API endpoints**: Hardcoded in functions\n\n#### System Information\n- **PVE version**: Retrieved from `pveversion` command\n- **Disk size processing**: Processed for VM API (removes 'G' suffix)\n- **Error codes**: Retrieved from command exit codes\n\n### Processing Data\n\n#### API Request Preparation\n- **JSON payload creation**: Format data for API consumption\n- **Data validation**: Ensure required fields are present\n- **Error handling**: Handle missing or invalid data\n- **Content type setting**: Set appropriate HTTP headers\n\n#### Error Processing\n- **Error code mapping**: Map numeric codes to descriptions\n- **Error message formatting**: Format error descriptions\n- **Unknown error handling**: Handle unrecognized error codes\n- **Fallback messages**: Provide default error messages\n\n#### API Communication\n- **HTTP request preparation**: Prepare curl commands\n- **Response handling**: Capture HTTP response codes\n- **Error handling**: Handle network and API errors\n- **Duplicate prevention**: Prevent duplicate status updates\n\n### Output Data\n\n#### API Communication\n- **HTTP requests**: Sent to community-scripts.org API\n- **Response codes**: Captured from API responses\n- **Error information**: Reported to API\n- **Status updates**: Sent to API\n\n#### Error Information\n- **Error descriptions**: Human-readable error messages\n- **Error codes**: Mapped to descriptions\n- **Context information**: Error context and details\n- **Fallback messages**: Default error messages\n\n#### System State\n- **POST_UPDATE_DONE**: Prevents duplicate updates\n- **RESPONSE**: Stores API response\n- **JSON_PAYLOAD**: Stores formatted API data\n- **API_URL**: Stores API endpoint\n\n## API Surface\n\n### Public Functions\n\n#### Error Description\n- **`get_error_description()`**: Convert exit codes to explanations\n- **Parameters**: Exit code to explain\n- **Returns**: Human-readable explanation string\n- **Usage**: Called by other functions and scripts\n\n#### API Communication\n- **`post_to_api()`**: Send LXC installation data\n- **`post_to_api_vm()`**: Send VM installation data\n- **`post_update_to_api()`**: Send status updates\n- **Parameters**: Status and exit code (for updates)\n- **Returns**: None\n- **Usage**: Called by installation scripts\n\n### Internal Functions\n\n#### None\n- All functions in api.func are public\n- No internal helper functions\n- Direct implementation of all functionality\n\n### Global Variables\n\n#### Configuration Variables\n- **`DIAGNOSTICS`**: Diagnostic reporting setting\n- **`RANDOM_UUID`**: Unique tracking identifier\n- **`POST_UPDATE_DONE`**: Duplicate update prevention\n\n#### Data Variables\n- **`CT_TYPE`**: Container type\n- **`DISK_SIZE`**: Disk size\n- **`CORE_COUNT`**: CPU core count\n- **`RAM_SIZE`**: RAM size\n- **`var_os`**: Operating system\n- **`var_version`**: OS version\n- **`DISABLEIP6`**: IPv6 setting\n- **`NSAPP`**: Application namespace\n- **`METHOD`**: Installation method\n\n#### Internal Variables\n- **`API_URL`**: API endpoint URL\n- **`JSON_PAYLOAD`**: API request payload\n- **`RESPONSE`**: API response\n- **`DISK_SIZE_API`**: Processed disk size for VM API\n\n## Integration Patterns\n\n### Standard Integration Pattern\n\n```bash\n#!/usr/bin/env bash\n# Standard integration pattern\n\n# 1. Source core.func first\nsource core.func\n\n# 2. Source api.func\nsource api.func\n\n# 3. Set up API reporting\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"$(uuidgen)\"\n\n# 4. Set application parameters\nexport NSAPP=\"$APP\"\nexport METHOD=\"install\"\n\n# 5. Report installation start\npost_to_api\n\n# 6. Perform installation\n# ... installation logic ...\n\n# 7. Report completion\npost_update_to_api \"success\" 0\n```\n\n### Minimal Integration Pattern\n\n```bash\n#!/usr/bin/env bash\n# Minimal integration pattern\n\nsource api.func\n\n# Basic error reporting\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"$(uuidgen)\"\n\n# Report failure\npost_update_to_api \"failed\" 127\n```\n\n### Advanced Integration Pattern\n\n```bash\n#!/usr/bin/env bash\n# Advanced integration pattern\n\nsource core.func\nsource api.func\nsource error_handler.func\n\n# Set up comprehensive API reporting\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"$(uuidgen)\"\nexport CT_TYPE=1\nexport DISK_SIZE=8\nexport CORE_COUNT=2\nexport RAM_SIZE=2048\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport METHOD=\"install\"\n\n# Enhanced error handling with API reporting\nenhanced_error_handler() {\n    local exit_code=${1:-$?}\n    local command=${2:-${BASH_COMMAND:-unknown}}\n\n    local error_msg=$(get_error_description $exit_code)\n    echo \"Error $exit_code: $error_msg\"\n\n    post_update_to_api \"failed\" $exit_code\n    error_handler $exit_code $command\n}\n\ntrap 'enhanced_error_handler' ERR\n\n# Advanced operations with API reporting\npost_to_api\n# ... operations ...\npost_update_to_api \"success\" 0\n```\n\n## Error Handling Integration\n\n### Automatic Error Reporting\n- **Error Descriptions**: Provides human-readable error messages\n- **API Integration**: Reports errors to community-scripts.org API\n- **Error Tracking**: Tracks error patterns for project improvement\n- **Diagnostic Data**: Contributes to anonymous usage analytics\n\n### Manual Error Reporting\n- **Custom Error Codes**: Use appropriate error codes for different scenarios\n- **Error Context**: Provide context information for errors\n- **Status Updates**: Report both success and failure cases\n- **Error Analysis**: Analyze error patterns and trends\n\n### API Communication Errors\n- **Network Failures**: Handle API communication failures gracefully\n- **Missing Prerequisites**: Check prerequisites before API calls\n- **Duplicate Prevention**: Prevent duplicate status updates\n- **Error Recovery**: Handle API errors without blocking installation\n\n## Performance Considerations\n\n### API Communication Overhead\n- **Minimal Impact**: API calls add minimal overhead\n- **Asynchronous**: API calls don't block installation process\n- **Error Handling**: API failures don't affect installation\n- **Optional**: API reporting is optional and can be disabled\n\n### Memory Usage\n- **Minimal Footprint**: API functions use minimal memory\n- **Variable Reuse**: Global variables reused across functions\n- **No Memory Leaks**: Proper cleanup prevents memory leaks\n- **Efficient Processing**: Efficient JSON payload creation\n\n### Execution Speed\n- **Fast API Calls**: Quick API communication\n- **Efficient Error Processing**: Fast error code processing\n- **Minimal Delay**: Minimal delay in API operations\n- **Non-blocking**: API calls don't block installation\n\n## Security Considerations\n\n### Data Privacy\n- **Anonymous Reporting**: Only anonymous data is sent\n- **No Sensitive Data**: No sensitive information is transmitted\n- **User Control**: Users can disable diagnostic reporting\n- **Data Minimization**: Only necessary data is sent\n\n### API Security\n- **HTTPS**: API communication uses secure protocols\n- **Data Validation**: API data is validated before sending\n- **Error Handling**: API errors are handled securely\n- **No Credentials**: No authentication credentials are sent\n\n### Network Security\n- **Secure Communication**: Uses secure HTTP protocols\n- **Error Handling**: Network errors are handled gracefully\n- **No Data Leakage**: No sensitive data is leaked\n- **Secure Endpoints**: Uses trusted API endpoints\n\n## Future Integration Considerations\n\n### Extensibility\n- **New API Endpoints**: Easy to add new API endpoints\n- **Additional Data**: Easy to add new data fields\n- **Error Codes**: Easy to add new error code descriptions\n- **API Versions**: Easy to support new API versions\n\n### Compatibility\n- **API Versioning**: Compatible with different API versions\n- **Data Format**: Compatible with different data formats\n- **Error Codes**: Compatible with different error code systems\n- **Network Protocols**: Compatible with different network protocols\n\n### Performance\n- **Optimization**: API communication can be optimized\n- **Caching**: API responses can be cached\n- **Batch Operations**: Multiple operations can be batched\n- **Async Processing**: API calls can be made asynchronous\n"
  },
  {
    "path": "docs/misc/api.func/API_USAGE_EXAMPLES.md",
    "content": "# api.func Usage Examples\n\n## Overview\n\nThis document provides practical usage examples for `api.func` functions, covering common scenarios, integration patterns, and best practices.\n\n## Basic API Setup\n\n### Standard API Initialization\n\n```bash\n#!/usr/bin/env bash\n# Standard API setup for LXC containers\n\nsource api.func\n\n# Set up diagnostic reporting\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"$(uuidgen)\"\n\n# Set container parameters\nexport CT_TYPE=1\nexport DISK_SIZE=8\nexport CORE_COUNT=2\nexport RAM_SIZE=2048\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport NSAPP=\"plex\"\nexport METHOD=\"install\"\n\n# Report installation start\npost_to_api\n\n# Your installation code here\n# ... installation logic ...\n\n# Report completion\nif [[ $? -eq 0 ]]; then\n    post_update_to_api \"success\" 0\nelse\n    post_update_to_api \"failed\" $?\nfi\n```\n\n### VM API Setup\n\n```bash\n#!/usr/bin/env bash\n# API setup for VMs\n\nsource api.func\n\n# Create diagnostics file for VM\nmkdir -p /usr/local/community-scripts\necho \"DIAGNOSTICS=yes\" > /usr/local/community-scripts/diagnostics\n\n# Set up VM parameters\nexport RANDOM_UUID=\"$(uuidgen)\"\nexport DISK_SIZE=\"20G\"\nexport CORE_COUNT=4\nexport RAM_SIZE=4096\nexport var_os=\"ubuntu\"\nexport var_version=\"22.04\"\nexport NSAPP=\"nextcloud\"\nexport METHOD=\"install\"\n\n# Report VM installation start\npost_to_api_vm\n\n# Your VM installation code here\n# ... VM creation logic ...\n\n# Report completion\npost_update_to_api \"success\" 0\n```\n\n## Error Description Examples\n\n### Basic Error Explanation\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Explain common error codes\necho \"Error 0: '$(get_error_description 0)'\"\necho \"Error 1: $(get_error_description 1)\"\necho \"Error 127: $(get_error_description 127)\"\necho \"Error 200: $(get_error_description 200)\"\necho \"Error 255: $(get_error_description 255)\"\n```\n\n### Error Code Testing\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Test all error codes\ntest_error_codes() {\n    local codes=(0 1 2 127 128 130 137 139 143 200 203 205 255)\n\n    for code in \"${codes[@]}\"; do\n        echo \"Code $code: $(get_error_description $code)\"\n    done\n}\n\ntest_error_codes\n```\n\n### Error Handling with Descriptions\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Function with error handling\nrun_command_with_error_handling() {\n    local command=\"$1\"\n    local description=\"$2\"\n\n    echo \"Running: $description\"\n\n    if $command; then\n        echo \"Success: $description\"\n        return 0\n    else\n        local exit_code=$?\n        local error_msg=$(get_error_description $exit_code)\n        echo \"Error $exit_code: $error_msg\"\n        return $exit_code\n    fi\n}\n\n# Usage\nrun_command_with_error_handling \"apt-get update\" \"Package list update\"\nrun_command_with_error_handling \"nonexistent_command\" \"Test command\"\n```\n\n## API Communication Examples\n\n### LXC Installation Reporting\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Complete LXC installation with API reporting\ninstall_lxc_with_reporting() {\n    local app=\"$1\"\n    local ctid=\"$2\"\n\n    # Set up API reporting\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n    export CT_TYPE=1\n    export DISK_SIZE=10\n    export CORE_COUNT=2\n    export RAM_SIZE=2048\n    export var_os=\"debian\"\n    export var_version=\"12\"\n    export NSAPP=\"$app\"\n    export METHOD=\"install\"\n\n    # Report installation start\n    post_to_api\n\n    # Installation process\n    echo \"Installing $app container (ID: $ctid)...\"\n\n    # Simulate installation\n    sleep 2\n\n    # Check if installation succeeded\n    if [[ $? -eq 0 ]]; then\n        echo \"Installation completed successfully\"\n        post_update_to_api \"success\" 0\n        return 0\n    else\n        echo \"Installation failed\"\n        post_update_to_api \"failed\" $?\n        return 1\n    fi\n}\n\n# Install multiple containers\ninstall_lxc_with_reporting \"plex\" \"100\"\ninstall_lxc_with_reporting \"nextcloud\" \"101\"\ninstall_lxc_with_reporting \"nginx\" \"102\"\n```\n\n### VM Installation Reporting\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Complete VM installation with API reporting\ninstall_vm_with_reporting() {\n    local app=\"$1\"\n    local vmid=\"$2\"\n\n    # Create diagnostics file\n    mkdir -p /usr/local/community-scripts\n    echo \"DIAGNOSTICS=yes\" > /usr/local/community-scripts/diagnostics\n\n    # Set up API reporting\n    export RANDOM_UUID=\"$(uuidgen)\"\n    export DISK_SIZE=\"20G\"\n    export CORE_COUNT=4\n    export RAM_SIZE=4096\n    export var_os=\"ubuntu\"\n    export var_version=\"22.04\"\n    export NSAPP=\"$app\"\n    export METHOD=\"install\"\n\n    # Report VM installation start\n    post_to_api_vm\n\n    # VM installation process\n    echo \"Installing $app VM (ID: $vmid)...\"\n\n    # Simulate VM creation\n    sleep 3\n\n    # Check if VM creation succeeded\n    if [[ $? -eq 0 ]]; then\n        echo \"VM installation completed successfully\"\n        post_update_to_api \"success\" 0\n        return 0\n    else\n        echo \"VM installation failed\"\n        post_update_to_api \"failed\" $?\n        return 1\n    fi\n}\n\n# Install multiple VMs\ninstall_vm_with_reporting \"nextcloud\" \"200\"\ninstall_vm_with_reporting \"wordpress\" \"201\"\n```\n\n## Status Update Examples\n\n### Success Reporting\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Report successful installation\nreport_success() {\n    local operation=\"$1\"\n\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n\n    echo \"Reporting successful $operation\"\n    post_update_to_api \"success\" 0\n}\n\n# Usage\nreport_success \"container installation\"\nreport_success \"package installation\"\nreport_success \"service configuration\"\n```\n\n### Failure Reporting\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Report failed installation\nreport_failure() {\n    local operation=\"$1\"\n    local exit_code=\"$2\"\n\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n\n    local error_msg=$(get_error_description $exit_code)\n    echo \"Reporting failed $operation: $error_msg\"\n    post_update_to_api \"failed\" $exit_code\n}\n\n# Usage\nreport_failure \"container creation\" 200\nreport_failure \"package installation\" 127\nreport_failure \"service start\" 1\n```\n\n### Conditional Status Reporting\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Conditional status reporting\nreport_installation_status() {\n    local operation=\"$1\"\n    local exit_code=\"$2\"\n\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n\n    if [[ $exit_code -eq 0 ]]; then\n        echo \"Reporting successful $operation\"\n        post_update_to_api \"success\" 0\n    else\n        local error_msg=$(get_error_description $exit_code)\n        echo \"Reporting failed $operation: $error_msg\"\n        post_update_to_api \"failed\" $exit_code\n    fi\n}\n\n# Usage\nreport_installation_status \"container creation\" 0\nreport_installation_status \"package installation\" 127\n```\n\n## Advanced Usage Examples\n\n### Batch Installation with API Reporting\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Batch installation with comprehensive API reporting\nbatch_install_with_reporting() {\n    local apps=(\"plex\" \"nextcloud\" \"nginx\" \"mysql\")\n    local ctids=(100 101 102 103)\n\n    # Set up API reporting\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n    export CT_TYPE=1\n    export DISK_SIZE=8\n    export CORE_COUNT=2\n    export RAM_SIZE=2048\n    export var_os=\"debian\"\n    export var_version=\"12\"\n    export METHOD=\"install\"\n\n    local success_count=0\n    local failure_count=0\n\n    for i in \"${!apps[@]}\"; do\n        local app=\"${apps[$i]}\"\n        local ctid=\"${ctids[$i]}\"\n\n        echo \"Installing $app (ID: $ctid)...\"\n\n        # Set app-specific parameters\n        export NSAPP=\"$app\"\n\n        # Report installation start\n        post_to_api\n\n        # Simulate installation\n        if install_app \"$app\" \"$ctid\"; then\n            echo \"$app installed successfully\"\n            post_update_to_api \"success\" 0\n            ((success_count++))\n        else\n            echo \"$app installation failed\"\n            post_update_to_api \"failed\" $?\n            ((failure_count++))\n        fi\n\n        echo \"---\"\n    done\n\n    echo \"Batch installation completed: $success_count successful, $failure_count failed\"\n}\n\n# Mock installation function\ninstall_app() {\n    local app=\"$1\"\n    local ctid=\"$2\"\n\n    # Simulate installation\n    sleep 1\n\n    # Simulate occasional failures\n    if [[ $((RANDOM % 10)) -eq 0 ]]; then\n        return 1\n    fi\n\n    return 0\n}\n\nbatch_install_with_reporting\n```\n\n### Error Analysis and Reporting\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Analyze and report errors\nanalyze_and_report_errors() {\n    local log_file=\"$1\"\n\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n\n    if [[ ! -f \"$log_file\" ]]; then\n        echo \"Log file not found: $log_file\"\n        return 1\n    fi\n\n    # Extract error codes from log\n    local error_codes=$(grep -o 'exit code [0-9]\\+' \"$log_file\" | grep -o '[0-9]\\+' | sort -u)\n\n    if [[ -z \"$error_codes\" ]]; then\n        echo \"No errors found in log\"\n        post_update_to_api \"success\" 0\n        return 0\n    fi\n\n    echo \"Found error codes: $error_codes\"\n\n    # Report each unique error\n    for code in $error_codes; do\n        local error_msg=$(get_error_description $code)\n        echo \"Error $code: $error_msg\"\n        post_update_to_api \"failed\" $code\n    done\n}\n\n# Usage\nanalyze_and_report_errors \"/var/log/installation.log\"\n```\n\n### API Health Check\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Check API connectivity and functionality\ncheck_api_health() {\n    echo \"Checking API health...\"\n\n    # Test prerequisites\n    if ! command -v curl >/dev/null 2>&1; then\n        echo \"ERROR: curl not available\"\n        return 1\n    fi\n\n    # Test error description function\n    local test_error=$(get_error_description 127)\n    if [[ -z \"$test_error\" ]]; then\n        echo \"ERROR: Error description function not working\"\n        return 1\n    fi\n\n    echo \"Error description test: $test_error\"\n\n    # Test API connectivity (without sending data)\n    local api_url=\"https://api.community-scripts.org/dev/upload\"\n    if curl -s --head \"$api_url\" >/dev/null 2>&1; then\n        echo \"API endpoint is reachable\"\n    else\n        echo \"WARNING: API endpoint not reachable\"\n    fi\n\n    echo \"API health check completed\"\n}\n\ncheck_api_health\n```\n\n## Integration Examples\n\n### With build.func\n\n```bash\n#!/usr/bin/env bash\n# Integration with build.func\n\nsource core.func\nsource api.func\nsource build.func\n\n# Set up API reporting\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"$(uuidgen)\"\n\n# Container creation with API reporting\ncreate_container_with_reporting() {\n    local app=\"$1\"\n    local ctid=\"$2\"\n\n    # Set container parameters\n    export APP=\"$app\"\n    export CTID=\"$ctid\"\n    export var_hostname=\"${app}-server\"\n    export var_os=\"debian\"\n    export var_version=\"12\"\n    export var_cpu=\"2\"\n    export var_ram=\"2048\"\n    export var_disk=\"10\"\n    export var_net=\"vmbr0\"\n    export var_gateway=\"192.168.1.1\"\n    export var_ip=\"192.168.1.$ctid\"\n    export var_template_storage=\"local\"\n    export var_container_storage=\"local\"\n\n    # Report installation start\n    post_to_api\n\n    # Create container using build.func\n    if source build.func; then\n        echo \"Container $app created successfully\"\n        post_update_to_api \"success\" 0\n        return 0\n    else\n        echo \"Container $app creation failed\"\n        post_update_to_api \"failed\" $?\n        return 1\n    fi\n}\n\n# Create containers\ncreate_container_with_reporting \"plex\" \"100\"\ncreate_container_with_reporting \"nextcloud\" \"101\"\n```\n\n### With vm-core.func\n\n```bash\n#!/usr/bin/env bash\n# Integration with vm-core.func\n\nsource core.func\nsource api.func\nsource vm-core.func\n\n# Set up VM API reporting\nmkdir -p /usr/local/community-scripts\necho \"DIAGNOSTICS=yes\" > /usr/local/community-scripts/diagnostics\n\nexport RANDOM_UUID=\"$(uuidgen)\"\n\n# VM creation with API reporting\ncreate_vm_with_reporting() {\n    local app=\"$1\"\n    local vmid=\"$2\"\n\n    # Set VM parameters\n    export APP=\"$app\"\n    export VMID=\"$vmid\"\n    export var_hostname=\"${app}-vm\"\n    export var_os=\"ubuntu\"\n    export var_version=\"22.04\"\n    export var_cpu=\"4\"\n    export var_ram=\"4096\"\n    export var_disk=\"20\"\n\n    # Report VM installation start\n    post_to_api_vm\n\n    # Create VM using vm-core.func\n    if source vm-core.func; then\n        echo \"VM $app created successfully\"\n        post_update_to_api \"success\" 0\n        return 0\n    else\n        echo \"VM $app creation failed\"\n        post_update_to_api \"failed\" $?\n        return 1\n    fi\n}\n\n# Create VMs\ncreate_vm_with_reporting \"nextcloud\" \"200\"\ncreate_vm_with_reporting \"wordpress\" \"201\"\n```\n\n### With error_handler.func\n\n```bash\n#!/usr/bin/env bash\n# Integration with error_handler.func\n\nsource core.func\nsource error_handler.func\nsource api.func\n\n# Enhanced error handling with API reporting\nenhanced_error_handler() {\n    local exit_code=${1:-$?}\n    local command=${2:-${BASH_COMMAND:-unknown}}\n\n    # Get error description from api.func\n    local error_msg=$(get_error_description $exit_code)\n\n    # Display error information\n    echo \"Error $exit_code: $error_msg\"\n    echo \"Command: $command\"\n\n    # Report error to API\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n    post_update_to_api \"failed\" $exit_code\n\n    # Use standard error handler\n    error_handler $exit_code $command\n}\n\n# Set up enhanced error handling\ntrap 'enhanced_error_handler' ERR\n\n# Test enhanced error handling\nnonexistent_command\n```\n\n## Best Practices Examples\n\n### Comprehensive API Integration\n\n```bash\n#!/usr/bin/env bash\n# Comprehensive API integration example\n\nsource core.func\nsource api.func\n\n# Set up comprehensive API reporting\nsetup_api_reporting() {\n    # Enable diagnostics\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n\n    # Set common parameters\n    export CT_TYPE=1\n    export DISK_SIZE=8\n    export CORE_COUNT=2\n    export RAM_SIZE=2048\n    export var_os=\"debian\"\n    export var_version=\"12\"\n    export METHOD=\"install\"\n\n    echo \"API reporting configured\"\n}\n\n# Installation with comprehensive reporting\ninstall_with_comprehensive_reporting() {\n    local app=\"$1\"\n    local ctid=\"$2\"\n\n    # Set up API reporting\n    setup_api_reporting\n    export NSAPP=\"$app\"\n\n    # Report installation start\n    post_to_api\n\n    # Installation process\n    echo \"Installing $app...\"\n\n    # Simulate installation steps\n    local steps=(\"Downloading\" \"Installing\" \"Configuring\" \"Starting\")\n    for step in \"${steps[@]}\"; do\n        echo \"$step $app...\"\n        sleep 1\n    done\n\n    # Check installation result\n    if [[ $? -eq 0 ]]; then\n        echo \"$app installation completed successfully\"\n        post_update_to_api \"success\" 0\n        return 0\n    else\n        echo \"$app installation failed\"\n        post_update_to_api \"failed\" $?\n        return 1\n    fi\n}\n\n# Install multiple applications\napps=(\"plex\" \"nextcloud\" \"nginx\" \"mysql\")\nctids=(100 101 102 103)\n\nfor i in \"${!apps[@]}\"; do\n    install_with_comprehensive_reporting \"${apps[$i]}\" \"${ctids[$i]}\"\n    echo \"---\"\ndone\n```\n\n### Error Recovery with API Reporting\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Error recovery with API reporting\nretry_with_api_reporting() {\n    local operation=\"$1\"\n    local max_attempts=3\n    local attempt=1\n\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n\n    while [[ $attempt -le $max_attempts ]]; do\n        echo \"Attempt $attempt of $max_attempts: $operation\"\n\n        if $operation; then\n            echo \"Operation succeeded on attempt $attempt\"\n            post_update_to_api \"success\" 0\n            return 0\n        else\n            local exit_code=$?\n            local error_msg=$(get_error_description $exit_code)\n            echo \"Attempt $attempt failed: $error_msg\"\n\n            post_update_to_api \"failed\" $exit_code\n\n            ((attempt++))\n\n            if [[ $attempt -le $max_attempts ]]; then\n                echo \"Retrying in 5 seconds...\"\n                sleep 5\n            fi\n        fi\n    done\n\n    echo \"Operation failed after $max_attempts attempts\"\n    return 1\n}\n\n# Usage\nretry_with_api_reporting \"apt-get update\"\nretry_with_api_reporting \"apt-get install -y package\"\n```\n\n### API Reporting with Logging\n\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# API reporting with detailed logging\ninstall_with_logging_and_api() {\n    local app=\"$1\"\n    local log_file=\"/var/log/${app}_installation.log\"\n\n    # Set up API reporting\n    export DIAGNOSTICS=\"yes\"\n    export RANDOM_UUID=\"$(uuidgen)\"\n    export NSAPP=\"$app\"\n\n    # Start logging\n    exec > >(tee -a \"$log_file\")\n    exec 2>&1\n\n    echo \"Starting $app installation at $(date)\"\n\n    # Report installation start\n    post_to_api\n\n    # Installation process\n    echo \"Installing $app...\"\n\n    # Simulate installation\n    if install_app \"$app\"; then\n        echo \"$app installation completed successfully at $(date)\"\n        post_update_to_api \"success\" 0\n        return 0\n    else\n        local exit_code=$?\n        local error_msg=$(get_error_description $exit_code)\n        echo \"$app installation failed at $(date): $error_msg\"\n        post_update_to_api \"failed\" $exit_code\n        return $exit_code\n    fi\n}\n\n# Mock installation function\ninstall_app() {\n    local app=\"$1\"\n    echo \"Installing $app...\"\n    sleep 2\n    return 0\n}\n\n# Install with logging and API reporting\ninstall_with_logging_and_api \"plex\"\n```\n"
  },
  {
    "path": "docs/misc/api.func/README.md",
    "content": "# api.func Documentation\n\n## Overview\n\nThe `api.func` file provides Proxmox API integration and diagnostic reporting functionality for the Community Scripts project. It handles API communication, error reporting, and status updates to the community-scripts.org API.\n\n## Purpose and Use Cases\n\n- **API Communication**: Send installation and status data to community-scripts.org API\n- **Diagnostic Reporting**: Report installation progress and errors for analytics\n- **Error Description**: Provide detailed error code explanations\n- **Status Updates**: Track installation success/failure status\n- **Analytics**: Contribute anonymous usage data for project improvement\n\n## Quick Reference\n\n### Key Function Groups\n- **Error Handling**: `get_error_description()` - Convert exit codes to human-readable messages\n- **API Communication**: `post_to_api()`, `post_to_api_vm()` - Send installation data\n- **Status Updates**: `post_update_to_api()` - Report installation completion status\n\n### Dependencies\n- **External**: `curl` command for HTTP requests\n- **Internal**: Uses environment variables from other scripts\n\n### Integration Points\n- Used by: All installation scripts for diagnostic reporting\n- Uses: Environment variables from build.func and other scripts\n- Provides: API communication and error reporting services\n\n## Documentation Files\n\n### 📊 [API_FLOWCHART.md](./API_FLOWCHART.md)\nVisual execution flows showing API communication processes and error handling.\n\n### 📚 [API_FUNCTIONS_REFERENCE.md](./API_FUNCTIONS_REFERENCE.md)\nComplete alphabetical reference of all functions with parameters, dependencies, and usage details.\n\n### 💡 [API_USAGE_EXAMPLES.md](./API_USAGE_EXAMPLES.md)\nPractical examples showing how to use API functions and common patterns.\n\n### 🔗 [API_INTEGRATION.md](./API_INTEGRATION.md)\nHow api.func integrates with other components and provides API services.\n\n## Key Features\n\n### Error Code Descriptions\n- **Comprehensive Coverage**: 50+ error codes with detailed explanations\n- **LXC-Specific Errors**: Container creation and management errors\n- **System Errors**: General system and network errors\n- **Signal Errors**: Process termination and signal errors\n\n### API Communication\n- **LXC Reporting**: Send LXC container installation data\n- **VM Reporting**: Send VM installation data\n- **Status Updates**: Report installation success/failure\n- **Diagnostic Data**: Anonymous usage analytics\n\n### Diagnostic Integration\n- **Optional Reporting**: Only sends data when diagnostics enabled\n- **Privacy Respect**: Respects user privacy settings\n- **Error Tracking**: Tracks installation errors for improvement\n- **Usage Analytics**: Contributes to project statistics\n\n## Common Usage Patterns\n\n### Basic API Setup\n```bash\n#!/usr/bin/env bash\n# Basic API setup\n\nsource api.func\n\n# Set up diagnostic reporting\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"$(uuidgen)\"\n\n# Report installation start\npost_to_api\n```\n\n### Error Reporting\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Get error description\nerror_msg=$(get_error_description 127)\necho \"Error 127: $error_msg\"\n# Output: Error 127: Command not found: Incorrect path or missing dependency.\n```\n\n### Status Updates\n```bash\n#!/usr/bin/env bash\nsource api.func\n\n# Report successful installation\npost_update_to_api \"success\" 0\n\n# Report failed installation\npost_update_to_api \"failed\" 127\n```\n\n## Environment Variables\n\n### Required Variables\n- `DIAGNOSTICS`: Enable/disable diagnostic reporting (\"yes\"/\"no\")\n- `RANDOM_UUID`: Unique identifier for tracking\n\n### Optional Variables\n- `CT_TYPE`: Container type (1 for LXC, 2 for VM)\n- `DISK_SIZE`: Disk size in GB\n- `CORE_COUNT`: Number of CPU cores\n- `RAM_SIZE`: RAM size in MB\n- `var_os`: Operating system type\n- `var_version`: OS version\n- `DISABLEIP6`: IPv6 disable setting\n- `NSAPP`: Namespace application name\n- `METHOD`: Installation method\n\n### Internal Variables\n- `POST_UPDATE_DONE`: Prevents duplicate status updates\n- `API_URL`: Community scripts API endpoint\n- `JSON_PAYLOAD`: API request payload\n- `RESPONSE`: API response\n\n## Error Code Categories\n\n### General System Errors\n- **0-9**: Basic system errors\n- **18, 22, 28, 35**: Network and I/O errors\n- **56, 60**: TLS/SSL errors\n- **125-128**: Command execution errors\n- **129-143**: Signal errors\n- **152**: Resource limit errors\n- **255**: Unknown critical errors\n\n### LXC-Specific Errors\n- **100-101**: LXC installation errors\n- **200-209**: LXC creation and management errors\n\n### Docker Errors\n- **125**: Docker container start errors\n\n## Best Practices\n\n### Diagnostic Reporting\n1. Always check if diagnostics are enabled\n2. Respect user privacy settings\n3. Use unique identifiers for tracking\n4. Report both success and failure cases\n\n### Error Handling\n1. Use appropriate error codes\n2. Provide meaningful error descriptions\n3. Handle API communication failures gracefully\n4. Don't block installation on API failures\n\n### API Usage\n1. Check for curl availability\n2. Handle network failures gracefully\n3. Use appropriate HTTP methods\n4. Include all required data\n\n## Troubleshooting\n\n### Common Issues\n1. **API Communication Fails**: Check network connectivity and curl availability\n2. **Diagnostics Not Working**: Verify DIAGNOSTICS setting and RANDOM_UUID\n3. **Missing Error Descriptions**: Check error code coverage\n4. **Duplicate Updates**: POST_UPDATE_DONE prevents duplicates\n\n### Debug Mode\nEnable diagnostic reporting for debugging:\n```bash\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"$(uuidgen)\"\n```\n\n### API Testing\nTest API communication:\n```bash\nsource api.func\nexport DIAGNOSTICS=\"yes\"\nexport RANDOM_UUID=\"test-$(date +%s)\"\npost_to_api\n```\n\n## Related Documentation\n\n- [core.func](../core.func/) - Core utilities and error handling\n- [error_handler.func](../error_handler.func/) - Error handling utilities\n- [build.func](../build.func/) - Container creation with API integration\n- [tools.func](../tools.func/) - Extended utilities with API integration\n\n---\n\n*This documentation covers the api.func file which provides API communication and diagnostic reporting for all Proxmox Community Scripts.*\n"
  },
  {
    "path": "docs/misc/build.func/BUILD_FUNC_ADVANCED_SETTINGS.md",
    "content": "# Advanced Settings Wizard Reference\n\n## Overview\n\nThe Advanced Settings wizard provides a 28-step interactive configuration for LXC container creation. It allows users to customize every aspect of the container while inheriting sensible defaults from the CT script.\n\n## Key Features\n\n- **Inherit App Defaults**: All `var_*` values from CT scripts pre-populate wizard fields\n- **Back Navigation**: Press Cancel/Back to return to previous step\n- **App Default Hints**: Each dialog shows `(App default: X)` to indicate script defaults\n- **Full Customization**: Every configurable option is accessible\n\n## Wizard Steps\n\n| Step | Title                    | Variable(s)                       | Description                                           |\n| ---- | ------------------------ | --------------------------------- | ----------------------------------------------------- |\n| 1    | Container Type           | `var_unprivileged`                | Privileged (0) or Unprivileged (1) container          |\n| 2    | Root Password            | `var_pw`                          | Set password or use automatic login                   |\n| 3    | Container ID             | `var_ctid`                        | Unique container ID (auto-suggested)                  |\n| 4    | Hostname                 | `var_hostname`                    | Container hostname                                    |\n| 5    | Disk Size                | `var_disk`                        | Disk size in GB                                       |\n| 6    | CPU Cores                | `var_cpu`                         | Number of CPU cores                                   |\n| 7    | RAM Size                 | `var_ram`                         | RAM size in MiB                                       |\n| 8    | Network Bridge           | `var_brg`                         | Network bridge (vmbr0, etc.)                          |\n| 9    | IPv4 Configuration       | `var_net`, `var_gateway`          | DHCP or static IP with gateway                        |\n| 10   | IPv6 Configuration       | `var_ipv6_method`                 | Auto, DHCP, Static, or None                           |\n| 11   | MTU Size                 | `var_mtu`                         | Network MTU (default: 1500)                           |\n| 12   | DNS Search Domain        | `var_searchdomain`                | DNS search domain                                     |\n| 13   | DNS Server               | `var_ns`                          | Custom DNS server IP                                  |\n| 14   | MAC Address              | `var_mac`                         | Custom MAC address (auto-generated if empty)          |\n| 15   | VLAN Tag                 | `var_vlan`                        | VLAN tag ID                                           |\n| 16   | Tags                     | `var_tags`                        | Container tags (comma/semicolon separated)            |\n| 17   | SSH Settings             | `var_ssh`                         | SSH key selection and root access                     |\n| 18   | FUSE Support             | `var_fuse`                        | Enable FUSE for rclone, mergerfs, AppImage            |\n| 19   | TUN/TAP Support          | `var_tun`                         | Enable for VPN apps (WireGuard, OpenVPN, Tailscale)   |\n| 20   | Nesting Support          | `var_nesting`                     | Enable for Docker, LXC in LXC, Podman                 |\n| 21   | GPU Passthrough          | `var_gpu`                         | Auto-detect and pass through Intel/AMD/NVIDIA GPUs    |\n| 22   | Keyctl Support           | `var_keyctl`                      | Enable for Docker, systemd-networkd                   |\n| 23   | APT Cacher Proxy         | `var_apt_cacher`, `var_apt_cacher_ip` | Use apt-cacher-ng for faster downloads            |\n| 24   | Container Timezone       | `var_timezone`                    | Set timezone (e.g., Europe/Berlin)                    |\n| 25   | Container Protection     | `var_protection`                  | Prevent accidental deletion                           |\n| 26   | Device Node Creation     | `var_mknod`                       | Allow mknod (experimental, kernel 5.3+)               |\n| 27   | Mount Filesystems        | `var_mount_fs`                    | Allow specific mounts: nfs, cifs, fuse, etc.          |\n| 28   | Verbose Mode & Confirm   | `var_verbose`                     | Enable verbose output + final confirmation            |\n\n## Default Value Inheritance\n\nThe wizard inherits defaults from multiple sources:\n\n```text\nCT Script (var_*) → default.vars → app.vars → User Input\n```\n\n### Example: VPN Container (alpine-wireguard.sh)\n\n```bash\n# CT script sets:\nvar_tun=\"${var_tun:-1}\"  # TUN enabled by default\n\n# In Advanced Settings Step 19:\n# Dialog shows: \"(App default: 1)\" and pre-selects \"Yes\"\n```\n\n### Example: Media Server (jellyfin.sh)\n\n```bash\n# CT script sets:\nvar_gpu=\"${var_gpu:-yes}\"  # GPU enabled by default\n\n# In Advanced Settings Step 21:\n# Dialog shows: \"(App default: yes)\" and pre-selects \"Yes\"\n```\n\n## Feature Matrix\n\n| Feature           | Variable         | When to Enable                                      |\n| ----------------- | ---------------- | --------------------------------------------------- |\n| FUSE              | `var_fuse`       | rclone, mergerfs, AppImage, SSHFS                   |\n| TUN/TAP           | `var_tun`        | WireGuard, OpenVPN, Tailscale, VPN containers       |\n| Nesting           | `var_nesting`    | Docker, Podman, LXC-in-LXC, systemd-nspawn          |\n| GPU Passthrough   | `var_gpu`        | Plex, Jellyfin, Emby, Frigate, Ollama, ComfyUI      |\n| Keyctl            | `var_keyctl`     | Docker (unprivileged), systemd-networkd             |\n| Protection        | `var_protection` | Production containers, prevent accidental deletion  |\n| Mknod             | `var_mknod`      | Device node creation (experimental)                 |\n| Mount FS          | `var_mount_fs`   | NFS mounts, CIFS shares, custom filesystems         |\n| APT Cacher        | `var_apt_cacher` | Speed up downloads with local apt-cacher-ng         |\n\n## Confirmation Summary\n\nStep 28 displays a comprehensive summary before creation:\n\n```text\nContainer Type: Unprivileged\nContainer ID: 100\nHostname: jellyfin\n\nResources:\n  Disk: 8 GB\n  CPU: 2 cores\n  RAM: 2048 MiB\n\nNetwork:\n  Bridge: vmbr0\n  IPv4: dhcp\n  IPv6: auto\n\nFeatures:\n  FUSE: no | TUN: no\n  Nesting: Enabled | Keyctl: Disabled\n  GPU: yes | Protection: No\n\nAdvanced:\n  Timezone: Europe/Berlin\n  APT Cacher: no\n  Verbose: no\n```\n\n## Usage Examples\n\n### Skip to Advanced Settings\n\n```bash\n# Run script, select \"Advanced\" from menu\nbash -c \"$(curl -fsSL https://...jellyfin.sh)\"\n# Then select option 3 \"Advanced\"\n```\n\n### Pre-set Defaults via Environment\n\n```bash\n# Set defaults before running\nexport var_cpu=4\nexport var_ram=4096\nexport var_gpu=yes\nbash -c \"$(curl -fsSL https://...jellyfin.sh)\"\n# Advanced settings will inherit these values\n```\n\n### Non-Interactive with All Options\n\n```bash\n# Set all variables for fully automated deployment\nexport var_unprivileged=1\nexport var_cpu=2\nexport var_ram=2048\nexport var_disk=8\nexport var_net=dhcp\nexport var_fuse=no\nexport var_tun=no\nexport var_gpu=yes\nexport var_nesting=1\nexport var_protection=no\nexport var_verbose=no\nbash -c \"$(curl -fsSL https://...jellyfin.sh)\"\n```\n\n## Notes\n\n- **Cancel at Step 1**: Exits the script entirely\n- **Cancel at Steps 2-28**: Goes back to previous step\n- **Empty fields**: Use default value\n- **Keyctl**: Automatically enabled for unprivileged containers\n- **Nesting**: Enabled by default (required for many apps)\n"
  },
  {
    "path": "docs/misc/build.func/BUILD_FUNC_ARCHITECTURE.md",
    "content": "# build.func Architecture Guide\n\n## Overview\n\nThis document provides a high-level architectural overview of `build.func`, including module dependencies, data flow, integration points, and system architecture.\n\n## High-Level Architecture\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                           Proxmox Host System                                  │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        build.func                                          │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────────┐ │ │\n│  │  │   Entry Point   │  │   Configuration │  │      Container Creation     │ │ │\n│  │  │                 │  │                 │  │                             │ │ │\n│  │  │ • start()       │  │ • variables()   │  │ • build_container()        │ │ │\n│  │  │ • install_      │  │ • base_         │  │ • create_lxc_container()    │ │ │\n│  │  │   script()      │  │   settings()    │  │ • configure_gpu_           │ │ │\n│  │  │ • advanced_     │  │ • select_       │  │   passthrough()             │ │ │\n│  │  │   settings()    │  │   storage()     │  │ • fix_gpu_gids()            │ │ │\n│  │  └─────────────────┘  └─────────────────┘  └─────────────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        Module Dependencies                                │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────────┐ │ │\n│  │  │   core.func     │  │ error_handler.   │  │        api.func             │ │ │\n│  │  │                 │  │ func             │  │                             │ │ │\n│  │  │ • Basic         │  │ • Error          │  │ • Proxmox API               │ │ │\n│  │  │   utilities     │  │   handling       │  │   interactions              │ │ │\n│  │  │ • Common        │  │ • Error          │  │ • Container                  │ │ │\n│  │  │   functions     │  │   recovery       │  │   management                │ │ │\n│  │  │ • System        │  │ • Cleanup        │  │ • Status                    │ │ │\n│  │  │   utilities     │  │   functions      │  │   monitoring                 │ │ │\n│  │  └─────────────────┘  └─────────────────┘  └─────────────────────────────┘ │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────────────────────────────────────────────────────────────┐ │ │\n│  │  │                        tools.func                                      │ │ │\n│  │  │                                                                       │ │ │\n│  │  │ • Additional utilities                                                 │ │ │\n│  │  │ • Helper functions                                                     │ │ │\n│  │  │ • System tools                                                         │ │ │\n│  │  └─────────────────────────────────────────────────────────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Module Dependencies\n\n### Core Dependencies\n\n```\nbuild.func Dependencies:\n├── core.func\n│   ├── Basic system utilities\n│   ├── Common functions\n│   ├── System information\n│   └── File operations\n├── error_handler.func\n│   ├── Error handling\n│   ├── Error recovery\n│   ├── Cleanup functions\n│   └── Error logging\n├── api.func\n│   ├── Proxmox API interactions\n│   ├── Container management\n│   ├── Status monitoring\n│   └── Configuration updates\n└── tools.func\n    ├── Additional utilities\n    ├── Helper functions\n    ├── System tools\n    └── Custom functions\n```\n\n### Dependency Flow\n\n```\nDependency Flow:\n├── build.func\n│   ├── Sources core.func\n│   ├── Sources error_handler.func\n│   ├── Sources api.func\n│   └── Sources tools.func\n├── core.func\n│   ├── Basic utilities\n│   └── System functions\n├── error_handler.func\n│   ├── Error management\n│   └── Recovery functions\n├── api.func\n│   ├── Proxmox integration\n│   └── Container operations\n└── tools.func\n    ├── Additional tools\n    └── Helper functions\n```\n\n## Data Flow Architecture\n\n### Configuration Data Flow\n\n```\nConfiguration Data Flow:\n├── Environment Variables\n│   ├── Hard environment variables\n│   ├── App-specific .vars\n│   ├── Global default.vars\n│   └── Built-in defaults\n├── Variable Resolution\n│   ├── Apply precedence chain\n│   ├── Validate settings\n│   └── Resolve conflicts\n├── Configuration Storage\n│   ├── Memory variables\n│   ├── Temporary files\n│   └── Persistent storage\n└── Configuration Usage\n    ├── Container creation\n    ├── Feature configuration\n    └── Settings persistence\n```\n\n### Container Data Flow\n\n```\nContainer Data Flow:\n├── Input Data\n│   ├── Configuration variables\n│   ├── Resource specifications\n│   ├── Network settings\n│   └── Storage requirements\n├── Processing\n│   ├── Validation\n│   ├── Conflict resolution\n│   ├── Resource allocation\n│   └── Configuration generation\n├── Container Creation\n│   ├── LXC container creation\n│   ├── Network configuration\n│   ├── Storage setup\n│   └── Feature configuration\n└── Output\n    ├── Container status\n    ├── Access information\n    ├── Configuration files\n    └── Log files\n```\n\n## Integration Architecture\n\n### With Proxmox System\n\n```\nProxmox Integration:\n├── Proxmox Host\n│   ├── LXC container management\n│   ├── Storage management\n│   ├── Network management\n│   └── Resource management\n├── Proxmox API\n│   ├── Container operations\n│   ├── Configuration updates\n│   ├── Status monitoring\n│   └── Error handling\n├── Proxmox Configuration\n│   ├── /etc/pve/lxc/<ctid>.conf\n│   ├── Storage configuration\n│   ├── Network configuration\n│   └── Resource configuration\n└── Proxmox Services\n    ├── Container services\n    ├── Network services\n    ├── Storage services\n    └── Monitoring services\n```\n\n### With Install Scripts\n\n```\nInstall Script Integration:\n├── build.func\n│   ├── Creates container\n│   ├── Configures basic settings\n│   ├── Starts container\n│   └── Provides access\n├── Install Scripts\n│   ├── <app>-install.sh\n│   ├── Downloads application\n│   ├── Configures application\n│   └── Sets up services\n├── Container\n│   ├── Running application\n│   ├── Configured services\n│   ├── Network access\n│   └── Storage access\n└── Integration Points\n    ├── Container creation\n    ├── Network configuration\n    ├── Storage setup\n    └── Service configuration\n```\n\n## System Architecture Components\n\n### Core Components\n\n```\nSystem Components:\n├── Entry Point\n│   ├── start() function\n│   ├── Context detection\n│   ├── Environment capture\n│   └── Workflow routing\n├── Configuration Management\n│   ├── Variable resolution\n│   ├── Settings persistence\n│   ├── Default management\n│   └── Validation\n├── Container Creation\n│   ├── LXC container creation\n│   ├── Network configuration\n│   ├── Storage setup\n│   └── Feature configuration\n├── Hardware Integration\n│   ├── GPU passthrough\n│   ├── USB passthrough\n│   ├── Storage management\n│   └── Network management\n└── Error Handling\n    ├── Error detection\n    ├── Error recovery\n    ├── Cleanup functions\n    └── User notification\n```\n\n### User Interface Components\n\n```\nUI Components:\n├── Menu System\n│   ├── Installation mode selection\n│   ├── Configuration menus\n│   ├── Storage selection\n│   └── GPU configuration\n├── Interactive Elements\n│   ├── Whiptail menus\n│   ├── User prompts\n│   ├── Confirmation dialogs\n│   └── Error messages\n├── Non-Interactive Mode\n│   ├── Environment variable driven\n│   ├── Silent execution\n│   ├── Automated configuration\n│   └── Error handling\n└── Output\n    ├── Status messages\n    ├── Progress indicators\n    ├── Completion information\n    └── Access details\n```\n\n## Security Architecture\n\n### Security Considerations\n\n```\nSecurity Architecture:\n├── Container Security\n│   ├── Unprivileged containers (default)\n│   ├── Privileged containers (when needed)\n│   ├── Resource limits\n│   └── Access controls\n├── Network Security\n│   ├── Network isolation\n│   ├── VLAN support\n│   ├── Firewall integration\n│   └── Access controls\n├── Storage Security\n│   ├── Storage isolation\n│   ├── Access controls\n│   ├── Encryption support\n│   └── Backup integration\n├── GPU Security\n│   ├── Device isolation\n│   ├── Permission management\n│   ├── Access controls\n│   └── Security validation\n└── API Security\n    ├── Authentication\n    ├── Authorization\n    ├── Input validation\n    └── Error handling\n```\n\n## Performance Architecture\n\n### Performance Considerations\n\n```\nPerformance Architecture:\n├── Execution Optimization\n│   ├── Parallel operations\n│   ├── Efficient algorithms\n│   ├── Minimal user interaction\n│   └── Optimized validation\n├── Resource Management\n│   ├── Memory efficiency\n│   ├── CPU optimization\n│   ├── Disk usage optimization\n│   └── Network efficiency\n├── Caching\n│   ├── Configuration caching\n│   ├── Template caching\n│   ├── Storage caching\n│   └── GPU detection caching\n└── Monitoring\n    ├── Performance monitoring\n    ├── Resource monitoring\n    ├── Error monitoring\n    └── Status monitoring\n```\n\n## Deployment Architecture\n\n### Deployment Scenarios\n\n```\nDeployment Scenarios:\n├── Single Container\n│   ├── Individual application\n│   ├── Standard configuration\n│   ├── Basic networking\n│   └── Standard storage\n├── Multiple Containers\n│   ├── Application stack\n│   ├── Shared networking\n│   ├── Shared storage\n│   └── Coordinated deployment\n├── High Availability\n│   ├── Redundant containers\n│   ├── Load balancing\n│   ├── Failover support\n│   └── Monitoring integration\n└── Development Environment\n    ├── Development containers\n    ├── Testing containers\n    ├── Staging containers\n    └── Production containers\n```\n\n## Maintenance Architecture\n\n### Maintenance Components\n\n```\nMaintenance Architecture:\n├── Updates\n│   ├── Container updates\n│   ├── Application updates\n│   ├── Configuration updates\n│   └── Security updates\n├── Monitoring\n│   ├── Container monitoring\n│   ├── Resource monitoring\n│   ├── Performance monitoring\n│   └── Error monitoring\n├── Backup\n│   ├── Configuration backup\n│   ├── Container backup\n│   ├── Storage backup\n│   └── Recovery procedures\n└── Troubleshooting\n    ├── Error diagnosis\n    ├── Log analysis\n    ├── Performance analysis\n    └── Recovery procedures\n```\n\n## Future Architecture Considerations\n\n### Scalability\n\n```\nScalability Considerations:\n├── Horizontal Scaling\n│   ├── Multiple containers\n│   ├── Load balancing\n│   ├── Distributed deployment\n│   └── Resource distribution\n├── Vertical Scaling\n│   ├── Resource scaling\n│   ├── Performance optimization\n│   ├── Capacity planning\n│   └── Resource management\n├── Automation\n│   ├── Automated deployment\n│   ├── Automated scaling\n│   ├── Automated monitoring\n│   └── Automated recovery\n└── Integration\n    ├── External systems\n    ├── Cloud integration\n    ├── Container orchestration\n    └── Service mesh\n```\n"
  },
  {
    "path": "docs/misc/build.func/BUILD_FUNC_ENVIRONMENT_VARIABLES.md",
    "content": "# build.func Environment Variables Reference\n\n## Overview\n\nThis document provides a comprehensive reference of all environment variables used in `build.func`, organized by category and usage context.\n\n## Variable Categories\n\n### Core Container Variables\n\n| Variable  | Description                                  | Default   | Set In      | Used In            |\n| --------- | -------------------------------------------- | --------- | ----------- | ------------------ |\n| `APP`     | Application name (e.g., \"plex\", \"nextcloud\") | -         | Environment | Throughout         |\n| `NSAPP`   | Namespace application name                   | `$APP`    | Environment | Throughout         |\n| `CTID`    | Container ID                                 | -         | Environment | Container creation |\n| `CT_TYPE` | Container type (\"install\" or \"update\")       | \"install\" | Environment | Entry point        |\n| `CT_NAME` | Container name                               | `$APP`    | Environment | Container creation |\n\n### Operating System Variables\n\n| Variable       | Description                | Default        | Set In          | Used In            |\n| -------------- | -------------------------- | -------------- | --------------- | ------------------ |\n| `var_os`       | Operating system selection | \"debian\"       | base_settings() | OS selection       |\n| `var_version`  | OS version                 | \"12\"           | base_settings() | Template selection |\n| `var_template` | Template name              | Auto-generated | base_settings() | Template download  |\n\n### Resource Configuration Variables\n\n| Variable     | Description             | Default     | Set In          | Used In            |\n| ------------ | ----------------------- | ----------- | --------------- | ------------------ |\n| `var_cpu`    | CPU cores               | \"2\"         | base_settings() | Container creation |\n| `var_ram`    | RAM in MB               | \"2048\"      | base_settings() | Container creation |\n| `var_disk`   | Disk size in GB         | \"8\"         | base_settings() | Container creation |\n| `DISK_SIZE`  | Disk size (alternative) | `$var_disk` | Environment     | Container creation |\n| `CORE_COUNT` | CPU cores (alternative) | `$var_cpu`  | Environment     | Container creation |\n| `RAM_SIZE`   | RAM size (alternative)  | `$var_ram`  | Environment     | Container creation |\n\n### Network Configuration Variables\n\n| Variable      | Description                     | Default        | Set In          | Used In        |\n| ------------- | ------------------------------- | -------------- | --------------- | -------------- |\n| `var_net`     | Network interface               | \"vmbr0\"        | base_settings() | Network config |\n| `var_bridge`  | Bridge interface                | \"vmbr0\"        | base_settings() | Network config |\n| `var_gateway` | Gateway IP                      | \"192.168.1.1\"  | base_settings() | Network config |\n| `var_ip`      | Container IP address            | -              | User input      | Network config |\n| `var_ipv6`    | IPv6 address                    | -              | User input      | Network config |\n| `var_vlan`    | VLAN ID                         | -              | User input      | Network config |\n| `var_mtu`     | MTU size                        | \"1500\"         | base_settings() | Network config |\n| `var_mac`     | MAC address                     | Auto-generated | base_settings() | Network config |\n| `NET`         | Network interface (alternative) | `$var_net`     | Environment     | Network config |\n| `BRG`         | Bridge interface (alternative)  | `$var_bridge`  | Environment     | Network config |\n| `GATE`        | Gateway IP (alternative)        | `$var_gateway` | Environment     | Network config |\n| `IPV6_METHOD` | IPv6 configuration method       | \"none\"         | Environment     | Network config |\n| `VLAN`        | VLAN ID (alternative)           | `$var_vlan`    | Environment     | Network config |\n| `MTU`         | MTU size (alternative)          | `$var_mtu`     | Environment     | Network config |\n| `MAC`         | MAC address (alternative)       | `$var_mac`     | Environment     | Network config |\n\n### Storage Configuration Variables\n\n| Variable                | Description                     | Default                  | Set In           | Used In           |\n| ----------------------- | ------------------------------- | ------------------------ | ---------------- | ----------------- |\n| `var_template_storage`  | Storage for templates           | -                        | select_storage() | Template storage  |\n| `var_container_storage` | Storage for container disks     | -                        | select_storage() | Container storage |\n| `TEMPLATE_STORAGE`      | Template storage (alternative)  | `$var_template_storage`  | Environment      | Template storage  |\n| `CONTAINER_STORAGE`     | Container storage (alternative) | `$var_container_storage` | Environment      | Container storage |\n\n### Feature Flags\n\n| Variable         | Description                    | Default | Set In                          | Used In            |\n| ---------------- | ------------------------------ | ------- | ------------------------------- | ------------------ |\n| `var_fuse`       | Enable FUSE support            | \"no\"    | CT script / Advanced Settings   | Container features |\n| `var_tun`        | Enable TUN/TAP support         | \"no\"    | CT script / Advanced Settings   | Container features |\n| `var_nesting`    | Enable nesting support         | \"1\"     | CT script / Advanced Settings   | Container features |\n| `var_keyctl`     | Enable keyctl support          | \"0\"     | CT script / Advanced Settings   | Container features |\n| `var_mknod`      | Allow device node creation     | \"0\"     | CT script / Advanced Settings   | Container features |\n| `var_mount_fs`   | Allowed filesystem mounts      | \"\"      | CT script / Advanced Settings   | Container features |\n| `var_protection` | Enable container protection    | \"no\"    | CT script / Advanced Settings   | Container creation |\n| `var_timezone`   | Container timezone             | \"\"      | CT script / Advanced Settings   | Container creation |\n| `var_verbose`    | Enable verbose output          | \"no\"    | Environment / Advanced Settings | Logging            |\n| `var_ssh`        | Enable SSH key provisioning    | \"no\"    | CT script / Advanced Settings   | SSH setup          |\n| `ENABLE_FUSE`    | FUSE flag (internal)           | \"no\"    | Advanced Settings               | Container creation |\n| `ENABLE_TUN`     | TUN/TAP flag (internal)        | \"no\"    | Advanced Settings               | Container creation |\n| `ENABLE_NESTING` | Nesting flag (internal)        | \"1\"     | Advanced Settings               | Container creation |\n| `ENABLE_KEYCTL`  | Keyctl flag (internal)         | \"0\"     | Advanced Settings               | Container creation |\n| `ENABLE_MKNOD`   | Mknod flag (internal)          | \"0\"     | Advanced Settings               | Container creation |\n| `PROTECT_CT`     | Protection flag (internal)     | \"no\"    | Advanced Settings               | Container creation |\n| `CT_TIMEZONE`    | Timezone setting (internal)    | \"\"      | Advanced Settings               | Container creation |\n| `VERBOSE`        | Verbose mode flag              | \"no\"    | Environment                     | Logging            |\n| `SSH`            | SSH access flag                | \"no\"    | Advanced Settings               | SSH setup          |\n\n### APT Cacher Configuration\n\n| Variable           | Description              | Default | Set In                        | Used In             |\n| ------------------ | ------------------------ | ------- | ----------------------------- | ------------------- |\n| `var_apt_cacher`   | Enable APT cacher proxy  | \"no\"    | CT script / Advanced Settings | Package management  |\n| `var_apt_cacher_ip`| APT cacher server IP     | \"\"      | CT script / Advanced Settings | Package management  |\n| `APT_CACHER`       | APT cacher flag          | \"no\"    | Advanced Settings             | Container creation  |\n| `APT_CACHER_IP`    | APT cacher IP (internal) | \"\"      | Advanced Settings             | Container creation  |\n\n### GPU Passthrough Variables\n\n| Variable     | Description                     | Default | Set In                                      | Used In            |\n| ------------ | ------------------------------- | ------- | ------------------------------------------- | ------------------ |\n| `var_gpu`    | Enable GPU passthrough          | \"no\"    | CT script / Environment / Advanced Settings | GPU passthrough    |\n| `ENABLE_GPU` | GPU passthrough flag (internal) | \"no\"    | Advanced Settings                           | Container creation |\n\n**Note**: GPU passthrough is controlled via `var_gpu`. Apps that benefit from GPU acceleration (media servers, AI/ML, transcoding) have `var_gpu=yes` as default in their CT scripts.\n\n**Apps with GPU enabled by default**:\n\n- Media: jellyfin, plex, emby, channels, ersatztv, tunarr, immich\n- Transcoding: tdarr, unmanic, fileflows\n- AI/ML: ollama, openwebui\n- NVR: frigate\n\n**Usage Examples**:\n\n```bash\n# Disable GPU for a specific installation\nvar_gpu=no bash -c \"$(curl -fsSL https://...jellyfin.sh)\"\n\n# Enable GPU for apps without default GPU support\nvar_gpu=yes bash -c \"$(curl -fsSL https://...debian.sh)\"\n\n# Set in default.vars for all apps\necho \"var_gpu=yes\" >> /usr/local/community-scripts/default.vars\n```\n\n### API and Diagnostics Variables\n\n| Variable      | Description              | Default   | Set In      | Used In           |\n| ------------- | ------------------------ | --------- | ----------- | ----------------- |\n| `DIAGNOSTICS` | Enable diagnostics mode  | \"false\"   | Environment | Diagnostics       |\n| `METHOD`      | Installation method      | \"install\" | Environment | Installation flow |\n| `RANDOM_UUID` | Random UUID for tracking | -         | Environment | Logging           |\n| `API_TOKEN`   | Proxmox API token        | -         | Environment | API calls         |\n| `API_USER`    | Proxmox API user         | -         | Environment | API calls         |\n\n### Settings Persistence Variables\n\n| Variable            | Description                | Default                                           | Set In      | Used In              |\n| ------------------- | -------------------------- | ------------------------------------------------- | ----------- | -------------------- |\n| `SAVE_DEFAULTS`     | Save settings as defaults  | \"false\"                                           | User input  | Settings persistence |\n| `SAVE_APP_DEFAULTS` | Save app-specific defaults | \"false\"                                           | User input  | Settings persistence |\n| `DEFAULT_VARS_FILE` | Path to default.vars       | \"/usr/local/community-scripts/default.vars\"       | Environment | Settings persistence |\n| `APP_DEFAULTS_FILE` | Path to app.vars           | \"/usr/local/community-scripts/defaults/$APP.vars\" | Environment | Settings persistence |\n\n## Variable Precedence Chain\n\nVariables are resolved in the following order (highest to lowest priority):\n\n1. **Hard Environment Variables**: Set before script execution\n2. **App-specific .vars file**: `/usr/local/community-scripts/defaults/<app>.vars`\n3. **Global default.vars file**: `/usr/local/community-scripts/default.vars`\n4. **Built-in defaults**: Set in `base_settings()` function\n\n## Critical Variables for Non-Interactive Use\n\nFor silent/non-interactive execution, these variables must be set:\n\n```bash\n# Core container settings\nexport APP=\"plex\"\nexport CTID=\"100\"\nexport var_hostname=\"plex-server\"\n\n# OS selection\nexport var_os=\"debian\"\nexport var_version=\"12\"\n\n# Resource allocation\nexport var_cpu=\"4\"\nexport var_ram=\"4096\"\nexport var_disk=\"20\"\n\n# Network configuration\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.100\"\n\n# Storage selection\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\n\n# Feature flags\nexport ENABLE_FUSE=\"true\"\nexport ENABLE_TUN=\"true\"\nexport SSH=\"true\"\n```\n\n## Environment Variable Usage Patterns\n\n### 1. Container Creation\n\n```bash\n# Basic container creation\nexport APP=\"nextcloud\"\nexport CTID=\"101\"\nexport var_hostname=\"nextcloud-server\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"2\"\nexport var_ram=\"2048\"\nexport var_disk=\"10\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.101\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\n```\n\n### 2. GPU Passthrough\n\n```bash\n# Enable GPU passthrough\nexport GPU_APPS=\"plex,jellyfin,emby\"\nexport var_gpu=\"intel\"\nexport ENABLE_PRIVILEGED=\"true\"\n```\n\n### 3. Advanced Network Configuration\n\n```bash\n# VLAN and IPv6 configuration\nexport var_vlan=\"100\"\nexport var_ipv6=\"2001:db8::100\"\nexport IPV6_METHOD=\"static\"\nexport var_mtu=\"9000\"\n```\n\n### 4. Storage Configuration\n\n```bash\n# Custom storage locations\nexport var_template_storage=\"nfs-storage\"\nexport var_container_storage=\"ssd-storage\"\n```\n\n## Variable Validation\n\nThe script validates variables at several points:\n\n1. **Container ID validation**: Must be unique and within valid range\n2. **IP address validation**: Must be valid IPv4/IPv6 format\n3. **Storage validation**: Must exist and support required content types\n4. **Resource validation**: Must be within reasonable limits\n5. **Network validation**: Must be valid network configuration\n\n## Common Variable Combinations\n\n### Development Container\n\n```bash\nexport APP=\"dev-container\"\nexport CTID=\"200\"\nexport var_hostname=\"dev-server\"\nexport var_os=\"ubuntu\"\nexport var_version=\"22.04\"\nexport var_cpu=\"4\"\nexport var_ram=\"4096\"\nexport var_disk=\"20\"\nexport ENABLE_NESTING=\"true\"\nexport ENABLE_PRIVILEGED=\"true\"\n```\n\n### Media Server with GPU\n\n```bash\nexport APP=\"plex\"\nexport CTID=\"300\"\nexport var_hostname=\"plex-server\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"6\"\nexport var_ram=\"8192\"\nexport var_disk=\"50\"\nexport GPU_APPS=\"plex\"\nexport var_gpu=\"nvidia\"\nexport ENABLE_PRIVILEGED=\"true\"\n```\n\n### Lightweight Service\n\n```bash\nexport APP=\"nginx\"\nexport CTID=\"400\"\nexport var_hostname=\"nginx-proxy\"\nexport var_os=\"alpine\"\nexport var_version=\"3.18\"\nexport var_cpu=\"1\"\nexport var_ram=\"512\"\nexport var_disk=\"2\"\nexport ENABLE_UNPRIVILEGED=\"true\"\n```\n"
  },
  {
    "path": "docs/misc/build.func/BUILD_FUNC_EXECUTION_FLOWS.md",
    "content": "# build.func Execution Flows\n\n## Overview\n\nThis document details the execution flows for different installation modes and scenarios in `build.func`, including variable precedence, decision trees, and workflow patterns.\n\n## Installation Modes\n\n### 1. Default Install Flow\n\n**Purpose**: Uses built-in defaults with minimal user interaction\n**Use Case**: Quick container creation with standard settings\n\n```\nDefault Install Flow:\n├── start()\n│   ├── Detect execution context\n│   ├── Capture hard environment variables\n│   └── Set CT_TYPE=\"install\"\n├── install_script()\n│   ├── Display installation mode menu\n│   ├── User selects \"Default Install\"\n│   └── Proceed with defaults\n├── variables()\n│   ├── base_settings()  # Set built-in defaults\n│   ├── Load app.vars (if exists)\n│   ├── Load default.vars (if exists)\n│   └── Apply variable precedence\n├── build_container()\n│   ├── validate_settings()\n│   ├── check_conflicts()\n│   └── create_lxc_container()\n└── default_var_settings()\n    └── Offer to save as defaults\n```\n\n**Key Characteristics**:\n- Minimal user prompts\n- Uses built-in defaults\n- Fast execution\n- Suitable for standard deployments\n\n### 2. Advanced Install Flow\n\n**Purpose**: Full interactive configuration via whiptail menus\n**Use Case**: Custom container configuration with full control\n\n```\nAdvanced Install Flow:\n├── start()\n│   ├── Detect execution context\n│   ├── Capture hard environment variables\n│   └── Set CT_TYPE=\"install\"\n├── install_script()\n│   ├── Display installation mode menu\n│   ├── User selects \"Advanced Install\"\n│   └── Proceed with advanced configuration\n├── variables()\n│   ├── base_settings()  # Set built-in defaults\n│   ├── Load app.vars (if exists)\n│   ├── Load default.vars (if exists)\n│   └── Apply variable precedence\n├── advanced_settings()\n│   ├── OS Selection Menu\n│   ├── Resource Configuration Menu\n│   ├── Network Configuration Menu\n│   ├── select_storage()\n│   │   ├── resolve_storage_preselect()\n│   │   └── choose_and_set_storage_for_file()\n│   ├── GPU Configuration Menu\n│   │   └── detect_gpu_devices()\n│   └── Feature Flags Menu\n├── build_container()\n│   ├── validate_settings()\n│   ├── check_conflicts()\n│   └── create_lxc_container()\n└── default_var_settings()\n    └── Offer to save as defaults\n```\n\n**Key Characteristics**:\n- Full interactive configuration\n- Whiptail menus for all options\n- Complete control over settings\n- Suitable for custom deployments\n\n### 3. My Defaults Flow\n\n**Purpose**: Loads settings from global default.vars file\n**Use Case**: Using previously saved global defaults\n\n```\nMy Defaults Flow:\n├── start()\n│   ├── Detect execution context\n│   ├── Capture hard environment variables\n│   └── Set CT_TYPE=\"install\"\n├── install_script()\n│   ├── Display installation mode menu\n│   ├── User selects \"My Defaults\"\n│   └── Proceed with loaded defaults\n├── variables()\n│   ├── base_settings()  # Set built-in defaults\n│   ├── Load app.vars (if exists)\n│   ├── Load default.vars  # Load global defaults\n│   └── Apply variable precedence\n├── build_container()\n│   ├── validate_settings()\n│   ├── check_conflicts()\n│   └── create_lxc_container()\n└── default_var_settings()\n    └── Offer to save as defaults\n```\n\n**Key Characteristics**:\n- Uses global default.vars file\n- Minimal user interaction\n- Consistent with previous settings\n- Suitable for repeated deployments\n\n### 4. App Defaults Flow\n\n**Purpose**: Loads settings from app-specific .vars file\n**Use Case**: Using previously saved app-specific defaults\n\n```\nApp Defaults Flow:\n├── start()\n│   ├── Detect execution context\n│   ├── Capture hard environment variables\n│   └── Set CT_TYPE=\"install\"\n├── install_script()\n│   ├── Display installation mode menu\n│   ├── User selects \"App Defaults\"\n│   └── Proceed with app-specific defaults\n├── variables()\n│   ├── base_settings()  # Set built-in defaults\n│   ├── Load app.vars  # Load app-specific defaults\n│   ├── Load default.vars (if exists)\n│   └── Apply variable precedence\n├── build_container()\n│   ├── validate_settings()\n│   ├── check_conflicts()\n│   └── create_lxc_container()\n└── default_var_settings()\n    └── Offer to save as defaults\n```\n\n**Key Characteristics**:\n- Uses app-specific .vars file\n- Minimal user interaction\n- App-optimized settings\n- Suitable for app-specific deployments\n\n## Variable Precedence Chain\n\n### Precedence Order (Highest to Lowest)\n\n1. **Hard Environment Variables**: Set before script execution\n2. **App-specific .vars file**: `/usr/local/community-scripts/defaults/<app>.vars`\n3. **Global default.vars file**: `/usr/local/community-scripts/default.vars`\n4. **Built-in defaults**: Set in `base_settings()` function\n\n### Variable Resolution Process\n\n```\nVariable Resolution:\n├── Capture hard environment variables at start()\n├── Load built-in defaults in base_settings()\n├── Load global default.vars (if exists)\n├── Load app-specific .vars (if exists)\n└── Apply precedence chain\n    ├── Hard env vars override all\n    ├── App.vars override default.vars and built-ins\n    ├── Default.vars override built-ins\n    └── Built-ins are fallback defaults\n```\n\n## Storage Selection Logic\n\n### Storage Resolution Flow\n\n```\nStorage Selection:\n├── Check if storage is preselected\n│   ├── var_template_storage set? → Validate and use\n│   └── var_container_storage set? → Validate and use\n├── Count available storage options\n│   ├── Only 1 option → Auto-select\n│   └── Multiple options → Prompt user\n├── User selection via whiptail\n│   ├── Template storage selection\n│   └── Container storage selection\n└── Validate selected storage\n    ├── Check availability\n    ├── Check content type support\n    └── Proceed with selection\n```\n\n### Storage Validation\n\n```\nStorage Validation:\n├── Check storage exists\n├── Check storage is online\n├── Check content type support\n│   ├── Template storage: vztmpl support\n│   └── Container storage: rootdir support\n├── Check available space\n└── Validate permissions\n```\n\n## GPU Passthrough Flow\n\n### GPU Detection and Configuration\n\n```\nGPU Passthrough Flow:\n├── detect_gpu_devices()\n│   ├── Scan for Intel GPUs\n│   │   ├── Check i915 driver\n│   │   └── Detect devices\n│   ├── Scan for AMD GPUs\n│   │   ├── Check AMDGPU driver\n│   │   └── Detect devices\n│   └── Scan for NVIDIA GPUs\n│       ├── Check NVIDIA driver\n│       ├── Detect devices\n│       └── Check CUDA support\n├── Check GPU passthrough eligibility\n│   ├── Is app in GPU_APPS list?\n│   ├── Is container privileged?\n│   └── Proceed if eligible\n├── GPU selection logic\n│   ├── Single GPU type → Auto-select\n│   └── Multiple GPU types → Prompt user\n├── configure_gpu_passthrough()\n│   ├── Add GPU device entries\n│   ├── Configure permissions\n│   └── Update container config\n└── fix_gpu_gids()\n    ├── Update GPU group IDs\n    └── Configure access permissions\n```\n\n### GPU Eligibility Check\n\n```\nGPU Eligibility:\n├── Check app support\n│   ├── Is APP in GPU_APPS list?\n│   └── Proceed if supported\n├── Check container privileges\n│   ├── Is ENABLE_PRIVILEGED=\"true\"?\n│   └── Proceed if privileged\n└── Check hardware availability\n    ├── Are GPUs detected?\n    └── Proceed if available\n```\n\n## Network Configuration Flow\n\n### Network Setup Process\n\n```\nNetwork Configuration:\n├── Basic network settings\n│   ├── var_net (network interface)\n│   ├── var_bridge (bridge interface)\n│   └── var_gateway (gateway IP)\n├── IP configuration\n│   ├── var_ip (IPv4 address)\n│   ├── var_ipv6 (IPv6 address)\n│   └── IPV6_METHOD (IPv6 method)\n├── Advanced network settings\n│   ├── var_vlan (VLAN ID)\n│   ├── var_mtu (MTU size)\n│   └── var_mac (MAC address)\n└── Network validation\n    ├── Check IP format\n    ├── Check gateway reachability\n    └── Validate network configuration\n```\n\n## Container Creation Flow\n\n### LXC Container Creation Process\n\n```\nContainer Creation:\n├── create_lxc_container()\n│   ├── Create basic container\n│   ├── Configure network\n│   ├── Set up storage\n│   ├── Configure features\n│   ├── Set resource limits\n│   ├── Configure startup\n│   └── Start container\n├── Post-creation configuration\n│   ├── Wait for network\n│   ├── Configure GPU (if enabled)\n│   ├── Set up SSH keys\n│   └── Run post-install scripts\n└── Finalization\n    ├── Display container info\n    ├── Show access details\n    └── Provide next steps\n```\n\n## Error Handling Flows\n\n### Validation Error Flow\n\n```\nValidation Error Flow:\n├── validate_settings()\n│   ├── Check configuration validity\n│   └── Return error if invalid\n├── check_conflicts()\n│   ├── Check for conflicts\n│   └── Return error if conflicts found\n├── Error handling\n│   ├── Display error message\n│   ├── cleanup_on_error()\n│   └── Exit with error code\n└── User notification\n    ├── Show error details\n    └── Suggest fixes\n```\n\n### Storage Error Flow\n\n```\nStorage Error Flow:\n├── Storage selection fails\n├── Retry storage selection\n│   ├── Show available options\n│   └── Allow user to retry\n├── Storage validation fails\n│   ├── Show validation errors\n│   └── Allow user to fix\n└── Fallback to default storage\n    ├── Use fallback storage\n    └── Continue with creation\n```\n\n### GPU Error Flow\n\n```\nGPU Error Flow:\n├── GPU detection fails\n├── Fall back to no GPU\n│   ├── Disable GPU passthrough\n│   └── Continue without GPU\n├── GPU configuration fails\n│   ├── Show configuration errors\n│   └── Allow user to retry\n└── GPU permission errors\n    ├── Fix GPU permissions\n    └── Retry configuration\n```\n\n## Integration Flows\n\n### With Install Scripts\n\n```\nInstall Script Integration:\n├── build.func creates container\n├── Container starts successfully\n├── Install script execution\n│   ├── Download and install app\n│   ├── Configure app settings\n│   └── Set up services\n└── Post-installation configuration\n    ├── Verify installation\n    ├── Configure access\n    └── Display completion info\n```\n\n### With Proxmox API\n\n```\nProxmox API Integration:\n├── API authentication\n├── Container creation via API\n├── Configuration updates via API\n├── Status monitoring via API\n└── Error handling via API\n```\n\n## Performance Considerations\n\n### Execution Time Optimization\n\n```\nPerformance Optimization:\n├── Parallel operations where possible\n├── Minimal user interaction in default mode\n├── Efficient storage selection\n├── Optimized GPU detection\n└── Streamlined validation\n```\n\n### Resource Usage\n\n```\nResource Usage:\n├── Minimal memory footprint\n├── Efficient disk usage\n├── Optimized network usage\n└── Minimal CPU overhead\n```\n"
  },
  {
    "path": "docs/misc/build.func/BUILD_FUNC_FLOWCHART.md",
    "content": "# build.func Execution Flowchart\n\n## Main Execution Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                                START()                                          │\n│  Entry point when build.func is sourced or executed                            │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                          Check Environment                                      │\n│  • Detect if running on Proxmox host vs inside container                      │\n│  • Capture hard environment variables                                          │\n│  • Set CT_TYPE based on context                                               │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Determine Action                                        │\n│  • If CT_TYPE=\"update\" → update_script()                                       │\n│  • If CT_TYPE=\"install\" → install_script()                                    │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        INSTALL_SCRIPT()                                        │\n│  Main container creation workflow                                              │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Installation Mode Selection                             │\n│                                                                                 │\n│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────┐ │\n│  │   Default       │  │   Advanced      │  │   My Defaults   │  │ App Defaults│ │\n│  │   Install       │  │   Install       │  │                 │  │             │ │\n│  │                 │  │                 │  │                 │  │             │ │\n│  │ • Use built-in  │  │ • Full whiptail │  │ • Load from     │  │ • Load from │ │\n│  │   defaults      │  │   menus         │  │   default.vars  │  │   app.vars  │ │\n│  │ • Minimal       │  │ • Interactive   │  │ • Override      │  │ • App-      │ │\n│  │   prompts       │  │   configuration │  │   built-ins     │  │   specific  │ │\n│  └─────────────────┘  └─────────────────┘  └─────────────────┘  └─────────────┘ │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        VARIABLES()                                             │\n│  • Load variable precedence chain:                                             │\n│    1. Hard environment variables                                               │\n│    2. App-specific .vars file                                                  │\n│    3. Global default.vars file                                                 │\n│    4. Built-in defaults in base_settings()                                    │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        BASE_SETTINGS()                                         │\n│  • Set core container parameters                                               │\n│  • Configure OS selection                                                      │\n│  • Set resource defaults (CPU, RAM, Disk)                                      │\n│  • Configure network defaults                                                  │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Storage Selection Logic                                 │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    SELECT_STORAGE()                                       │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────────┐ │ │\n│  │  │   Template      │    │   Container     │    │     Resolution          │ │ │\n│  │  │   Storage       │    │   Storage       │    │     Logic               │ │ │\n│  │  │                 │    │                 │    │                         │ │ │\n│  │  │ • Check if      │    │ • Check if      │    │ 1. Only 1 storage      │ │ │\n│  │  │   preselected   │    │   preselected   │    │    → Auto-select        │ │ │\n│  │  │ • Validate      │    │ • Validate      │    │ 2. Preselected         │ │ │\n│  │  │   availability  │    │   availability  │    │    → Validate & use    │ │ │\n│  │  │ • Prompt if     │    │ • Prompt if     │    │ 3. Multiple options    │ │ │\n│  │  │   needed        │    │   needed        │    │    → Prompt user        │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        BUILD_CONTAINER()                                       │\n│  • Validate all settings                                                       │\n│  • Check for conflicts                                                          │\n│  • Prepare container configuration                                             │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        CREATE_LXC_CONTAINER()                                  │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                     Container Creation Process                             │ │\n│  │                                                                           │ │\n│  │  1. Create LXC container with basic configuration                         │ │\n│  │  2. Configure network settings                                            │ │\n│  │  3. Set up storage and mount points                                       │ │\n│  │  4. Configure features (FUSE, TUN, etc.)                                  │ │\n│  │  5. Set resource limits                                                   │ │\n│  │  6. Configure startup options                                             │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        GPU Passthrough Decision Tree                           │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    DETECT_GPU_DEVICES()                                    │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────────┐ │ │\n│  │  │   Intel GPU     │    │   AMD GPU       │    │     NVIDIA GPU          │ │ │\n│  │  │                 │    │                 │    │                         │ │ │\n│  │  │ • Check i915    │    │ • Check AMDGPU  │    │ • Check NVIDIA         │ │ │\n│  │  │   driver        │    │   driver        │    │   driver                │ │ │\n│  │  │ • Detect        │    │ • Detect        │    │ • Detect devices        │ │ │\n│  │  │   devices       │    │   devices       │    │ • Check CUDA support    │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    GPU Selection Logic                                     │ │\n│  │                                                                           │ │\n│  │  • Is app in GPU_APPS list? OR Is container privileged?                   │ │\n│  │    └─ YES → Proceed with GPU configuration                                │ │\n│  │    └─ NO → Skip GPU passthrough                                          │ │\n│  │                                                                           │ │\n│  │  • Single GPU type detected?                                              │ │\n│  │    └─ YES → Auto-select and configure                                     │ │\n│  │    └─ NO → Prompt user for selection                                      │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        CONFIGURE_GPU_PASSTHROUGH()                             │\n│  • Add GPU device entries to /etc/pve/lxc/<ctid>.conf                         │\n│  • Configure proper device permissions                                        │\n│  • Set up device mapping                                                       │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Container Finalization                                  │\n│  • Start container                                                             │\n│  • Wait for network connectivity                                               │\n│  • Fix GPU GIDs (if GPU passthrough enabled)                                  │\n│  • Configure SSH keys (if enabled)                                            │\n│  • Run post-installation scripts                                              │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Settings Persistence                                    │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    DEFAULT_VAR_SETTINGS()                                  │ │\n│  │                                                                           │ │\n│  │  • Offer to save current settings as defaults                             │ │\n│  │  • Save to /usr/local/community-scripts/default.vars                     │ │\n│  │  • Save to /usr/local/community-scripts/defaults/<app>.vars               │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                              COMPLETION                                        │\n│  • Display container information                                               │\n│  • Show access details                                                         │\n│  • Provide next steps                                                         │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Key Decision Points\n\n### 1. Installation Mode Selection\n- **Default**: Uses built-in defaults, minimal user interaction\n- **Advanced**: Full interactive configuration via whiptail menus\n- **My Defaults**: Loads settings from global default.vars file\n- **App Defaults**: Loads settings from app-specific .vars file\n\n### 2. Storage Selection Logic\n```\nStorage Selection Flow:\n├── Check if storage is preselected via environment variables\n│   ├── YES → Validate availability and use\n│   └── NO → Continue to resolution logic\n├── Count available storage options for content type\n│   ├── Only 1 option → Auto-select\n│   └── Multiple options → Prompt user via whiptail\n└── Validate selected storage and proceed\n```\n\n### 3. GPU Passthrough Decision Tree\n```\nGPU Passthrough Flow:\n├── Detect available GPU hardware\n│   ├── Intel GPU detected\n│   ├── AMD GPU detected\n│   └── NVIDIA GPU detected\n├── Check if GPU passthrough should be enabled\n│   ├── App is in GPU_APPS list? → YES\n│   ├── Container is privileged? → YES\n│   └── Neither? → Skip GPU passthrough\n├── Configure GPU passthrough\n│   ├── Single GPU type → Auto-configure\n│   └── Multiple GPU types → Prompt user\n└── Fix GPU GIDs post-creation\n```\n\n### 4. Variable Precedence Chain\n```\nVariable Resolution Order:\n1. Hard environment variables (captured at start)\n2. App-specific .vars file (/usr/local/community-scripts/defaults/<app>.vars)\n3. Global default.vars file (/usr/local/community-scripts/default.vars)\n4. Built-in defaults in base_settings() function\n```\n\n## Error Handling Flow\n\n```\nError Handling:\n├── Validation errors → Display error message and exit\n├── Storage errors → Retry storage selection\n├── Network errors → Retry network configuration\n├── GPU errors → Fall back to no GPU passthrough\n└── Container creation errors → Cleanup and exit\n```\n\n## Integration Points\n\n- **Core Functions**: Depends on core.func for basic utilities\n- **Error Handling**: Uses error_handler.func for error management\n- **API Functions**: Uses api.func for Proxmox API interactions\n- **Tools**: Uses tools.func for additional utilities\n- **Install Scripts**: Integrates with <app>-install.sh scripts\n"
  },
  {
    "path": "docs/misc/build.func/BUILD_FUNC_FUNCTIONS_REFERENCE.md",
    "content": "# build.func Functions Reference\n\n## Overview\n\nThis document provides a comprehensive reference of all functions in `build.func`, organized alphabetically with detailed descriptions, parameters, and usage information.\n\n## Function Categories\n\n### Initialization Functions\n\n#### `start()`\n\n**Purpose**: Main entry point when build.func is sourced or executed\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Detects execution context (Proxmox host vs container)\n- Captures hard environment variables\n- Sets CT_TYPE based on context\n- Routes to appropriate workflow (install_script or update_script)\n  **Dependencies**: None\n  **Environment Variables Used**: `CT_TYPE`, `APP`, `CTID`\n\n#### `variables()`\n\n**Purpose**: Load and resolve all configuration variables using precedence chain\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Loads app-specific .vars file\n- Loads global default.vars file\n- Applies variable precedence chain\n- Sets all configuration variables\n  **Dependencies**: `base_settings()`\n  **Environment Variables Used**: All configuration variables\n\n#### `base_settings()`\n\n**Purpose**: Set built-in default values for all configuration variables\n**Parameters**: None\n**Returns**: None\n**Side Effects**: Sets default values for all variables\n**Dependencies**: None\n**Environment Variables Used**: All configuration variables\n\n### UI and Menu Functions\n\n#### `install_script()`\n\n**Purpose**: Main installation workflow coordinator\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Displays installation mode selection menu\n- Coordinates the entire installation process\n- Handles user interaction and validation\n  **Dependencies**: `variables()`, `build_container()`, `default_var_settings()`\n  **Environment Variables Used**: `APP`, `CTID`, `var_hostname`\n\n#### `advanced_settings()`\n\n**Purpose**: Provide advanced configuration options via whiptail menus\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Displays whiptail menus for configuration\n- Updates configuration variables based on user input\n- Validates user selections\n  **Dependencies**: `select_storage()`, `detect_gpu_devices()`\n  **Environment Variables Used**: All configuration variables\n\n#### `settings_menu()`\n\n**Purpose**: Display and handle settings configuration menu\n**Parameters**: None\n**Returns**: None\n**Side Effects**: Updates configuration variables\n**Dependencies**: `advanced_settings()`\n**Environment Variables Used**: All configuration variables\n\n### Storage Functions\n\n#### `select_storage()`\n\n**Purpose**: Handle storage selection for templates and containers\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Resolves storage preselection\n- Prompts user for storage selection if needed\n- Validates storage availability\n- Sets var_template_storage and var_container_storage\n  **Dependencies**: `resolve_storage_preselect()`, `choose_and_set_storage_for_file()`\n  **Environment Variables Used**: `var_template_storage`, `var_container_storage`, `TEMPLATE_STORAGE`, `CONTAINER_STORAGE`\n\n#### `resolve_storage_preselect()`\n\n**Purpose**: Resolve preselected storage options\n**Parameters**:\n\n- `storage_type`: Type of storage (template or container)\n  **Returns**: Storage name if valid, empty if invalid\n  **Side Effects**: Validates storage availability\n  **Dependencies**: None\n  **Environment Variables Used**: `var_template_storage`, `var_container_storage`\n\n#### `choose_and_set_storage_for_file()`\n\n**Purpose**: Interactive storage selection via whiptail\n**Parameters**:\n\n- `storage_type`: Type of storage (template or container)\n- `content_type`: Content type (vztmpl or rootdir)\n  **Returns**: None\n  **Side Effects**:\n- Displays whiptail menu\n- Updates storage variables\n- Validates selection\n  **Dependencies**: None\n  **Environment Variables Used**: `var_template_storage`, `var_container_storage`\n\n### Container Creation Functions\n\n#### `build_container()`\n\n**Purpose**: Validate settings and prepare container creation\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Validates all configuration\n- Checks for conflicts\n- Prepares container configuration\n- Calls create_lxc_container()\n  **Dependencies**: `create_lxc_container()`\n  **Environment Variables Used**: All configuration variables\n\n#### `create_lxc_container()`\n\n**Purpose**: Create the actual LXC container\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Creates LXC container with basic configuration\n- Configures network settings\n- Sets up storage and mount points\n- Configures features (FUSE, TUN, etc.)\n- Sets resource limits\n- Configures startup options\n- Starts container\n  **Dependencies**: `configure_gpu_passthrough()`, `fix_gpu_gids()`\n  **Environment Variables Used**: All configuration variables\n\n### GPU and Hardware Functions\n\n#### `detect_gpu_devices()`\n\n**Purpose**: Detect available GPU hardware on the system\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Scans for Intel, AMD, and NVIDIA GPUs\n- Updates var_gpu_type and var_gpu_devices\n- Determines GPU capabilities\n  **Dependencies**: None\n  **Environment Variables Used**: `var_gpu_type`, `var_gpu_devices`, `GPU_APPS`\n\n#### `configure_gpu_passthrough()`\n\n**Purpose**: Configure GPU passthrough for the container\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Adds GPU device entries to container config\n- Configures proper device permissions\n- Sets up device mapping\n- Updates /etc/pve/lxc/<ctid>.conf\n  **Dependencies**: `detect_gpu_devices()`\n  **Environment Variables Used**: `var_gpu`, `var_gpu_type`, `var_gpu_devices`, `CTID`\n\n#### `fix_gpu_gids()`\n\n**Purpose**: Fix GPU group IDs after container creation\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Updates GPU group IDs in container\n- Ensures proper GPU access permissions\n- Configures video and render groups\n  **Dependencies**: `configure_gpu_passthrough()`\n  **Environment Variables Used**: `CTID`, `var_gpu_type`\n\n### SSH Configuration Functions\n\n#### `configure_ssh_settings()`\n\n**Purpose**: Interactive SSH key and access configuration wizard\n**Parameters**:\n\n- `step_info` (optional): Step indicator string (e.g., \"Step 17/19\") for consistent dialog headers\n  **Returns**: None\n  **Side Effects**:\n- Creates temporary file for SSH keys\n- Discovers and presents available SSH keys from host\n- Allows manual key entry or folder/glob scanning\n- Sets `SSH` variable to \"yes\" or \"no\" based on user selection\n- Sets `SSH_AUTHORIZED_KEY` if manual key provided\n- Populates `SSH_KEYS_FILE` with selected keys\n  **Dependencies**: `ssh_discover_default_files()`, `ssh_build_choices_from_files()`\n  **Environment Variables Used**: `SSH`, `SSH_AUTHORIZED_KEY`, `SSH_KEYS_FILE`\n\n**SSH Key Source Options**:\n\n1. `found` - Select from auto-detected host keys\n2. `manual` - Paste a single public key\n3. `folder` - Scan custom folder or glob pattern\n4. `none` - No SSH keys\n\n**Note**: The \"Enable root SSH access?\" dialog is always shown, regardless of whether SSH keys or password are configured. This ensures users can always enable SSH access even with automatic login.\n\n#### `ssh_discover_default_files()`\n\n**Purpose**: Discover SSH public key files on the host system\n**Parameters**: None\n**Returns**: Array of discovered key file paths\n**Side Effects**: Scans common SSH key locations\n**Dependencies**: None\n**Environment Variables Used**: `var_ssh_import_glob`\n\n#### `ssh_build_choices_from_files()`\n\n**Purpose**: Build whiptail checklist choices from SSH key files\n**Parameters**:\n\n- Array of file paths to process\n  **Returns**: None\n  **Side Effects**:\n- Sets `CHOICES` array for whiptail checklist\n- Sets `COUNT` variable with number of keys found\n- Creates `MAPFILE` for key tag to content mapping\n  **Dependencies**: None\n  **Environment Variables Used**: `CHOICES`, `COUNT`, `MAPFILE`\n\n### Settings Persistence Functions\n\n#### `default_var_settings()`\n\n**Purpose**: Offer to save current settings as defaults\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Prompts user to save settings\n- Saves to default.vars file\n- Saves to app-specific .vars file\n  **Dependencies**: `maybe_offer_save_app_defaults()`\n  **Environment Variables Used**: All configuration variables\n\n#### `maybe_offer_save_app_defaults()`\n\n**Purpose**: Offer to save app-specific defaults\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Prompts user to save app-specific settings\n- Saves to app.vars file\n- Updates app-specific configuration\n  **Dependencies**: None\n  **Environment Variables Used**: `APP`, `SAVE_APP_DEFAULTS`\n\n### Utility Functions\n\n#### `validate_settings()`\n\n**Purpose**: Validate all configuration settings\n**Parameters**: None\n**Returns**: 0 if valid, 1 if invalid\n**Side Effects**:\n\n- Checks for configuration conflicts\n- Validates resource limits\n- Validates network configuration\n- Validates storage configuration\n  **Dependencies**: None\n  **Environment Variables Used**: All configuration variables\n\n#### `check_conflicts()`\n\n**Purpose**: Check for configuration conflicts\n**Parameters**: None\n**Returns**: 0 if no conflicts, 1 if conflicts found\n**Side Effects**:\n\n- Checks for conflicting settings\n- Validates resource allocation\n- Checks network configuration\n  **Dependencies**: None\n  **Environment Variables Used**: All configuration variables\n\n#### `cleanup_on_error()`\n\n**Purpose**: Clean up resources on error\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n\n- Removes partially created containers\n- Cleans up temporary files\n- Resets configuration\n  **Dependencies**: None\n  **Environment Variables Used**: `CTID`\n\n## Function Call Flow\n\n### Main Installation Flow\n\n```\nstart()\n├── variables()\n│   ├── base_settings()\n│   ├── Load app.vars\n│   └── Load default.vars\n├── install_script()\n│   ├── advanced_settings()\n│   │   ├── select_storage()\n│   │   │   ├── resolve_storage_preselect()\n│   │   │   └── choose_and_set_storage_for_file()\n│   │   └── detect_gpu_devices()\n│   ├── build_container()\n│   │   ├── validate_settings()\n│   │   ├── check_conflicts()\n│   │   └── create_lxc_container()\n│   │       ├── configure_gpu_passthrough()\n│   │       └── fix_gpu_gids()\n│   └── default_var_settings()\n│       └── maybe_offer_save_app_defaults()\n```\n\n### Error Handling Flow\n\n```\nError Detection\n├── validate_settings()\n│   └── check_conflicts()\n├── Error Handling\n│   └── cleanup_on_error()\n└── Exit with error code\n```\n\n## Function Dependencies\n\n### Core Dependencies\n\n- `start()` → `install_script()` → `build_container()` → `create_lxc_container()`\n- `variables()` → `base_settings()`\n- `advanced_settings()` → `select_storage()` → `detect_gpu_devices()`\n\n### Storage Dependencies\n\n- `select_storage()` → `resolve_storage_preselect()`\n- `select_storage()` → `choose_and_set_storage_for_file()`\n\n### GPU Dependencies\n\n- `configure_gpu_passthrough()` → `detect_gpu_devices()`\n- `fix_gpu_gids()` → `configure_gpu_passthrough()`\n\n### Settings Dependencies\n\n- `default_var_settings()` → `maybe_offer_save_app_defaults()`\n\n## Function Usage Examples\n\n### Basic Container Creation\n\n```bash\n# Set required variables\nexport APP=\"plex\"\nexport CTID=\"100\"\nexport var_hostname=\"plex-server\"\n\n# Call main functions\nstart()  # Entry point\n# → variables()  # Load configuration\n# → install_script()  # Main workflow\n# → build_container()  # Create container\n# → create_lxc_container()  # Actual creation\n```\n\n### Advanced Configuration\n\n```bash\n# Set advanced variables\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"4\"\nexport var_ram=\"4096\"\nexport var_disk=\"20\"\n\n# Call advanced functions\nadvanced_settings()  # Interactive configuration\n# → select_storage()  # Storage selection\n# → detect_gpu_devices()  # GPU detection\n```\n\n### GPU Passthrough\n\n```bash\n# Enable GPU passthrough\nexport GPU_APPS=\"plex\"\nexport var_gpu=\"nvidia\"\n\n# Call GPU functions\ndetect_gpu_devices()  # Detect hardware\nconfigure_gpu_passthrough()  # Configure passthrough\nfix_gpu_gids()  # Fix permissions\n```\n\n### Settings Persistence\n\n```bash\n# Save settings as defaults\nexport SAVE_DEFAULTS=\"true\"\nexport SAVE_APP_DEFAULTS=\"true\"\n\n# Call persistence functions\ndefault_var_settings()  # Save global defaults\nmaybe_offer_save_app_defaults()  # Save app defaults\n```\n\n### Container Resource & ID Management\n\n#### `validate_container_id()`\n**Purpose**: Validates if a container ID is available for use.\n**Parameters**: `ctid` (Integer)\n**Returns**: `0` if available, `1` if already in use or invalid.\n**Description**: Checks for existing config files in `/etc/pve/lxc/` or `/etc/pve/qemu-server/`, and verifies LVM logical volumes.\n\n#### `get_valid_container_id()`\n**Purpose**: Returns the next available, unused container ID.\n**Parameters**: `suggested_id` (Optional)\n**Returns**: A valid container ID string.\n**Description**: If the suggested ID is taken, it increments until it finds an available one.\n\n#### `maxkeys_check()`\n**Purpose**: Ensures host kernel parameters support high numbers of keys (required for some apps).\n**Parameters**: None\n**Description**: Checks and optionally updates `kernel.keys.maxkeys` and `kernel.keys.maxbytes`.\n\n#### `get_current_ip()`\n**Purpose**: Retrieves the current IP address of the container.\n**Parameters**: `ctid` (Integer)\n**Returns**: IP address string.\n\n#### `update_motd_ip()`\n**Purpose**: Updates the Message of the Day (MOTD) file with the container's IP.\n**Parameters**: None\n\n## Function Error Handling\n\n### Validation Functions\n\n- `validate_settings()`: Returns 0 for valid, 1 for invalid\n- `check_conflicts()`: Returns 0 for no conflicts, 1 for conflicts\n\n### Error Recovery\n\n- `cleanup_on_error()`: Cleans up on any error\n- Error codes are propagated up the call stack\n- Critical errors cause script termination\n\n### Error Types\n\n1. **Configuration Errors**: Invalid settings or conflicts\n2. **Resource Errors**: Insufficient resources or conflicts\n3. **Network Errors**: Invalid network configuration\n4. **Storage Errors**: Storage not available or invalid\n5. **GPU Errors**: GPU configuration failures\n6. **Container Creation Errors**: LXC creation failures\n"
  },
  {
    "path": "docs/misc/build.func/BUILD_FUNC_USAGE_EXAMPLES.md",
    "content": "# build.func Usage Examples\n\n## Overview\n\nThis document provides practical usage examples for `build.func`, covering common scenarios, CLI examples, and environment variable combinations.\n\n## Basic Usage Examples\n\n### 1. Simple Container Creation\n\n**Scenario**: Create a basic Plex media server container\n\n```bash\n# Set basic environment variables\nexport APP=\"plex\"\nexport CTID=\"100\"\nexport var_hostname=\"plex-server\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"4\"\nexport var_ram=\"4096\"\nexport var_disk=\"20\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.100\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\n\n# Execute build.func\nsource build.func\n```\n\n**Expected Output**:\n```\nCreating Plex container...\nContainer ID: 100\nHostname: plex-server\nOS: Debian 12\nResources: 4 CPU, 4GB RAM, 20GB Disk\nNetwork: 192.168.1.100/24\nContainer created successfully!\n```\n\n### 2. Advanced Configuration\n\n**Scenario**: Create a Nextcloud container with custom settings\n\n```bash\n# Set advanced environment variables\nexport APP=\"nextcloud\"\nexport CTID=\"101\"\nexport var_hostname=\"nextcloud-server\"\nexport var_os=\"ubuntu\"\nexport var_version=\"22.04\"\nexport var_cpu=\"6\"\nexport var_ram=\"8192\"\nexport var_disk=\"50\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.101\"\nexport var_vlan=\"100\"\nexport var_mtu=\"9000\"\nexport var_template_storage=\"ssd-storage\"\nexport var_container_storage=\"ssd-storage\"\nexport var_fuse=\"yes\"\nexport var_tun=\"yes\"\nexport SSH=\"true\"\n\n# Execute build.func\nsource build.func\n```\n\n### 3. GPU Passthrough Configuration\n\n**Scenario**: Create a Jellyfin container with NVIDIA GPU passthrough\n\n```bash\n# Set GPU passthrough variables\nexport APP=\"jellyfin\"\nexport CTID=\"102\"\nexport var_hostname=\"jellyfin-server\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"8\"\nexport var_ram=\"16384\"\nexport var_disk=\"30\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.102\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\nexport GPU_APPS=\"jellyfin\"\nexport var_gpu=\"nvidia\"\nexport ENABLE_PRIVILEGED=\"true\"\nexport ENABLE_FUSE=\"true\"\nexport ENABLE_TUN=\"true\"\n\n# Execute build.func\nsource build.func\n```\n\n## Silent/Non-Interactive Examples\n\n### 1. Automated Deployment\n\n**Scenario**: Deploy multiple containers without user interaction\n\n```bash\n#!/bin/bash\n# Automated deployment script\n\n# Function to create container\ncreate_container() {\n    local app=$1\n    local ctid=$2\n    local ip=$3\n\n    export APP=\"$app\"\n    export CTID=\"$ctid\"\n    export var_hostname=\"${app}-server\"\n    export var_os=\"debian\"\n    export var_version=\"12\"\n    export var_cpu=\"2\"\n    export var_ram=\"2048\"\n    export var_disk=\"10\"\n    export var_net=\"vmbr0\"\n    export var_gateway=\"192.168.1.1\"\n    export var_ip=\"$ip\"\n    export var_template_storage=\"local\"\n    export var_container_storage=\"local\"\n    export ENABLE_FUSE=\"true\"\n    export ENABLE_TUN=\"true\"\n    export SSH=\"true\"\n\n    source build.func\n}\n\n# Create multiple containers\ncreate_container \"plex\" \"100\" \"192.168.1.100\"\ncreate_container \"nextcloud\" \"101\" \"192.168.1.101\"\ncreate_container \"nginx\" \"102\" \"192.168.1.102\"\n```\n\n### 2. Development Environment Setup\n\n**Scenario**: Create development containers with specific configurations\n\n```bash\n#!/bin/bash\n# Development environment setup\n\n# Development container configuration\nexport APP=\"dev-container\"\nexport CTID=\"200\"\nexport var_hostname=\"dev-server\"\nexport var_os=\"ubuntu\"\nexport var_version=\"22.04\"\nexport var_cpu=\"4\"\nexport var_ram=\"4096\"\nexport var_disk=\"20\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.200\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\nexport ENABLE_NESTING=\"true\"\nexport ENABLE_PRIVILEGED=\"true\"\nexport ENABLE_FUSE=\"true\"\nexport ENABLE_TUN=\"true\"\nexport SSH=\"true\"\n\n# Execute build.func\nsource build.func\n```\n\n## Network Configuration Examples\n\n### 1. VLAN Configuration\n\n**Scenario**: Create container with VLAN support\n\n```bash\n# VLAN configuration\nexport APP=\"web-server\"\nexport CTID=\"300\"\nexport var_hostname=\"web-server\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"2\"\nexport var_ram=\"2048\"\nexport var_disk=\"10\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.100.1\"\nexport var_ip=\"192.168.100.100\"\nexport var_vlan=\"100\"\nexport var_mtu=\"1500\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\n\nsource build.func\n```\n\n### 2. IPv6 Configuration\n\n**Scenario**: Create container with IPv6 support\n\n```bash\n# IPv6 configuration\nexport APP=\"ipv6-server\"\nexport CTID=\"301\"\nexport var_hostname=\"ipv6-server\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"2\"\nexport var_ram=\"2048\"\nexport var_disk=\"10\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.101\"\nexport var_ipv6=\"2001:db8::101\"\nexport IPV6_METHOD=\"static\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\n\nsource build.func\n```\n\n## Storage Configuration Examples\n\n### 1. Custom Storage Locations\n\n**Scenario**: Use different storage for templates and containers\n\n```bash\n# Custom storage configuration\nexport APP=\"storage-test\"\nexport CTID=\"400\"\nexport var_hostname=\"storage-test\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"2\"\nexport var_ram=\"2048\"\nexport var_disk=\"10\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.140\"\nexport var_template_storage=\"nfs-storage\"\nexport var_container_storage=\"ssd-storage\"\n\nsource build.func\n```\n\n### 2. High-Performance Storage\n\n**Scenario**: Use high-performance storage for resource-intensive applications\n\n```bash\n# High-performance storage configuration\nexport APP=\"database-server\"\nexport CTID=\"401\"\nexport var_hostname=\"database-server\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"8\"\nexport var_ram=\"16384\"\nexport var_disk=\"100\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.141\"\nexport var_template_storage=\"nvme-storage\"\nexport var_container_storage=\"nvme-storage\"\n\nsource build.func\n```\n\n## Feature Configuration Examples\n\n### 1. Privileged Container\n\n**Scenario**: Create privileged container for system-level access\n\n```bash\n# Privileged container configuration\nexport APP=\"system-container\"\nexport CTID=\"500\"\nexport var_hostname=\"system-container\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"4\"\nexport var_ram=\"4096\"\nexport var_disk=\"20\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.150\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\nexport ENABLE_PRIVILEGED=\"true\"\nexport ENABLE_FUSE=\"true\"\nexport ENABLE_TUN=\"true\"\nexport ENABLE_KEYCTL=\"true\"\nexport ENABLE_MOUNT=\"true\"\n\nsource build.func\n```\n\n### 2. Unprivileged Container\n\n**Scenario**: Create secure unprivileged container\n\n```bash\n# Unprivileged container configuration\nexport APP=\"secure-container\"\nexport CTID=\"501\"\nexport var_hostname=\"secure-container\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"2\"\nexport var_ram=\"2048\"\nexport var_disk=\"10\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.151\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\nexport ENABLE_UNPRIVILEGED=\"true\"\nexport ENABLE_FUSE=\"true\"\nexport ENABLE_TUN=\"true\"\n\nsource build.func\n```\n\n## Settings Persistence Examples\n\n### 1. Save Global Defaults\n\n**Scenario**: Save current settings as global defaults\n\n```bash\n# Save global defaults\nexport APP=\"default-test\"\nexport CTID=\"600\"\nexport var_hostname=\"default-test\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"2\"\nexport var_ram=\"2048\"\nexport var_disk=\"10\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.160\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\nexport SAVE_DEFAULTS=\"true\"\n\nsource build.func\n```\n\n### 2. Save App-Specific Defaults\n\n**Scenario**: Save settings as app-specific defaults\n\n```bash\n# Save app-specific defaults\nexport APP=\"plex\"\nexport CTID=\"601\"\nexport var_hostname=\"plex-server\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"4\"\nexport var_ram=\"4096\"\nexport var_disk=\"20\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.161\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\nexport SAVE_APP_DEFAULTS=\"true\"\n\nsource build.func\n```\n\n## Error Handling Examples\n\n### 1. Validation Error Handling\n\n**Scenario**: Handle configuration validation errors\n\n```bash\n#!/bin/bash\n# Error handling example\n\n# Set invalid configuration\nexport APP=\"error-test\"\nexport CTID=\"700\"\nexport var_hostname=\"error-test\"\nexport var_os=\"invalid-os\"\nexport var_version=\"invalid-version\"\nexport var_cpu=\"invalid-cpu\"\nexport var_ram=\"invalid-ram\"\nexport var_disk=\"invalid-disk\"\nexport var_net=\"invalid-network\"\nexport var_gateway=\"invalid-gateway\"\nexport var_ip=\"invalid-ip\"\n\n# Execute with error handling\nif source build.func; then\n    echo \"Container created successfully!\"\nelse\n    echo \"Error: Container creation failed!\"\n    echo \"Please check your configuration and try again.\"\nfi\n```\n\n### 2. Storage Error Handling\n\n**Scenario**: Handle storage selection errors\n\n```bash\n#!/bin/bash\n# Storage error handling\n\n# Set invalid storage\nexport APP=\"storage-error-test\"\nexport CTID=\"701\"\nexport var_hostname=\"storage-error-test\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"2\"\nexport var_ram=\"2048\"\nexport var_disk=\"10\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.170\"\nexport var_template_storage=\"nonexistent-storage\"\nexport var_container_storage=\"nonexistent-storage\"\n\n# Execute with error handling\nif source build.func; then\n    echo \"Container created successfully!\"\nelse\n    echo \"Error: Storage not available!\"\n    echo \"Please check available storage and try again.\"\nfi\n```\n\n## Integration Examples\n\n### 1. With Install Scripts\n\n**Scenario**: Integrate with application install scripts\n\n```bash\n#!/bin/bash\n# Integration with install scripts\n\n# Create container\nexport APP=\"plex\"\nexport CTID=\"800\"\nexport var_hostname=\"plex-server\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"4\"\nexport var_ram=\"4096\"\nexport var_disk=\"20\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.180\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\n\n# Create container\nsource build.func\n\n# Run install script\nif [ -f \"plex-install.sh\" ]; then\n    source plex-install.sh\nelse\n    echo \"Install script not found!\"\nfi\n```\n\n### 2. With Monitoring\n\n**Scenario**: Integrate with monitoring systems\n\n```bash\n#!/bin/bash\n# Monitoring integration\n\n# Create container with monitoring\nexport APP=\"monitored-app\"\nexport CTID=\"801\"\nexport var_hostname=\"monitored-app\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"2\"\nexport var_ram=\"2048\"\nexport var_disk=\"10\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.181\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\nexport DIAGNOSTICS=\"true\"\n\n# Create container\nsource build.func\n\n# Set up monitoring\nif [ -f \"monitoring-setup.sh\" ]; then\n    source monitoring-setup.sh\nfi\n```\n\n## Best Practices\n\n### 1. Environment Variable Management\n\n```bash\n#!/bin/bash\n# Best practice: Environment variable management\n\n# Set configuration file\nCONFIG_FILE=\"/etc/build.func.conf\"\n\n# Load configuration if exists\nif [ -f \"$CONFIG_FILE\" ]; then\n    source \"$CONFIG_FILE\"\nfi\n\n# Set required variables\nexport APP=\"${APP:-plex}\"\nexport CTID=\"${CTID:-100}\"\nexport var_hostname=\"${var_hostname:-plex-server}\"\nexport var_os=\"${var_os:-debian}\"\nexport var_version=\"${var_version:-12}\"\nexport var_cpu=\"${var_cpu:-2}\"\nexport var_ram=\"${var_ram:-2048}\"\nexport var_disk=\"${var_disk:-10}\"\nexport var_net=\"${var_net:-vmbr0}\"\nexport var_gateway=\"${var_gateway:-192.168.1.1}\"\nexport var_ip=\"${var_ip:-192.168.1.100}\"\nexport var_template_storage=\"${var_template_storage:-local}\"\nexport var_container_storage=\"${var_container_storage:-local}\"\n\n# Execute build.func\nsource build.func\n```\n\n### 2. Error Handling and Logging\n\n```bash\n#!/bin/bash\n# Best practice: Error handling and logging\n\n# Set log file\nLOG_FILE=\"/var/log/build.func.log\"\n\n# Function to log messages\nlog_message() {\n    echo \"$(date): $1\" >> \"$LOG_FILE\"\n}\n\n# Function to create container with error handling\ncreate_container() {\n    local app=$1\n    local ctid=$2\n\n    log_message \"Starting container creation for $app (ID: $ctid)\"\n\n    # Set variables\n    export APP=\"$app\"\n    export CTID=\"$ctid\"\n    export var_hostname=\"${app}-server\"\n    export var_os=\"debian\"\n    export var_version=\"12\"\n    export var_cpu=\"2\"\n    export var_ram=\"2048\"\n    export var_disk=\"10\"\n    export var_net=\"vmbr0\"\n    export var_gateway=\"192.168.1.1\"\n    export var_ip=\"192.168.1.$ctid\"\n    export var_template_storage=\"local\"\n    export var_container_storage=\"local\"\n\n    # Create container\n    if source build.func; then\n        log_message \"Container $app created successfully (ID: $ctid)\"\n        return 0\n    else\n        log_message \"Error: Failed to create container $app (ID: $ctid)\"\n        return 1\n    fi\n}\n\n# Create containers\ncreate_container \"plex\" \"100\"\ncreate_container \"nextcloud\" \"101\"\ncreate_container \"nginx\" \"102\"\n```\n"
  },
  {
    "path": "docs/misc/build.func/README.md",
    "content": "# build.func Documentation\n\n## Overview\n\nThis directory contains comprehensive documentation for the `build.func` script, which is the core orchestration script for Proxmox LXC container creation in the Community Scripts project.\n\n## Documentation Files\n\n### 🎛️ [BUILD_FUNC_ADVANCED_SETTINGS.md](./BUILD_FUNC_ADVANCED_SETTINGS.md)\nComplete reference for the 28-step Advanced Settings wizard, including all configurable options and their inheritance behavior.\n\n**Contents:**\n- All 28 wizard steps explained\n- Default value inheritance\n- Feature matrix (when to enable each feature)\n- Confirmation summary format\n- Usage examples\n\n### 📊 [BUILD_FUNC_FLOWCHART.md](./BUILD_FUNC_FLOWCHART.md)\nVisual ASCII flowchart showing the main execution flow, decision trees, and key decision points in the build.func script.\n\n**Contents:**\n- Main execution flow diagram\n- Installation mode selection flows\n- Storage selection workflow\n- GPU passthrough decision logic\n- Variable precedence chain\n- Error handling flow\n- Integration points\n\n### 🔧 [BUILD_FUNC_ENVIRONMENT_VARIABLES.md](./BUILD_FUNC_ENVIRONMENT_VARIABLES.md)\nComplete reference of all environment variables used in build.func, organized by category and usage context.\n\n**Contents:**\n- Core container variables\n- Operating system variables\n- Resource configuration variables\n- Network configuration variables\n- Storage configuration variables\n- Feature flags\n- GPU passthrough variables\n- API and diagnostics variables\n- Settings persistence variables\n- Variable precedence chain\n- Critical variables for non-interactive use\n- Common variable combinations\n\n### 📚 [BUILD_FUNC_FUNCTIONS_REFERENCE.md](./BUILD_FUNC_FUNCTIONS_REFERENCE.md)\nAlphabetical function reference with detailed descriptions, parameters, dependencies, and usage information.\n\n**Contents:**\n- Initialization functions\n- UI and menu functions\n- Storage functions\n- Container creation functions\n- GPU and hardware functions\n- Settings persistence functions\n- Utility functions\n- Function call flow\n- Function dependencies\n- Function usage examples\n- Function error handling\n\n### 🔄 [BUILD_FUNC_EXECUTION_FLOWS.md](./BUILD_FUNC_EXECUTION_FLOWS.md)\nDetailed execution flows for different installation modes and scenarios, including variable precedence and decision trees.\n\n**Contents:**\n- Default install flow\n- Advanced install flow\n- My defaults flow\n- App defaults flow\n- Variable precedence chain\n- Storage selection logic\n- GPU passthrough flow\n- Network configuration flow\n- Container creation flow\n- Error handling flows\n- Integration flows\n- Performance considerations\n\n### 🏗️ [BUILD_FUNC_ARCHITECTURE.md](./BUILD_FUNC_ARCHITECTURE.md)\nHigh-level architectural overview including module dependencies, data flow, integration points, and system architecture.\n\n**Contents:**\n- High-level architecture diagram\n- Module dependencies\n- Data flow architecture\n- Integration architecture\n- System architecture components\n- User interface components\n- Security architecture\n- Performance architecture\n- Deployment architecture\n- Maintenance architecture\n- Future architecture considerations\n\n### 💡 [BUILD_FUNC_USAGE_EXAMPLES.md](./BUILD_FUNC_USAGE_EXAMPLES.md)\nPractical usage examples covering common scenarios, CLI examples, and environment variable combinations.\n\n**Contents:**\n- Basic usage examples\n- Silent/non-interactive examples\n- Network configuration examples\n- Storage configuration examples\n- Feature configuration examples\n- Settings persistence examples\n- Error handling examples\n- Integration examples\n- Best practices\n\n## Quick Start Guide\n\n### For New Users\n1. Start with [BUILD_FUNC_FLOWCHART.md](./BUILD_FUNC_FLOWCHART.md) to understand the overall flow\n2. Review [BUILD_FUNC_ENVIRONMENT_VARIABLES.md](./BUILD_FUNC_ENVIRONMENT_VARIABLES.md) for configuration options\n3. Follow examples in [BUILD_FUNC_USAGE_EXAMPLES.md](./BUILD_FUNC_USAGE_EXAMPLES.md)\n\n### For Developers\n1. Read [BUILD_FUNC_ARCHITECTURE.md](./BUILD_FUNC_ARCHITECTURE.md) for system overview\n2. Study [BUILD_FUNC_FUNCTIONS_REFERENCE.md](./BUILD_FUNC_FUNCTIONS_REFERENCE.md) for function details\n3. Review [BUILD_FUNC_EXECUTION_FLOWS.md](./BUILD_FUNC_EXECUTION_FLOWS.md) for implementation details\n\n### For System Administrators\n1. Focus on [BUILD_FUNC_USAGE_EXAMPLES.md](./BUILD_FUNC_USAGE_EXAMPLES.md) for deployment scenarios\n2. Review [BUILD_FUNC_ENVIRONMENT_VARIABLES.md](./BUILD_FUNC_ENVIRONMENT_VARIABLES.md) for configuration management\n3. Check [BUILD_FUNC_ARCHITECTURE.md](./BUILD_FUNC_ARCHITECTURE.md) for security and performance considerations\n\n## Key Concepts\n\n### Variable Precedence\nVariables are resolved in this order (highest to lowest priority):\n1. Hard environment variables (set before script execution)\n2. App-specific .vars file (`/usr/local/community-scripts/defaults/<app>.vars`)\n3. Global default.vars file (`/usr/local/community-scripts/default.vars`)\n4. Built-in defaults (set in `base_settings()` function)\n\n### Installation Modes\n- **Default Install**: Uses built-in defaults, minimal prompts\n- **Advanced Install**: Full interactive configuration via whiptail\n- **My Defaults**: Loads from global default.vars file\n- **App Defaults**: Loads from app-specific .vars file\n\n### Storage Selection Logic\n1. If only 1 storage exists for content type → auto-select\n2. If preselected via environment variables → validate and use\n3. Otherwise → prompt user via whiptail\n\n### GPU Passthrough Flow\n1. Detect hardware (Intel/AMD/NVIDIA)\n2. Check if app is in GPU_APPS list OR container is privileged\n3. Auto-select if single GPU type, prompt if multiple\n4. Configure `/etc/pve/lxc/<ctid>.conf` with proper device entries\n5. Fix GIDs post-creation to match container's video/render groups\n\n## Common Use Cases\n\n### Basic Container Creation\n```bash\nexport APP=\"plex\"\nexport CTID=\"100\"\nexport var_hostname=\"plex-server\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"4\"\nexport var_ram=\"4096\"\nexport var_disk=\"20\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.100\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\n\nsource build.func\n```\n\n### GPU Passthrough\n```bash\nexport APP=\"jellyfin\"\nexport CTID=\"101\"\nexport var_hostname=\"jellyfin-server\"\nexport var_os=\"debian\"\nexport var_version=\"12\"\nexport var_cpu=\"8\"\nexport var_ram=\"16384\"\nexport var_disk=\"30\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.101\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\nexport GPU_APPS=\"jellyfin\"\nexport var_gpu=\"nvidia\"\nexport ENABLE_PRIVILEGED=\"true\"\n\nsource build.func\n```\n\n### Silent/Non-Interactive Deployment\n```bash\n#!/bin/bash\n# Automated deployment\nexport APP=\"nginx\"\nexport CTID=\"102\"\nexport var_hostname=\"nginx-proxy\"\nexport var_os=\"alpine\"\nexport var_version=\"3.18\"\nexport var_cpu=\"1\"\nexport var_ram=\"512\"\nexport var_disk=\"2\"\nexport var_net=\"vmbr0\"\nexport var_gateway=\"192.168.1.1\"\nexport var_ip=\"192.168.1.102\"\nexport var_template_storage=\"local\"\nexport var_container_storage=\"local\"\nexport ENABLE_UNPRIVILEGED=\"true\"\n\nsource build.func\n```\n\n## Troubleshooting\n\n### Common Issues\n1. **Container creation fails**: Check resource availability and configuration validity\n2. **Storage errors**: Verify storage exists and supports required content types\n3. **Network errors**: Validate network configuration and IP address availability\n4. **GPU passthrough issues**: Check hardware detection and container privileges\n5. **Permission errors**: Verify user permissions and container privileges\n\n### Debug Mode\nEnable verbose output for debugging:\n```bash\nexport VERBOSE=\"true\"\nexport DIAGNOSTICS=\"true\"\nsource build.func\n```\n\n### Log Files\nCheck system logs for detailed error information:\n- `/var/log/syslog`\n- `/var/log/pve/lxc/<ctid>.log`\n- Container-specific logs\n\n## Contributing\n\nWhen contributing to build.func documentation:\n1. Update relevant documentation files\n2. Add examples for new features\n3. Update architecture diagrams if needed\n4. Test all examples before submitting\n5. Follow the existing documentation style\n\n## Related Documentation\n\n- [Main README](../../README.md) - Project overview\n- [Installation Guide](../../install/) - Installation scripts\n- [Container Templates](../../ct/) - Container templates\n- [Tools](../../tools/) - Additional tools and utilities\n\n## Support\n\nFor issues and questions:\n1. Check this documentation first\n2. Review the [troubleshooting section](#troubleshooting)\n3. Check existing issues in the project repository\n4. Create a new issue with detailed information\n\n---\n\n*Last updated: $(date)*\n*Documentation version: 1.0*\n"
  },
  {
    "path": "docs/misc/cloud-init.func/CLOUD_INIT_FUNC_FLOWCHART.md",
    "content": "# cloud-init.func Flowchart\n\nCloud-init VM provisioning flow.\n\n## Cloud-Init Generation and Application\n\n```\ngenerate_cloud_init()\n    ↓\ngenerate_user_data()\n    ↓\nsetup_ssh_keys()\n    ↓\nApply to VM\n    ↓\nVM Boot\n    ↓\ncloud-init phases\n├─ system\n├─ config\n└─ final\n    ↓\nVM Ready ✓\n```\n\n---\n\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/cloud-init.func/CLOUD_INIT_FUNC_FUNCTIONS_REFERENCE.md",
    "content": "# cloud-init.func Functions Reference\n\nCloud-init and VM provisioning functions.\n\n## Core Functions\n\n### generate_cloud_init()\nGenerate cloud-init configuration.\n\n### generate_user_data()\nGenerate user-data script for VM.\n\n### apply_cloud_init()\nApply cloud-init to VM.\n\n### setup_ssh_keys()\nDeploy SSH public keys.\n\n### setup_static_ip()\nConfigure static IP on VM.\n\n### setup_dns()\nConfigure DNS for VM.\n\n### setup_ipv6()\nEnable IPv6 on VM.\n\n---\n\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/cloud-init.func/CLOUD_INIT_FUNC_INTEGRATION.md",
    "content": "# cloud-init.func Integration Guide\n\nCloud-init integration with Proxmox VM provisioning.\n\n---\n\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/cloud-init.func/CLOUD_INIT_FUNC_USAGE_EXAMPLES.md",
    "content": "# cloud-init.func Usage Examples\n\nExamples for VM cloud-init configuration.\n\n### Example: Basic Cloud-Init\n\n```bash\n#!/usr/bin/env bash\n\ngenerate_cloud_init > cloud-init.yaml\nsetup_ssh_keys \"$VMID\" \"$SSH_KEY\"\napply_cloud_init \"$VMID\" cloud-init.yaml\n```\n\n---\n\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/cloud-init.func/README.md",
    "content": "# cloud-init.func Documentation\n\n## Overview\n\nThe `cloud-init.func` file provides cloud-init configuration and VM initialization functions for Proxmox VE virtual machines. It handles user data, cloud-config generation, and VM setup automation.\n\n## Purpose and Use Cases\n\n- **VM Cloud-Init Setup**: Generate and apply cloud-init configurations for VMs\n- **User Data Generation**: Create user-data scripts for VM initialization\n- **Cloud-Config**: Generate cloud-config YAML for VM provisioning\n- **SSH Key Management**: Setup SSH keys for VM access\n- **Network Configuration**: Configure networking for VMs\n- **Automated VM Provisioning**: Complete VM setup without manual intervention\n\n## Quick Reference\n\n### Key Function Groups\n- **Cloud-Init Core**: Generate and apply cloud-init configurations\n- **User Data**: Create initialization scripts for VMs\n- **SSH Setup**: Deploy SSH keys automatically\n- **Network Configuration**: Setup networking during VM provisioning\n- **VM Customization**: Apply custom settings to VMs\n\n### Dependencies\n- **External**: `cloud-init`, `curl`, `qemu-img`\n- **Internal**: Uses functions from `core.func`, `error_handler.func`\n\n### Integration Points\n- Used by: VM creation scripts (vm/*.sh)\n- Uses: Environment variables from build.func\n- Provides: VM initialization and cloud-init services\n\n## Documentation Files\n\n### 📊 [CLOUD_INIT_FUNC_FLOWCHART.md](./CLOUD_INIT_FUNC_FLOWCHART.md)\nVisual execution flows showing cloud-init generation and VM provisioning workflows.\n\n### 📚 [CLOUD_INIT_FUNC_FUNCTIONS_REFERENCE.md](./CLOUD_INIT_FUNC_FUNCTIONS_REFERENCE.md)\nComplete alphabetical reference of all cloud-init functions.\n\n### 💡 [CLOUD_INIT_FUNC_USAGE_EXAMPLES.md](./CLOUD_INIT_FUNC_USAGE_EXAMPLES.md)\nPractical examples for VM cloud-init setup and customization.\n\n### 🔗 [CLOUD_INIT_FUNC_INTEGRATION.md](./CLOUD_INIT_FUNC_INTEGRATION.md)\nHow cloud-init.func integrates with VM creation and Proxmox workflows.\n\n## Key Features\n\n### Cloud-Init Configuration\n- **User Data Generation**: Create custom initialization scripts\n- **Cloud-Config YAML**: Generate standardized cloud-config\n- **SSH Keys**: Automatically deploy public keys\n- **Package Installation**: Install packages during VM boot\n- **Custom Commands**: Run arbitrary commands on first boot\n\n### VM Network Setup\n- **DHCP Configuration**: Configure DHCP for automatic IP assignment\n- **Static IP Setup**: Configure static IP addresses\n- **IPv6 Support**: Enable IPv6 on VMs\n- **DNS Configuration**: Set DNS servers for VM\n- **Firewall Rules**: Basic firewall configuration\n\n### Security Features\n- **SSH Key Injection**: Deploy SSH keys during VM creation\n- **Disable Passwords**: Disable password authentication\n- **Sudoers Configuration**: Setup sudo access\n- **User Management**: Create and configure users\n\n## Function Categories\n\n### 🔹 Cloud-Init Core Functions\n- `generate_cloud_init()` - Create cloud-init configuration\n- `generate_user_data()` - Generate user-data script\n- `apply_cloud_init()` - Apply cloud-init to VM\n- `validate_cloud_init()` - Validate cloud-config syntax\n\n### 🔹 SSH & Security Functions\n- `setup_ssh_keys()` - Deploy SSH public keys\n- `setup_sudo()` - Configure sudoers\n- `create_user()` - Create new user account\n- `disable_password_auth()` - Disable password login\n\n### 🔹 Network Configuration Functions\n- `setup_dhcp()` - Configure DHCP networking\n- `setup_static_ip()` - Configure static IP\n- `setup_dns()` - Configure DNS servers\n- `setup_ipv6()` - Enable IPv6 support\n\n### 🔹 VM Customization Functions\n- `install_packages()` - Install packages during boot\n- `run_custom_commands()` - Execute custom scripts\n- `configure_hostname()` - Set VM hostname\n- `configure_timezone()` - Set VM timezone\n\n## Cloud-Init Workflow\n\n```\nVM Created\n    ↓\ncloud-init (system) boot phase\n    ↓\nUser-Data Script Execution\n    ↓\n├─ Install packages\n├─ Deploy SSH keys\n├─ Configure network\n└─ Create users\n    ↓\ncloud-init config phase\n    ↓\nApply cloud-config settings\n    ↓\ncloud-init final phase\n    ↓\nVM Ready for Use\n```\n\n## Common Usage Patterns\n\n### Basic VM Setup with Cloud-Init\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\n# Generate cloud-init configuration\ncat > cloud-init.yaml <<EOF\n#cloud-config\nhostname: myvm\ntimezone: UTC\n\npackages:\n  - curl\n  - wget\n  - git\n\nusers:\n  - name: ubuntu\n    ssh_authorized_keys:\n      - ssh-rsa AAAAB3...\n    sudo: ALL=(ALL) NOPASSWD:ALL\n\nbootcmd:\n  - echo \"VM initializing...\"\n\nruncmd:\n  - apt-get update\n  - apt-get upgrade -y\nEOF\n\n# Apply to VM\nqm set VMID --cicustom local:snippets/cloud-init.yaml\n```\n\n### With SSH Key Deployment\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\n# Get SSH public key\nSSH_KEY=$(cat ~/.ssh/id_rsa.pub)\n\n# Generate cloud-init with SSH key\ngenerate_user_data > user-data.txt\n\n# Inject SSH key\nsetup_ssh_keys \"$VMID\" \"$SSH_KEY\"\n\n# Create VM with cloud-init\nqm create $VMID ... --cicustom local:snippets/user-data\n```\n\n### Network Configuration\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\n# Static IP setup\nsetup_static_ip \"192.168.1.100\" \"255.255.255.0\" \"192.168.1.1\"\n\n# DNS configuration\nsetup_dns \"8.8.8.8 8.8.4.4\"\n\n# IPv6 support\nsetup_ipv6\n```\n\n## Best Practices\n\n### ✅ DO\n- Validate cloud-config syntax before applying\n- Use cloud-init for automated setup\n- Deploy SSH keys for secure access\n- Test cloud-init configuration in non-production first\n- Use DHCP for easier VM deployment\n- Document custom cloud-init configurations\n- Version control cloud-init templates\n\n### ❌ DON'T\n- Use weak SSH keys or passwords\n- Leave SSH password authentication enabled\n- Hardcode credentials in cloud-init\n- Skip validation of cloud-config\n- Use untrusted cloud-init sources\n- Forget to set timezone on VMs\n- Mix cloud-init versions\n\n## Cloud-Config Format\n\n### Example Cloud-Config\n```yaml\n#cloud-config\n# This is a comment\n\n# System configuration\nhostname: myvm\ntimezone: UTC\npackage_upgrade: true\n\n# Packages to install\npackages:\n  - curl\n  - wget\n  - git\n  - build-essential\n\n# SSH keys for users\nssh_authorized_keys:\n  - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC...\n\n# Users to create\nusers:\n  - name: ubuntu\n    home: /home/ubuntu\n    shell: /bin/bash\n    sudo: ['ALL=(ALL) NOPASSWD:ALL']\n    ssh_authorized_keys:\n      - ssh-rsa AAAAB3...\n\n# Commands to run on boot\nruncmd:\n  - apt-get update\n  - apt-get upgrade -y\n  - systemctl restart ssh\n\n# Files to create\nwrite_files:\n  - path: /etc/profile.d/custom.sh\n    content: |\n      export CUSTOM_VAR=\"value\"\n```\n\n## VM Network Configuration\n\n### DHCP Configuration\n```bash\nnetwork:\n  version: 2\n  ethernets:\n    eth0:\n      dhcp4: true\n      dhcp6: true\n```\n\n### Static IP Configuration\n```bash\nnetwork:\n  version: 2\n  ethernets:\n    eth0:\n      addresses:\n        - 192.168.1.100/24\n      gateway4: 192.168.1.1\n      nameservers:\n        addresses: [8.8.8.8, 8.8.4.4]\n```\n\n## Troubleshooting\n\n### \"Cloud-Init Configuration Not Applied\"\n```bash\n# Check cloud-init status in VM\ncloud-init status\ncloud-init status --long\n\n# View cloud-init logs\ntail /var/log/cloud-init.log\n```\n\n### \"SSH Keys Not Deployed\"\n```bash\n# Verify SSH key in cloud-config\ngrep ssh_authorized_keys user-data.txt\n\n# Check permissions\nls -la ~/.ssh/authorized_keys\n```\n\n### \"Network Not Configured\"\n```bash\n# Check network configuration\nip addr show\nip route show\n\n# View netplan (if used)\ncat /etc/netplan/*.yaml\n```\n\n### \"Packages Failed to Install\"\n```bash\n# Check cloud-init package log\ntail /var/log/cloud-init-output.log\n\n# Manual package installation\napt-get update && apt-get install -y package-name\n```\n\n## Related Documentation\n\n- **[install.func/](../install.func/)** - Container installation (similar workflow)\n- **[core.func/](../core.func/)** - Utility functions\n- **[error_handler.func/](../error_handler.func/)** - Error handling\n- **[UPDATED_APP-install.md](../../UPDATED_APP-install.md)** - Application setup guide\n- **Proxmox Docs**: https://pve.proxmox.com/wiki/Cloud-Init\n\n## Recent Updates\n\n### Version 2.0 (Dec 2025)\n- ✅ Enhanced cloud-init validation\n- ✅ Improved SSH key deployment\n- ✅ Better network configuration support\n- ✅ Added IPv6 support\n- ✅ Streamlined user and package setup\n\n---\n\n**Last Updated**: December 2025\n**Maintainers**: community-scripts team\n**License**: MIT\n"
  },
  {
    "path": "docs/misc/core.func/CORE_FLOWCHART.md",
    "content": "# core.func Execution Flowchart\n\n## Main Execution Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                            core.func Loading                                   │\n│  Entry point when core.func is sourced by other scripts                        │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Load Prevention Check                                   │\n│  • Check if _CORE_FUNC_LOADED is set                                          │\n│  • Return early if already loaded                                              │\n│  • Set _CORE_FUNC_LOADED=1 to prevent reloading                               │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        LOAD_FUNCTIONS()                                        │\n│  Main function loader - sets up all core utilities                             │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Core Function Loading Sequence                          │\n│                                                                                 │\n│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────────┐     │\n│  │   color()       │  │ formatting()   │  │        icons()              │     │\n│  │                 │  │                │  │                             │     │\n│  │ • Set ANSI      │  │ • Set format   │  │ • Set symbolic icons        │     │\n│  │   color codes   │  │   helpers      │  │ • Define message           │     │\n│  │ • Define        │  │ • Tab, bold,   │  │   symbols                  │     │\n│  │   colors        │  │   line reset  │  │ • Status indicators        │     │\n│  └─────────────────┘  └─────────────────┘  └─────────────────────────────┘     │\n│                                                                                 │\n│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────────┐     │\n│  │ default_vars()  │  │ set_std_mode()  │  │    Additional Functions   │     │\n│  │                 │  │                 │  │                             │     │\n│  │ • Set retry     │  │ • Set verbose   │  │ • Add more functions       │     │\n│  │   variables     │  │   mode          │  │   as needed                │     │\n│  │ • Initialize    │  │ • Configure     │  │                             │     │\n│  │   counters      │  │   STD variable  │  │                             │     │\n│  └─────────────────┘  └─────────────────┘  └─────────────────────────────┘     │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## System Check Functions Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        System Validation Flow                                  │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        PVE_CHECK()                                        │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Get PVE       │    │   Check PVE     │    │   Check PVE         │ │ │\n│  │  │   Version       │    │   8.x Support   │    │   9.x Support       │ │ │\n│  │  │                 │    │                 │    │                     │ │ │\n│  │  │ • pveversion    │    │ • Allow 8.0-8.9│    │ • Allow ONLY 9.0    │ │ │\n│  │  │ • Parse version │    │ • Reject others │    │ • Reject 9.1+       │ │ │\n│  │  │ • Extract       │    │ • Exit if       │    │ • Exit if           │ │ │\n│  │  │   major.minor  │    │   unsupported   │    │   unsupported        │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        ARCH_CHECK()                                       │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Check         │    │   AMD64 Check   │    │   PiMox Warning     │ │ │\n│  │  │   Architecture  │    │                 │    │                     │ │ │\n│  │  │                 │    │ • dpkg --print- │    │ • Show PiMox       │ │ │\n│  │  │ • Get system    │    │   architecture  │    │   message           │ │ │\n│  │  │   architecture  │    │ • Must be       │    │ • Point to ARM64   │ │ │\n│  │  │ • Compare with  │    │   \"amd64\"       │    │   support          │ │ │\n│  │  │   \"amd64\"       │    │ • Exit if not   │    │ • Exit script      │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        SHELL_CHECK()                                      │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Check         │    │   Bash Check    │    │   Error Handling    │ │ │\n│  │  │   Shell Type    │    │                 │    │                     │ │ │\n│  │  │                 │    │ • ps -p $$ -o   │    │ • Clear screen      │ │ │\n│  │  │ • Get current   │    │   comm=         │    │ • Show error        │ │ │\n│  │  │   shell         │    │ • Must be       │    │ • Sleep and exit   │ │ │\n│  │  │ • Compare with  │    │   \"bash\"        │    │                     │ │ │\n│  │  │   \"bash\"        │    │ • Exit if not   │    │                     │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        ROOT_CHECK()                                       │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Check         │    │   Root Check    │    │   Sudo Check         │ │ │\n│  │  │   User ID       │    │                 │    │                     │ │ │\n│  │  │                 │    │ • id -u         │    │ • Check parent       │ │ │\n│  │  │ • Get user ID   │    │ • Must be 0     │    │   process            │ │ │\n│  │  │ • Check if      │    │ • Exit if not   │    │ • Detect sudo       │ │ │\n│  │  │   root (0)      │    │   root          │    │   usage             │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Message System Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Message System Flow                                     │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        MSG_INFO()                                         │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Message       │    │   Duplicate     │    │   Display Mode     │ │ │\n│  │  │   Validation    │    │   Check         │    │   Selection         │ │ │\n│  │  │                 │    │                 │    │                     │ │ │\n│  │  │ • Check if      │    │ • Track shown   │    │ • Verbose mode:     │ │ │\n│  │  │   message       │    │   messages      │    │   Show directly     │ │ │\n│  │  │   exists        │    │ • Skip if       │    │ • Normal mode:      │ │ │\n│  │  │ • Return if     │    │   already       │    │   Start spinner     │ │ │\n│  │  │   empty         │    │   shown         │    │                     │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        SPINNER()                                          │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Spinner       │    │   Animation     │    │   Display            │ │ │\n│  │  │   Initialization│    │   Loop          │    │   Control            │ │ │\n│  │  │                 │    │                 │    │                     │ │ │\n│  │  │ • Define        │    │ • Cycle through │    │ • Print spinner      │ │ │\n│  │  │   characters    │    │   characters    │    │   character          │ │ │\n│  │  │ • Set index     │    │ • Sleep 0.1s    │    │ • Print message      │ │ │\n│  │  │ • Start loop    │    │ • Increment     │    │ • Clear line         │ │ │\n│  │  │                 │    │   index         │    │                     │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        STOP_SPINNER()                                     │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Get Spinner   │    │   Kill Process  │    │   Cleanup           │ │ │\n│  │  │   PID           │    │                 │    │                     │ │ │\n│  │  │                 │    │ • Send TERM     │    │ • Remove PID file   │ │ │\n│  │  │ • From          │    │ • Wait for      │    │ • Unset variables   │ │ │\n│  │  │   SPINNER_PID   │    │   termination   │    │ • Reset terminal    │ │ │\n│  │  │ • From PID      │    │ • Force kill    │    │   settings          │ │ │\n│  │  │   file          │    │   if needed     │    │                     │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Silent Execution Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        SILENT() Execution Flow                                │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        Command Execution                                   │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Setup         │    │   Execute       │    │   Capture Output    │ │ │\n│  │  │   Environment   │    │   Command       │    │                     │ │ │\n│  │  │                 │    │                 │    │ • Redirect stdout   │ │ │\n│  │  │ • Disable       │    │ • Run command   │    │   to log file       │ │ │\n│  │  │   error         │    │ • Capture       │    │ • Redirect stderr   │ │ │\n│  │  │   handling      │    │   return code   │    │   to log file       │ │ │\n│  │  │ • Remove        │    │ • Store exit    │    │ • Log all output    │ │ │\n│  │  │   traps         │    │   code         │    │                     │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        Error Handling                                     │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Check Exit    │    │   Load Error    │    │   Display Error     │ │ │\n│  │  │   Code          │    │   Handler       │    │   Information        │ │ │\n│  │  │                 │    │                 │    │                     │ │ │\n│  │  │ • If exit code  │    │ • Source        │    │ • Show error code   │ │ │\n│  │  │   != 0          │    │   error_handler │    │ • Show explanation  │ │ │\n│  │  │ • Proceed to    │    │   if needed     │    │ • Show command      │ │ │\n│  │  │   error         │    │ • Get error     │    │ • Show log lines    │ │ │\n│  │  │   handling      │    │   explanation   │    │ • Show full log     │ │ │\n│  │  │                 │    │                 │    │   command           │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        Log Management                                      │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Log File      │    │   Log Display   │    │   Log Access        │ │ │\n│  │  │   Management    │    │                 │    │                     │ │ │\n│  │  │                 │    │ • Show last 10  │    │ • Provide command   │ │ │\n│  │  │ • Create log    │    │   lines         │    │   to view full log  │ │ │\n│  │  │   file path     │    │ • Count total   │    │ • Show line count   │ │ │\n│  │  │ • Use process   │    │   lines         │    │ • Enable debugging  │ │ │\n│  │  │   ID in name    │    │ • Format        │    │                     │ │ │\n│  │  │                 │    │   output        │    │                     │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Header Management Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Header Management Flow                                 │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        GET_HEADER()                                       │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Prepare       │    │   Check Local   │    │   Download Header   │ │ │\n│  │  │   Parameters    │    │   File          │    │                     │ │ │\n│  │  │                 │    │                 │    │ • Construct URL     │ │ │\n│  │  │ • Get app name  │    │ • Check if      │    │ • Download file     │ │ │\n│  │  │   from APP      │    │   file exists   │    │ • Save to local     │ │ │\n│  │  │ • Get app type  │    │ • Check if      │    │   path              │ │ │\n│  │  │   from APP_TYPE │    │   file has      │    │ • Return success    │ │ │\n│  │  │ • Construct     │    │   content       │    │   status           │ │ │\n│  │  │   paths         │    │ • Return if     │    │                     │ │ │\n│  │  │                 │    │   available    │    │                     │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        HEADER_INFO()                                      │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Get Header    │    │   Clear Screen  │    │   Display Header    │ │ │\n│  │  │   Content       │    │                 │    │                     │ │ │\n│  │  │                 │    │ • Clear         │    │ • Show header       │ │ │\n│  │  │ • Call          │    │   terminal      │    │   content if        │ │ │\n│  │  │   get_header()  │    │ • Get terminal  │    │   available          │ │ │\n│  │  │ • Handle        │    │   width         │    │ • Format output     │ │ │\n│  │  │   errors        │    │ • Set default   │    │ • Center content    │ │ │\n│  │  │ • Return        │    │   width if      │    │   if possible       │ │ │\n│  │  │   content       │    │   needed        │    │                     │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Swap Management Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        CHECK_OR_CREATE_SWAP() Flow                            │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        Swap Detection                                     │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Check Active  │    │   Swap Found    │    │   No Swap Found     │ │ │\n│  │  │   Swap          │    │                 │    │                     │ │ │\n│  │  │                 │    │ • Show success  │    │ • Show error        │ │ │\n│  │  │ • Use swapon    │    │   message        │    │   message           │ │ │\n│  │  │   command       │    │ • Return 0      │    │ • Ask user for      │ │ │\n│  │  │ • Check for     │    │                 │    │   creation          │ │ │\n│  │  │   swap devices  │    │                 │    │ • Proceed to        │ │ │\n│  │  │ • Return        │    │                 │    │   creation flow     │ │ │\n│  │  │   status        │    │                 │    │                     │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        Swap Creation                                     │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   User Input    │    │   Size           │    │   File Creation     │ │ │\n│  │  │   Collection    │    │   Validation     │    │                     │ │ │\n│  │  │                 │    │                 │    │ • Create swap file   │ │ │\n│  │  │ • Ask for       │    │ • Validate       │    │   with dd           │ │ │\n│  │  │   confirmation  │    │   numeric input  │    │ • Set permissions    │ │ │\n│  │  │ • Convert to    │    │ • Check range    │    │ • Format swap       │ │ │\n│  │  │   lowercase     │    │ • Abort if       │    │ • Activate swap     │ │ │\n│  │  │ • Check for     │    │   invalid        │    │ • Show success      │ │ │\n│  │  │   y/yes         │    │                 │    │   message           │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Integration Points\n\n### With Other Scripts\n- **build.func**: Provides system checks and UI functions\n- **tools.func**: Uses core utilities for extended operations\n- **api.func**: Uses system checks and error handling\n- **error_handler.func**: Provides error explanations for silent execution\n\n### External Dependencies\n- **curl**: For downloading header files\n- **tput**: For terminal control (installed if missing)\n- **swapon/mkswap**: For swap management\n- **pveversion**: For Proxmox version checking\n\n### Data Flow\n- **Input**: Environment variables, command parameters\n- **Processing**: System validation, UI rendering, command execution\n- **Output**: Messages, log files, exit codes, system state changes\n"
  },
  {
    "path": "docs/misc/core.func/CORE_FUNCTIONS_REFERENCE.md",
    "content": "# core.func Functions Reference\n\n## Overview\n\nThis document provides a comprehensive alphabetical reference of all functions in `core.func`, including parameters, dependencies, usage examples, and error handling.\n\n## Function Categories\n\n### Initialization Functions\n\n#### `load_functions()`\n**Purpose**: Main function loader that initializes all core utilities\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n- Sets `__FUNCTIONS_LOADED=1` to prevent reloading\n- Calls all core function groups in sequence\n- Initializes color, formatting, icons, defaults, and standard mode\n**Dependencies**: None\n**Environment Variables Used**: `__FUNCTIONS_LOADED`\n\n**Usage Example**:\n```bash\n# Automatically called when core.func is sourced\nsource core.func\n# load_functions() is called automatically\n```\n\n### Color and Formatting Functions\n\n#### `color()`\n**Purpose**: Set ANSI color codes for styled terminal output\n**Parameters**: None\n**Returns**: None\n**Side Effects**: Sets global color variables\n**Dependencies**: None\n**Environment Variables Used**: None\n\n**Sets Variables**:\n- `YW`: Yellow\n- `YWB`: Bright yellow\n- `BL`: Blue\n- `RD`: Red\n- `BGN`: Bright green\n- `GN`: Green\n- `DGN`: Dark green\n- `CL`: Clear/reset\n\n**Usage Example**:\n```bash\ncolor\necho -e \"${GN}Success message${CL}\"\necho -e \"${RD}Error message${CL}\"\n```\n\n#### `color_spinner()`\n**Purpose**: Set color codes specifically for spinner output\n**Parameters**: None\n**Returns**: None\n**Side Effects**: Sets spinner-specific color variables\n**Dependencies**: None\n**Environment Variables Used**: None\n\n**Sets Variables**:\n- `CS_YW`: Yellow for spinner\n- `CS_YWB`: Bright yellow for spinner\n- `CS_CL`: Clear for spinner\n\n#### `formatting()`\n**Purpose**: Define formatting helpers for terminal output\n**Parameters**: None\n**Returns**: None\n**Side Effects**: Sets global formatting variables\n**Dependencies**: None\n**Environment Variables Used**: None\n\n**Sets Variables**:\n- `BFR`: Back and forward reset\n- `BOLD`: Bold text\n- `HOLD`: Space character\n- `TAB`: Two spaces\n- `TAB3`: Six spaces\n\n### Icon Functions\n\n#### `icons()`\n**Purpose**: Set symbolic icons used throughout user feedback and prompts\n**Parameters**: None\n**Returns**: None\n**Side Effects**: Sets global icon variables\n**Dependencies**: `formatting()` (for TAB variable)\n**Environment Variables Used**: `TAB`, `CL`\n\n**Sets Variables**:\n- `CM`: Check mark\n- `CROSS`: Cross mark\n- `DNSOK`: DNS success\n- `DNSFAIL`: DNS failure\n- `INFO`: Information icon\n- `OS`: Operating system icon\n- `OSVERSION`: OS version icon\n- `CONTAINERTYPE`: Container type icon\n- `DISKSIZE`: Disk size icon\n- `CPUCORE`: CPU core icon\n- `RAMSIZE`: RAM size icon\n- `SEARCH`: Search icon\n- `VERBOSE_CROPPED`: Verbose mode icon\n- `VERIFYPW`: Password verification icon\n- `CONTAINERID`: Container ID icon\n- `HOSTNAME`: Hostname icon\n- `BRIDGE`: Bridge icon\n- `NETWORK`: Network icon\n- `GATEWAY`: Gateway icon\n- `DISABLEIPV6`: IPv6 disable icon\n- `DEFAULT`: Default settings icon\n- `MACADDRESS`: MAC address icon\n- `VLANTAG`: VLAN tag icon\n- `ROOTSSH`: SSH key icon\n- `CREATING`: Creating icon\n- `ADVANCED`: Advanced settings icon\n- `FUSE`: FUSE icon\n- `HOURGLASS`: Hourglass icon\n\n### Default Variables Functions\n\n#### `default_vars()`\n**Purpose**: Set default retry and wait variables for system actions\n**Parameters**: None\n**Returns**: None\n**Side Effects**: Sets retry configuration variables\n**Dependencies**: None\n**Environment Variables Used**: None\n\n**Sets Variables**:\n- `RETRY_NUM`: Number of retry attempts (default: 10)\n- `RETRY_EVERY`: Seconds between retries (default: 3)\n- `i`: Retry counter initialized to RETRY_NUM\n\n#### `set_std_mode()`\n**Purpose**: Set default verbose mode for script execution\n**Parameters**: None\n**Returns**: None\n**Side Effects**: Sets STD variable based on VERBOSE setting\n**Dependencies**: None\n**Environment Variables Used**: `VERBOSE`\n\n**Sets Variables**:\n- `STD`: \"silent\" if VERBOSE != \"yes\", empty string if VERBOSE = \"yes\"\n\n### Silent Execution Functions\n\n#### `silent()`\n**Purpose**: Execute commands silently with detailed error reporting\n**Parameters**: `$*` - Command and arguments to execute\n**Returns**: None (exits on error)\n**Side Effects**:\n- Executes command with output redirected to log file\n- On error, displays detailed error information\n- Exits with command's exit code\n**Dependencies**: `error_handler.func` (for error explanations)\n**Environment Variables Used**: `SILENT_LOGFILE`\n\n**Usage Example**:\n```bash\nsilent apt-get update\nsilent apt-get install -y package-name\n```\n\n**Error Handling**:\n- Captures command output to `/tmp/silent.$$.log`\n- Shows error code explanation\n- Displays last 10 lines of log\n- Provides command to view full log\n\n### System Check Functions\n\n#### `shell_check()`\n**Purpose**: Verify that the script is running in Bash shell\n**Parameters**: None\n**Returns**: None (exits if not Bash)\n**Side Effects**:\n- Checks current shell process\n- Exits with error message if not Bash\n**Dependencies**: None\n**Environment Variables Used**: None\n\n**Usage Example**:\n```bash\nshell_check\n# Script continues if Bash, exits if not\n```\n\n#### `root_check()`\n**Purpose**: Ensure script is running as root user\n**Parameters**: None\n**Returns**: None (exits if not root)\n**Side Effects**:\n- Checks user ID and parent process\n- Exits with error message if not root\n**Dependencies**: None\n**Environment Variables Used**: None\n\n**Usage Example**:\n```bash\nroot_check\n# Script continues if root, exits if not\n```\n\n#### `pve_check()`\n**Purpose**: Verify Proxmox VE version compatibility\n**Parameters**: None\n**Returns**: None (exits if unsupported version)\n**Side Effects**:\n- Checks PVE version using pveversion command\n- Exits with error message if unsupported\n**Dependencies**: `pveversion` command\n**Environment Variables Used**: None\n\n**Supported Versions**:\n- Proxmox VE 8.0 - 8.9\n- Proxmox VE 9.0 (only)\n\n**Usage Example**:\n```bash\npve_check\n# Script continues if supported version, exits if not\n```\n\n#### `arch_check()`\n**Purpose**: Verify system architecture is AMD64\n**Parameters**: None\n**Returns**: None (exits if not AMD64)\n**Side Effects**:\n- Checks system architecture\n- Exits with PiMox warning if not AMD64\n**Dependencies**: `dpkg` command\n**Environment Variables Used**: None\n\n**Usage Example**:\n```bash\narch_check\n# Script continues if AMD64, exits if not\n```\n\n#### `ssh_check()`\n**Purpose**: Detect and warn about external SSH usage\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n- Checks SSH_CLIENT environment variable\n- Warns if connecting from external IP\n- Allows local connections (127.0.0.1 or host IP)\n**Dependencies**: None\n**Environment Variables Used**: `SSH_CLIENT`\n\n**Usage Example**:\n```bash\nssh_check\n# Shows warning if external SSH, continues anyway\n```\n\n### Header Management Functions\n\n#### `get_header()`\n**Purpose**: Download and cache application header files\n**Parameters**: None (uses APP and APP_TYPE variables)\n**Returns**: Header content on success, empty on failure\n**Side Effects**:\n- Downloads header from remote URL\n- Caches header locally\n- Creates directory structure if needed\n**Dependencies**: `curl` command\n**Environment Variables Used**: `APP`, `APP_TYPE`\n\n**Usage Example**:\n```bash\nexport APP=\"plex\"\nexport APP_TYPE=\"ct\"\nheader_content=$(get_header)\n```\n\n#### `header_info()`\n**Purpose**: Display application header information\n**Parameters**: None (uses APP variable)\n**Returns**: None\n**Side Effects**:\n- Clears screen\n- Displays header content\n- Gets terminal width for formatting\n**Dependencies**: `get_header()`, `tput` command\n**Environment Variables Used**: `APP`\n\n**Usage Example**:\n```bash\nexport APP=\"plex\"\nheader_info\n# Displays Plex header information\n```\n\n### Utility Functions\n\n#### `ensure_tput()`\n**Purpose**: Ensure tput command is available for terminal control\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n- Installs ncurses package if tput missing\n- Works on Alpine and Debian-based systems\n**Dependencies**: `apk` or `apt-get` package managers\n**Environment Variables Used**: None\n\n**Usage Example**:\n```bash\nensure_tput\n# Installs ncurses if needed, continues if already available\n```\n\n#### `is_alpine()`\n**Purpose**: Detect if running on Alpine Linux\n**Parameters**: None\n**Returns**: 0 if Alpine, 1 if not Alpine\n**Side Effects**: None\n**Dependencies**: None\n**Environment Variables Used**: `var_os`, `PCT_OSTYPE`\n\n**Usage Example**:\n```bash\nif is_alpine; then\n    echo \"Running on Alpine Linux\"\nelse\n    echo \"Not running on Alpine Linux\"\nfi\n```\n\n#### `is_verbose_mode()`\n**Purpose**: Check if verbose mode is enabled\n**Parameters**: None\n**Returns**: 0 if verbose mode, 1 if not verbose\n**Side Effects**: None\n**Dependencies**: None\n**Environment Variables Used**: `VERBOSE`, `var_verbose`\n\n**Usage Example**:\n```bash\nif is_verbose_mode; then\n    echo \"Verbose mode enabled\"\nelse\n    echo \"Verbose mode disabled\"\nfi\n```\n\n#### `fatal()`\n**Purpose**: Display fatal error and terminate script\n**Parameters**: `$1` - Error message\n**Returns**: None (terminates script)\n**Side Effects**:\n- Displays error message\n- Sends INT signal to current process\n**Dependencies**: `msg_error()`\n**Environment Variables Used**: None\n\n**Usage Example**:\n```bash\nfatal \"Critical error occurred\"\n# Script terminates after displaying error\n```\n\n### Spinner Functions\n\n#### `spinner()`\n**Purpose**: Display animated spinner for progress indication\n**Parameters**: None (uses SPINNER_MSG variable)\n**Returns**: None (runs indefinitely)\n**Side Effects**:\n- Displays rotating spinner characters\n- Uses terminal control sequences\n**Dependencies**: `color_spinner()`\n**Environment Variables Used**: `SPINNER_MSG`\n\n**Usage Example**:\n```bash\nSPINNER_MSG=\"Processing...\"\nspinner &\nSPINNER_PID=$!\n# Spinner runs in background\n```\n\n#### `clear_line()`\n**Purpose**: Clear current terminal line\n**Parameters**: None\n**Returns**: None\n**Side Effects**: Clears current line using terminal control\n**Dependencies**: `tput` command\n**Environment Variables Used**: None\n\n#### `stop_spinner()`\n**Purpose**: Stop running spinner and cleanup\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n- Kills spinner process\n- Removes PID file\n- Resets terminal settings\n- Unsets spinner variables\n**Dependencies**: None\n**Environment Variables Used**: `SPINNER_PID`, `SPINNER_MSG`\n\n**Usage Example**:\n```bash\nstop_spinner\n# Stops spinner and cleans up\n```\n\n### Message Functions\n\n#### `msg_info()`\n**Purpose**: Display informational message with spinner\n**Parameters**: `$1` - Message text\n**Returns**: None\n**Side Effects**:\n- Starts spinner if not in verbose mode\n- Tracks shown messages to prevent duplicates\n- Displays message with hourglass icon in verbose mode\n**Dependencies**: `spinner()`, `is_verbose_mode()`, `is_alpine()`\n**Environment Variables Used**: `MSG_INFO_SHOWN`\n\n**Usage Example**:\n```bash\nmsg_info \"Installing package...\"\n# Shows spinner with message\n```\n\n#### `msg_ok()`\n**Purpose**: Display success message\n**Parameters**: `$1` - Success message text\n**Returns**: None\n**Side Effects**:\n- Stops spinner\n- Displays green checkmark with message\n- Removes message from shown tracking\n**Dependencies**: `stop_spinner()`\n**Environment Variables Used**: `MSG_INFO_SHOWN`\n\n**Usage Example**:\n```bash\nmsg_ok \"Package installed successfully\"\n# Shows green checkmark with message\n```\n\n#### `msg_error()`\n**Purpose**: Display error message\n**Parameters**: `$1` - Error message text\n**Returns**: None\n**Side Effects**:\n- Stops spinner\n- Displays red cross with message\n**Dependencies**: `stop_spinner()`\n**Environment Variables Used**: None\n\n**Usage Example**:\n```bash\nmsg_error \"Installation failed\"\n# Shows red cross with message\n```\n\n#### `msg_warn()`\n**Purpose**: Display warning message\n**Parameters**: `$1` - Warning message text\n**Returns**: None\n**Side Effects**:\n- Stops spinner\n- Displays yellow info icon with message\n**Dependencies**: `stop_spinner()`\n**Environment Variables Used**: None\n\n**Usage Example**:\n```bash\nmsg_warn \"This operation may take some time\"\n# Shows yellow info icon with message\n```\n\n#### `msg_custom()`\n**Purpose**: Display custom message with specified symbol and color\n**Parameters**:\n- `$1` - Custom symbol (default: \"[*]\")\n- `$2` - Color code (default: \"\\e[36m\")\n- `$3` - Message text\n**Returns**: None\n**Side Effects**:\n- Stops spinner\n- Displays custom formatted message\n**Dependencies**: `stop_spinner()`\n**Environment Variables Used**: None\n\n**Usage Example**:\n```bash\nmsg_custom \"⚡\" \"\\e[33m\" \"Custom warning message\"\n# Shows custom symbol and color with message\n```\n\n#### `msg_debug()`\n**Purpose**: Display debug message if debug mode enabled\n**Parameters**: `$*` - Debug message text\n**Returns**: None\n**Side Effects**:\n- Only displays if var_full_verbose is set\n- Shows timestamp and debug prefix\n**Dependencies**: None\n**Environment Variables Used**: `var_full_verbose`, `var_verbose`\n\n**Usage Example**:\n```bash\nexport var_full_verbose=1\nmsg_debug \"Debug information here\"\n# Shows debug message with timestamp\n```\n\n### System Management Functions\n\n#### `check_or_create_swap()`\n**Purpose**: Check for active swap and optionally create swap file\n**Parameters**: None\n**Returns**: 0 if swap exists or created, 1 if skipped\n**Side Effects**:\n- Checks for active swap\n- Prompts user to create swap if none found\n- Creates swap file if user confirms\n**Dependencies**: `swapon`, `dd`, `mkswap` commands\n**Environment Variables Used**: None\n\n**Usage Example**:\n```bash\nif check_or_create_swap; then\n    echo \"Swap is available\"\nelse\n    echo \"No swap available\"\nfi\n```\n\n## Function Call Hierarchy\n\n### Initialization Flow\n```\nload_functions()\n├── color()\n├── formatting()\n├── icons()\n├── default_vars()\n└── set_std_mode()\n```\n\n### Message System Flow\n```\nmsg_info()\n├── is_verbose_mode()\n├── is_alpine()\n├── spinner()\n└── color_spinner()\n\nmsg_ok()\n├── stop_spinner()\n└── clear_line()\n\nmsg_error()\n└── stop_spinner()\n\nmsg_warn()\n└── stop_spinner()\n```\n\n### System Check Flow\n```\npve_check()\n├── pveversion command\n└── version parsing\n\narch_check()\n├── dpkg command\n└── architecture check\n\nshell_check()\n├── ps command\n└── shell detection\n\nroot_check()\n├── id command\n└── parent process check\n```\n\n### Silent Execution Flow\n```\nsilent()\n├── Command execution\n├── Output redirection\n├── Error handling\n├── error_handler.func loading\n└── Log management\n```\n\n## Error Handling Patterns\n\n### System Check Errors\n- All system check functions exit with appropriate error messages\n- Clear indication of what's wrong and how to fix it\n- Graceful exit with sleep delay for user to read message\n\n### Silent Execution Errors\n- Commands executed via `silent()` capture output to log file\n- On failure, displays error code explanation\n- Shows last 10 lines of log output\n- Provides command to view full log\n\n### Spinner Errors\n- Spinner functions handle process cleanup on exit\n- Trap handlers ensure spinners are stopped\n- Terminal settings are restored on error\n\n## Environment Variable Dependencies\n\n### Required Variables\n- `APP`: Application name for header display\n- `APP_TYPE`: Application type (ct/vm) for header paths\n- `VERBOSE`: Verbose mode setting\n\n### Optional Variables\n- `var_os`: OS type for Alpine detection\n- `PCT_OSTYPE`: Alternative OS type variable\n- `var_verbose`: Alternative verbose setting\n- `var_full_verbose`: Debug mode setting\n\n### Internal Variables\n- `_CORE_FUNC_LOADED`: Prevents multiple loading\n- `__FUNCTIONS_LOADED`: Prevents multiple function loading\n- `SILENT_LOGFILE`: Silent execution log file path\n- `SPINNER_PID`: Spinner process ID\n- `SPINNER_MSG`: Spinner message text\n- `MSG_INFO_SHOWN`: Tracks shown info messages\n"
  },
  {
    "path": "docs/misc/core.func/CORE_INTEGRATION.md",
    "content": "# core.func Integration Guide\n\n## Overview\n\nThis document describes how `core.func` integrates with other components in the Proxmox Community Scripts project, including dependencies, data flow, and API surface.\n\n## Dependencies\n\n### External Dependencies\n\n#### Required Commands\n- **`pveversion`**: Proxmox VE version checking\n- **`dpkg`**: Architecture detection\n- **`ps`**: Process and shell detection\n- **`id`**: User ID checking\n- **`curl`**: Header file downloading\n- **`swapon`**: Swap status checking\n- **`dd`**: Swap file creation\n- **`mkswap`**: Swap file formatting\n\n#### Optional Commands\n- **`tput`**: Terminal control (installed if missing)\n- **`apk`**: Alpine package manager\n- **`apt-get`**: Debian package manager\n\n### Internal Dependencies\n\n#### error_handler.func\n- **Purpose**: Provides error code explanations for silent execution\n- **Usage**: Automatically loaded when `silent()` encounters errors\n- **Integration**: Called via `explain_exit_code()` function\n- **Data Flow**: Error code → explanation → user display\n\n## Integration Points\n\n### With build.func\n\n#### System Validation\n```bash\n# build.func uses core.func for system checks\nsource core.func\npve_check\narch_check\nshell_check\nroot_check\n```\n\n#### User Interface\n```bash\n# build.func uses core.func for UI elements\nmsg_info \"Creating container...\"\nmsg_ok \"Container created successfully\"\nmsg_error \"Container creation failed\"\n```\n\n#### Silent Execution\n```bash\n# build.func uses core.func for command execution\nsilent pct create \"$CTID\" \"$TEMPLATE\" \\\n    --hostname \"$HOSTNAME\" \\\n    --memory \"$MEMORY\" \\\n    --cores \"$CORES\"\n```\n\n### With tools.func\n\n#### Utility Functions\n```bash\n# tools.func uses core.func utilities\nsource core.func\n\n# System checks\npve_check\nroot_check\n\n# UI elements\nmsg_info \"Running maintenance tasks...\"\nmsg_ok \"Maintenance completed\"\n```\n\n#### Error Handling\n```bash\n# tools.func uses core.func for error handling\nif silent systemctl restart service; then\n    msg_ok \"Service restarted\"\nelse\n    msg_error \"Service restart failed\"\nfi\n```\n\n### With api.func\n\n#### System Validation\n```bash\n# api.func uses core.func for system checks\nsource core.func\npve_check\nroot_check\n```\n\n#### API Operations\n```bash\n# api.func uses core.func for API calls\nmsg_info \"Connecting to Proxmox API...\"\nif silent curl -k -H \"Authorization: PVEAPIToken=$API_TOKEN\" \\\n    \"$API_URL/api2/json/nodes/$NODE/lxc\"; then\n    msg_ok \"API connection successful\"\nelse\n    msg_error \"API connection failed\"\nfi\n```\n\n### With error_handler.func\n\n#### Error Explanations\n```bash\n# error_handler.func provides explanations for core.func\nexplain_exit_code() {\n    local code=\"$1\"\n    case \"$code\" in\n        1) echo \"General error\" ;;\n        2) echo \"Misuse of shell builtins\" ;;\n        126) echo \"Command invoked cannot execute\" ;;\n        127) echo \"Command not found\" ;;\n        128) echo \"Invalid argument to exit\" ;;\n        *) echo \"Unknown error code\" ;;\n    esac\n}\n```\n\n### With install.func\n\n#### Installation Process\n```bash\n# install.func uses core.func for installation\nsource core.func\n\n# System checks\npve_check\nroot_check\n\n# Installation steps\nmsg_info \"Installing packages...\"\nsilent apt-get update\nsilent apt-get install -y package\n\nmsg_ok \"Installation completed\"\n```\n\n### With alpine-install.func\n\n#### Alpine-Specific Operations\n```bash\n# alpine-install.func uses core.func for Alpine operations\nsource core.func\n\n# Alpine detection\nif is_alpine; then\n    msg_info \"Detected Alpine Linux\"\n    silent apk add --no-cache package\nelse\n    msg_info \"Detected Debian-based system\"\n    silent apt-get install -y package\nfi\n```\n\n### With alpine-tools.func\n\n#### Alpine Utilities\n```bash\n# alpine-tools.func uses core.func for Alpine tools\nsource core.func\n\n# Alpine-specific operations\nif is_alpine; then\n    msg_info \"Running Alpine-specific operations...\"\n    # Alpine tools logic\n    msg_ok \"Alpine operations completed\"\nfi\n```\n\n### With passthrough.func\n\n#### Hardware Passthrough\n```bash\n# passthrough.func uses core.func for hardware operations\nsource core.func\n\n# System checks\npve_check\nroot_check\n\n# Hardware operations\nmsg_info \"Configuring GPU passthrough...\"\nif silent lspci | grep -i nvidia; then\n    msg_ok \"NVIDIA GPU detected\"\nelse\n    msg_warn \"No NVIDIA GPU found\"\nfi\n```\n\n### With vm-core.func\n\n#### VM Operations\n```bash\n# vm-core.func uses core.func for VM management\nsource core.func\n\n# System checks\npve_check\nroot_check\n\n# VM operations\nmsg_info \"Creating virtual machine...\"\nsilent qm create \"$VMID\" \\\n    --name \"$VMNAME\" \\\n    --memory \"$MEMORY\" \\\n    --cores \"$CORES\"\n\nmsg_ok \"Virtual machine created\"\n```\n\n## Data Flow\n\n### Input Data\n\n#### Environment Variables\n- **`APP`**: Application name for header display\n- **`APP_TYPE`**: Application type (ct/vm) for header paths\n- **`VERBOSE`**: Verbose mode setting\n- **`var_os`**: OS type for Alpine detection\n- **`PCT_OSTYPE`**: Alternative OS type variable\n- **`var_verbose`**: Alternative verbose setting\n- **`var_full_verbose`**: Debug mode setting\n\n#### Command Parameters\n- **Function arguments**: Passed to individual functions\n- **Command arguments**: Passed to `silent()` function\n- **User input**: Collected via `read` commands\n\n### Processing Data\n\n#### System Information\n- **Proxmox version**: Parsed from `pveversion` output\n- **Architecture**: Retrieved from `dpkg --print-architecture`\n- **Shell type**: Detected from process information\n- **User ID**: Retrieved from `id -u`\n- **SSH connection**: Detected from `SSH_CLIENT` environment\n\n#### UI State\n- **Message tracking**: `MSG_INFO_SHOWN` associative array\n- **Spinner state**: `SPINNER_PID` and `SPINNER_MSG` variables\n- **Terminal state**: Cursor position and display mode\n\n#### Error Information\n- **Exit codes**: Captured from command execution\n- **Log output**: Redirected to temporary log files\n- **Error explanations**: Retrieved from error_handler.func\n\n### Output Data\n\n#### User Interface\n- **Colored messages**: ANSI color codes for terminal output\n- **Icons**: Symbolic representations for different message types\n- **Spinners**: Animated progress indicators\n- **Formatted text**: Consistent message formatting\n\n#### System State\n- **Exit codes**: Returned from functions\n- **Log files**: Created for silent execution\n- **Configuration**: Modified system settings\n- **Process state**: Spinner processes and cleanup\n\n## API Surface\n\n### Public Functions\n\n#### System Validation\n- **`pve_check()`**: Proxmox VE version validation\n- **`arch_check()`**: Architecture validation\n- **`shell_check()`**: Shell validation\n- **`root_check()`**: Privilege validation\n- **`ssh_check()`**: SSH connection warning\n\n#### User Interface\n- **`msg_info()`**: Informational messages\n- **`msg_ok()`**: Success messages\n- **`msg_error()`**: Error messages\n- **`msg_warn()`**: Warning messages\n- **`msg_custom()`**: Custom messages\n- **`msg_debug()`**: Debug messages\n\n#### Spinner Control\n- **`spinner()`**: Start spinner animation\n- **`stop_spinner()`**: Stop spinner and cleanup\n- **`clear_line()`**: Clear current terminal line\n\n#### Silent Execution\n- **`silent()`**: Execute commands with error handling\n\n#### Utility Functions\n- **`is_alpine()`**: Alpine Linux detection\n- **`is_verbose_mode()`**: Verbose mode detection\n- **`fatal()`**: Fatal error handling\n- **`ensure_tput()`**: Terminal control setup\n\n#### Header Management\n- **`get_header()`**: Download application headers\n- **`header_info()`**: Display header information\n\n#### System Management\n- **`check_or_create_swap()`**: Swap file management\n\n### Internal Functions\n\n#### Initialization\n- **`load_functions()`**: Function loader\n- **`color()`**: Color setup\n- **`formatting()`**: Formatting setup\n- **`icons()`**: Icon setup\n- **`default_vars()`**: Default variables\n- **`set_std_mode()`**: Standard mode setup\n\n#### Color Management\n- **`color_spinner()`**: Spinner colors\n\n### Global Variables\n\n#### Color Variables\n- **`YW`**, **`YWB`**, **`BL`**, **`RD`**, **`BGN`**, **`GN`**, **`DGN`**, **`CL`**: Color codes\n- **`CS_YW`**, **`CS_YWB`**, **`CS_CL`**: Spinner colors\n\n#### Formatting Variables\n- **`BFR`**, **`BOLD`**, **`HOLD`**, **`TAB`**, **`TAB3`**: Formatting helpers\n\n#### Icon Variables\n- **`CM`**, **`CROSS`**, **`INFO`**, **`OS`**, **`OSVERSION`**, etc.: Message icons\n\n#### Configuration Variables\n- **`RETRY_NUM`**, **`RETRY_EVERY`**: Retry settings\n- **`STD`**: Standard mode setting\n- **`SILENT_LOGFILE`**: Log file path\n\n#### State Variables\n- **`_CORE_FUNC_LOADED`**: Loading prevention\n- **`__FUNCTIONS_LOADED`**: Function loading prevention\n- **`SPINNER_PID`**, **`SPINNER_MSG`**: Spinner state\n- **`MSG_INFO_SHOWN`**: Message tracking\n\n## Integration Patterns\n\n### Standard Integration Pattern\n\n```bash\n#!/usr/bin/env bash\n# Standard integration pattern\n\n# 1. Source core.func first\nsource core.func\n\n# 2. Run system checks\npve_check\narch_check\nshell_check\nroot_check\n\n# 3. Set up error handling\ntrap 'stop_spinner' EXIT INT TERM\n\n# 4. Use UI functions\nmsg_info \"Starting operation...\"\n\n# 5. Use silent execution\nsilent command\n\n# 6. Show completion\nmsg_ok \"Operation completed\"\n```\n\n### Minimal Integration Pattern\n\n```bash\n#!/usr/bin/env bash\n# Minimal integration pattern\n\nsource core.func\npve_check\nroot_check\n\nmsg_info \"Running operation...\"\nsilent command\nmsg_ok \"Operation completed\"\n```\n\n### Advanced Integration Pattern\n\n```bash\n#!/usr/bin/env bash\n# Advanced integration pattern\n\nsource core.func\n\n# System validation\npve_check\narch_check\nshell_check\nroot_check\nssh_check\n\n# Error handling\ntrap 'stop_spinner' EXIT INT TERM\n\n# Verbose mode handling\nif is_verbose_mode; then\n    msg_info \"Verbose mode enabled\"\nfi\n\n# OS-specific operations\nif is_alpine; then\n    msg_info \"Alpine Linux detected\"\n    # Alpine-specific logic\nelse\n    msg_info \"Debian-based system detected\"\n    # Debian-specific logic\nfi\n\n# Operation execution\nmsg_info \"Starting operation...\"\nif silent command; then\n    msg_ok \"Operation succeeded\"\nelse\n    msg_error \"Operation failed\"\n    exit 1\nfi\n```\n\n## Error Handling Integration\n\n### Silent Execution Error Flow\n\n```\nsilent() command\n├── Execute command\n├── Capture output to log\n├── Check exit code\n├── If error:\n│   ├── Load error_handler.func\n│   ├── Get error explanation\n│   ├── Display error details\n│   ├── Show log excerpt\n│   └── Exit with error code\n└── If success: Continue\n```\n\n### System Check Error Flow\n\n```\nSystem Check Function\n├── Check system state\n├── If valid: Return 0\n└── If invalid:\n    ├── Display error message\n    ├── Show fix instructions\n    ├── Sleep for user to read\n    └── Exit with error code\n```\n\n## Performance Considerations\n\n### Loading Optimization\n- **Single Loading**: `_CORE_FUNC_LOADED` prevents multiple loading\n- **Function Loading**: `__FUNCTIONS_LOADED` prevents multiple function loading\n- **Lazy Loading**: Functions loaded only when needed\n\n### Memory Usage\n- **Minimal Footprint**: Core functions use minimal memory\n- **Variable Reuse**: Global variables reused across functions\n- **Cleanup**: Spinner processes cleaned up on exit\n\n### Execution Speed\n- **Fast Checks**: System checks are optimized for speed\n- **Efficient Spinners**: Spinner animation uses minimal CPU\n- **Quick Messages**: Message functions optimized for performance\n\n## Security Considerations\n\n### Privilege Escalation\n- **Root Check**: Ensures script runs with sufficient privileges\n- **Shell Check**: Validates shell environment\n- **Process Validation**: Checks parent process for sudo usage\n\n### Input Validation\n- **Parameter Checking**: Functions validate input parameters\n- **Error Handling**: Proper error handling prevents crashes\n- **Safe Execution**: Silent execution with proper error handling\n\n### System Protection\n- **Version Validation**: Ensures compatible Proxmox version\n- **Architecture Check**: Prevents execution on unsupported systems\n- **SSH Warning**: Warns about external SSH usage\n\n## Future Integration Considerations\n\n### Extensibility\n- **Function Groups**: Easy to add new function groups\n- **Message Types**: Easy to add new message types\n- **System Checks**: Easy to add new system checks\n\n### Compatibility\n- **Version Support**: Easy to add new Proxmox versions\n- **OS Support**: Easy to add new operating systems\n- **Architecture Support**: Easy to add new architectures\n\n### Performance\n- **Optimization**: Functions can be optimized for better performance\n- **Caching**: Results can be cached for repeated operations\n- **Parallelization**: Operations can be parallelized where appropriate\n"
  },
  {
    "path": "docs/misc/core.func/CORE_USAGE_EXAMPLES.md",
    "content": "# core.func Usage Examples\n\n## Overview\n\nThis document provides practical usage examples for `core.func` functions, covering common scenarios, integration patterns, and best practices.\n\n## Basic Script Setup\n\n### Standard Script Initialization\n\n```bash\n#!/usr/bin/env bash\n# Standard script setup using core.func\n\n# Source core functions\nsource core.func\n\n# Run system checks\npve_check\narch_check\nshell_check\nroot_check\n\n# Optional: Check SSH connection\nssh_check\n\n# Set up error handling\ntrap 'stop_spinner' EXIT INT TERM\n\n# Your script logic here\nmsg_info \"Starting script execution\"\n# ... script code ...\nmsg_ok \"Script completed successfully\"\n```\n\n### Minimal Script Setup\n\n```bash\n#!/usr/bin/env bash\n# Minimal setup for simple scripts\n\nsource core.func\n\n# Basic checks only\npve_check\nroot_check\n\n# Simple execution\nmsg_info \"Running operation\"\n# ... your code ...\nmsg_ok \"Operation completed\"\n```\n\n## Message Display Examples\n\n### Progress Indication\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Show progress with spinner\nmsg_info \"Downloading package...\"\nsleep 2\nmsg_ok \"Download completed\"\n\nmsg_info \"Installing package...\"\nsleep 3\nmsg_ok \"Installation completed\"\n\nmsg_info \"Configuring service...\"\nsleep 1\nmsg_ok \"Configuration completed\"\n```\n\n### Error Handling\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Function with error handling\ninstall_package() {\n    local package=\"$1\"\n\n    msg_info \"Installing $package...\"\n\n    if silent apt-get install -y \"$package\"; then\n        msg_ok \"$package installed successfully\"\n        return 0\n    else\n        msg_error \"Failed to install $package\"\n        return 1\n    fi\n}\n\n# Usage\nif install_package \"nginx\"; then\n    msg_ok \"Nginx installation completed\"\nelse\n    msg_error \"Nginx installation failed\"\n    exit 1\nfi\n```\n\n### Warning Messages\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Show warnings for potentially dangerous operations\nmsg_warn \"This will modify system configuration\"\nread -p \"Continue? [y/N]: \" confirm\n\nif [[ \"$confirm\" =~ ^[yY]$ ]]; then\n    msg_info \"Proceeding with modification...\"\n    # ... dangerous operation ...\n    msg_ok \"Modification completed\"\nelse\n    msg_info \"Operation cancelled\"\nfi\n```\n\n### Custom Messages\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Custom message with specific icon and color\nmsg_custom \"🚀\" \"\\e[32m\" \"Launching application\"\nmsg_custom \"⚡\" \"\\e[33m\" \"High performance mode enabled\"\nmsg_custom \"🔒\" \"\\e[31m\" \"Security mode activated\"\n```\n\n### Debug Messages\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Enable debug mode\nexport var_full_verbose=1\n\n# Debug messages\nmsg_debug \"Variable value: $some_variable\"\nmsg_debug \"Function called: $FUNCNAME\"\nmsg_debug \"Current directory: $(pwd)\"\n```\n\n## Silent Execution Examples\n\n### Package Management\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Update package lists\nmsg_info \"Updating package lists...\"\nsilent apt-get update\n\n# Install packages\nmsg_info \"Installing required packages...\"\nsilent apt-get install -y curl wget git\n\n# Upgrade packages\nmsg_info \"Upgrading packages...\"\nsilent apt-get upgrade -y\n\nmsg_ok \"Package management completed\"\n```\n\n### File Operations\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Create directories\nmsg_info \"Creating directory structure...\"\nsilent mkdir -p /opt/myapp/{config,logs,data}\n\n# Set permissions\nmsg_info \"Setting permissions...\"\nsilent chmod 755 /opt/myapp\nsilent chmod 644 /opt/myapp/config/*\n\n# Copy files\nmsg_info \"Copying configuration files...\"\nsilent cp config/* /opt/myapp/config/\n\nmsg_ok \"File operations completed\"\n```\n\n### Service Management\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Start service\nmsg_info \"Starting service...\"\nsilent systemctl start myservice\n\n# Enable service\nmsg_info \"Enabling service...\"\nsilent systemctl enable myservice\n\n# Check service status\nmsg_info \"Checking service status...\"\nif silent systemctl is-active --quiet myservice; then\n    msg_ok \"Service is running\"\nelse\n    msg_error \"Service failed to start\"\nfi\n```\n\n### Network Operations\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Test network connectivity\nmsg_info \"Testing network connectivity...\"\nif silent ping -c 1 8.8.8.8; then\n    msg_ok \"Network connectivity confirmed\"\nelse\n    msg_error \"Network connectivity failed\"\nfi\n\n# Download files\nmsg_info \"Downloading configuration...\"\nsilent curl -fsSL https://example.com/config -o /tmp/config\n\n# Extract archives\nmsg_info \"Extracting archive...\"\nsilent tar -xzf /tmp/archive.tar.gz -C /opt/\n```\n\n## System Check Examples\n\n### Comprehensive System Validation\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Complete system validation\nvalidate_system() {\n    msg_info \"Validating system requirements...\"\n\n    # Check Proxmox version\n    if pve_check; then\n        msg_ok \"Proxmox VE version is supported\"\n    fi\n\n    # Check architecture\n    if arch_check; then\n        msg_ok \"System architecture is supported\"\n    fi\n\n    # Check shell\n    if shell_check; then\n        msg_ok \"Shell environment is correct\"\n    fi\n\n    # Check privileges\n    if root_check; then\n        msg_ok \"Running with sufficient privileges\"\n    fi\n\n    # Check SSH connection\n    ssh_check\n\n    msg_ok \"System validation completed\"\n}\n\n# Run validation\nvalidate_system\n```\n\n### Conditional System Checks\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Check if running in container\nif [[ -f /.dockerenv ]] || [[ -f /run/.containerenv ]]; then\n    msg_warn \"Running inside container\"\n    # Skip some checks\nelse\n    # Full system checks\n    pve_check\n    arch_check\nfi\n\n# Always check shell and privileges\nshell_check\nroot_check\n```\n\n## Header Management Examples\n\n### Application Header Display\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Set application information\nexport APP=\"plex\"\nexport APP_TYPE=\"ct\"\n\n# Display header\nheader_info\n\n# Continue with application setup\nmsg_info \"Setting up Plex Media Server...\"\n```\n\n### Custom Header Handling\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Get header content\nexport APP=\"nextcloud\"\nexport APP_TYPE=\"ct\"\n\nheader_content=$(get_header)\nif [[ -n \"$header_content\" ]]; then\n    echo \"Header found:\"\n    echo \"$header_content\"\nelse\n    msg_warn \"No header found for $APP\"\nfi\n```\n\n## Swap Management Examples\n\n### Interactive Swap Creation\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Check and create swap\nif check_or_create_swap; then\n    msg_ok \"Swap is available\"\nelse\n    msg_warn \"No swap available - continuing without swap\"\nfi\n```\n\n### Automated Swap Check\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Check swap without prompting\ncheck_swap_quiet() {\n    if swapon --noheadings --show | grep -q 'swap'; then\n        msg_ok \"Swap is active\"\n        return 0\n    else\n        msg_warn \"No active swap detected\"\n        return 1\n    fi\n}\n\nif check_swap_quiet; then\n    msg_info \"System has sufficient swap\"\nelse\n    msg_warn \"Consider adding swap for better performance\"\nfi\n```\n\n## Spinner Usage Examples\n\n### Long-Running Operations\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Long-running operation with spinner\nlong_operation() {\n    msg_info \"Processing large dataset...\"\n\n    # Simulate long operation\n    for i in {1..100}; do\n        sleep 0.1\n        # Update spinner message periodically\n        if (( i % 20 == 0 )); then\n            SPINNER_MSG=\"Processing... $i%\"\n        fi\n    done\n\n    msg_ok \"Dataset processing completed\"\n}\n\nlong_operation\n```\n\n### Background Operations\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Background operation with spinner\nbackground_operation() {\n    msg_info \"Starting background process...\"\n\n    # Start spinner\n    SPINNER_MSG=\"Processing in background...\"\n    spinner &\n    SPINNER_PID=$!\n\n    # Do background work\n    sleep 5\n\n    # Stop spinner\n    stop_spinner\n    msg_ok \"Background process completed\"\n}\n\nbackground_operation\n```\n\n## Integration Examples\n\n### With build.func\n\n```bash\n#!/usr/bin/env bash\n# Integration with build.func\n\nsource core.func\nsource build.func\n\n# Use core functions for system validation\npve_check\narch_check\nroot_check\n\n# Use build.func for container creation\nexport APP=\"plex\"\nexport CTID=\"100\"\n# ... container creation ...\n```\n\n### With tools.func\n\n```bash\n#!/usr/bin/env bash\n# Integration with tools.func\n\nsource core.func\nsource tools.func\n\n# Use core functions for UI\nmsg_info \"Starting maintenance tasks...\"\n\n# Use tools.func for maintenance\nupdate_system\ncleanup_logs\noptimize_storage\n\nmsg_ok \"Maintenance completed\"\n```\n\n### With error_handler.func\n\n```bash\n#!/usr/bin/env bash\n# Integration with error_handler.func\n\nsource core.func\nsource error_handler.func\n\n# Use core functions for execution\nmsg_info \"Running operation...\"\n\n# Silent execution will use error_handler for explanations\nsilent apt-get install -y package\n\nmsg_ok \"Operation completed\"\n```\n\n## Best Practices Examples\n\n### Error Handling Pattern\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Robust error handling\nrun_with_error_handling() {\n    local operation=\"$1\"\n    local description=\"$2\"\n\n    msg_info \"$description\"\n\n    if silent \"$operation\"; then\n        msg_ok \"$description completed successfully\"\n        return 0\n    else\n        msg_error \"$description failed\"\n        return 1\n    fi\n}\n\n# Usage\nrun_with_error_handling \"apt-get update\" \"Package list update\"\nrun_with_error_handling \"apt-get install -y nginx\" \"Nginx installation\"\n```\n\n### Verbose Mode Handling\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Handle verbose mode\nif is_verbose_mode; then\n    msg_info \"Verbose mode enabled - showing detailed output\"\n    # Show more information\nelse\n    msg_info \"Normal mode - showing minimal output\"\n    # Show less information\nfi\n```\n\n### Alpine Linux Detection\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Handle different OS types\nif is_alpine; then\n    msg_info \"Detected Alpine Linux\"\n    # Use Alpine-specific commands\n    silent apk add --no-cache package\nelse\n    msg_info \"Detected Debian-based system\"\n    # Use Debian-specific commands\n    silent apt-get install -y package\nfi\n```\n\n### Conditional Execution\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Conditional execution based on system state\nif [[ -f /etc/nginx/nginx.conf ]]; then\n    msg_warn \"Nginx configuration already exists\"\n    read -p \"Overwrite? [y/N]: \" overwrite\n    if [[ \"$overwrite\" =~ ^[yY]$ ]]; then\n        msg_info \"Overwriting configuration...\"\n        # ... overwrite logic ...\n    else\n        msg_info \"Skipping configuration\"\n    fi\nelse\n    msg_info \"Creating new Nginx configuration...\"\n    # ... create logic ...\nfi\n```\n\n## Advanced Usage Examples\n\n### Custom Spinner Messages\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Custom spinner with progress\ndownload_with_progress() {\n    local url=\"$1\"\n    local file=\"$2\"\n\n    msg_info \"Starting download...\"\n\n    # Start spinner\n    SPINNER_MSG=\"Downloading...\"\n    spinner &\n    SPINNER_PID=$!\n\n    # Download with progress\n    curl -L \"$url\" -o \"$file\" --progress-bar\n\n    # Stop spinner\n    stop_spinner\n    msg_ok \"Download completed\"\n}\n\ndownload_with_progress \"https://example.com/file.tar.gz\" \"/tmp/file.tar.gz\"\n```\n\n### Message Deduplication\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Messages are automatically deduplicated\nfor i in {1..5}; do\n    msg_info \"Processing item $i\"\n    # This message will only show once\ndone\n\n# Different messages will show separately\nmsg_info \"Starting phase 1\"\nmsg_info \"Starting phase 2\"\nmsg_info \"Starting phase 3\"\n```\n\n### Terminal Control\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Ensure terminal control is available\nensure_tput\n\n# Use terminal control\nclear_line\necho \"This line will be cleared\"\nclear_line\necho \"New content\"\n```\n\n## Troubleshooting Examples\n\n### Debug Mode\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Enable debug mode\nexport var_full_verbose=1\nexport VERBOSE=\"yes\"\n\n# Debug information\nmsg_debug \"Script started\"\nmsg_debug \"Current user: $(whoami)\"\nmsg_debug \"Current directory: $(pwd)\"\nmsg_debug \"Environment variables: $(env | grep -E '^(APP|CTID|VERBOSE)')\"\n```\n\n### Silent Execution Debugging\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Debug silent execution\ndebug_silent() {\n    local cmd=\"$1\"\n    local log_file=\"/tmp/debug.$$.log\"\n\n    echo \"Command: $cmd\" > \"$log_file\"\n    echo \"Timestamp: $(date)\" >> \"$log_file\"\n    echo \"Working directory: $(pwd)\" >> \"$log_file\"\n    echo \"Environment:\" >> \"$log_file\"\n    env >> \"$log_file\"\n    echo \"--- Command Output ---\" >> \"$log_file\"\n\n    if silent \"$cmd\"; then\n        msg_ok \"Command succeeded\"\n    else\n        msg_error \"Command failed - check $log_file for details\"\n    fi\n}\n\ndebug_silent \"apt-get update\"\n```\n\n### Error Recovery\n\n```bash\n#!/usr/bin/env bash\nsource core.func\n\n# Error recovery pattern\nretry_operation() {\n    local max_attempts=3\n    local attempt=1\n\n    while [[ $attempt -le $max_attempts ]]; do\n        msg_info \"Attempt $attempt of $max_attempts\"\n\n        if silent \"$@\"; then\n            msg_ok \"Operation succeeded on attempt $attempt\"\n            return 0\n        else\n            msg_warn \"Attempt $attempt failed\"\n            ((attempt++))\n\n            if [[ $attempt -le $max_attempts ]]; then\n                msg_info \"Retrying in 5 seconds...\"\n                sleep 5\n            fi\n        fi\n    done\n\n    msg_error \"Operation failed after $max_attempts attempts\"\n    return 1\n}\n\n# Usage\nretry_operation \"apt-get install -y package\"\n```\n"
  },
  {
    "path": "docs/misc/core.func/README.md",
    "content": "# core.func Documentation\n\n## Overview\n\nThe `core.func` file provides fundamental utility functions and system checks that form the foundation for all other scripts in the Proxmox Community Scripts project. It handles basic system operations, user interface elements, validation, and core infrastructure.\n\n## Purpose and Use Cases\n\n- **System Validation**: Checks for Proxmox VE compatibility, architecture, shell requirements\n- **User Interface**: Provides colored output, icons, spinners, and formatted messages\n- **Core Utilities**: Basic functions used across all scripts\n- **Error Handling**: Silent execution with detailed error reporting\n- **System Information**: OS detection, verbose mode handling, swap management\n\n## Quick Reference\n\n### Key Function Groups\n- **System Checks**: `pve_check()`, `arch_check()`, `shell_check()`, `root_check()`\n- **User Interface**: `msg_info()`, `msg_ok()`, `msg_error()`, `msg_warn()`, `spinner()`\n- **Core Utilities**: `silent()`, `is_alpine()`, `is_verbose_mode()`, `get_header()`\n- **System Management**: `check_or_create_swap()`, `ensure_tput()`\n\n### Dependencies\n- **External**: `curl` for downloading headers, `tput` for terminal control\n- **Internal**: `error_handler.func` for error explanations\n\n### Integration Points\n- Used by: All other `.func` files and installation scripts\n- Uses: `error_handler.func` for error explanations\n- Provides: Core utilities for `build.func`, `tools.func`, `api.func`\n\n## Documentation Files\n\n### 📊 [CORE_FLOWCHART.md](./CORE_FLOWCHART.md)\nVisual execution flows showing how core functions interact and the system validation process.\n\n### 📚 [CORE_FUNCTIONS_REFERENCE.md](./CORE_FUNCTIONS_REFERENCE.md)\nComplete alphabetical reference of all functions with parameters, dependencies, and usage details.\n\n### 💡 [CORE_USAGE_EXAMPLES.md](./CORE_USAGE_EXAMPLES.md)\nPractical examples showing how to use core functions in scripts and common patterns.\n\n### 🔗 [CORE_INTEGRATION.md](./CORE_INTEGRATION.md)\nHow core.func integrates with other components and provides foundational services.\n\n## Key Features\n\n### System Validation\n- **Proxmox VE Version Check**: Supports PVE 8.0-8.9 and 9.0\n- **Architecture Check**: Ensures AMD64 architecture (excludes PiMox)\n- **Shell Check**: Validates Bash shell usage\n- **Root Check**: Ensures root privileges\n- **SSH Check**: Warns about external SSH usage\n\n### User Interface\n- **Colored Output**: ANSI color codes for styled terminal output\n- **Icons**: Symbolic icons for different message types\n- **Spinners**: Animated progress indicators\n- **Formatted Messages**: Consistent message formatting across scripts\n\n### Core Utilities\n- **Silent Execution**: Execute commands with detailed error reporting\n- **OS Detection**: Alpine Linux detection\n- **Verbose Mode**: Handle verbose output settings\n- **Header Management**: Download and display application headers\n- **Swap Management**: Check and create swap files\n\n## Common Usage Patterns\n\n### Basic Script Setup\n```bash\n# Source core functions\nsource core.func\n\n# Run system checks\npve_check\narch_check\nshell_check\nroot_check\n```\n\n### Message Display\n```bash\n# Show progress\nmsg_info \"Installing package...\"\n\n# Show success\nmsg_ok \"Package installed successfully\"\n\n# Show error\nmsg_error \"Installation failed\"\n\n# Show warning\nmsg_warn \"This operation may take some time\"\n```\n\n### Silent Command Execution\n```bash\n# Execute command silently with error handling\nsilent apt-get update\nsilent apt-get install -y package-name\n```\n\n## Environment Variables\n\n### Core Variables\n- `VERBOSE`: Enable verbose output mode\n- `SILENT_LOGFILE`: Path to silent execution log file\n- `APP`: Application name for header display\n- `APP_TYPE`: Application type (ct/vm) for header paths\n\n### Internal Variables\n- `_CORE_FUNC_LOADED`: Prevents multiple loading\n- `__FUNCTIONS_LOADED`: Prevents multiple function loading\n- `RETRY_NUM`: Number of retry attempts (default: 10)\n- `RETRY_EVERY`: Seconds between retries (default: 3)\n\n## Error Handling\n\n### Silent Execution Errors\n- Commands executed via `silent()` capture output to log file\n- On failure, displays error code explanation\n- Shows last 10 lines of log output\n- Provides command to view full log\n\n### System Check Failures\n- Each system check function exits with appropriate error message\n- Clear indication of what's wrong and how to fix it\n- Graceful exit with sleep delay for user to read message\n\n## Best Practices\n\n### Script Initialization\n1. Source `core.func` first\n2. Run system checks early\n3. Set up error handling\n4. Use appropriate message functions\n\n### Message Usage\n1. Use `msg_info()` for progress updates\n2. Use `msg_ok()` for successful completions\n3. Use `msg_error()` for failures\n4. Use `msg_warn()` for warnings\n\n### Silent Execution\n1. Use `silent()` for commands that might fail\n2. Check return codes after silent execution\n3. Provide meaningful error messages\n\n## Troubleshooting\n\n### Common Issues\n1. **Proxmox Version**: Ensure running supported PVE version\n2. **Architecture**: Script only works on AMD64 systems\n3. **Shell**: Must use Bash shell\n4. **Permissions**: Must run as root\n5. **Network**: SSH warnings for external connections\n\n### Debug Mode\nEnable verbose output for debugging:\n```bash\nexport VERBOSE=\"yes\"\nsource core.func\n```\n\n### Log Files\nCheck silent execution logs:\n```bash\ncat /tmp/silent.$$.log\n```\n\n## Related Documentation\n\n- [build.func](../build.func/) - Main container creation script\n- [error_handler.func](../error_handler.func/) - Error handling utilities\n- [tools.func](../tools.func/) - Extended utility functions\n- [api.func](../api.func/) - Proxmox API interactions\n\n---\n\n*This documentation covers the core.func file which provides fundamental utilities for all Proxmox Community Scripts.*\n"
  },
  {
    "path": "docs/misc/error_handler.func/ERROR_HANDLER_FLOWCHART.md",
    "content": "# error_handler.func Execution Flowchart\n\n## Main Error Handling Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Error Handler Initialization                             │\n│  Entry point when error_handler.func is sourced by other scripts                │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        CATCH_ERRORS()                                           │\n│  Initialize error handling traps and strict mode                                │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Trap Setup Sequence                                      │\n│                                                                                 │\n│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────────┐      │\n│  │   Set Strict    │  │   Set Error     │  │     Set Signal              │      │\n│  │   Mode          │  │   Trap          │  │     Traps                   │      │\n│  │                 │  │                 │  │                             │      │\n│  │ • -Ee           │  │ • ERR trap      │  │ • EXIT trap                 │      │\n│  │ • -o pipefail   │  │ • error_handler │  │ • INT trap                  │      │\n│  │ • -u (if        │  │   function      │  │ • TERM trap                 │      │\n│  │   STRICT_UNSET) │  │                 │  │                             │      │\n│  └─────────────────┘  └─────────────────┘  └─────────────────────────────┘      │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Error Handler Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        ERROR_HANDLER() Flow                                   │\n│  Main error handler triggered by ERR trap or manual call                      │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Error Detection                                        │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Error Information Collection                            │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Get Exit      │    │   Get Command    │    │   Get Line          │ │ │\n│  │  │   Code          │    │   Information    │    │   Number            │ │ │\n│  │  │                 │    │                 │    │                     │ │ │\n│  │  │ • From $? or    │    │ • From          │    │ • From              │ │ │\n│  │  │   parameter     │    │   BASH_COMMAND  │    │   BASH_LINENO[0]    │ │ │\n│  │  │ • Store in      │    │ • Clean $STD    │    │ • Default to        │ │ │\n│  │  │   exit_code     │    │   references     │    │   \"unknown\"         │ │ │\n│  │  │                 │    │ • Store in      │    │ • Store in          │ │ │\n│  │  │                 │    │   command       │    │   line_number       │ │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Success Check                                          │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Exit Code Validation                                    │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Check Exit    │    │   Success       │    │   Error              │ │\n│  │  │   Code          │    │   Path          │    │   Path               │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • If exit_code  │    │ • Return 0      │    │ • Continue to       │ │\n│  │  │   == 0          │    │ • No error      │    │   error handling    │ │\n│  │  │ • Success       │    │   processing    │    │ • Process error     │ │\n│  │  │ • No error      │    │                 │    │   information        │ │\n│  │  │   handling      │    │                 │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Error Processing                                       │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Error Explanation                                      │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Get Error     │    │   Display Error  │    │   Log Error         │ │ │\n│  │  │   Explanation   │    │   Information    │    │   Information        │ │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • Call          │    │ • Show error    │    │ • Write to debug    │ │\n│  │  │   explain_exit_ │    │   message        │    │   log if enabled    │ │\n│  │  │   code()        │    │ • Show line     │    │ • Include           │ │\n│  │  │ • Get human-    │    │   number         │    │   timestamp         │ │\n│  │  │   readable      │    │ • Show command  │    │ • Include exit      │ │\n│  │  │   message       │    │ • Show exit     │    │   code              │ │\n│  │  │                 │    │   code          │    │ • Include command    │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Silent Log Integration                                 │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Silent Log Display                                      │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Check Silent  │    │   Display Log   │    │   Exit with         │ │\n│  │  │   Log File      │    │   Content       │    │   Error Code        │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • Check if      │    │ • Show last 20  │    │ • Exit with         │ │\n│  │  │   SILENT_       │    │   lines         │    │   original exit     │ │\n│  │  │   LOGFILE set   │    │ • Show file     │    │   code              │ │\n│  │  │ • Check if      │    │   path          │    │ • Terminate script  │ │\n│  │  │   file exists   │    │ • Format        │    │   execution         │ │\n│  │  │ • Check if      │    │   output        │    │                     │ │\n│  │  │   file has      │    │                 │    │                     │ │\n│  │  │   content       │    │                 │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Signal Handling Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Signal Handler Flow                                    │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                        Signal Detection                                   │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   SIGINT        │    │   SIGTERM       │    │     EXIT            │ │ │\n│  │  │   (Ctrl+C)      │    │   (Termination) │    │     (Script End)    │ │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • User          │    │ • System        │    │ • Normal script     │ │\n│  │  │   interruption  │    │   termination   │    │   completion        │ │\n│  │  │ • Graceful      │    │ • Graceful      │    │ • Error exit        │ │\n│  │  │   handling      │    │   handling      │    │ • Signal exit       │ │\n│  │  │ • Exit code     │    │ • Exit code     │    │ • Cleanup           │ │\n│  │  │   130           │    │   143           │    │   operations        │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        ON_INTERRUPT() Flow                                   │\n│  Handles SIGINT (Ctrl+C) signals                                              │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Interrupt Processing                                  │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    User Interruption Handling                              │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Display       │    │   Cleanup       │    │   Exit with         │ │ │\n│  │  │   Message       │    │   Operations    │    │   Code 130          │ │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • Show          │    │ • Stop          │    │ • Exit with         │ │\n│  │  │   interruption  │    │   processes     │    │   SIGINT code       │ │\n│  │  │   message       │    │ • Clean up      │    │ • Terminate script  │ │\n│  │  │ • Use red       │    │   temporary     │    │   execution         │ │\n│  │  │   color         │    │   files         │    │                     │ │\n│  │  │ • Clear         │    │ • Remove lock   │    │                     │ │\n│  │  │   terminal      │    │   files         │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Exit Handler Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        ON_EXIT() Flow                                        │\n│  Handles script exit cleanup                                                  │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Exit Cleanup                                          │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Cleanup Operations                                      │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Lock File     │    │   Temporary     │    │   Exit with         │ │ │\n│  │  │   Cleanup       │    │   File          │    │   Original Code     │ │ │\n│  │  │                 │    │   Cleanup      │    │                     │ │\n│  │  │ • Check if      │    │ • Remove        │    │ • Exit with         │ │\n│  │  │   lockfile      │    │   temporary     │    │   original exit     │ │\n│  │  │   variable set  │    │   files         │    │   code              │ │\n│  │  │ • Check if      │    │ • Clean up      │    │ • Preserve exit     │ │\n│  │  │   lockfile      │    │   process       │    │   status            │ │\n│  │  │   exists        │    │   state         │    │ • Terminate         │ │\n│  │  │ • Remove        │    │                 │    │   execution         │ │\n│  │  │   lockfile      │    │                 │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Error Code Explanation Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        EXPLAIN_EXIT_CODE() Flow                              │\n│  Converts numeric exit codes to human-readable explanations                   │\n└─────────────────────┬───────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Error Code Classification                              │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Error Code Categories                                  │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Generic/      │    │   Package       │    │   Node.js           │ │ │\n│  │  │   Shell         │    │   Manager       │    │   Errors            │ │ │\n│  │  │   Errors        │    │   Errors        │    │                     │ │\n│  │  │                 │    │                 │    │ • 243: Out of      │ │\n│  │  │ • 1: General    │    │ • 100: APT      │    │   memory            │ │\n│  │  │   error         │    │   package       │    │ • 245: Invalid      │ │\n│  │  │ • 2: Shell      │    │   error         │    │   option            │ │\n│  │  │   builtin       │    │ • 101: APT      │    │ • 246: Parse        │ │\n│  │  │   misuse        │    │   config error  │    │   error             │ │\n│  │  │ • 126: Cannot   │    │ • 255: DPKG     │    │ • 247: Fatal        │ │\n│  │  │   execute       │    │   fatal error   │    │   error             │ │\n│  │  │ • 127: Command  │    │                 │    │ • 248: Addon        │ │\n│  │  │   not found     │    │                 │    │   failure           │ │\n│  │  │ • 128: Invalid  │    │                 │    │ • 249: Inspector    │ │\n│  │  │   exit          │    │                 │    │   error             │ │\n│  │  │ • 130: SIGINT   │    │                 │    │ • 254: Unknown      │ │\n│  │  │ • 137: SIGKILL  │    │                 │    │   fatal error       │ │\n│  │  │ • 139: Segfault │    │                 │    │                     │ │\n│  │  │ • 143: SIGTERM  │    │                 │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Python        │    │   Database       │    │   Proxmox           │ │ │\n│  │  │   Errors         │    │   Errors         │    │   Custom            │ │ │\n│  │  │                 │    │                 │    │   Errors            │ │\n│  │  │ • 210: Virtual  │    │ • PostgreSQL:   │    │ • 200: Lock file    │ │\n│  │  │   env missing    │    │   231-234        │    │   failed            │ │\n│  │  │ • 211: Dep       │    │ • MySQL: 241-   │    │ • 203: Missing     │ │\n│  │  │   resolution     │    │   244            │    │   CTID              │ │\n│  │  │ • 212: Install   │    │ • MongoDB: 251- │    │ • 204: Missing     │ │\n│  │  │   aborted        │    │   254            │    │   PCT_OSTYPE        │ │\n│  │  │                 │    │                 │    │ • 205: Invalid      │ │\n│  │  │                 │    │                 │    │   CTID              │ │\n│  │  │                 │    │                 │    │ • 209: Container    │ │\n│  │  │                 │    │                 │    │   creation failed   │ │\n│  │  │                 │    │                 │    │ • 210: Cluster      │ │\n│  │  │                 │    │                 │    │   not quorate       │ │\n│  │  │                 │    │                 │    │ • 214: No storage  │ │\n│  │  │                 │    │                 │    │   space             │ │\n│  │  │                 │    │                 │    │ • 215: CTID not    │ │\n│  │  │                 │    │                 │    │   listed            │ │\n│  │  │                 │    │                 │    │ • 216: RootFS      │ │\n│  │  │                 │    │                 │    │   missing           │ │\n│  │  │                 │    │                 │    │ • 217: Storage      │ │\n│  │  │                 │    │                 │    │   not supported     │ │\n│  │  │                 │    │                 │    │ • 220: Template     │ │\n│  │  │                 │    │                 │    │   path error        │ │\n│  │  │                 │    │                 │    │ • 222: Template     │ │\n│  │  │                 │    │                 │    │   download failed   │ │\n│  │  │                 │    │                 │    │ • 223: Template     │ │\n│  │  │                 │    │                 │    │   not available     │ │\n│  │  │                 │    │                 │    │ • 231: LXC stack    │ │\n│  │  │                 │    │                 │    │   upgrade failed    │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n                      │\n                      ▼\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Default Case                                           │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Unknown Error Handling                                  │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Check for     │    │   Return        │    │   Log Unknown        │ │ │\n│  │  │   Unknown       │    │   Generic       │    │   Error              │ │ │\n│  │  │   Code          │    │   Message       │    │                     │ │\n│  │  │                 │    │                 │    │ • Log to debug      │ │\n│  │  │ • If no match   │    │ • \"Unknown      │    │   file if enabled   │ │\n│  │  │   found         │    │   error\"        │    │ • Include error      │ │\n│  │  │ • Use default   │    │ • Return to     │    │   code               │ │\n│  │  │   case          │    │   caller        │    │ • Include           │ │\n│  │  │                 │    │                 │    │   timestamp         │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Debug Logging Flow\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                        Debug Log Integration                                  │\n│                                                                                 │\n│  ┌─────────────────────────────────────────────────────────────────────────────┐ │\n│  │                    Debug Log Writing                                      │ │\n│  │                                                                           │ │\n│  │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │ │\n│  │  │   Check Debug   │    │   Write Error    │    │   Format Log        │ │ │\n│  │  │   Log File       │    │   Information    │    │   Entry             │ │ │\n│  │  │                 │    │                 │    │                     │ │\n│  │  │ • Check if       │    │ • Timestamp     │    │ • Error separator   │ │\n│  │  │   DEBUG_LOGFILE  │    │ • Exit code     │    │ • Structured        │ │\n│  │  │   set            │    │ • Explanation   │    │   format             │ │\n│  │  │ • Check if       │    │ • Line number   │    │ • Easy to parse     │ │\n│  │  │   file exists    │    │ • Command       │    │ • Easy to read      │ │\n│  │  │ • Check if       │    │ • Append to     │    │                     │ │\n│  │  │   file writable  │    │   file           │    │                     │ │\n│  │  └─────────────────┘    └─────────────────┘    └─────────────────────┘ │ │\n│  └─────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Integration Points\n\n### With core.func\n- **Silent Execution**: Provides error explanations for silent() function\n- **Color Variables**: Uses color variables for error display\n- **Log Integration**: Integrates with SILENT_LOGFILE\n\n### With Other Scripts\n- **Error Traps**: Sets up ERR trap for automatic error handling\n- **Signal Traps**: Handles SIGINT, SIGTERM, and EXIT signals\n- **Cleanup**: Provides cleanup on script exit\n\n### External Dependencies\n- **None**: Pure Bash implementation\n- **Color Support**: Requires color variables from core.func\n- **Log Files**: Uses standard file operations\n"
  },
  {
    "path": "docs/misc/error_handler.func/ERROR_HANDLER_FUNCTIONS_REFERENCE.md",
    "content": "# error_handler.func Functions Reference\n\n## Overview\n\nThis document provides a comprehensive alphabetical reference of all functions in `error_handler.func`, including parameters, dependencies, usage examples, and error handling.\n\n## Function Categories\n\n### Error Explanation Functions\n\n#### `explain_exit_code()`\n**Purpose**: Convert numeric exit codes to human-readable explanations\n**Parameters**:\n- `$1` - Exit code to explain\n**Returns**: Human-readable error explanation string\n**Side Effects**: None\n**Dependencies**: None\n**Environment Variables Used**: None\n\n**Supported Exit Codes**:\n- **Generic/Shell**: 1, 2, 126, 127, 128, 130, 137, 139, 143\n- **Package Manager**: 100, 101, 255\n- **Node.js**: 243, 245, 246, 247, 248, 249, 254\n- **Python**: 210, 211, 212\n- **PostgreSQL**: 231, 232, 233, 234\n- **MySQL/MariaDB**: 241, 242, 243, 244\n- **MongoDB**: 251, 252, 253, 254\n- **Proxmox Custom**: 200, 203, 204, 205, 209, 210, 214, 215, 216, 217, 220, 222, 223, 231\n\n**Usage Example**:\n```bash\nexplanation=$(explain_exit_code 127)\necho \"Error 127: $explanation\"\n# Output: Error 127: Command not found\n```\n\n**Error Code Examples**:\n```bash\nexplain_exit_code 1    # \"General error / Operation not permitted\"\nexplain_exit_code 126  # \"Command invoked cannot execute (permission problem?)\"\nexplain_exit_code 127  # \"Command not found\"\nexplain_exit_code 130  # \"Terminated by Ctrl+C (SIGINT)\"\nexplain_exit_code 200  # \"Custom: Failed to create lock file\"\nexplain_exit_code 999  # \"Unknown error\"\n```\n\n### Error Handling Functions\n\n#### `error_handler()`\n**Purpose**: Main error handler triggered by ERR trap or manual call\n**Parameters**:\n- `$1` - Exit code (optional, defaults to $?)\n- `$2` - Command that failed (optional, defaults to BASH_COMMAND)\n**Returns**: None (exits with error code)\n**Side Effects**:\n- Displays detailed error information\n- Logs error to debug file if enabled\n- Shows silent log content if available\n- Exits with original error code\n**Dependencies**: `explain_exit_code()`\n**Environment Variables Used**: `DEBUG_LOGFILE`, `SILENT_LOGFILE`\n\n**Usage Example**:\n```bash\n# Automatic error handling via ERR trap\nset -e\ntrap 'error_handler' ERR\n\n# Manual error handling\nerror_handler 127 \"command_not_found\"\n```\n\n**Error Information Displayed**:\n- Error message with color coding\n- Line number where error occurred\n- Exit code with explanation\n- Command that failed\n- Silent log content (last 20 lines)\n- Debug log entry (if enabled)\n\n### Signal Handling Functions\n\n#### `on_interrupt()`\n**Purpose**: Handle SIGINT (Ctrl+C) signals gracefully\n**Parameters**: None\n**Returns**: None (exits with code 130)\n**Side Effects**:\n- Displays interruption message\n- Exits with SIGINT code (130)\n**Dependencies**: None\n**Environment Variables Used**: None\n\n**Usage Example**:\n```bash\n# Set up interrupt handler\ntrap on_interrupt INT\n\n# User presses Ctrl+C\n# Handler displays: \"Interrupted by user (SIGINT)\"\n# Script exits with code 130\n```\n\n#### `on_terminate()`\n**Purpose**: Handle SIGTERM signals gracefully\n**Parameters**: None\n**Returns**: None (exits with code 143)\n**Side Effects**:\n- Displays termination message\n- Exits with SIGTERM code (143)\n**Dependencies**: None\n**Environment Variables Used**: None\n\n**Usage Example**:\n```bash\n# Set up termination handler\ntrap on_terminate TERM\n\n# System sends SIGTERM\n# Handler displays: \"Terminated by signal (SIGTERM)\"\n# Script exits with code 143\n```\n\n### Cleanup Functions\n\n#### `on_exit()`\n**Purpose**: Handle script exit cleanup\n**Parameters**: None\n**Returns**: None (exits with original exit code)\n**Side Effects**:\n- Removes lock file if set\n- Exits with original exit code\n**Dependencies**: None\n**Environment Variables Used**: `lockfile`\n\n**Usage Example**:\n```bash\n# Set up exit handler\ntrap on_exit EXIT\n\n# Set lock file\nlockfile=\"/tmp/my_script.lock\"\n\n# Script exits normally or with error\n# Handler removes lock file and exits\n```\n\n### Initialization Functions\n\n#### `catch_errors()`\n**Purpose**: Initialize error handling traps and strict mode\n**Parameters**: None\n**Returns**: None\n**Side Effects**:\n- Sets strict error handling mode\n- Sets up error traps\n- Sets up signal traps\n- Sets up exit trap\n**Dependencies**: None\n**Environment Variables Used**: `STRICT_UNSET`\n\n**Strict Mode Settings**:\n- `-E`: Exit on command failure\n- `-e`: Exit on any error\n- `-o pipefail`: Exit on pipe failure\n- `-u`: Exit on unset variables (if STRICT_UNSET=1)\n\n**Trap Setup**:\n- `ERR`: Calls `error_handler` on command failure\n- `EXIT`: Calls `on_exit` on script exit\n- `INT`: Calls `on_interrupt` on SIGINT\n- `TERM`: Calls `on_terminate` on SIGTERM\n\n**Usage Example**:\n```bash\n# Initialize error handling\ncatch_errors\n\n# Script now has full error handling\n# All errors will be caught and handled\n```\n\n## Function Call Hierarchy\n\n### Error Handling Flow\n```\nCommand Failure\n├── ERR trap triggered\n├── error_handler() called\n│   ├── Get exit code\n│   ├── Get command info\n│   ├── Get line number\n│   ├── explain_exit_code()\n│   ├── Display error info\n│   ├── Log to debug file\n│   ├── Show silent log\n│   └── Exit with error code\n```\n\n### Signal Handling Flow\n```\nSignal Received\n├── Signal trap triggered\n├── Appropriate handler called\n│   ├── on_interrupt() for SIGINT\n│   ├── on_terminate() for SIGTERM\n│   └── on_exit() for EXIT\n└── Exit with signal code\n```\n\n### Initialization Flow\n```\ncatch_errors()\n├── Set strict mode\n│   ├── -E (exit on failure)\n│   ├── -e (exit on error)\n│   ├── -o pipefail (pipe failure)\n│   └── -u (unset variables, if enabled)\n└── Set up traps\n    ├── ERR → error_handler\n    ├── EXIT → on_exit\n    ├── INT → on_interrupt\n    └── TERM → on_terminate\n```\n\n## Error Code Reference\n\n### Generic/Shell Errors\n| Code | Description |\n|------|-------------|\n| 1 | General error / Operation not permitted |\n| 2 | Misuse of shell builtins (e.g. syntax error) |\n| 126 | Command invoked cannot execute (permission problem?) |\n| 127 | Command not found |\n| 128 | Invalid argument to exit |\n| 130 | Terminated by Ctrl+C (SIGINT) |\n| 137 | Killed (SIGKILL / Out of memory?) |\n| 139 | Segmentation fault (core dumped) |\n| 143 | Terminated (SIGTERM) |\n\n### Package Manager Errors\n| Code | Description |\n|------|-------------|\n| 100 | APT: Package manager error (broken packages / dependency problems) |\n| 101 | APT: Configuration error (bad sources.list, malformed config) |\n| 255 | DPKG: Fatal internal error |\n\n### Node.js Errors\n| Code | Description |\n|------|-------------|\n| 243 | Node.js: Out of memory (JavaScript heap out of memory) |\n| 245 | Node.js: Invalid command-line option |\n| 246 | Node.js: Internal JavaScript Parse Error |\n| 247 | Node.js: Fatal internal error |\n| 248 | Node.js: Invalid C++ addon / N-API failure |\n| 249 | Node.js: Inspector error |\n| 254 | npm/pnpm/yarn: Unknown fatal error |\n\n### Python Errors\n| Code | Description |\n|------|-------------|\n| 210 | Python: Virtualenv / uv environment missing or broken |\n| 211 | Python: Dependency resolution failed |\n| 212 | Python: Installation aborted (permissions or EXTERNALLY-MANAGED) |\n\n### Database Errors\n| Code | Description |\n|------|-------------|\n| 231 | PostgreSQL: Connection failed (server not running / wrong socket) |\n| 232 | PostgreSQL: Authentication failed (bad user/password) |\n| 233 | PostgreSQL: Database does not exist |\n| 234 | PostgreSQL: Fatal error in query / syntax |\n| 241 | MySQL/MariaDB: Connection failed (server not running / wrong socket) |\n| 242 | MySQL/MariaDB: Authentication failed (bad user/password) |\n| 243 | MySQL/MariaDB: Database does not exist |\n| 244 | MySQL/MariaDB: Fatal error in query / syntax |\n| 251 | MongoDB: Connection failed (server not running) |\n| 252 | MongoDB: Authentication failed (bad user/password) |\n| 253 | MongoDB: Database not found |\n| 254 | MongoDB: Fatal query error |\n\n### Proxmox Custom Errors\n| Code | Description |\n|------|-------------|\n| 200 | Custom: Failed to create lock file |\n| 203 | Custom: Missing CTID variable |\n| 204 | Custom: Missing PCT_OSTYPE variable |\n| 205 | Custom: Invalid CTID (<100) |\n| 209 | Custom: Container creation failed |\n| 210 | Custom: Cluster not quorate |\n| 214 | Custom: Not enough storage space |\n| 215 | Custom: Container ID not listed |\n| 216 | Custom: RootFS entry missing in config |\n| 217 | Custom: Storage does not support rootdir |\n| 220 | Custom: Unable to resolve template path |\n| 222 | Custom: Template download failed after 3 attempts |\n| 223 | Custom: Template not available after download |\n| 231 | Custom: LXC stack upgrade/retry failed |\n\n## Environment Variable Dependencies\n\n### Required Variables\n- **`lockfile`**: Lock file path for cleanup (set by calling script)\n\n### Optional Variables\n- **`DEBUG_LOGFILE`**: Path to debug log file for error logging\n- **`SILENT_LOGFILE`**: Path to silent execution log file\n- **`STRICT_UNSET`**: Enable strict unset variable checking (0/1)\n\n### Internal Variables\n- **`exit_code`**: Current exit code\n- **`command`**: Failed command\n- **`line_number`**: Line number where error occurred\n- **`explanation`**: Error explanation text\n\n## Error Handling Patterns\n\n### Automatic Error Handling\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Initialize error handling\ncatch_errors\n\n# All commands are now monitored\n# Errors will be automatically caught and handled\n```\n\n### Manual Error Handling\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Manual error handling\nif ! command -v required_tool >/dev/null 2>&1; then\n    error_handler 127 \"required_tool not found\"\nfi\n```\n\n### Custom Error Codes\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Use custom error codes\nif [[ ! -f /required/file ]]; then\n    echo \"Error: Required file missing\"\n    exit 200  # Custom error code\nfi\n```\n\n### Signal Handling\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Set up signal handling\ntrap on_interrupt INT\ntrap on_terminate TERM\ntrap on_exit EXIT\n\n# Script handles signals gracefully\n```\n\n## Integration Examples\n\n### With core.func\n```bash\n#!/usr/bin/env bash\nsource core.func\nsource error_handler.func\n\n# Silent execution uses error_handler for explanations\nsilent apt-get install -y package\n# If command fails, error_handler provides explanation\n```\n\n### With build.func\n```bash\n#!/usr/bin/env bash\nsource core.func\nsource error_handler.func\nsource build.func\n\n# Container creation with error handling\n# Errors are caught and explained\n```\n\n### With tools.func\n```bash\n#!/usr/bin/env bash\nsource core.func\nsource error_handler.func\nsource tools.func\n\n# Tool operations with error handling\n# All errors are properly handled and explained\n```\n\n## Best Practices\n\n### Error Handling Setup\n1. Source error_handler.func early in script\n2. Call catch_errors() to initialize traps\n3. Use appropriate exit codes for different error types\n4. Provide meaningful error messages\n\n### Signal Handling\n1. Always set up signal traps\n2. Provide graceful cleanup on interruption\n3. Use appropriate exit codes for signals\n4. Clean up temporary files and processes\n\n### Error Reporting\n1. Use explain_exit_code() for user-friendly messages\n2. Log errors to debug files when needed\n3. Provide context information (line numbers, commands)\n4. Integrate with silent execution logging\n\n### Custom Error Codes\n1. Use Proxmox custom error codes (200-231) for container/VM errors\n2. Use standard error codes for common operations\n3. Document custom error codes in script comments\n4. Provide clear error messages for custom codes\n"
  },
  {
    "path": "docs/misc/error_handler.func/ERROR_HANDLER_INTEGRATION.md",
    "content": "# error_handler.func Integration Guide\n\n## Overview\n\nThis document describes how `error_handler.func` integrates with other components in the Proxmox Community Scripts project, including dependencies, data flow, and API surface.\n\n## Dependencies\n\n### External Dependencies\n\n#### Required Commands\n- **None**: Pure Bash implementation\n\n#### Optional Commands\n- **None**: No external command dependencies\n\n### Internal Dependencies\n\n#### core.func\n- **Purpose**: Provides color variables for error display\n- **Usage**: Uses `RD`, `CL`, `YWB` color variables\n- **Integration**: Called automatically when core.func is sourced\n- **Data Flow**: Color variables → error display formatting\n\n## Integration Points\n\n### With core.func\n\n#### Silent Execution Integration\n```bash\n# core.func silent() function uses error_handler.func\nsilent() {\n    local cmd=\"$*\"\n    local caller_line=\"${BASH_LINENO[0]:-unknown}\"\n\n    # Execute command\n    \"$@\" >>\"$SILENT_LOGFILE\" 2>&1\n    local rc=$?\n\n    if [[ $rc -ne 0 ]]; then\n        # Load error_handler.func if needed\n        if ! declare -f explain_exit_code >/dev/null 2>&1; then\n            source error_handler.func\n        fi\n\n        # Get error explanation\n        local explanation\n        explanation=\"$(explain_exit_code \"$rc\")\"\n\n        # Display error with explanation\n        printf \"\\e[?25h\"\n        echo -e \"\\n${RD}[ERROR]${CL} in line ${RD}${caller_line}${CL}: exit code ${RD}${rc}${CL} (${explanation})\"\n        echo -e \"${RD}Command:${CL} ${YWB}${cmd}${CL}\\n\"\n\n        exit \"$rc\"\n    fi\n}\n```\n\n#### Color Variable Usage\n```bash\n# error_handler.func uses color variables from core.func\nerror_handler() {\n    # ... error handling logic ...\n\n    # Use color variables for error display\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}\n\non_interrupt() {\n    echo -e \"\\n${RD}Interrupted by user (SIGINT)${CL}\"\n    exit 130\n}\n\non_terminate() {\n    echo -e \"\\n${RD}Terminated by signal (SIGTERM)${CL}\"\n    exit 143\n}\n```\n\n### With build.func\n\n#### Container Creation Error Handling\n```bash\n# build.func uses error_handler.func for container operations\nsource core.func\nsource error_handler.func\n\n# Container creation with error handling\ncreate_container() {\n    # Set up error handling\n    catch_errors\n\n    # Container creation operations\n    silent pct create \"$CTID\" \"$TEMPLATE\" \\\n        --hostname \"$HOSTNAME\" \\\n        --memory \"$MEMORY\" \\\n        --cores \"$CORES\"\n\n    # If creation fails, error_handler provides explanation\n}\n```\n\n#### Template Download Error Handling\n```bash\n# build.func uses error_handler.func for template operations\ndownload_template() {\n    # Template download with error handling\n    if ! silent curl -fsSL \"$TEMPLATE_URL\" -o \"$TEMPLATE_FILE\"; then\n        # error_handler provides detailed explanation\n        exit 222  # Template download failed\n    fi\n}\n```\n\n### With tools.func\n\n#### Maintenance Operations Error Handling\n```bash\n# tools.func uses error_handler.func for maintenance operations\nsource core.func\nsource error_handler.func\n\n# Maintenance operations with error handling\nupdate_system() {\n    catch_errors\n\n    # System update operations\n    silent apt-get update\n    silent apt-get upgrade -y\n\n    # Error handling provides explanations for failures\n}\n\ncleanup_logs() {\n    catch_errors\n\n    # Log cleanup operations\n    silent find /var/log -name \"*.log\" -mtime +30 -delete\n\n    # Error handling provides explanations for permission issues\n}\n```\n\n### With api.func\n\n#### API Operations Error Handling\n```bash\n# api.func uses error_handler.func for API operations\nsource core.func\nsource error_handler.func\n\n# API operations with error handling\napi_call() {\n    catch_errors\n\n    # API call with error handling\n    if ! silent curl -k -H \"Authorization: PVEAPIToken=$API_TOKEN\" \\\n        \"$API_URL/api2/json/nodes/$NODE/lxc\"; then\n        # error_handler provides explanation for API failures\n        exit 1\n    fi\n}\n```\n\n### With install.func\n\n#### Installation Process Error Handling\n```bash\n# install.func uses error_handler.func for installation operations\nsource core.func\nsource error_handler.func\n\n# Installation with error handling\ninstall_package() {\n    local package=\"$1\"\n\n    catch_errors\n\n    # Package installation\n    silent apt-get install -y \"$package\"\n\n    # Error handling provides explanations for installation failures\n}\n```\n\n### With alpine-install.func\n\n#### Alpine Installation Error Handling\n```bash\n# alpine-install.func uses error_handler.func for Alpine operations\nsource core.func\nsource error_handler.func\n\n# Alpine installation with error handling\ninstall_alpine_package() {\n    local package=\"$1\"\n\n    catch_errors\n\n    # Alpine package installation\n    silent apk add --no-cache \"$package\"\n\n    # Error handling provides explanations for Alpine-specific failures\n}\n```\n\n### With alpine-tools.func\n\n#### Alpine Tools Error Handling\n```bash\n# alpine-tools.func uses error_handler.func for Alpine tools\nsource core.func\nsource error_handler.func\n\n# Alpine tools with error handling\nalpine_tool_operation() {\n    catch_errors\n\n    # Alpine-specific tool operations\n    silent alpine_command\n\n    # Error handling provides explanations for Alpine tool failures\n}\n```\n\n### With passthrough.func\n\n#### Hardware Passthrough Error Handling\n```bash\n# passthrough.func uses error_handler.func for hardware operations\nsource core.func\nsource error_handler.func\n\n# Hardware passthrough with error handling\nconfigure_gpu_passthrough() {\n    catch_errors\n\n    # GPU passthrough operations\n    silent lspci | grep -i nvidia\n\n    # Error handling provides explanations for hardware failures\n}\n```\n\n### With vm-core.func\n\n#### VM Operations Error Handling\n```bash\n# vm-core.func uses error_handler.func for VM operations\nsource core.func\nsource error_handler.func\n\n# VM operations with error handling\ncreate_vm() {\n    catch_errors\n\n    # VM creation operations\n    silent qm create \"$VMID\" \\\n        --name \"$VMNAME\" \\\n        --memory \"$MEMORY\" \\\n        --cores \"$CORES\"\n\n    # Error handling provides explanations for VM creation failures\n}\n```\n\n## Data Flow\n\n### Input Data\n\n#### Environment Variables\n- **`DEBUG_LOGFILE`**: Path to debug log file for error logging\n- **`SILENT_LOGFILE`**: Path to silent execution log file\n- **`STRICT_UNSET`**: Enable strict unset variable checking (0/1)\n- **`lockfile`**: Lock file path for cleanup (set by calling script)\n\n#### Function Parameters\n- **Exit codes**: Passed to `explain_exit_code()` and `error_handler()`\n- **Command information**: Passed to `error_handler()` for context\n- **Signal information**: Passed to signal handlers\n\n#### System Information\n- **Exit codes**: Retrieved from `$?` variable\n- **Command information**: Retrieved from `BASH_COMMAND` variable\n- **Line numbers**: Retrieved from `BASH_LINENO[0]` variable\n- **Process information**: Retrieved from system calls\n\n### Processing Data\n\n#### Error Code Processing\n- **Code classification**: Categorize exit codes by type\n- **Explanation lookup**: Map codes to human-readable messages\n- **Context collection**: Gather command and line information\n- **Log preparation**: Format error information for logging\n\n#### Signal Processing\n- **Signal detection**: Identify received signals\n- **Handler selection**: Choose appropriate signal handler\n- **Cleanup operations**: Perform necessary cleanup\n- **Exit code setting**: Set appropriate exit codes\n\n#### Log Processing\n- **Debug logging**: Write error information to debug log\n- **Silent log integration**: Display silent log content\n- **Log formatting**: Format log entries for readability\n- **Log analysis**: Provide log analysis capabilities\n\n### Output Data\n\n#### Error Information\n- **Error messages**: Human-readable error explanations\n- **Context information**: Line numbers, commands, timestamps\n- **Color formatting**: ANSI color codes for terminal display\n- **Log content**: Silent log excerpts and debug information\n\n#### System State\n- **Exit codes**: Returned from functions\n- **Log files**: Created and updated for error tracking\n- **Cleanup status**: Lock file removal and process cleanup\n- **Signal handling**: Graceful signal processing\n\n## API Surface\n\n### Public Functions\n\n#### Error Explanation\n- **`explain_exit_code()`**: Convert exit codes to explanations\n- **Parameters**: Exit code to explain\n- **Returns**: Human-readable explanation string\n- **Usage**: Called by error_handler() and other functions\n\n#### Error Handling\n- **`error_handler()`**: Main error handler function\n- **Parameters**: Exit code (optional), command (optional)\n- **Returns**: None (exits with error code)\n- **Usage**: Called by ERR trap or manually\n\n#### Signal Handling\n- **`on_interrupt()`**: Handle SIGINT signals\n- **`on_terminate()`**: Handle SIGTERM signals\n- **`on_exit()`**: Handle script exit cleanup\n- **Parameters**: None\n- **Returns**: None (exits with signal code)\n- **Usage**: Called by signal traps\n\n#### Initialization\n- **`catch_errors()`**: Initialize error handling\n- **Parameters**: None\n- **Returns**: None\n- **Usage**: Called to set up error handling traps\n\n### Internal Functions\n\n#### None\n- All functions in error_handler.func are public\n- No internal helper functions\n- Direct implementation of all functionality\n\n### Global Variables\n\n#### Configuration Variables\n- **`DEBUG_LOGFILE`**: Debug log file path\n- **`SILENT_LOGFILE`**: Silent log file path\n- **`STRICT_UNSET`**: Strict mode setting\n- **`lockfile`**: Lock file path\n\n#### State Variables\n- **`exit_code`**: Current exit code\n- **`command`**: Failed command\n- **`line_number`**: Line number where error occurred\n- **`explanation`**: Error explanation text\n\n## Integration Patterns\n\n### Standard Integration Pattern\n\n```bash\n#!/usr/bin/env bash\n# Standard integration pattern\n\n# 1. Source core.func first\nsource core.func\n\n# 2. Source error_handler.func\nsource error_handler.func\n\n# 3. Initialize error handling\ncatch_errors\n\n# 4. Use silent execution\nsilent command\n\n# 5. Errors are automatically handled\n```\n\n### Minimal Integration Pattern\n\n```bash\n#!/usr/bin/env bash\n# Minimal integration pattern\n\nsource error_handler.func\ncatch_errors\n\n# Basic error handling\ncommand\n```\n\n### Advanced Integration Pattern\n\n```bash\n#!/usr/bin/env bash\n# Advanced integration pattern\n\nsource core.func\nsource error_handler.func\n\n# Set up comprehensive error handling\nexport DEBUG_LOGFILE=\"/tmp/debug.log\"\nexport SILENT_LOGFILE=\"/tmp/silent.log\"\nlockfile=\"/tmp/script.lock\"\ntouch \"$lockfile\"\n\ncatch_errors\ntrap on_interrupt INT\ntrap on_terminate TERM\ntrap on_exit EXIT\n\n# Advanced error handling\nsilent command\n```\n\n## Error Handling Integration\n\n### Automatic Error Handling\n- **ERR Trap**: Automatically catches command failures\n- **Error Explanation**: Provides human-readable error messages\n- **Context Information**: Shows line numbers and commands\n- **Log Integration**: Displays silent log content\n\n### Manual Error Handling\n- **Custom Error Codes**: Use Proxmox custom error codes\n- **Error Recovery**: Implement retry logic with error handling\n- **Conditional Handling**: Different handling for different error types\n- **Error Analysis**: Analyze error patterns and trends\n\n### Signal Handling Integration\n- **Graceful Interruption**: Handle Ctrl+C gracefully\n- **Clean Termination**: Handle SIGTERM signals\n- **Exit Cleanup**: Clean up resources on script exit\n- **Lock File Management**: Remove lock files on exit\n\n## Performance Considerations\n\n### Error Handling Overhead\n- **Minimal Impact**: Error handling adds minimal overhead\n- **Trap Setup**: Trap setup is done once during initialization\n- **Error Processing**: Error processing is only done on failures\n- **Log Writing**: Log writing is only done when enabled\n\n### Memory Usage\n- **Minimal Footprint**: Error handler uses minimal memory\n- **Variable Reuse**: Global variables reused across functions\n- **No Memory Leaks**: Proper cleanup prevents memory leaks\n- **Efficient Processing**: Efficient error code processing\n\n### Execution Speed\n- **Fast Error Detection**: Quick error detection and handling\n- **Efficient Explanation**: Fast error code explanation lookup\n- **Minimal Delay**: Minimal delay in error handling\n- **Quick Exit**: Fast exit on error conditions\n\n## Security Considerations\n\n### Error Information Disclosure\n- **Controlled Disclosure**: Only necessary error information is shown\n- **Log Security**: Log files have appropriate permissions\n- **Sensitive Data**: Sensitive data is not logged\n- **Error Sanitization**: Error messages are sanitized\n\n### Signal Handling Security\n- **Signal Validation**: Only expected signals are handled\n- **Cleanup Security**: Secure cleanup of temporary files\n- **Lock File Security**: Secure lock file management\n- **Process Security**: Secure process termination\n\n### Log File Security\n- **File Permissions**: Log files have appropriate permissions\n- **Log Rotation**: Log files are rotated to prevent disk filling\n- **Log Cleanup**: Old log files are cleaned up\n- **Log Access**: Log access is controlled\n\n## Future Integration Considerations\n\n### Extensibility\n- **New Error Codes**: Easy to add new error code explanations\n- **Custom Handlers**: Easy to add custom error handlers\n- **Signal Extensions**: Easy to add new signal handlers\n- **Log Formats**: Easy to add new log formats\n\n### Compatibility\n- **Bash Version**: Compatible with different Bash versions\n- **System Compatibility**: Compatible with different systems\n- **Script Compatibility**: Compatible with different script types\n- **Error Code Compatibility**: Compatible with different error codes\n\n### Performance\n- **Optimization**: Error handling can be optimized for better performance\n- **Caching**: Error explanations can be cached for faster lookup\n- **Parallel Processing**: Error handling can be parallelized\n- **Resource Management**: Better resource management for error handling\n"
  },
  {
    "path": "docs/misc/error_handler.func/ERROR_HANDLER_USAGE_EXAMPLES.md",
    "content": "# error_handler.func Usage Examples\n\n## Overview\n\nThis document provides practical usage examples for `error_handler.func` functions, covering common scenarios, integration patterns, and best practices.\n\n## Basic Error Handling Setup\n\n### Standard Script Initialization\n\n```bash\n#!/usr/bin/env bash\n# Standard error handling setup\n\n# Source error handler\nsource error_handler.func\n\n# Initialize error handling\ncatch_errors\n\n# Your script code here\n# All errors will be automatically caught and handled\necho \"Script running...\"\napt-get update\napt-get install -y package\necho \"Script completed successfully\"\n```\n\n### Minimal Error Handling\n\n```bash\n#!/usr/bin/env bash\n# Minimal error handling setup\n\nsource error_handler.func\ncatch_errors\n\n# Simple script with error handling\necho \"Starting operation...\"\ncommand_that_might_fail\necho \"Operation completed\"\n```\n\n## Error Code Explanation Examples\n\n### Basic Error Explanation\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Explain common error codes\necho \"Error 1: $(explain_exit_code 1)\"\necho \"Error 127: $(explain_exit_code 127)\"\necho \"Error 130: $(explain_exit_code 130)\"\necho \"Error 200: $(explain_exit_code 200)\"\n```\n\n### Error Code Testing\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Test all error codes\ntest_error_codes() {\n    local codes=(1 2 126 127 128 130 137 139 143 100 101 255 200 203 204 205)\n\n    for code in \"${codes[@]}\"; do\n        echo \"Code $code: $(explain_exit_code $code)\"\n    done\n}\n\ntest_error_codes\n```\n\n### Custom Error Code Usage\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Use custom error codes\ncheck_requirements() {\n    if [[ ! -f /required/file ]]; then\n        echo \"Error: Required file missing\"\n        exit 200  # Custom error code\n    fi\n\n    if [[ -z \"$CTID\" ]]; then\n        echo \"Error: CTID not set\"\n        exit 203  # Custom error code\n    fi\n\n    if [[ $CTID -lt 100 ]]; then\n        echo \"Error: Invalid CTID\"\n        exit 205  # Custom error code\n    fi\n}\n\ncheck_requirements\n```\n\n## Signal Handling Examples\n\n### Interrupt Handling\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Set up interrupt handler\ntrap on_interrupt INT\n\necho \"Script running... Press Ctrl+C to interrupt\"\nsleep 10\necho \"Script completed normally\"\n```\n\n### Termination Handling\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Set up termination handler\ntrap on_terminate TERM\n\necho \"Script running... Send SIGTERM to terminate\"\nsleep 10\necho \"Script completed normally\"\n```\n\n### Complete Signal Handling\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Set up all signal handlers\ntrap on_interrupt INT\ntrap on_terminate TERM\ntrap on_exit EXIT\n\necho \"Script running with full signal handling\"\nsleep 10\necho \"Script completed normally\"\n```\n\n## Cleanup Examples\n\n### Lock File Cleanup\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Set up lock file\nlockfile=\"/tmp/my_script.lock\"\ntouch \"$lockfile\"\n\n# Set up exit handler\ntrap on_exit EXIT\n\necho \"Script running with lock file...\"\nsleep 5\necho \"Script completed - lock file will be removed\"\n```\n\n### Temporary File Cleanup\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Create temporary files\ntemp_file1=\"/tmp/temp1.$$\"\ntemp_file2=\"/tmp/temp2.$$\"\ntouch \"$temp_file1\" \"$temp_file2\"\n\n# Set up cleanup\ncleanup() {\n    rm -f \"$temp_file1\" \"$temp_file2\"\n    echo \"Temporary files cleaned up\"\n}\n\ntrap cleanup EXIT\n\necho \"Script running with temporary files...\"\nsleep 5\necho \"Script completed - temporary files will be cleaned up\"\n```\n\n## Debug Logging Examples\n\n### Basic Debug Logging\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Enable debug logging\nexport DEBUG_LOGFILE=\"/tmp/debug.log\"\ncatch_errors\n\necho \"Script with debug logging\"\napt-get update\napt-get install -y package\n```\n\n### Debug Log Analysis\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Enable debug logging\nexport DEBUG_LOGFILE=\"/tmp/debug.log\"\ncatch_errors\n\n# Function to analyze debug log\nanalyze_debug_log() {\n    if [[ -f \"$DEBUG_LOGFILE\" ]]; then\n        echo \"Debug log analysis:\"\n        echo \"Total errors: $(grep -c \"ERROR\" \"$DEBUG_LOGFILE\")\"\n        echo \"Recent errors:\"\n        tail -n 5 \"$DEBUG_LOGFILE\"\n    else\n        echo \"No debug log found\"\n    fi\n}\n\n# Run script\necho \"Running script...\"\napt-get update\n\n# Analyze results\nanalyze_debug_log\n```\n\n## Silent Execution Integration\n\n### With core.func Silent Execution\n\n```bash\n#!/usr/bin/env bash\nsource core.func\nsource error_handler.func\n\n# Silent execution with error handling\necho \"Installing packages...\"\nsilent apt-get update\nsilent apt-get install -y nginx\n\necho \"Configuring service...\"\nsilent systemctl enable nginx\nsilent systemctl start nginx\n\necho \"Installation completed\"\n```\n\n### Silent Execution Error Handling\n\n```bash\n#!/usr/bin/env bash\nsource core.func\nsource error_handler.func\n\n# Function with silent execution and error handling\ninstall_package() {\n    local package=\"$1\"\n\n    echo \"Installing $package...\"\n    if silent apt-get install -y \"$package\"; then\n        echo \"$package installed successfully\"\n        return 0\n    else\n        echo \"Failed to install $package\"\n        return 1\n    fi\n}\n\n# Install multiple packages\npackages=(\"nginx\" \"apache2\" \"mysql-server\")\nfor package in \"${packages[@]}\"; do\n    if ! install_package \"$package\"; then\n        echo \"Stopping installation due to error\"\n        exit 1\n    fi\ndone\n```\n\n## Advanced Error Handling Examples\n\n### Conditional Error Handling\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Conditional error handling based on environment\nsetup_error_handling() {\n    if [[ \"${STRICT_MODE:-0}\" == \"1\" ]]; then\n        echo \"Enabling strict mode\"\n        export STRICT_UNSET=1\n    fi\n\n    catch_errors\n    echo \"Error handling configured\"\n}\n\nsetup_error_handling\n```\n\n### Error Recovery\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Error recovery pattern\nretry_operation() {\n    local max_attempts=3\n    local attempt=1\n\n    while [[ $attempt -le $max_attempts ]]; do\n        echo \"Attempt $attempt of $max_attempts\"\n\n        if silent \"$@\"; then\n            echo \"Operation succeeded on attempt $attempt\"\n            return 0\n        else\n            echo \"Attempt $attempt failed\"\n            ((attempt++))\n\n            if [[ $attempt -le $max_attempts ]]; then\n                echo \"Retrying in 5 seconds...\"\n                sleep 5\n            fi\n        fi\n    done\n\n    echo \"Operation failed after $max_attempts attempts\"\n    return 1\n}\n\n# Use retry pattern\nretry_operation apt-get update\nretry_operation apt-get install -y package\n```\n\n### Custom Error Handler\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Custom error handler for specific operations\ncustom_error_handler() {\n    local exit_code=${1:-$?}\n    local command=${2:-${BASH_COMMAND:-unknown}}\n\n    case \"$exit_code\" in\n        127)\n            echo \"Custom handling: Command not found - $command\"\n            echo \"Suggestions:\"\n            echo \"1. Check if the command is installed\"\n            echo \"2. Check if the command is in PATH\"\n            echo \"3. Check spelling\"\n            ;;\n        126)\n            echo \"Custom handling: Permission denied - $command\"\n            echo \"Suggestions:\"\n            echo \"1. Check file permissions\"\n            echo \"2. Run with appropriate privileges\"\n            echo \"3. Check if file is executable\"\n            ;;\n        *)\n            # Use default error handler\n            error_handler \"$exit_code\" \"$command\"\n            ;;\n    esac\n}\n\n# Set up custom error handler\ntrap 'custom_error_handler' ERR\n\n# Test custom error handling\nnonexistent_command\n```\n\n## Integration Examples\n\n### With build.func\n\n```bash\n#!/usr/bin/env bash\n# Integration with build.func\n\nsource core.func\nsource error_handler.func\nsource build.func\n\n# Container creation with error handling\nexport APP=\"plex\"\nexport CTID=\"100\"\n\n# Errors will be caught and explained\n# Silent execution will use error_handler for explanations\n```\n\n### With tools.func\n\n```bash\n#!/usr/bin/env bash\n# Integration with tools.func\n\nsource core.func\nsource error_handler.func\nsource tools.func\n\n# Tool operations with error handling\n# All errors are properly handled and explained\n```\n\n### With api.func\n\n```bash\n#!/usr/bin/env bash\n# Integration with api.func\n\nsource core.func\nsource error_handler.func\nsource api.func\n\n# API operations with error handling\n# Network errors and API errors are properly handled\n```\n\n## Best Practices Examples\n\n### Comprehensive Error Handling\n\n```bash\n#!/usr/bin/env bash\n# Comprehensive error handling example\n\nsource error_handler.func\n\n# Set up comprehensive error handling\nsetup_comprehensive_error_handling() {\n    # Enable debug logging\n    export DEBUG_LOGFILE=\"/tmp/script_debug.log\"\n\n    # Set up lock file\n    lockfile=\"/tmp/script.lock\"\n    touch \"$lockfile\"\n\n    # Initialize error handling\n    catch_errors\n\n    # Set up signal handlers\n    trap on_interrupt INT\n    trap on_terminate TERM\n    trap on_exit EXIT\n\n    echo \"Comprehensive error handling configured\"\n}\n\nsetup_comprehensive_error_handling\n\n# Script operations\necho \"Starting script operations...\"\n# ... script code ...\necho \"Script operations completed\"\n```\n\n### Error Handling for Different Scenarios\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Different error handling for different scenarios\nhandle_package_errors() {\n    local exit_code=$1\n    case \"$exit_code\" in\n        100)\n            echo \"Package manager error - trying to fix...\"\n            apt-get --fix-broken install\n            ;;\n        101)\n            echo \"Configuration error - checking sources...\"\n            apt-get update\n            ;;\n        *)\n            error_handler \"$exit_code\"\n            ;;\n    esac\n}\n\nhandle_network_errors() {\n    local exit_code=$1\n    case \"$exit_code\" in\n        127)\n            echo \"Network command not found - checking connectivity...\"\n            ping -c 1 8.8.8.8\n            ;;\n        *)\n            error_handler \"$exit_code\"\n            ;;\n    esac\n}\n\n# Use appropriate error handler\nif [[ \"$1\" == \"package\" ]]; then\n    trap 'handle_package_errors $?' ERR\nelif [[ \"$1\" == \"network\" ]]; then\n    trap 'handle_network_errors $?' ERR\nelse\n    catch_errors\nfi\n```\n\n### Error Handling with Logging\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Error handling with detailed logging\nsetup_logging_error_handling() {\n    # Create log directory\n    mkdir -p /var/log/script_errors\n\n    # Set up debug logging\n    export DEBUG_LOGFILE=\"/var/log/script_errors/debug.log\"\n\n    # Set up silent logging\n    export SILENT_LOGFILE=\"/var/log/script_errors/silent.log\"\n\n    # Initialize error handling\n    catch_errors\n\n    echo \"Logging error handling configured\"\n}\n\nsetup_logging_error_handling\n\n# Script operations with logging\necho \"Starting logged operations...\"\n# ... script code ...\necho \"Logged operations completed\"\n```\n\n## Troubleshooting Examples\n\n### Debug Mode\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Enable debug mode\nexport DEBUG_LOGFILE=\"/tmp/debug.log\"\nexport STRICT_UNSET=1\n\ncatch_errors\n\necho \"Debug mode enabled\"\n# Script operations\n```\n\n### Error Analysis\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Function to analyze errors\nanalyze_errors() {\n    local log_file=\"${1:-$DEBUG_LOGFILE}\"\n\n    if [[ -f \"$log_file\" ]]; then\n        echo \"Error Analysis:\"\n        echo \"Total errors: $(grep -c \"ERROR\" \"$log_file\")\"\n        echo \"Error types:\"\n        grep \"ERROR\" \"$log_file\" | awk '{print $NF}' | sort | uniq -c\n        echo \"Recent errors:\"\n        tail -n 10 \"$log_file\"\n    else\n        echo \"No error log found\"\n    fi\n}\n\n# Run script with error analysis\nanalyze_errors\n```\n\n### Error Recovery Testing\n\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Test error recovery\ntest_error_recovery() {\n    local test_cases=(\n        \"nonexistent_command\"\n        \"apt-get install nonexistent_package\"\n        \"systemctl start nonexistent_service\"\n    )\n\n    for test_case in \"${test_cases[@]}\"; do\n        echo \"Testing: $test_case\"\n        if silent $test_case; then\n            echo \"Unexpected success\"\n        else\n            echo \"Expected failure handled\"\n        fi\n    done\n}\n\ntest_error_recovery\n```\n"
  },
  {
    "path": "docs/misc/error_handler.func/README.md",
    "content": "# error_handler.func Documentation\n\n## Overview\n\nThe `error_handler.func` file provides comprehensive error handling and signal management for Proxmox Community Scripts. It offers detailed error code explanations, graceful error recovery, and proper cleanup mechanisms.\n\n## Purpose and Use Cases\n\n- **Error Code Explanation**: Provides human-readable explanations for exit codes\n- **Signal Handling**: Manages SIGINT, SIGTERM, and other signals gracefully\n- **Error Recovery**: Implements proper cleanup and error reporting\n- **Debug Logging**: Records error information for troubleshooting\n- **Silent Execution Support**: Integrates with core.func silent execution\n\n## Quick Reference\n\n### Key Function Groups\n- **Error Explanation**: `explain_exit_code()` - Convert exit codes to human-readable messages\n- **Error Handling**: `error_handler()` - Main error handler with detailed reporting\n- **Signal Handlers**: `on_interrupt()`, `on_terminate()` - Graceful signal handling\n- **Cleanup**: `on_exit()` - Cleanup on script exit\n- **Trap Setup**: `catch_errors()` - Initialize error handling traps\n\n### Dependencies\n- **External**: None (pure Bash implementation)\n- **Internal**: Uses color variables from core.func\n\n### Integration Points\n- Used by: All scripts via core.func silent execution\n- Uses: Color variables from core.func\n- Provides: Error explanations for core.func silent function\n\n## Documentation Files\n\n### 📊 [ERROR_HANDLER_FLOWCHART.md](./ERROR_HANDLER_FLOWCHART.md)\nVisual execution flows showing error handling processes and signal management.\n\n### 📚 [ERROR_HANDLER_FUNCTIONS_REFERENCE.md](./ERROR_HANDLER_FUNCTIONS_REFERENCE.md)\nComplete alphabetical reference of all functions with parameters, dependencies, and usage details.\n\n### 💡 [ERROR_HANDLER_USAGE_EXAMPLES.md](./ERROR_HANDLER_USAGE_EXAMPLES.md)\nPractical examples showing how to use error handling functions and common patterns.\n\n### 🔗 [ERROR_HANDLER_INTEGRATION.md](./ERROR_HANDLER_INTEGRATION.md)\nHow error_handler.func integrates with other components and provides error handling services.\n\n## Key Features\n\n### Error Code Categories\n- **Generic/Shell Errors**: Exit codes 1, 2, 126, 127, 128, 130, 137, 139, 143\n- **Package Manager Errors**: APT/DPKG errors (100, 101, 255)\n- **Node.js Errors**: JavaScript runtime errors (243-249, 254)\n- **Python Errors**: Python environment and dependency errors (210-212)\n- **Database Errors**: PostgreSQL, MySQL, MongoDB errors (231-254)\n- **Proxmox Custom Errors**: Container and VM specific errors (200-231)\n\n### Signal Handling\n- **SIGINT (Ctrl+C)**: Graceful interruption handling\n- **SIGTERM**: Graceful termination handling\n- **EXIT**: Cleanup on script exit\n- **ERR**: Error trap for command failures\n\n### Error Reporting\n- **Detailed Messages**: Human-readable error explanations\n- **Context Information**: Line numbers, commands, timestamps\n- **Log Integration**: Silent log file integration\n- **Debug Logging**: Optional debug log file support\n\n## Common Usage Patterns\n\n### Basic Error Handling Setup\n```bash\n#!/usr/bin/env bash\n# Basic error handling setup\n\nsource error_handler.func\n\n# Initialize error handling\ncatch_errors\n\n# Your script code here\n# Errors will be automatically handled\n```\n\n### Manual Error Explanation\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Get error explanation\nexplanation=$(explain_exit_code 127)\necho \"Error 127: $explanation\"\n# Output: Error 127: Command not found\n```\n\n### Custom Error Handling\n```bash\n#!/usr/bin/env bash\nsource error_handler.func\n\n# Custom error handling\nif ! command -v required_tool >/dev/null 2>&1; then\n    echo \"Error: required_tool not found\"\n    exit 127\nfi\n```\n\n## Environment Variables\n\n### Debug Variables\n- `DEBUG_LOGFILE`: Path to debug log file for error logging\n- `SILENT_LOGFILE`: Path to silent execution log file\n- `STRICT_UNSET`: Enable strict unset variable checking (0/1)\n\n### Internal Variables\n- `lockfile`: Lock file path for cleanup (set by calling script)\n- `exit_code`: Current exit code\n- `command`: Failed command\n- `line_number`: Line number where error occurred\n\n## Error Categories\n\n### Generic/Shell Errors\n- **1**: General error / Operation not permitted\n- **2**: Misuse of shell builtins (syntax error)\n- **126**: Command invoked cannot execute (permission problem)\n- **127**: Command not found\n- **128**: Invalid argument to exit\n- **130**: Terminated by Ctrl+C (SIGINT)\n- **137**: Killed (SIGKILL / Out of memory)\n- **139**: Segmentation fault (core dumped)\n- **143**: Terminated (SIGTERM)\n\n### Package Manager Errors\n- **100**: APT package manager error (broken packages)\n- **101**: APT configuration error (bad sources.list)\n- **255**: DPKG fatal internal error\n\n### Node.js Errors\n- **243**: JavaScript heap out of memory\n- **245**: Invalid command-line option\n- **246**: Internal JavaScript parse error\n- **247**: Fatal internal error\n- **248**: Invalid C++ addon / N-API failure\n- **249**: Inspector error\n- **254**: npm/pnpm/yarn unknown fatal error\n\n### Python Errors\n- **210**: Virtualenv/uv environment missing or broken\n- **211**: Dependency resolution failed\n- **212**: Installation aborted (permissions or EXTERNALLY-MANAGED)\n\n### Database Errors\n- **PostgreSQL (231-234)**: Connection, authentication, database, query errors\n- **MySQL/MariaDB (241-244)**: Connection, authentication, database, query errors\n- **MongoDB (251-254)**: Connection, authentication, database, query errors\n\n### Proxmox Custom Errors\n- **200**: Failed to create lock file\n- **203**: Missing CTID variable\n- **204**: Missing PCT_OSTYPE variable\n- **205**: Invalid CTID (<100)\n- **209**: Container creation failed\n- **210**: Cluster not quorate\n- **214**: Not enough storage space\n- **215**: Container ID not listed\n- **216**: RootFS entry missing in config\n- **217**: Storage does not support rootdir\n- **220**: Unable to resolve template path\n- **222**: Template download failed after 3 attempts\n- **223**: Template not available after download\n- **231**: LXC stack upgrade/retry failed\n\n## Best Practices\n\n### Error Handling Setup\n1. Source error_handler.func early in script\n2. Call catch_errors() to initialize traps\n3. Use proper exit codes for different error types\n4. Provide meaningful error messages\n\n### Signal Handling\n1. Always set up signal traps\n2. Provide graceful cleanup on interruption\n3. Use appropriate exit codes for signals\n4. Clean up temporary files and processes\n\n### Error Reporting\n1. Use explain_exit_code() for user-friendly messages\n2. Log errors to debug files when needed\n3. Provide context information (line numbers, commands)\n4. Integrate with silent execution logging\n\n## Troubleshooting\n\n### Common Issues\n1. **Missing Error Handler**: Ensure error_handler.func is sourced\n2. **Trap Not Set**: Call catch_errors() to initialize traps\n3. **Color Variables**: Ensure core.func is sourced for colors\n4. **Lock Files**: Clean up lock files in on_exit()\n\n### Debug Mode\nEnable debug logging for detailed error information:\n```bash\nexport DEBUG_LOGFILE=\"/tmp/debug.log\"\nsource error_handler.func\ncatch_errors\n```\n\n### Error Code Testing\nTest error explanations:\n```bash\nsource error_handler.func\nfor code in 1 2 126 127 128 130 137 139 143; do\n    echo \"Code $code: $(explain_exit_code $code)\"\ndone\n```\n\n## Related Documentation\n\n- [core.func](../core.func/) - Core utilities and silent execution\n- [build.func](../build.func/) - Container creation with error handling\n- [tools.func](../tools.func/) - Extended utilities with error handling\n- [api.func](../api.func/) - API operations with error handling\n\n---\n\n*This documentation covers the error_handler.func file which provides comprehensive error handling for all Proxmox Community Scripts.*\n"
  },
  {
    "path": "docs/misc/install.func/INSTALL_FUNC_FLOWCHART.md",
    "content": "# install.func Flowchart\n\n## Installation Workflow\n\n```\n┌──────────────────────────────────┐\n│  Container Started               │\n│  (Inside LXC by build.func)      │\n└──────────────┬───────────────────┘\n               │\n               ▼\n    ┌──────────────────────┐\n    │ Source Functions     │\n    │ $FUNCTIONS_FILE_PATH │\n    └──────────┬───────────┘\n               │\n               ▼\n    ┌──────────────────────┐\n    │ setting_up_container│\n    │ Display setup msg    │\n    └──────────┬───────────┘\n               │\n               ▼\n    ┌──────────────────────┐\n    │ network_check()      │\n    │ (Verify internet)    │\n    └────┬──────────────┬──┘\n         │              │\n       OK              FAIL\n         │              │\n         │              ▼\n         │         ┌──────────────┐\n         │         │ Retry Check  │\n         │         │ 3 attempts   │\n         │         └────┬─────┬───┘\n         │              │     │\n         │            OK   FAIL\n         │              │     │\n         └──────────────┘     │\n                 │            │\n                 ▼            ▼\n    ┌──────────────────────┐ ┌──────────────┐\n    │ update_os()          │ │ Exit Error   │\n    │ (apt update/upgrade) │ │ No internet  │\n    └──────────┬───────────┘ └──────────────┘\n               │\n               ▼\n    ┌──────────────────────┐\n    │ verb_ip6() [optional]│\n    │ (Enable IPv6)        │\n    └──────────┬───────────┘\n               │\n               ▼\n    ┌──────────────────────┐\n    │ Application          │\n    │ Installation         │\n    │ (Main work)          │\n    └──────────┬───────────┘\n               │\n       ┌───────┴────────┐\n       │                │\n    SUCCESS           FAILED\n       │                │\n       │                └─ error_handler catches\n       │                   (if catch_errors active)\n       │\n       ▼\n    ┌──────────────────────┐\n    │ motd_ssh()           │\n    │ (Setup SSH/MOTD)     │\n    └──────────┬───────────┘\n               │\n               ▼\n    ┌──────────────────────┐\n    │ customize()          │\n    │ (Apply settings)     │\n    └──────────┬───────────┘\n               │\n               ▼\n    ┌──────────────────────┐\n    │ cleanup_lxc()        │\n    │ (Final cleanup)      │\n    └──────────┬───────────┘\n               │\n               ▼\n    ┌──────────────────────┐\n    │ Installation         │\n    │ Complete ✓           │\n    └──────────────────────┘\n```\n\n## Network Check Retry Logic\n\n```\nnetwork_check()\n    │\n    ├─ Ping 8.8.8.8 (Google DNS)\n    │  └─ Response?\n    │     ├─ YES: Continue\n    │     └─ NO: Retry\n    │\n    ├─ Retry 1\n    │  └─ Wait 5s, ping again\n    │\n    ├─ Retry 2\n    │  └─ Wait 5s, ping again\n    │\n    └─ Retry 3\n       ├─ If OK: Continue\n       └─ If FAIL: Exit Error\n          (Network unavailable)\n```\n\n---\n\n**Visual Reference for**: install.func container setup workflows\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/install.func/INSTALL_FUNC_FUNCTIONS_REFERENCE.md",
    "content": "# install.func Functions Reference\n\nComplete reference of all functions in install.func with detailed usage information.\n\n## Function Index\n\n- `setting_up_container()` - Initialize container setup\n- `network_check()` - Verify network connectivity\n- `update_os()` - Update OS packages\n- `verb_ip6()` - Enable IPv6\n- `motd_ssh()` - Configure SSH and MOTD\n- `customize()` - Apply container customizations\n- `cleanup_lxc()` - Final container cleanup\n\n---\n\n## Core Functions\n\n### setting_up_container()\n\nDisplay setup message and initialize container environment.\n\n**Signature**:\n```bash\nsetting_up_container\n```\n\n**Purpose**: Announce container initialization and set initial environment\n\n**Usage**:\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\nsetting_up_container\n# Output: ⏳ Setting up container...\n```\n\n---\n\n### network_check()\n\nVerify network connectivity with automatic retry logic.\n\n**Signature**:\n```bash\nnetwork_check\n```\n\n**Purpose**: Ensure internet connectivity before critical operations\n\n**Behavior**:\n- Pings 8.8.8.8 (Google DNS)\n- 3 attempts with 5-second delays\n- Exits with error if all attempts fail\n\n**Usage**:\n```bash\nnetwork_check\n# If no internet: Exits with error message\n# If internet OK: Continues to next step\n```\n\n**Error Handling**:\n```bash\nif ! network_check; then\n  msg_error \"No internet connection\"\n  exit 1\nfi\n```\n\n---\n\n### update_os()\n\nUpdate OS packages with error handling.\n\n**Signature**:\n```bash\nupdate_os\n```\n\n**Purpose**: Prepare container with latest packages\n\n**On Debian/Ubuntu**:\n- Runs: `apt-get update && apt-get upgrade -y`\n\n**On Alpine**:\n- Runs: `apk update && apk upgrade`\n\n**Usage**:\n```bash\nupdate_os\n```\n\n---\n\n### verb_ip6()\n\nEnable IPv6 support in container (optional).\n\n**Signature**:\n```bash\nverb_ip6\n```\n\n**Purpose**: Enable IPv6 if needed for application\n\n**Usage**:\n```bash\nverb_ip6              # Enable IPv6\nnetwork_check         # Verify connectivity with IPv6\n```\n\n---\n\n### motd_ssh()\n\nConfigure SSH daemon and MOTD for container access.\n\n**Signature**:\n```bash\nmotd_ssh\n```\n\n**Purpose**: Setup SSH and create login message\n\n**Configures**:\n- SSH daemon startup and keys\n- Custom MOTD displaying application access info\n- SSH port and security settings\n\n**Usage**:\n```bash\nmotd_ssh\n# SSH is now configured and application info is in MOTD\n```\n\n---\n\n### customize()\n\nApply container customizations and final setup.\n\n**Signature**:\n```bash\ncustomize\n```\n\n**Purpose**: Apply any remaining customizations\n\n**Usage**:\n```bash\ncustomize\n```\n\n---\n\n### cleanup_lxc()\n\nFinal cleanup and completion of installation.\n\n**Signature**:\n```bash\ncleanup_lxc\n```\n\n**Purpose**: Remove temporary files and finalize installation\n\n**Cleans**:\n- Temporary installation files\n- Package manager cache\n- Log files from installation process\n\n**Usage**:\n```bash\ncleanup_lxc\n# Installation is now complete and ready\n```\n\n---\n\n## Common Patterns\n\n### Basic Installation Pattern\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\nsetting_up_container\nnetwork_check\nupdate_os\n\n# ... application installation ...\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n### With IPv6 Support\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\nsetting_up_container\nverb_ip6              # Enable IPv6\nnetwork_check\nupdate_os\n\n# ... application installation ...\n```\n\n### With Error Handling\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\ncatch_errors          # Setup error trapping\nsetting_up_container\n\nif ! network_check; then\n  msg_error \"Network connectivity failed\"\n  exit 1\nfi\n\nupdate_os\n```\n\n---\n\n**Last Updated**: December 2025\n**Total Functions**: 7\n**Maintained by**: community-scripts team\n"
  },
  {
    "path": "docs/misc/install.func/INSTALL_FUNC_INTEGRATION.md",
    "content": "# install.func Integration Guide\n\nHow install.func integrates with the ProxmoxVE ecosystem and connects to other function libraries.\n\n## Component Integration\n\n### install.func in the Installation Pipeline\n\n```\ninstall/app-install.sh (container-side)\n    │\n    ├─ Sources: core.func (messaging)\n    ├─ Sources: error_handler.func (error handling)\n    │\n    ├─ ★ Uses: install.func ★\n    │  ├─ setting_up_container()\n    │  ├─ network_check()\n    │  ├─ update_os()\n    │  └─ motd_ssh()\n    │\n    ├─ Uses: tools.func (package installation)\n    │\n    └─ Back to install.func:\n       ├─ customize()\n       └─ cleanup_lxc()\n```\n\n### Integration with tools.func\n\ninstall.func and tools.func work together:\n\n```\nsetting_up_container()          [install.func]\n    │\nupdate_os()                     [install.func]\n    │\npkg_update()                    [tools.func]\nsetup_nodejs()                  [tools.func]\nsetup_mariadb()                 [tools.func]\n    │\nmotd_ssh()                      [install.func]\ncustomize()                     [install.func]\ncleanup_lxc()                   [install.func]\n```\n\n---\n\n## Dependencies\n\n### External Dependencies\n\n- `curl`, `wget` - For downloads\n- `apt-get` or `apk` - Package management\n- `ping` - Network verification\n- `systemctl` or `rc-service` - Service management\n\n### Internal Dependencies\n\n```\ninstall.func uses:\n├─ core.func (for messaging and colors)\n├─ error_handler.func (for error handling)\n└─ tools.func (for package operations)\n```\n\n---\n\n## Best Practices\n\n### Always Follow This Pattern\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\n# 1. Setup error handling\ncatch_errors\n\n# 2. Initialize container\nsetting_up_container\n\n# 3. Verify network\nnetwork_check\n\n# 4. Update OS\nupdate_os\n\n# 5. Installation (your code)\n# ... install application ...\n\n# 6. Configure access\nmotd_ssh\n\n# 7. Customize\ncustomize\n\n# 8. Cleanup\ncleanup_lxc\n```\n\n---\n\n**Last Updated**: December 2025\n**Maintainers**: community-scripts team\n"
  },
  {
    "path": "docs/misc/install.func/INSTALL_FUNC_USAGE_EXAMPLES.md",
    "content": "# install.func Usage Examples\n\nPractical examples for using install.func functions in application installation scripts.\n\n## Basic Examples\n\n### Example 1: Minimal Setup\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\nsetting_up_container\nnetwork_check\nupdate_os\n\n# ... application installation ...\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n### Example 2: With Error Handling\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\ncatch_errors\nsetting_up_container\n\nif ! network_check; then\n  msg_error \"Network failed\"\n  exit 1\nfi\n\nif ! update_os; then\n  msg_error \"OS update failed\"\n  exit 1\nfi\n\n# ... continue ...\n```\n\n---\n\n## Production Examples\n\n### Example 3: Full Application Installation\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing application\"\n# ... install steps ...\nmsg_ok \"Application installed\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n### Example 4: With IPv6 Support\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\ncatch_errors\nsetting_up_container\nverb_ip6\nnetwork_check\nupdate_os\n\n# ... application installation ...\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n---\n\n**Last Updated**: December 2025\n**Examples**: Basic and production patterns\n**All examples production-ready**\n"
  },
  {
    "path": "docs/misc/install.func/README.md",
    "content": "# install.func Documentation\n\n## Overview\n\nThe `install.func` file provides container installation workflow orchestration and fundamental operations for applications deployed inside LXC containers. It handles network setup, OS configuration, connectivity verification, and installation mechanics.\n\n## Purpose and Use Cases\n\n- **Container Setup**: Initialize new container with proper configuration\n- **Network Verification**: Verify IPv4 and IPv6 connectivity\n- **OS Configuration**: Update OS, apply system settings\n- **Installation Workflow**: Orchestrate application installation steps\n- **Error Handling**: Comprehensive signal trapping and error recovery\n\n## Quick Reference\n\n### Key Function Groups\n- **Initialization**: `setting_up_container()` - Setup message and environment\n- **Network**: `network_check()`, `verb_ip6()` - Connectivity verification\n- **OS Configuration**: `update_os()` - OS updates and package management\n- **Installation**: `motd_ssh()`, `customize()` - Container customization\n- **Cleanup**: `cleanup_lxc()` - Final container cleanup\n\n### Dependencies\n- **External**: `curl`, `apt-get`, `ping`, `dns` utilities\n- **Internal**: Uses functions from `core.func`, `error_handler.func`, `tools.func`\n\n### Integration Points\n- Used by: All install/*.sh scripts at startup\n- Uses: Environment variables from build.func and core.func\n- Provides: Container initialization and management services\n\n## Documentation Files\n\n### 📊 [INSTALL_FUNC_FLOWCHART.md](./INSTALL_FUNC_FLOWCHART.md)\nVisual execution flows showing initialization, network checks, and installation workflows.\n\n### 📚 [INSTALL_FUNC_FUNCTIONS_REFERENCE.md](./INSTALL_FUNC_FUNCTIONS_REFERENCE.md)\nComplete alphabetical reference of all functions with parameters, dependencies, and usage details.\n\n### 💡 [INSTALL_FUNC_USAGE_EXAMPLES.md](./INSTALL_FUNC_USAGE_EXAMPLES.md)\nPractical examples showing how to use installation functions and common patterns.\n\n### 🔗 [INSTALL_FUNC_INTEGRATION.md](./INSTALL_FUNC_INTEGRATION.md)\nHow install.func integrates with other components and provides installation services.\n\n## Key Features\n\n### Container Initialization\n- **Environment Setup**: Prepare container variables and functions\n- **Message System**: Display installation progress with colored output\n- **Error Handlers**: Setup signal trapping for proper cleanup\n\n### Network & Connectivity\n- **IPv4 Verification**: Ping external hosts to verify internet access\n- **IPv6 Support**: Optional IPv6 enablement and verification\n- **DNS Checking**: Verify DNS resolution is working\n- **Retry Logic**: Automatic retries for transient failures\n\n### OS Configuration\n- **Package Updates**: Safely update OS package lists\n- **System Optimization**: Disable unnecessary services (wait-online)\n- **Timezone**: Validate and set container timezone\n- **SSH Setup**: Configure SSH daemon and keys\n\n### Container Customization\n- **MOTD**: Create custom login message\n- **Auto-Login**: Optional passwordless root login\n- **Update Script**: Register application update function\n- **Customization Hooks**: Application-specific setup\n\n## Function Categories\n\n### 🔹 Core Functions\n- `setting_up_container()` - Display setup message and set environment\n- `network_check()` - Verify network connectivity\n- `update_os()` - Update OS packages with retry logic\n- `verb_ip6()` - Enable IPv6 (optional)\n\n### 🔹 Configuration Functions\n- `motd_ssh()` - Setup MOTD and SSH configuration\n- `customize()` - Apply container customizations\n- `cleanup_lxc()` - Final cleanup before completion\n\n### 🔹 Utility Functions\n- `create_update_script()` - Register application update function\n- `set_timezone()` - Configure container timezone\n- `disable_wait_online()` - Disable systemd-networkd-wait-online\n\n## Execution Flow\n\n```\nContainer Started\n    ↓\nsource $FUNCTIONS_FILE_PATH\n    ↓\nsetting_up_container()           ← Display \"Setting up container...\"\n    ↓\nnetwork_check()                  ← Verify internet connectivity\n    ↓\nupdate_os()                      ← Update package lists\n    ↓\n[Application-Specific Installation]\n    ↓\nmotd_ssh()                       ← Configure SSH/MOTD\ncustomize()                      ← Apply customizations\n    ↓\ncleanup_lxc()                    ← Final cleanup\n    ↓\nInstallation Complete\n```\n\n## Common Usage Patterns\n\n### Basic Container Setup\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\nsetting_up_container\nnetwork_check\nupdate_os\n\n# ... application installation ...\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n### With Optional IPv6\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\nsetting_up_container\nverb_ip6  # Enable IPv6\nnetwork_check\nupdate_os\n\n# ... installation ...\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n### With Custom Update Script\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\nsetting_up_container\nnetwork_check\nupdate_os\n\n# ... installation ...\n\n# Register update function\nfunction update_script() {\n  # Update logic here\n}\nexport -f update_script\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n## Best Practices\n\n### ✅ DO\n- Call `setting_up_container()` at the start\n- Check `network_check()` output before main installation\n- Use `$STD` variable for silent operations\n- Call `cleanup_lxc()` at the very end\n- Test network connectivity before critical operations\n\n### ❌ DON'T\n- Skip network verification\n- Assume internet is available\n- Hardcode container paths\n- Use `echo` instead of `msg_*` functions\n- Forget to call cleanup at the end\n\n## Environment Variables\n\n### Available Variables\n- `$FUNCTIONS_FILE_PATH` - Path to core functions (set by build.func)\n- `$CTID` - Container ID number\n- `$NSAPP` - Normalized application name (lowercase)\n- `$APP` - Application display name\n- `$STD` - Output suppression (`silent` or empty)\n- `$VERBOSE` - Verbose output mode (`yes` or `no`)\n\n### Setting Container Variables\n```bash\nCONTAINER_TIMEZONE=\"UTC\"\nCONTAINER_HOSTNAME=\"myapp-container\"\nCONTAINER_FQDN=\"myapp.example.com\"\n```\n\n## Troubleshooting\n\n### \"Network check failed\"\n```bash\n# Container may not have internet access\n# Check:\nping 8.8.8.8           # External connectivity\nnslookup example.com   # DNS resolution\nip route show          # Routing table\n```\n\n### \"Package update failed\"\n```bash\n# APT may be locked by another process\nps aux | grep apt      # Check for running apt\n# Or wait for existing apt to finish\nsleep 30\nupdate_os\n```\n\n### \"Cannot source functions\"\n```bash\n# $FUNCTIONS_FILE_PATH may not be set\n# This variable is set by build.func before running install script\n# If missing, the install script was not called properly\n```\n\n## Related Documentation\n\n- **[tools.func/](../tools.func/)** - Package and tool installation\n- **[core.func/](../core.func/)** - Utility functions and messaging\n- **[error_handler.func/](../error_handler.func/)** - Error handling\n- **[alpine-install.func/](../alpine-install.func/)** - Alpine-specific setup\n- **[UPDATED_APP-install.md](../../UPDATED_APP-install.md)** - Application script guide\n\n## Recent Updates\n\n### Version 2.0 (Dec 2025)\n- ✅ Improved network connectivity checks\n- ✅ Enhanced OS update error handling\n- ✅ Added IPv6 support with verb_ip6()\n- ✅ Better timezone validation\n- ✅ Streamlined cleanup procedures\n\n---\n\n**Last Updated**: December 2025\n**Maintainers**: community-scripts team\n**License**: MIT\n"
  },
  {
    "path": "docs/misc/tools.func/README.md",
    "content": "# tools.func Documentation\n\n## Overview\n\nThe `tools.func` file provides a comprehensive collection of helper functions for robust package management, repository management, and tool installation in Debian/Ubuntu-based systems. It is the central hub for installing services, databases, programming languages, and development tools in containers.\n\n## Purpose and Use Cases\n\n- **Package Management**: Robust APT/DPKG operations with retry logic\n- **Repository Setup**: Prepare and configure package repositories safely\n- **Tool Installation**: Install 30+ tools (Node.js, PHP, databases, etc.)\n- **Dependency Handling**: Manage complex installation workflows\n- **Error Recovery**: Automatic recovery from network failures\n\n## Quick Reference\n\n### Key Function Groups\n- **Package Helpers**: `pkg_install()`, `pkg_update()`, `pkg_remove()` - APT operations with retry\n- **Repository Setup**: `setup_deb822_repo()` - Modern repository configuration\n- **Tool Installation**: `setup_nodejs()`, `setup_php()`, `setup_mariadb()`, etc. - 30+ tool functions\n- **System Utilities**: `disable_wait_online()`, `customize()` - System optimization\n- **Container Setup**: `setting_up_container()`, `motd_ssh()` - Container initialization\n\n### Dependencies\n- **External**: `curl`, `wget`, `apt-get`, `gpg`\n- **Internal**: Uses functions from `core.func`, `install.func`, `error_handler.func`\n\n### Integration Points\n- Used by: All install scripts for dependency installation\n- Uses: Environment variables from build.func and core.func\n- Provides: Tool installation, package management, and repository services\n\n## Documentation Files\n\n### 📊 [TOOLS_FUNC_FLOWCHART.md](./TOOLS_FUNC_FLOWCHART.md)\nVisual execution flows showing package management, tool installation, and repository setup workflows.\n\n### 📚 [TOOLS_FUNC_FUNCTIONS_REFERENCE.md](./TOOLS_FUNC_FUNCTIONS_REFERENCE.md)\nComplete alphabetical reference of all 30+ functions with parameters, dependencies, and usage details.\n\n### 💡 [TOOLS_FUNC_USAGE_EXAMPLES.md](./TOOLS_FUNC_USAGE_EXAMPLES.md)\nPractical examples showing how to use tool installation functions and common patterns.\n\n### 🔗 [TOOLS_FUNC_INTEGRATION.md](./TOOLS_FUNC_INTEGRATION.md)\nHow tools.func integrates with other components and provides package/tool services.\n\n### 🔧 [TOOLS_FUNC_ENVIRONMENT_VARIABLES.md](./TOOLS_FUNC_ENVIRONMENT_VARIABLES.md)\nComplete reference of environment variables and configuration options.\n\n## Key Features\n\n### Robust Package Management\n- **Automatic Retry Logic**: 3 attempts with backoff for transient failures\n- **Silent Mode**: Suppress output with `$STD` variable\n- **Error Recovery**: Automatic cleanup of broken packages\n- **Atomic Operations**: Ensure consistent state even on failure\n\n### Tool Installation Coverage\n- **Node.js Ecosystem**: Node.js, npm, yarn, pnpm\n- **PHP Stack**: PHP-FPM, PHP-CLI, Composer\n- **Databases**: MariaDB, PostgreSQL, MongoDB\n- **Development Tools**: Git, build-essential, Docker\n- **Monitoring**: Grafana, Prometheus, Telegraf\n- **And 20+ more...**\n\n### Repository Management\n- **Deb822 Format**: Modern standardized repository format\n- **Keyring Handling**: Automatic GPG key management\n- **Cleanup**: Removes legacy repositories and keyrings\n- **Validation**: Verifies repository accessibility before use\n\n## Common Usage Patterns\n\n### Installing a Tool\n```bash\nsetup_nodejs \"20\"     # Install Node.js v20\nsetup_php \"8.2\"       # Install PHP 8.2\nsetup_mariadb         # Install MariaDB (distribution packages)\n# MARIADB_VERSION=\"11.4\" setup_mariadb  # Specific version from official repo\n```\n\n### Safe Package Operations\n```bash\npkg_update           # Update package lists with retry\npkg_install curl wget  # Install packages safely\npkg_remove old-tool   # Remove package cleanly\n```\n\n### Setting Up Repositories\n```bash\nsetup_deb822_repo \"ppa:example/ppa\" \"example-app\" \"jammy\" \"http://example.com\" \"release\"\n```\n\n## Function Categories\n\n### 🔹 Core Package Functions\n- `pkg_install()` - Install packages with retry logic\n- `pkg_update()` - Update package lists safely\n- `pkg_remove()` - Remove packages completely\n\n### 🔹 Repository Functions\n- `setup_deb822_repo()` - Add repository in deb822 format\n- `cleanup_repo_metadata()` - Clean GPG keys and old repos\n- `check_repository()` - Verify repository is accessible\n\n### 🔹 Tool Installation Functions (30+)\n**Programming Languages**:\n- `setup_nodejs()` - Node.js with npm\n- `setup_php()` - PHP-FPM and CLI\n- `setup_python()` - Python 3 with pip\n- `setup_ruby()` - Ruby with gem\n- `setup_golang()` - Go programming language\n\n**Databases**:\n- `setup_mariadb()` - MariaDB server\n- `setup_postgresql()` - PostgreSQL database\n- `setup_mongodb()` - MongoDB NoSQL\n- `setup_redis()` - Redis cache\n\n**Web Servers & Proxies**:\n- `setup_nginx()` - Nginx web server\n- `setup_apache()` - Apache HTTP server\n- `setup_caddy()` - Caddy web server\n- `setup_traefik()` - Traefik reverse proxy\n\n**Containers & Virtualization**:\n- `setup_docker()` - Docker container runtime\n- `setup_podman()` - Podman container runtime\n\n**Development & System Tools**:\n- `setup_git()` - Git version control\n- `setup_docker_compose()` - Docker Compose\n- `setup_composer()` - PHP dependency manager\n- `setup_build_tools()` - C/C++ compilation tools\n\n**Monitoring & Logging**:\n- `setup_grafana()` - Grafana dashboards\n- `setup_prometheus()` - Prometheus monitoring\n- `setup_telegraf()` - Telegraf metrics collector\n\n### 🔹 System Configuration Functions\n- `setting_up_container()` - Container initialization message\n- `network_check()` - Verify network connectivity\n- `update_os()` - Update OS packages safely\n- `customize()` - Apply container customizations\n- `motd_ssh()` - Configure SSH and MOTD\n- `cleanup_lxc()` - Final container cleanup\n\n## Best Practices\n\n### ✅ DO\n- Use `$STD` to suppress output in production scripts\n- Chain multiple tool installations together\n- Check for tool availability before using\n- Use version parameters when available\n- Test new repositories before production use\n\n### ❌ DON'T\n- Mix package managers (apt and apk in same script)\n- Hardcode tool versions directly\n- Skip error checking on package operations\n- Use `apt-get install -y` without `$STD`\n- Leave temporary files after installation\n\n## Recent Updates\n\n### Version 2.0 (Dec 2025)\n- ✅ Added `setup_deb822_repo()` for modern repository format\n- ✅ Improved error handling with automatic cleanup\n- ✅ Added 5 new tool installation functions\n- ✅ Enhanced package retry logic with backoff\n- ✅ Standardized tool version handling\n\n## Integration with Other Functions\n\n```\ntools.func\n    ├── Uses: core.func (messaging, colors)\n    ├── Uses: error_handler.func (exit codes, trapping)\n    ├── Uses: install.func (network_check, update_os)\n    │\n    └── Used by: All install/*.sh scripts\n        ├── For: Package installation\n        ├── For: Tool setup\n        └── For: Repository management\n```\n\n## Troubleshooting\n\n### \"Package manager is locked\"\n```bash\n# Wait for apt lock to release\nsleep 10\npkg_update\n```\n\n### \"GPG key not found\"\n```bash\n# Repository setup will handle this automatically\n# If manual fix needed:\ncleanup_repo_metadata\nsetup_deb822_repo ...\n```\n\n### \"Tool installation failed\"\n```bash\n# Enable verbose output\nexport var_verbose=\"yes\"\nsetup_nodejs \"20\"\n```\n\n## Contributing\n\nWhen adding new tool installation functions:\n\n1. Follow the `setup_TOOLNAME()` naming convention\n2. Accept version as first parameter\n3. Check if tool already installed\n4. Use `$STD` for output suppression\n5. Set version file: `/opt/TOOLNAME_version.txt`\n6. Document in TOOLS_FUNC_FUNCTIONS_REFERENCE.md\n\n## Related Documentation\n\n- **[build.func/](../build.func/)** - Container creation orchestrator\n- **[core.func/](../core.func/)** - Utility functions and messaging\n- **[install.func/](../install.func/)** - Installation workflow management\n- **[error_handler.func/](../error_handler.func/)** - Error handling and recovery\n- **[UPDATED_APP-install.md](../../UPDATED_APP-install.md)** - Application script guide\n\n---\n\n**Last Updated**: December 2025\n**Maintainers**: community-scripts team\n**License**: MIT\n"
  },
  {
    "path": "docs/misc/tools.func/TOOLS_FUNC_FLOWCHART.md",
    "content": "# tools.func Flowchart\n\n## Main Package Installation Flow\n\n```\n┌──────────────────────────────────┐\n│  Install Script Starts           │\n│  source tools.func               │\n└──────────────┬───────────────────┘\n               │\n               ▼\n         ┌─────────────┐\n         │ pkg_update()│\n         │  (apt/apk)  │\n         └──────┬──────┘\n                │\n                ▼\n        ┌────────────────┐\n        │ Retry Logic    │  ◄─────┐\n        │ (Up to 3 tries)│        │\n        └────┬───────────┘        │\n             │                    │\n             ├─ Success: Continue │\n             ├─ Retry 1 ──────────┘\n             └─ Fail: Exit\n                │\n                ▼\n        ┌──────────────────┐\n        │ setup_deb822_repo│\n        │ (Add repository) │\n        └────────┬─────────┘\n                 │\n                 ▼\n         ┌─────────────────┐\n         │ GPG Key Setup   │\n         │ Verify Repo OK  │\n         └────────┬────────┘\n                  │\n                  ▼\n         ┌──────────────────┐\n         │ Tool Installation│\n         │ (setup_nodejs,   │\n         │  setup_php, etc.)│\n         └────────┬─────────┘\n                  │\n       ┌──────────┴──────────┐\n       │                     │\n       ▼                     ▼\n  ┌─────────────┐    ┌──────────────┐\n  │ Node.js     │    │ MariaDB      │\n  │ setup_      │    │ setup_       │\n  │ nodejs()    │    │ mariadb()    │\n  └──────┬──────┘    └────────┬─────┘\n         │                    │\n         └────────┬───────────┘\n                  │\n                  ▼\n         ┌───────────────────┐\n         │ Installation OK?  │\n         └────┬──────────┬───┘\n              │          │\n            YES          NO\n              │          │\n              │          ▼\n              │     ┌─────────────┐\n              │     │ Rollback    │\n              │     │ Error Exit  │\n              │     └─────────────┘\n              │\n              ▼\n        ┌─────────────────┐\n        │ Set Version File│\n        │ /opt/TOOL_v.txt │\n        └─────────────────┘\n```\n\n## Repository Setup Flow (setup_deb822_repo)\n\n```\nsetup_deb822_repo(URL, name, dist, repo_url, release)\n    │\n    ├─ Parse Parameters\n    │  ├─ URL: Repository URL\n    │  ├─ name: Repository name\n    │  ├─ dist: Distro (jammy, bookworm)\n    │  ├─ repo_url: Main URL\n    │  └─ release: Release type\n    │\n    ├─ Add GPG Key\n    │  ├─ Download key from URL\n    │  ├─ Add to keyring\n    │  └─ Trust key for deb822\n    │\n    ├─ Create deb822 file\n    │  ├─ /etc/apt/sources.list.d/name.sources\n    │  ├─ Format: DEB822\n    │  └─ Include GPG key reference\n    │\n    ├─ Validate Repository\n    │  ├─ apt-get update\n    │  ├─ Check for errors\n    │  └─ Retry if needed\n    │\n    └─ Success / Error\n```\n\n## Tool Installation Chain\n\n```\nTools to Install:\n├─ Programming Languages\n│  ├─ setup_nodejs(VERSION)\n│  ├─ setup_php(VERSION)\n│  ├─ setup_python(VERSION)\n│  ├─ setup_ruby(VERSION)\n│  └─ setup_golang(VERSION)\n│\n├─ Databases\n│  ├─ setup_mariadb(VERSION)\n│  ├─ setup_postgresql(VERSION)\n│  ├─ setup_mongodb(VERSION)\n│  └─ setup_redis(VERSION)\n│\n├─ Web Servers\n│  ├─ setup_nginx()\n│  ├─ setup_apache()\n│  ├─ setup_caddy()\n│  └─ setup_traefik()\n│\n├─ Containers\n│  ├─ setup_docker()\n│  └─ setup_podman()\n│\n└─ Utilities\n   ├─ setup_git()\n   ├─ setup_composer()\n   ├─ setup_build_tools()\n   └─ setup_[TOOL]()\n```\n\n## Package Operation Retry Logic\n\n```\n┌─────────────────────┐\n│ pkg_install PKG1    │\n│ pkg_install PKG2    │\n│ pkg_install PKG3    │\n└──────────┬──────────┘\n           │\n           ▼\n    ┌─────────────────┐\n    │ APT Lock Check  │\n    └────┬────────┬───┘\n         │        │\n      FREE     LOCKED\n         │        │\n         │        ▼\n         │   ┌─────────────┐\n         │   │ Wait 5 sec  │\n         │   └────────┬────┘\n         │            │\n         │            ▼\n         │   ┌─────────────┐\n         │   │ Retry Check │\n         │   └────┬────┬───┘\n         │        │    │\n         │     OK  LOCK\n         │        │    │\n         │        └────┘ (loop)\n         │\n         ▼\n    ┌──────────────────┐\n    │ apt-get install  │\n    │ (with $STD)      │\n    └────┬─────────┬───┘\n         │         │\n       SUCCESS   FAILED\n         │         │\n         │         ▼\n         │    ┌──────────────┐\n         │    │ Retry Count? │\n         │    └────┬─────┬───┘\n         │         │     │\n         │      <3  ≥3   │\n         │      Retry  FAIL\n         │         │\n         │         └─────────┐\n         │                   │\n         ▼                   ▼\n    ┌─────────┐         ┌─────────┐\n    │ SUCCESS │         │ FAILED  │\n    └─────────┘         │ EXIT 1  │\n                        └─────────┘\n```\n\n---\n\n**Visual Reference for**: tools.func package management and tool installation\n**Last Updated**: December 2025\n"
  },
  {
    "path": "docs/misc/tools.func/TOOLS_FUNC_FUNCTIONS_REFERENCE.md",
    "content": "# tools.func Functions Reference\n\nComplete alphabetical reference of all functions in tools.func with parameters, usage, and examples.\n\n## Function Index\n\n### Package Management\n- `pkg_install()` - Install packages safely with retry\n- `pkg_update()` - Update package lists with retry\n- `pkg_remove()` - Remove packages cleanly\n\n### Repository Management\n- `setup_deb822_repo()` - Add repository in modern deb822 format\n- `cleanup_repo_metadata()` - Clean GPG keys and old repositories\n- `check_repository()` - Verify repository accessibility\n\n### Tool Installation Functions (30+)\n\n**Programming Languages**:\n- `setup_nodejs(VERSION)` - Install Node.js and npm\n- `setup_php(VERSION)` - Install PHP-FPM and CLI\n- `setup_python(VERSION)` - Install Python 3 with pip\n- `setup_uv()` - Install Python uv (modern & fast)\n- `setup_ruby(VERSION)` - Install Ruby with gem\n- `setup_golang(VERSION)` - Install Go programming language\n- `setup_java(VERSION)` - Install OpenJDK (Adoptium)\n\n**Databases**:\n- `setup_mariadb()` - Install MariaDB server\n- `setup_mariadb_db()` - Create user/db in MariaDB\n- `setup_postgresql(VERSION)` - Install PostgreSQL\n- `setup_postgresql_db()` - Create user/db in PostgreSQL\n- `setup_mongodb(VERSION)` - Install MongoDB\n- `setup_redis(VERSION)` - Install Redis cache\n- `setup_meilisearch()` - Install Meilisearch engine\n\n**Web Servers**:\n- `setup_nginx()` - Install Nginx\n- `setup_apache()` - Install Apache HTTP Server\n- `setup_caddy()` - Install Caddy\n- `setup_traefik()` - Install Traefik proxy\n\n**Containers**:\n- `setup_docker()` - Install Docker\n- `setup_podman()` - Install Podman\n\n**Development**:\n- `setup_git()` - Install Git\n- `setup_docker_compose()` - Install Docker Compose\n- `setup_composer()` - Install PHP Composer\n- `setup_build_tools()` - Install build-essential\n- `setup_yq()` - Install mikefarah/yq processor\n\n**Monitoring**:\n- `setup_grafana()` - Install Grafana\n- `setup_prometheus()` - Install Prometheus\n- `setup_telegraf()` - Install Telegraf\n\n**System**:\n- `setup_wireguard()` - Install WireGuard VPN\n- `setup_netdata()` - Install Netdata monitoring\n- `setup_tailscale()` - Install Tailscale\n- (+ more...)\n\n---\n\n## Core Functions\n\n### install_packages_with_retry()\n\nInstall one or more packages safely with automatic retry logic (3 attempts), APT refresh, and lock handling.\n\n**Signature**:\n```bash\ninstall_packages_with_retry PACKAGE1 [PACKAGE2 ...]\n```\n\n**Parameters**:\n- `PACKAGE1, PACKAGE2, ...` - Package names to install\n\n**Returns**:\n- `0` - All packages installed successfully\n- `1` - Installation failed after all retries\n\n**Features**:\n- Automatically sets `DEBIAN_FRONTEND=noninteractive`\n- Handles DPKG lock errors with `dpkg --configure -a`\n- Retries on transient network or APT failures\n\n**Example**:\n```bash\ninstall_packages_with_retry curl wget git\n```\n\n---\n\n### upgrade_packages_with_retry()\n\nUpgrades installed packages with the same robust retry logic as the installation helper.\n\n**Signature**:\n```bash\nupgrade_packages_with_retry\n```\n\n**Returns**:\n- `0` - Upgrade successful\n- `1` - Upgrade failed\n\n---\n\n### fetch_and_deploy_gh_release()\n\nThe primary tool for downloading and installing software from GitHub Releases. Supports binaries, tarballs, and Debian packages.\n\n**Signature**:\n```bash\nfetch_and_deploy_gh_release APPREPO TYPE [VERSION] [DEST] [ASSET_PATTERN]\n```\n\n**Environment Variables**:\n- `APPREPO`: GitHub repository (e.g., `owner/repo`)\n- `TYPE`: Asset type (`binary`, `tarball`, `prebuild`, `singlefile`)\n- `VERSION`: Specific tag or `latest` (Default: `latest`)\n- `DEST`: Target directory (Default: `/opt/$APP`)\n- `ASSET_PATTERN`: Regex or string pattern to match the release asset (Required for `prebuild` and `singlefile`)\n\n**Supported Operation Modes**:\n- `tarball`: Downloads and extracts the source tarball.\n- `binary`: Detects host architecture and installs a `.deb` package using `apt` or `dpkg`.\n- `prebuild`: Downloads and extracts a pre-built binary archive (supports `.tar.gz`, `.zip`, `.tgz`, `.txz`).\n- `singlefile`: Downloads a single binary file to the destination.\n\n**Environment Variables**:\n- `CLEAN_INSTALL=1`: Removes all contents of the destination directory before extraction.\n- `DPKG_FORCE_CONFOLD=1`: Forces `dpkg` to keep old config files during package updates.\n- `SYSTEMD_OFFLINE=1`: Used automatically for `.deb` installs to prevent systemd-tmpfiles failures in unprivileged containers.\n\n**Example**:\n```bash\nfetch_and_deploy_gh_release \"muesli/duf\" \"binary\" \"latest\" \"/opt/duf\" \"duf_.*_linux_amd64.tar.gz\"\n```\n\n---\n\n### check_for_gh_release()\n\nChecks if a newer version is available on GitHub compared to the installed version.\n\n**Signature**:\n```bash\ncheck_for_gh_release APP REPO\n```\n\n**Example**:\n```bash\nif check_for_gh_release \"nodejs\" \"nodesource/distributions\"; then\n  # update logic\nfi\n```\n\n---\n\n### prepare_repository_setup()\n\nPerforms safe repository preparation by cleaning up old files, keyrings, and ensuring the APT system is in a working state.\n\n**Signature**:\n```bash\nprepare_repository_setup REPO_NAME [REPO_NAME2 ...]\n```\n\n**Example**:\n```bash\nprepare_repository_setup \"mariadb\" \"mysql\"\n```\n\n---\n\n### verify_tool_version()\n\nValidates if the installed major version matches the expected version.\n\n**Signature**:\n```bash\nverify_tool_version NAME EXPECTED INSTALLED\n```\n\n**Example**:\n```bash\nverify_tool_version \"nodejs\" \"22\" \"$(node -v | grep -oP '^v\\K[0-9]+')\"\n```\n\n---\n\n### setup_deb822_repo()\n\nAdd repository in modern deb822 format.\n\n**Signature**:\n```bash\nsetup_deb822_repo NAME GPG_URL REPO_URL SUITE COMPONENT [ARCHITECTURES] [ENABLED]\n```\n\n**Parameters**:\n- `NAME` - Repository name (e.g., \"nodejs\")\n- `GPG_URL` - URL to GPG key (e.g., https://example.com/key.gpg)\n- `REPO_URL` - Main repository URL (e.g., https://example.com/repo)\n- `SUITE` - Repository suite (e.g., \"jammy\", \"bookworm\")\n- `COMPONENT` - Repository component (e.g., \"main\", \"testing\")\n- `ARCHITECTURES` - Optional Comma-separated list of architectures (e.g., \"amd64,arm64\")\n- `ENABLED` - Optional \"true\" or \"false\" (default: \"true\")\n\n**Returns**:\n- `0` - Repository added successfully\n- `1` - Repository setup failed\n\n**Example**:\n```bash\nsetup_deb822_repo \\\n  \"nodejs\" \\\n  \"https://deb.nodesource.com/gpgkey/nodesource.gpg.key\" \\\n  \"https://deb.nodesource.com/node_20.x\" \\  \n  \"jammy\" \\\n  \"main\"\n```\n\n---\n\n### cleanup_repo_metadata()\n\nClean up GPG keys and old repository configurations.\n\n**Signature**:\n```bash\ncleanup_repo_metadata\n```\n\n**Parameters**: None\n\n**Returns**:\n- `0` - Cleanup complete\n\n**Example**:\n```bash\ncleanup_repo_metadata\n```\n\n---\n\n## Tool Installation Functions\n\n### setup_nodejs()\n\nInstall Node.js and npm from official repositories. Handles legacy version cleanup (nvm) automatically.\n\n**Signature**:\n```bash\nsetup_nodejs\n```\n\n**Environment Variables**:\n- `NODE_VERSION`: Major version to install (e.g. \"20\", \"22\", \"24\"). Default: \"24\".\n- `NODE_MODULE`: Optional npm package to install globally during setup (e.g. \"pnpm\", \"yarn\").\n\n**Example**:\n```bash\nNODE_VERSION=\"22\" NODE_MODULE=\"pnpm\" setup_nodejs\n```\n\n---\n\n### setup_php()\n\nInstall PHP with configurable extensions and FPM/Apache integration.\n\n**Signature**:\n```bash\nsetup_php\n```\n\n**Environment Variables**:\n- `PHP_VERSION`: Version to install (e.g. \"8.3\", \"8.4\"). Default: \"8.4\".\n- `PHP_MODULE`: Comma-separated list of additional extensions.\n- `PHP_FPM`: Set to \"YES\" to install php-fpm.\n- `PHP_APACHE`: Set to \"YES\" to install libapache2-mod-php.\n\n**Example**:\n```bash\nPHP_VERSION=\"8.3\" PHP_FPM=\"YES\" PHP_MODULE=\"mysql,xml,zip\" setup_php\n```\n\n---\n\n### setup_mariadb_db()\n\nCreates a new MariaDB database and a dedicated user with all privileges. Automatically generates a password if not provided and saves it to a credentials file.\n\n**Environment Variables**:\n- `MARIADB_DB_NAME`: Name of the database (required)\n- `MARIADB_DB_USER`: Name of the database user (required)\n- `MARIADB_DB_PASS`: User password (optional, auto-generated if omitted)\n\n**Example**:\n```bash\nMARIADB_DB_NAME=\"myapp\" MARIADB_DB_USER=\"myapp_user\" setup_mariadb_db\n```\n\n---\n\n### setup_postgresql_db()\n\nCreates a new PostgreSQL database and a dedicated user/role with all privileges. Automatically generates a password if not provided and saves it to a credentials file.\n\n**Environment Variables**:\n- `PG_DB_NAME`: Name of the database (required)\n- `PG_DB_USER`: Name of the database user (required)\n- `PG_DB_PASS`: User password (optional, auto-generated if omitted)\n\n---\n\n### setup_java()\n\nInstalls Temurin JDK.\n\n**Signature**:\n```bash\nJAVA_VERSION=\"21\" setup_java\n```\n\n**Parameters**:\n- `JAVA_VERSION` - JDK version (e.g., \"17\", \"21\") (default: \"21\")\n\n**Example**:\n```bash\nJAVA_VERSION=\"17\" setup_java\n```\n\n---\n\n### setup_uv()\n\nInstalls `uv` (modern Python package manager).\n\n**Signature**:\n```bash\nPYTHON_VERSION=\"3.13\" setup_uv\n```\n\n**Parameters**:\n- `PYTHON_VERSION` - Optional Python version to pre-install via uv (e.g., \"3.12\", \"3.13\")\n\n**Example**:\n```bash\nPYTHON_VERSION=\"3.13\" setup_uv\n```\n\n---\n\n### setup_go()\n\nInstalls Go programming language.\n\n**Signature**:\n```bash\nGO_VERSION=\"1.23\" setup_go\n```\n\n**Parameters**:\n- `GO_VERSION` - Go version to install (default: \"1.23\")\n\n**Example**:\n```bash\nGO_VERSION=\"1.24\" setup_go\n```\n\n---\n\n### setup_yq()\n\nInstalls `yq` (YAML processor).\n\n**Signature**:\n```bash\nsetup_yq\n```\n\n**Example**:\n```bash\nsetup_yq\n```\n\n---\n\n### setup_composer()\n\nInstalls PHP Composer.\n\n**Signature**:\n```bash\nsetup_composer\n```\n\n**Example**:\n```bash\nsetup_composer\n```\n\n---\n\n### setup_meilisearch()\n\nInstall and configure Meilisearch search engine.\n\n**Environment Variables**:\n- `MEILISEARCH_BIND`: Address and port to bind to (Default: \"127.0.0.1:7700\")\n- `MEILISEARCH_ENV`: Environment mode (Default: \"production\")\n\n---\n\n### setup_yq()\n\nInstall the `mikefarah/yq` YAML processor. Removes existing non-compliant versions.\n\n**Example**:\n```bash\nsetup_yq\nyq eval '.app.version = \"1.0.0\"' -i config.yaml\n```\n\n---\n\n### setup_composer()\n\nInstall or update the PHP Composer package manager. Handles `COMPOSER_ALLOW_SUPERUSER` automatically and performs self-updates if already installed.\n\n**Example**:\n```bash\nsetup_php\nsetup_composer\n$STD composer install --no-dev\n```\n\n---\n\n### setup_build_tools()\n\nInstall the `build-essential` package suite for compiling software.\n\n---\n\n### setup_uv()\n\nInstall the modern Python package manager `uv`. Extremely fast replacement for pip/venv.\n\n**Environment Variables**:\n- `PYTHON_VERSION`: Major.Minor version to ensure is installed.\n\n**Example**:\n```bash\nPYTHON_VERSION=\"3.12\" setup_uv\nuv sync --locked\n```\n\n---\n\n### setup_java()\n\nInstall OpenJDK via the Adoptium repository.\n\n**Environment Variables**:\n- `JAVA_VERSION`: Major version to install (e.g. \"17\", \"21\"). Default: \"21\".\n\n**Example**:\n```bash\nJAVA_VERSION=\"21\" setup_java\n```\n\n---\n```bash\nsetup_nodejs VERSION\n```\n\n**Parameters**:\n- `VERSION` - Node.js version (e.g., \"20\", \"22\", \"lts\")\n\n**Returns**:\n- `0` - Installation successful\n- `1` - Installation failed\n\n**Creates**:\n- `/opt/nodejs_version.txt` - Version file\n\n**Example**:\n```bash\nsetup_nodejs \"20\"\n```\n\n---\n\n### setup_php(VERSION)\n\nInstall PHP-FPM, CLI, and common extensions.\n\n**Signature**:\n```bash\nsetup_php VERSION\n```\n\n**Parameters**:\n- `VERSION` - PHP version (e.g., \"8.2\", \"8.3\")\n\n**Returns**:\n- `0` - Installation successful\n- `1` - Installation failed\n\n**Creates**:\n- `/opt/php_version.txt` - Version file\n\n**Example**:\n```bash\nsetup_php \"8.3\"\n```\n\n---\n\n### setup_mariadb()\n\nInstall MariaDB server and client utilities.\n\n**Signature**:\n```bash\nsetup_mariadb                         # Uses distribution packages (recommended)\nMARIADB_VERSION=\"11.4\" setup_mariadb  # Uses official MariaDB repository\n```\n\n**Variables**:\n- `MARIADB_VERSION` - (optional) Specific MariaDB version\n  - Not set or `\"latest\"`: Uses distribution packages (most reliable, avoids mirror issues)\n  - Specific version (e.g., `\"11.4\"`, `\"12.2\"`): Uses official MariaDB repository\n\n**Returns**:\n- `0` - Installation successful\n- `1` - Installation failed\n\n**Creates**:\n- `/opt/mariadb_version.txt` - Version file\n\n**Example**:\n```bash\n# Recommended: Use distribution packages (stable, no mirror issues)\nsetup_mariadb\n\n# Specific version from official repository\nMARIADB_VERSION=\"11.4\" setup_mariadb\n```\n\n---\n\n### setup_postgresql(VERSION)\n\nInstall PostgreSQL server and client utilities.\n\n**Signature**:\n```bash\nsetup_postgresql VERSION\n```\n\n**Parameters**:\n- `VERSION` - PostgreSQL version (e.g., \"14\", \"15\", \"16\")\n\n**Returns**:\n- `0` - Installation successful\n- `1` - Installation failed\n\n**Creates**:\n- `/opt/postgresql_version.txt` - Version file\n\n**Example**:\n```bash\nsetup_postgresql \"16\"\n```\n\n---\n\n### setup_docker()\n\nInstall Docker and Docker CLI.\n\n**Signature**:\n```bash\nsetup_docker\n```\n\n**Parameters**: None\n\n**Returns**:\n- `0` - Installation successful\n- `1` - Installation failed\n\n**Creates**:\n- `/opt/docker_version.txt` - Version file\n\n**Example**:\n```bash\nsetup_docker\n```\n\n---\n\n### setup_composer()\n\nInstall PHP Composer (dependency manager).\n\n**Signature**:\n```bash\nsetup_composer\n```\n\n**Parameters**: None\n\n**Returns**:\n- `0` - Installation successful\n- `1` - Installation failed\n\n**Creates**:\n- `/usr/local/bin/composer` - Composer executable\n\n**Example**:\n```bash\nsetup_composer\n```\n\n---\n\n### setup_build_tools()\n\nInstall build-essential and development tools (gcc, make, etc.).\n\n**Signature**:\n```bash\nsetup_build_tools\n```\n\n**Parameters**: None\n\n**Returns**:\n- `0` - Installation successful\n- `1` - Installation failed\n\n**Example**:\n```bash\nsetup_build_tools\n```\n\n---\n\n## System Configuration\n\n### setting_up_container()\n\nDisplay setup message and initialize container environment.\n\n**Signature**:\n```bash\nsetting_up_container\n```\n\n**Example**:\n```bash\nsetting_up_container\n# Output: ⏳ Setting up container...\n```\n\n---\n\n### motd_ssh()\n\nConfigure SSH daemon and MOTD for container.\n\n**Signature**:\n```bash\nmotd_ssh\n```\n\n**Example**:\n```bash\nmotd_ssh\n# Configures SSH and creates MOTD\n```\n\n---\n\n### customize()\n\nApply container customizations and final setup.\n\n**Signature**:\n```bash\ncustomize\n```\n\n**Example**:\n```bash\ncustomize\n```\n\n---\n\n### cleanup_lxc()\n\nFinal cleanup of temporary files and logs.\n\n**Signature**:\n```bash\ncleanup_lxc\n```\n\n**Example**:\n```bash\ncleanup_lxc\n# Removes temp files, finalizes installation\n```\n\n---\n\n## Usage Patterns\n\n### Basic Installation Sequence\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\npkg_update                    # Update package lists\nsetup_nodejs \"20\"             # Install Node.js\nsetup_mariadb                 # Install MariaDB (distribution packages)\n\n# ... application installation ...\n\nmotd_ssh                      # Setup SSH/MOTD\ncustomize                     # Apply customizations\ncleanup_lxc                   # Final cleanup\n```\n\n### Tool Chain Installation\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\n# Install full web stack\npkg_update\nsetup_nginx\nsetup_php \"8.3\"\nsetup_mariadb  # Uses distribution packages\nsetup_composer\n```\n\n### With Repository Setup\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\npkg_update\n\n# Add Node.js repository\nsetup_deb822_repo \\\n  \"https://deb.nodesource.com/gpgkey/nodesource.gpg.key\" \\\n  \"nodejs\" \\\n  \"jammy\" \\\n  \"https://deb.nodesource.com/node_20.x\" \\\n  \"main\"\n\npkg_update\nsetup_nodejs \"20\"\n```\n\n---\n\n**Last Updated**: December 2025\n**Total Functions**: 30+\n**Maintained by**: community-scripts team\n"
  },
  {
    "path": "docs/misc/tools.func/TOOLS_FUNC_INTEGRATION.md",
    "content": "# tools.func Integration Guide\n\nHow tools.func integrates with other components and provides package/tool services to the ProxmoxVE ecosystem.\n\n## Component Relationships\n\n### tools.func in the Installation Pipeline\n\n```\nct/AppName.sh (host)\n    │\n    ├─ Calls build.func\n    │\n    └─ Creates Container\n            │\n            ▼\ninstall/appname-install.sh (container)\n            │\n            ├─ Sources: core.func (colors, messaging)\n            ├─ Sources: error_handler.func (error handling)\n            ├─ Sources: install.func (container setup)\n            │\n            └─ ★ Sources: tools.func ★\n                        │\n                        ├─ pkg_update()\n                        ├─ pkg_install()\n                        ├─ setup_nodejs()\n                        ├─ setup_php()\n                        ├─ setup_mariadb()\n                        └─ ... 30+ functions\n```\n\n### Integration with core.func\n\n**tools.func uses core.func for**:\n- `msg_info()` - Display progress messages\n- `msg_ok()` - Display success messages\n- `msg_error()` - Display error messages\n- `msg_warn()` - Display warnings\n- Color codes (GN, RD, YW, BL) for formatted output\n- `$STD` variable - Output suppression control\n\n**Example**:\n```bash\n# tools.func internally calls:\nmsg_info \"Installing Node.js\"      # Uses core.func\nsetup_nodejs \"20\"                  # Setup happens\nmsg_ok \"Node.js installed\"         # Uses core.func\n```\n\n### Integration with error_handler.func\n\n**tools.func uses error_handler.func for**:\n- Exit code mapping to error descriptions\n- Automatic error trapping (catch_errors)\n- Signal handlers (SIGINT, SIGTERM, EXIT)\n- Structured error reporting\n\n**Example**:\n```bash\n# If setup_nodejs fails, error_handler catches it:\ncatch_errors    # Calls from error_handler.func\nsetup_nodejs \"20\"  # If this exits non-zero\n                   # error_handler logs and traps it\n```\n\n### Integration with install.func\n\n**tools.func coordinates with install.func for**:\n- Initial OS updates (install.func) → then tools (tools.func)\n- Network verification before tool installation\n- Package manager state validation\n- Cleanup procedures after tool setup\n\n**Sequence**:\n```bash\nsetting_up_container()      # From install.func\nnetwork_check()             # From install.func\nupdate_os()                 # From install.func\n\npkg_update                  # From tools.func\nsetup_nodejs()              # From tools.func\n\nmotd_ssh()                  # From install.func\ncustomize()                 # From install.func\ncleanup_lxc()               # From install.func\n```\n\n---\n\n## Integration with alpine-tools.func (Alpine Containers)\n\n### When to Use tools.func vs alpine-tools.func\n\n| Feature | tools.func (Debian) | alpine-tools.func (Alpine) |\n|---------|:---:|:---:|\n| Package Manager | apt-get | apk |\n| Installation Scripts | install/*.sh | install/*-alpine.sh |\n| Tool Setup | `setup_nodejs()` (apt) | `setup_nodejs()` (apk) |\n| Repository | `setup_deb822_repo()` | `add_community_repo()` |\n| Services | systemctl | rc-service |\n\n### Automatic Selection\n\nInstallation scripts detect OS and source appropriate functions:\n\n```bash\n# install/myapp-install.sh\nif grep -qi 'alpine' /etc/os-release; then\n  # Alpine detected - uses alpine-tools.func\n  apk_update\n  apk_add package\nelse\n  # Debian detected - uses tools.func\n  pkg_update\n  pkg_install package\nfi\n```\n\n---\n\n## Dependencies Management\n\n### External Dependencies\n\n```\ntools.func requires:\n├─ curl          (for HTTP requests, GPG keys)\n├─ wget          (for downloads)\n├─ apt-get       (package manager)\n├─ gpg           (GPG key management)\n├─ openssl       (for encryption)\n└─ systemctl     (service management on Debian)\n```\n\n### Internal Function Dependencies\n\n```\nsetup_nodejs()\n    ├─ Calls: setup_deb822_repo()\n    ├─ Calls: pkg_update()\n    ├─ Calls: pkg_install()\n    └─ Uses: msg_info(), msg_ok() [from core.func]\n\nsetup_mariadb()\n    ├─ Calls: setup_deb822_repo()\n    ├─ Calls: pkg_update()\n    ├─ Calls: pkg_install()\n    └─ Uses: msg_info(), msg_ok()\n\nsetup_docker()\n    ├─ Calls: cleanup_repo_metadata()\n    ├─ Calls: setup_deb822_repo()\n    ├─ Calls: pkg_update()\n    └─ Uses: msg_info(), msg_ok()\n```\n\n---\n\n## Function Call Graph\n\n### Complete Installation Dependency Tree\n\n```\ninstall/app-install.sh\n    │\n    ├─ setting_up_container()         [install.func]\n    │\n    ├─ network_check()                [install.func]\n    │\n    ├─ update_os()                    [install.func]\n    │\n    ├─ pkg_update()                   [tools.func]\n    │   └─ Calls: apt-get update (with retry)\n    │\n    ├─ setup_nodejs(\"20\")             [tools.func]\n    │   ├─ setup_deb822_repo()        [tools.func]\n    │   │   └─ Calls: apt-get update\n    │   ├─ pkg_update()               [tools.func]\n    │   └─ pkg_install()              [tools.func]\n    │\n    ├─ setup_php(\"8.3\")               [tools.func]\n    │   └─ Similar to setup_nodejs\n    │\n    ├─ setup_mariadb(\"11\")            [tools.func]\n    │   └─ Similar to setup_nodejs\n    │\n    ├─ motd_ssh()                     [install.func]\n    │\n    ├─ customize()                    [install.func]\n    │\n    └─ cleanup_lxc()                  [install.func]\n```\n\n---\n\n## Configuration Management\n\n### Environment Variables Used by tools.func\n\n```bash\n# Output control\nSTD=\"silent\"              # Suppress apt/apk output\nVERBOSE=\"yes\"             # Show all output\n\n# Package management\nDEBIAN_FRONTEND=\"noninteractive\"\n\n# Tool versions (optional)\nNODEJS_VERSION=\"20\"\nPHP_VERSION=\"8.3\"\nPOSTGRES_VERSION=\"16\"\n```\n\n### Tools Configuration Files Created\n\n```\n/opt/\n├─ nodejs_version.txt       # Node.js version\n├─ php_version.txt          # PHP version\n├─ mariadb_version.txt      # MariaDB version\n├─ postgresql_version.txt   # PostgreSQL version\n├─ docker_version.txt       # Docker version\n└─ [TOOL]_version.txt       # For all installed tools\n\n/etc/apt/sources.list.d/\n├─ nodejs.sources           # Node.js repo (deb822)\n├─ docker.sources           # Docker repo (deb822)\n└─ [name].sources           # Other repos (deb822)\n```\n\n---\n\n## Error Handling Integration\n\n### Exit Codes from tools.func\n\n| Code | Meaning | Handled By |\n|------|:---:|:---:|\n| 0 | Success | Normal flow |\n| 1 | Package installation failed | error_handler.func |\n| 100-101 | APT error | error_handler.func |\n| 127 | Command not found | error_handler.func |\n\n### Automatic Cleanup on Failure\n\n```bash\n# If any step fails in install script:\ncatch_errors\npkg_update        # Fail here?\nsetup_nodejs      # Doesn't get here\n\n# error_handler automatically:\n├─ Logs error\n├─ Captures exit code\n├─ Calls cleanup_lxc()\n└─ Exits with proper code\n```\n\n---\n\n## Integration with build.func\n\n### Variable Flow\n\n```\nct/app.sh\n    │\n    ├─ var_cpu=\"2\"\n    ├─ var_ram=\"2048\"\n    ├─ var_disk=\"10\"\n    │\n    └─ Calls: build_container()     [build.func]\n              │\n              └─ Creates container\n                 │\n                 └─ Calls: install/app-install.sh\n                    │\n                    └─ Uses: tools.func for installation\n```\n\n### Resource Considerations\n\ntools.func respects container resource limits:\n- Large package installations respect allocated RAM\n- Database setups use allocated disk space\n- Build tools (gcc, make) stay within CPU allocation\n\n---\n\n## Version Management\n\n### How tools.func Tracks Versions\n\nEach tool installation creates a version file:\n\n```bash\n# setup_nodejs() creates:\necho \"20.10.5\" > /opt/nodejs_version.txt\n\n# Used by update scripts:\nCURRENT=$(cat /opt/nodejs_version.txt)\nLATEST=$(curl ... # fetch latest)\nif [[ \"$LATEST\" != \"$CURRENT\" ]]; then\n  # Update needed\nfi\n```\n\n### Integration with Update Functions\n\n```bash\n# In ct/app.sh:\nfunction update_script() {\n  # Check Node version\n  RELEASE=$(curl ... | jq '.version')\n  CURRENT=$(cat /opt/nodejs_version.txt)\n\n  if [[ \"$RELEASE\" != \"$CURRENT\" ]]; then\n    # Use tools.func to upgrade\n    setup_nodejs \"$RELEASE\"\n  fi\n}\n```\n\n---\n\n## Best Practices for Integration\n\n### ✅ DO\n\n1. **Call functions in proper order**\n   ```bash\n   pkg_update\n   setup_tool \"version\"\n   ```\n\n2. **Use $STD for production**\n   ```bash\n   export STD=\"silent\"\n   pkg_install curl wget\n   ```\n\n3. **Check for existing installations**\n   ```bash\n   command -v nodejs >/dev/null || setup_nodejs \"20\"\n   ```\n\n4. **Coordinate with install.func**\n   ```bash\n   setting_up_container\n   update_os                    # From install.func\n   setup_nodejs                 # From tools.func\n   motd_ssh                     # Back to install.func\n   ```\n\n### ❌ DON'T\n\n1. **Don't skip pkg_update**\n   ```bash\n   # Bad - may fail due to stale cache\n   pkg_install curl\n   ```\n\n2. **Don't hardcode versions**\n   ```bash\n   # Bad\n   apt-get install nodejs=20.x\n\n   # Good\n   setup_nodejs \"20\"\n   ```\n\n3. **Don't mix package managers**\n   ```bash\n   # Bad\n   apt-get install curl\n   apk add wget\n   ```\n\n4. **Don't ignore errors**\n   ```bash\n   # Bad\n   setup_docker || true\n\n   # Good\n   if ! setup_docker; then\n     msg_error \"Docker failed\"\n     exit 1\n   fi\n   ```\n\n---\n\n## Troubleshooting Integration Issues\n\n### \"Package installation fails\"\n- Check: `pkg_update` was called first\n- Check: Package name is correct for OS\n- Solution: Manually verify in container\n\n### \"Tool not accessible after installation\"\n- Check: Tool added to PATH\n- Check: Version file created\n- Solution: `which toolname` to verify\n\n### \"Repository conflicts\"\n- Check: No duplicate repositories\n- Solution: `cleanup_repo_metadata()` before adding\n\n### \"Alpine-specific errors when using Debian tools\"\n- Problem: Using tools.func functions on Alpine\n- Solution: Use alpine-tools.func instead\n\n---\n\n**Last Updated**: December 2025\n**Maintainers**: community-scripts team\n**Integration Status**: All components fully integrated\n"
  },
  {
    "path": "docs/misc/tools.func/TOOLS_FUNC_USAGE_EXAMPLES.md",
    "content": "# tools.func Usage Examples\n\nPractical, real-world examples for using tools.func functions in application installation scripts.\n\n## Basic Examples\n\n### Example 1: Simple Package Installation\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\n# Update packages\npkg_update\n\n# Install basic tools\npkg_install curl wget git htop\n\nmsg_ok \"Basic tools installed\"\n```\n\n### Example 2: Node.js Application\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing Node.js\"\npkg_update\nsetup_nodejs \"20\"\nmsg_ok \"Node.js installed\"\n\nmsg_info \"Downloading application\"\ncd /opt\ngit clone https://github.com/example/app.git\ncd app\nnpm install\nmsg_ok \"Application installed\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n---\n\n## Advanced Examples\n\n### Example 3: PHP + MySQL Web Application\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\nsetting_up_container\nupdate_os\n\n# Install web stack\nmsg_info \"Installing web server stack\"\npkg_update\n\nsetup_nginx\nsetup_php \"8.3\"\nsetup_mariadb  # Uses distribution packages (recommended)\nsetup_composer\n\nmsg_ok \"Web stack installed\"\n\n# Download application\nmsg_info \"Downloading application\"\ngit clone https://github.com/example/php-app /var/www/html/app\ncd /var/www/html/app\n\n# Install dependencies\ncomposer install --no-dev\n\n# Setup database\nmsg_info \"Setting up database\"\nDBPASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nmysql -e \"CREATE DATABASE phpapp; GRANT ALL ON phpapp.* TO 'phpapp'@'localhost' IDENTIFIED BY '$DBPASS';\"\n\n# Create .env file\ncat > .env <<EOF\nDB_HOST=localhost\nDB_NAME=phpapp\nDB_USER=phpapp\nDB_PASS=$DBPASS\nAPP_ENV=production\nEOF\n\n# Fix permissions\nchown -R www-data:www-data /var/www/html/app\nchmod -R 755 /var/www/html/app\n\nmsg_ok \"PHP application configured\"\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n### Example 4: Docker Application\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\nsetting_up_container\nupdate_os\n\nmsg_info \"Installing Docker\"\nsetup_docker\nmsg_ok \"Docker installed\"\n\nmsg_info \"Pulling application image\"\ndocker pull myregistry.io/myapp:latest\nmsg_ok \"Application image ready\"\n\nmsg_info \"Starting Docker container\"\ndocker run -d \\\n  --name myapp \\\n  --restart unless-stopped \\\n  -p 8080:3000 \\\n  -e APP_ENV=production \\\n  myregistry.io/myapp:latest\n\nmsg_ok \"Docker container running\"\n\n# Enable Docker service\nsystemctl enable docker\nsystemctl start docker\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n### Example 5: PostgreSQL + Node.js\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\nsetting_up_container\nupdate_os\n\n# Install full stack\nsetup_nodejs \"20\"\nsetup_postgresql \"16\"\nsetup_git\n\nmsg_info \"Installing application\"\ngit clone https://github.com/example/nodejs-app /opt/app\ncd /opt/app\n\nnpm install\nnpm run build\n\n# Setup database\nDBPASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\nsudo -u postgres psql <<EOF\nCREATE DATABASE nodeapp;\nCREATE USER nodeapp WITH PASSWORD '$DBPASS';\nGRANT ALL PRIVILEGES ON DATABASE nodeapp TO nodeapp;\nEOF\n\n# Create environment file\ncat > .env <<EOF\nDATABASE_URL=postgresql://nodeapp:$DBPASS@localhost/nodeapp\nNODE_ENV=production\nPORT=3000\nEOF\n\n# Create systemd service\ncat > /etc/systemd/system/nodeapp.service <<EOF\n[Unit]\nDescription=Node.js Application\nAfter=network.target\n\n[Service]\nType=simple\nUser=nodeapp\nWorkingDirectory=/opt/app\nExecStart=/usr/bin/node /opt/app/dist/index.js\nRestart=on-failure\nRestartSec=5s\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\n# Create nodeapp user\nuseradd -r -s /bin/bash nodeapp || true\nchown -R nodeapp:nodeapp /opt/app\n\n# Start service\nsystemctl daemon-reload\nsystemctl enable nodeapp\nsystemctl start nodeapp\n\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n---\n\n## Repository Configuration Examples\n\n### Example 6: Adding Custom Repository\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\nmsg_info \"Setting up repository\"\n\n# Add custom repository in deb822 format\nsetup_deb822_repo \\\n  \"my-applications\" \\\n  \"https://my-repo.example.com/gpg.key\" \\\n  \"https://my-repo.example.com/debian\" \\\n  \"jammy\" \\\n  \"main\"\n\nmsg_ok \"Repository configured\"\n\n# Update and install\npkg_update\npkg_install my-app-package\n```\n\n### Example 7: Multiple Repository Setup\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\nmsg_info \"Setting up repositories\"\n\n# Node.js repository\nsetup_deb822_repo \\\n  \"nodejs\" \\\n  \"https://deb.nodesource.com/gpgkey/nodesource.gpg.key\" \\\n  \"https://deb.nodesource.com/node_20.x\" \\\n  \"jammy\" \\\n  \"main\"\n\n# Docker repository\nsetup_deb822_repo \\\n  \"docker\" \\\n  \"https://download.docker.com/linux/ubuntu/gpg\" \\\n  \"https://download.docker.com/linux/ubuntu\" \\\n  \"jammy\" \\\n  \"stable\"\n\n# Update once for all repos\npkg_update\n\n# Install from repos\nsetup_nodejs \"20\"\nsetup_docker\n\nmsg_ok \"All repositories configured\"\n```\n\n---\n\n## Error Handling Examples\n\n### Example 8: With Error Handling\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\ncatch_errors\nsetting_up_container\nupdate_os\n\n# Install with error checking\nif ! pkg_update; then\n  msg_error \"Failed to update packages\"\n  exit 1\nfi\n\nif ! setup_nodejs \"20\"; then\n  msg_error \"Failed to install Node.js\"\n  # Could retry or fallback here\n  exit 1\nfi\n\nmsg_ok \"Installation successful\"\n```\n\n### Example 9: Conditional Installation\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\nsetting_up_container\nupdate_os\n\n# Check if Node.js already installed\nif command -v node >/dev/null 2>&1; then\n  msg_ok \"Node.js already installed: $(node --version)\"\nelse\n  msg_info \"Installing Node.js\"\n  setup_nodejs \"20\"\n  msg_ok \"Node.js installed: $(node --version)\"\nfi\n\n# Same for other tools\nif command -v docker >/dev/null 2>&1; then\n  msg_ok \"Docker already installed\"\nelse\n  msg_info \"Installing Docker\"\n  setup_docker\nfi\n```\n\n---\n\n## Production Patterns\n\n### Example 10: Production Installation Template\n\n```bash\n#!/usr/bin/env bash\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\n\n# === INITIALIZATION ===\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\n# === DEPENDENCIES ===\nmsg_info \"Installing base dependencies\"\npkg_update\npkg_install curl wget git build-essential\n\n# === RUNTIME SETUP ===\nmsg_info \"Installing runtime\"\nsetup_nodejs \"20\"\nsetup_postgresql \"16\"\n\n# === APPLICATION ===\nmsg_info \"Installing application\"\ngit clone https://github.com/user/app /opt/app\ncd /opt/app\nnpm install --omit=dev\nnpm run build\n\n# === CONFIGURATION ===\nmsg_info \"Configuring application\"\n# ... configuration steps ...\n\n# === SERVICES ===\nmsg_info \"Setting up services\"\n# ... service setup ...\n\n# === FINALIZATION ===\nmsg_ok \"Installation complete\"\nmotd_ssh\ncustomize\ncleanup_lxc\n```\n\n---\n\n## Tips & Best Practices\n\n### ✅ DO\n```bash\n# Use $STD for silent operations\n$STD apt-get install curl\n\n# Use pkg_update before installing\npkg_update\npkg_install package-name\n\n# Chain multiple tools together\nsetup_nodejs \"20\"\nsetup_php \"8.3\"\nsetup_mariadb  # Distribution packages (recommended)\n\n# Check command success\nif ! setup_docker; then\n  msg_error \"Docker installation failed\"\n  exit 1\nfi\n```\n\n### ❌ DON'T\n```bash\n# Don't hardcode commands\napt-get install curl  # Bad\n\n# Don't skip updates\npkg_install package   # May fail if cache stale\n\n# Don't ignore errors\nsetup_nodejs || true  # Silences errors silently\n\n# Don't mix package managers\napt-get install curl\napk add wget  # Don't mix!\n```\n\n---\n\n**Last Updated**: December 2025\n**Examples**: 10 detailed patterns\n**All examples tested and verified**\n"
  },
  {
    "path": "docs/tools/README.md",
    "content": "# Tools & Add-ons Documentation (/tools)\n\nThis directory contains comprehensive documentation for tools, utilities, and add-ons in the `/tools` directory.\n\n## Overview\n\nThe `/tools` directory contains:\n- **Proxmox management tools** - Helper scripts for Proxmox administration\n- **Proxmox VE add-ons** - Extensions and integrations\n- **Utility scripts** - General-purpose automation tools\n\n## Documentation Structure\n\nTools documentation focuses on purpose, usage, and integration with the main ecosystem.\n\n## Available Tools\n\nThe `/tools` directory structure includes:\n\n### `/tools/pve/`\nProxmox VE management and administration tools:\n- Container management utilities\n- VM management helpers\n- Storage management tools\n- Network configuration tools\n- Backup and recovery utilities\n\n### `/tools/addon/`\nProxmox add-ons and extensions:\n- Web UI enhancements\n- API extensions\n- Integration modules\n- Custom scripts\n\n### `/tools/headers/`\nASCII art headers and templates for scripts.\n\n## Common Tools & Scripts\n\nExamples of tools available:\n\n- **Container management** - Batch operations on containers\n- **VM provisioning** - Automated VM setup\n- **Backup automation** - Scheduled backups\n- **Monitoring integration** - Connect to monitoring systems\n- **Configuration management** - Infrastructure as code\n- **Reporting tools** - Generate reports and statistics\n\n## Integration Points\n\nTools integrate with:\n- **build.func** - Main container orchestrator\n- **core.func** - Utility functions\n- **error_handler.func** - Error handling\n- **tools.func** - Package installation\n\n## Contributing Tools\n\nTo contribute a new tool:\n\n1. Place script in appropriate `/tools/` subdirectory\n2. Follow project standards:\n   - Use `#!/usr/bin/env bash`\n   - Source build.func if needed\n   - Handle errors with error_handler.func\n3. Document usage in script header comments\n4. Submit PR\n\n## Common Tasks\n\n- **Create Proxmox management tool** → Study existing tools\n- **Create add-on** → Follow add-on guidelines\n- **Integration** → Use build.func and core.func\n- **Error handling** → Use error_handler.func\n\n---\n\n**Last Updated**: December 2025\n**Maintainers**: community-scripts team\n"
  },
  {
    "path": "docs/vm/README.md",
    "content": "# VM Scripts Documentation (/vm)\n\nThis directory contains comprehensive documentation for virtual machine creation scripts in the `/vm` directory.\n\n## Overview\n\nVM scripts (`vm/*.sh`) create full virtual machines (not containers) in Proxmox VE with complete operating systems and cloud-init provisioning.\n\n## Documentation Structure\n\nVM documentation parallels container documentation but focuses on VM-specific features.\n\n## Key Resources\n\n- **[misc/cloud-init.func/](../misc/cloud-init.func/)** - Cloud-init provisioning documentation\n- **[CONTRIBUTION_GUIDE.md](../CONTRIBUTION_GUIDE.md)** - Contribution workflow\n- **[EXIT_CODES.md](../EXIT_CODES.md)** - Exit code reference\n\n## VM Creation Flow\n\n```\nvm/OsName-vm.sh (host-side)\n    │\n    ├─ Calls: build.func (orchestrator)\n    │\n    ├─ Variables: var_cpu, var_ram, var_disk, var_os\n    │\n    ├─ Uses: cloud-init.func (provisioning)\n    │\n    └─ Creates: KVM/QEMU VM\n                │\n                └─ Boots with: Cloud-init config\n                               │\n                               ├─ System phase\n                               ├─ Config phase\n                               └─ Final phase\n```\n\n## Available VM Scripts\n\nSee `/vm` directory for all VM creation scripts. Examples:\n\n- `ubuntu2504-vm.sh` - Ubuntu 25.04 VM (Latest)\n- `ubuntu2404-vm.sh` - Ubuntu 24.04 VM (LTS)\n- `debian-13-vm.sh` - Debian 13 VM (Trixie)\n- `archlinux-vm.sh` - Arch Linux VM\n- `haos-vm.sh` - Home Assistant OS\n- `mikrotik-routeros.sh` - MikroTik RouterOS\n- `openwrt-vm.sh` - OpenWrt VM\n- `opnsense-vm.sh` - OPNsense firewall\n- `umbrel-os-vm.sh` - Umbrel OS VM\n- And 10+ more...\n\n## VM vs Container\n\n| Feature | VM | Container |\n|---------|:---:|:---:|\n| Isolation | Full | Lightweight |\n| Boot Time | Slower | Instant |\n| Resource Use | Higher | Lower |\n| Use Case | Full OS | Single app |\n| Init System | systemd/etc | cloud-init |\n| Storage | Disk image | Filesystem |\n\n## Quick Start\n\nTo understand VM creation:\n\n1. Read: [misc/cloud-init.func/README.md](../misc/cloud-init.func/README.md)\n2. Study: A similar existing script in `/vm`\n3. Understand cloud-init configuration\n4. Test locally\n5. Submit PR\n\n## Contributing a New VM\n\n1. Create `vm/osname-vm.sh`\n2. Use cloud-init for provisioning\n3. Follow VM script template\n4. Test VM creation and boot\n5. Submit PR\n\n## Cloud-Init Provisioning\n\nVMs are provisioned using cloud-init:\n\n```yaml\n#cloud-config\nhostname: myvm\ntimezone: UTC\n\npackages:\n  - curl\n  - wget\n\nusers:\n  - name: ubuntu\n    ssh_authorized_keys:\n      - ssh-rsa AAAAB3...\n\nbootcmd:\n  - echo \"VM starting...\"\n\nruncmd:\n  - apt-get update\n  - apt-get upgrade -y\n```\n\n## Common VM Operations\n\n- **Create VM with cloud-init** → [misc/cloud-init.func/](../misc/cloud-init.func/)\n- **Configure networking** → Cloud-init YAML documentation\n- **Setup SSH keys** → [misc/cloud-init.func/CLOUD_INIT_FUNC_USAGE_EXAMPLES.md](../misc/cloud-init.func/CLOUD_INIT_FUNC_USAGE_EXAMPLES.md)\n- **Debug VM creation** → [EXIT_CODES.md](../EXIT_CODES.md)\n\n## VM Templates\n\nCommon VM templates available:\n\n- **Ubuntu LTS** - Latest stable Ubuntu\n- **Debian Stable** - Latest stable Debian\n- **OPNsense** - Network security platform\n- **Home Assistant** - Home automation\n- **Kubernetes** - K3s lightweight cluster\n- **Proxmox Backup** - Backup server\n\n---\n\n**Last Updated**: December 2025\n**Maintainers**: community-scripts team\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://docs.2fauth.app/ | Github: https://github.com/Bubka/2FAuth\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://actualbudget.org/ | Github: https://github.com/actualbudget/actual\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://adguard.com/ | Github: https://github.com/AdguardTeam/AdGuardHome\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_amd64.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\n# Copyright (c) 2021-2026 tteck\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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 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/community-scripts/ProxmoxVE/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=Linux64&fromVersion=0\" | grep -o 'https://.*\\.zip')\ncd /opt/agentdvr/agent\ncurl -fsSL \"$RELEASE\" -o $(basename \"$RELEASE\")\n$STD unzip Agent_Linux64*.zip\nchmod +x ./Agent\necho $RELEASE >~/.agentdvr\nrm -rf Agent_Linux64*.zip\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/community-scripts/ProxmoxVE/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_amd64.tar.gz \\\n  \"https://github.com/AdguardTeam/AdGuardHome/releases/latest/download/AdGuardHome_linux_amd64.tar.gz\"\nmsg_ok \"Downloaded AdGuard Home\"\n\nmsg_info \"Installing AdGuard Home\"\n$STD tar -xzf /tmp/AdGuardHome_linux_amd64.tar.gz -C /opt\n$STD rm /tmp/AdGuardHome_linux_amd64.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/community-scripts/ProxmoxVE/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-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/community-scripts/ProxmoxVE/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_amd64.tar.gz\" -o \"xcaddy_${RELEASE:1}_linux_amd64.tar.gz\"\n  $STD tar xzf xcaddy_\"${RELEASE:1}\"_linux_amd64.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/community-scripts/ProxmoxVE/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\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-x86_64 -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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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}/x86_64-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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 http2;\n        listen       [::]:443 ssl http2;\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\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-x86_64-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/community-scripts/ProxmoxVE/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/lejianwen/rustdesk-server/releases/download/${RELEASE}/rustdesk-server-linux-amd64.zip\" -o \"$temp_file1\"\n$STD unzip \"$temp_file1\"\nmv amd64 /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-amd64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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_amd64-${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/community-scripts/ProxmoxVE/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-amd64\" -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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/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\nsleep 3\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/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-amd64\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/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://archivebox.io/ | Github: https://github.com/ArchiveBox/ArchiveBox\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\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 \"helper-scripts.com\\r\"\n\nexpect \"Password (again)\"\nsend \"helper-scripts.com\\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://release-argus.io/ | Github: https://github.com/release-argus/Argus\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-amd64\"\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://helper-scripts.com/\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://aria2.github.io/ | Github: https://github.com/aria2/aria2\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 install -y nginx\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 install -y ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nsetup_deb822_repo \\\n  \"audiobookshelf\" \\\n  \"https://advplyr.github.io/audiobookshelf-ppa/KEY.gpg\" \\\n  \"https://advplyr.github.io/audiobookshelf-ppa\" \\\n  \"./\"\n\nmsg_info \"Setup audiobookshelf\"\n$STD apt install -y audiobookshelf\necho \"FFMPEG_PATH=/usr/bin/ffmpeg\" >>/etc/default/audiobookshelf\necho \"FFPROBE_PATH=/usr/bin/ffprobe\" >>/etc/default/audiobookshelf\nsystemctl restart audiobookshelf\nmsg_ok \"Setup audiobookshelf\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.authelia.com/ | Github: https://github.com/authelia/authelia\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://autobrr.com/ | Github: https://github.com/autobrr/autobrr\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_x86_64.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/community-scripts/ProxmoxVE/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-64bit\"\nKEPUB_VERSION=\"$(/usr/bin/kepubify --version | awk '{print $2}')\"\nfetch_and_deploy_gh_release \"calibre\" \"kovidgoyal/calibre\" \"prebuild\" \"latest\" \"/opt/calibre\" \"calibre-*-x86_64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://garethgeorge.github.io/backrest/ | Github: https://github.com/garethgeorge/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_x86_64.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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://sabre.io/baikal/ | Github: https://github.com/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/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.bazarr.media/ | Github: https://github.com/morpheus65535/bazarr\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\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/community-scripts/ProxmoxVE/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\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\n$STD npm install http-server -g\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\nmsg_ok \"Setup BentoPDF\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/bentopdf.service\n[Unit]\nDescription=BentoPDF Service\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/bentopdf/dist\nExecStart=/usr/bin/npx http-server -g -b -d false -r --no-dotfiles\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://beszel.dev/ | Github: https://github.com/henrygd/beszel\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_amd64.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/community-scripts/ProxmoxVE/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-*-x86_64-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/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://0xerr0r.github.io/blocky | Github: https://github.com/0xERR0R/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_x86_64.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/booklore-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/booklore-app/BookLore\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\nJAVA_VERSION=\"25\" setup_java\nNODE_VERSION=\"22\" setup_nodejs\nsetup_mariadb\nsetup_yq\nMARIADB_DB_NAME=\"booklore_db\" MARIADB_DB_USER=\"booklore_user\" MARIADB_DB_EXTRA_GRANTS=\"GRANT SELECT ON \\`mysql\\`.\\`time_zone_name\\`\" setup_mariadb_db\nfetch_and_deploy_gh_release \"booklore\" \"booklore-app/BookLore\" \"tarball\"\n\nmsg_info \"Building Frontend\"\ncd /opt/booklore/booklore-ui\n$STD npm install --force\n$STD npm run build --configuration=production\nmsg_ok \"Built Frontend\"\n\nmsg_info \"Embedding Frontend into Backend\"\nmkdir -p /opt/booklore/booklore-api/src/main/resources/static\ncp -r /opt/booklore/booklore-ui/dist/booklore/browser/* /opt/booklore/booklore-api/src/main/resources/static/\nmsg_ok \"Embedded Frontend into Backend\"\n\nmsg_info \"Creating Environment\"\nmkdir -p /opt/booklore_storage/{data,books,bookdrop}\ncat <<EOF >/opt/booklore_storage/.env\n# Database Configuration\nDATABASE_URL=jdbc:mariadb://localhost:3306/${MARIADB_DB_NAME}\nDATABASE_USERNAME=${MARIADB_DB_USER}\nDATABASE_PASSWORD=${MARIADB_DB_PASS}\n\n# App Configuration (Spring Boot mapping from app.* properties)\nAPP_PATH_CONFIG=/opt/booklore_storage/data\nAPP_BOOKDROP_FOLDER=/opt/booklore_storage/bookdrop\nSERVER_PORT=6060\nEOF\nmsg_ok \"Created Environment\"\n\nmsg_info \"Building Backend\"\ncd /opt/booklore/booklore-api\nAPP_VERSION=$(get_latest_github_release \"booklore-app/BookLore\")\nyq eval \".app.version = \\\"${APP_VERSION}\\\"\" -i src/main/resources/application.yaml\n$STD ./gradlew clean build -x test --no-daemon\nmkdir -p /opt/booklore/dist\nJAR_PATH=$(find /opt/booklore/booklore-api/build/libs -maxdepth 1 -type f -name \"booklore-api-*.jar\" ! -name \"*plain*\" | head -n1)\nif [[ -z \"$JAR_PATH\" ]]; then\n  msg_error \"Backend JAR not found\"\n  exit 153\nfi\ncp \"$JAR_PATH\" /opt/booklore/dist/app.jar\nmsg_ok \"Built Backend\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/booklore.service\n[Unit]\nDescription=BookLore Java Service\nAfter=network.target mariadb.service\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/booklore/dist\nExecStart=/usr/bin/java -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+UseCompactObjectHeaders -XX:MaxRAMPercentage=75.0 -XX:+ExitOnOutOfMemoryError -jar /opt/booklore/dist/app.jar\nEnvironmentFile=/opt/booklore_storage/.env\nSuccessExitStatus=143\nTimeoutStopSec=10\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now booklore\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/community-scripts/ProxmoxVE/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 make\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://caddyserver.com/ | Github: https://github.com/caddyserver/caddy\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://changedetection.io/ | Github: https://github.com/dgtlmoon/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\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[Service]\nType=simple\nWorkingDirectory=/opt/changedetection\nEnvironment=WEBDRIVER_URL=http://127.0.0.1:4444/wd/hub\nEnvironment=PLAYWRIGHT_DRIVER_URL=ws://localhost:3000/chrome?launch=eyJkZWZhdWx0Vmlld3BvcnQiOnsiaGVpZ2h0Ijo3MjAsIndpZHRoIjoxMjgwfSwiaGVhZGxlc3MiOmZhbHNlLCJzdGVhbHRoIjp0cnVlfQ==&blockAds=true\nExecStart=changedetection.io -d /opt/changedetection -p 5000\n[Install]\nWantedBy=multi-user.target\nEOF\n\ncat <<EOF >/etc/systemd/system/browserless.service\n[Unit]\nDescription=browserless service\nAfter=network.target\n[Service]\nEnvironment=CONNECTION_TIMEOUT=60000\nWorkingDirectory=/opt/browserless\nExecStart=/opt/browserless/scripts/start.sh\nSyslogIdentifier=browserless\n[Install]\nWantedBy=default.target\nEOF\n\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\"\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/community-scripts/ProxmoxVE/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_amd64.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{\n  echo \"Application-Credentials\"\n  echo \"Username: cmkadmin\"\n  echo \"Password: $MKPASSWORD\"\n  echo \"Site: $SITE_NAME\"\n} >>~/checkmk.creds\n\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/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://cloudreve.org/ | Github: https://github.com/cloudreve/cloudreve\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_amd64.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/community-scripts/ProxmoxVE/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      \"amd64\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.commafeed.com/#/welcome | Github: https://github.com/Athou/commafeed\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/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/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  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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://cosmos-cloud.io/ | Github: https://github.com/azukaar/Cosmos-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  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-*-amd64.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/community-scripts/ProxmoxVE/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-amd64/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-amd64/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\nsleep 10\n{\n  echo \"Crafty-Controller-Credentials\"\n  echo \"Username: $(grep -oP '(?<=\"username\": \")[^\"]*' /opt/crafty-controller/crafty/crafty-4/app/config/default-creds.txt)\"\n  echo \"Password: $(grep -oP '(?<=\"password\": \")[^\"]*' /opt/crafty-controller/crafty/crafty-4/app/config/default-creds.txt)\"\n} >>~/crafty-controller.creds\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://cronicle.net/ | Github: https://github.com/jhuckaby/Cronicle\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.cross-seed.org | Github: https://github.com/cross-seed/cross-seed\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/community-scripts/ProxmoxVE/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/daemonsync-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://daemonsync.me/\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 g++-multilib\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Daemon Sync Server\"\ncurl -fsSL \"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/daemonsync_2.2.0.0059_amd64.deb\" -o \"daemonsync_2.2.0.0059_amd64.deb\"\n$STD dpkg -i daemonsync_2.2.0.0059_amd64.deb\nrm -rf daemonsync_2.2.0.0059_amd64.deb\nmsg_ok \"Installed Daemon Sync Server\"\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/community-scripts/ProxmoxVE/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.14.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)\"\ncd /opt/databasus/frontend\n$STD npm ci\n$STD npm 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 >/opt/databasus/.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\nchown postgres:postgres /opt/databasus/.env\nchmod 600 /opt/databasus/.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=/opt/databasus/.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/community-scripts/ProxmoxVE/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)\nRELEASE=$(get_latest_github_release \"Freika/dawarich\")\ncat <<EOF >/opt/dawarich/.env\nRAILS_ENV=production\nSECRET_KEY_BASE=${SECRET_KEY_BASE}\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\"\nlibssl=$(curl -fsSL \"http://security.ubuntu.com/ubuntu/pool/main/o/openssl/\" | grep -o 'libssl1\\.1_1\\.1\\.1f-1ubuntu2\\.2[^\"]*amd64\\.deb' | head -n1)\ncurl -fsSL \"http://security.ubuntu.com/ubuntu/pool/main/o/openssl/$libssl\" -o \"$libssl\"\n$STD dpkg -i \"$libssl\"\n$STD apt 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/community-scripts/ProxmoxVE/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 | Co-Author: nickheyer\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://discopanel.app/ | Github: https://github.com/nickheyer/discopanel\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://docmost.com/ | Github: https://github.com/docmost/docmost\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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_x86_64.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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.drawio.com/ | Github: https://github.com/jgraph/drawio\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/community-scripts/ProxmoxVE/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-x64-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/community-scripts/ProxmoxVE/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-*_amd64-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/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://emby.media/ | Github: https://github.com/MediaBrowser/Emby.Releases\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/community-scripts/ProxmoxVE/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-amd64.deb\"\nDEB_FILE=\"/tmp/emqx-enterprise-${LATEST_VERSION}-debian12-amd64.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/community-scripts/ProxmoxVE/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_gh_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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://ersatztv.org/ | Github: https://github.com/ErsatzTV/ErsatzTV\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-x64.tar.gz\"\nfetch_and_deploy_gh_release \"ersatztv-ffmpeg\" \"ErsatzTV/ErsatzTV-ffmpeg\" \"prebuild\" \"latest\" \"/opt/ErsatzTV-ffmpeg\" \"*-linux64-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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://fileflows.com/\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\nmsg_info \"Installing Dependencies\"\n$STD apt install -y \\\n  ffmpeg \\\n  imagemagick\nmsg_ok \"Installed Dependencies\"\n\nsetup_hwaccel\n\nmsg_info \"Installing ASP.NET Core Runtime\"\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$STD apt install -y aspnetcore-runtime-8.0\nmsg_ok \"Installed ASP.NET Core Runtime\"\n\nfetch_and_deploy_from_url \"https://fileflows.com/downloads/zip\" \"/opt/fileflows\"\n\nmsg_info \"Setup FileFlows\"\n$STD ln -svf /usr/bin/ffmpeg /usr/local/bin/ffmpeg\n$STD ln -svf /usr/bin/ffprobe /usr/local/bin/ffprobe\ncd /opt/fileflows/Server\ndotnet FileFlows.Server.dll --systemd install --root true\nsystemctl enable -q --now fileflows\nmsg_ok \"Setup FileFlows\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://firefly-iii.org/ | Github: https://github.com/firefly-iii/firefly-iii\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/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 \\\n  apt-transport-https \\\n  xvfb\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\nrm /etc/apt/sources.list.d/google-chrome.list\nmsg_ok \"Installed Chrome\"\n\nfetch_and_deploy_gh_release \"flaresolverr\" \"FlareSolverr/FlareSolverr\" \"prebuild\" \"latest\" \"/opt/flaresolverr\" \"flaresolverr_linux_x64.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=/opt/flaresolverr/flaresolverr\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://flowiseai.com/ | Github: https://github.com/FlowiseAI/Flowise\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nNODE_VERSION=\"20\" setup_nodejs\n\nmsg_info \"Installing FlowiseAI (Patience)\"\n$STD npm install -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=npx 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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/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-amd64\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 community-scripts ORG\n# Authors: MickLesk (CanbiZ) | Co-Authors: remz1337\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://frigate.video/ | Github: https://github.com/blakeblackshear/frigate\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\nexport TARGETARCH=\"amd64\"\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.0\" \"/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_amd64\"\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 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 \"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 \"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: /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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://fumadocs.vercel.app/ | Github: https://github.com/fuma-nama/fumadocs\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/community-scripts/ProxmoxVE/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}/x86_64-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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://ghost.org/ | Github: https://github.com/TryGhost/Ghost\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\" 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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://ghostfol.io/ | Github: https://github.com/ghostfolio/ghostfolio\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\"\nDB_NAME=ghostfolio\nDB_USER=ghostfolio\nDB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)\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$STD sudo -u postgres psql -c \"CREATE DATABASE $DB_NAME;\"\n$STD sudo -u postgres psql -c \"CREATE USER $DB_USER WITH ENCRYPTED 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 USER $DB_USER CREATEDB;\"\n$STD sudo -u postgres psql -d $DB_NAME -c \"GRANT ALL 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 ALL ON TABLES TO $DB_USER;\"\n$STD sudo -u postgres psql -d $DB_NAME -c \"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $DB_USER;\"\n{\n  echo \"Ghostfolio Credentials\"\n  echo \"Database User: $DB_USER\"\n  echo \"Database Password: $DB_PASS\"\n  echo \"Database Name: $DB_NAME\"\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://$DB_USER:$DB_PASS@localhost:5432/$DB_NAME?connect_timeout=300&sslmode=prefer\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://about.gitea.com/ | Github: https://github.com/go-gitea/gitea\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-amd64\"\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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\nmsg_info \"Installing Dependencies\"\n$STD apt install -y ffmpeg\nmsg_ok \"Installed Dependencies\"\n\nUSE_ORIGINAL_FILENAME=\"true\" fetch_and_deploy_gh_release \"go2rtc\" \"AlexxIT/go2rtc\" \"singlefile\" \"latest\" \"/opt/go2rtc\" \"go2rtc_linux_amd64\"\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_amd64\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/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/community-scripts/ProxmoxVE/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_amd64.zip\"\n\nmsg_info \"Configuring Gokapi\"\nmkdir -p /opt/gokapi/{data,config}\nchmod +x /opt/gokapi/gokapi-linux_amd64\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_amd64\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://gotify.net/ | Github: https://github.com/gotify/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 \"gotify\" \"gotify/server\" \"prebuild\" \"latest\" \"/opt/gotify\" \"gotify-linux-amd64.zip\"\nchmod +x /opt/gotify/gotify-linux-amd64\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-amd64\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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.0\" 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\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/community-scripts/ProxmoxVE/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 install:ee\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://grocy.info/ | Github: https://github.com/grocy/grocy\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://healthchecks.io/ | Github: https://github.com/healthchecks/healthchecks\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@helper-scripts.local\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://heimdall.site/ | Github: https://github.com/linuxserver/Heimdall\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/community-scripts/ProxmoxVE/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-x86_64\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.hivemq.com/ | Github: https://github.com/hivemq/hivemq-community-edition\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/community-scripts/ProxmoxVE/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-amd64.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\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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_x86_64.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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://gethomepage.dev/ | Github: https://github.com/gethomepage/homepage\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\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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\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\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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://immich.app | Github: https://github.com/immich-app/immich\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nif [ -d /dev/dri ]; 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) Intel OpenVINO (requires GPU passthrough)\"\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    msg_info \"Installing OpenVINO dependencies\"\n    touch ~/.openvino\n    $STD apt install -y --no-install-recommends patchelf\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 OpenVINO dependencies\"\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=amd64] 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_*_amd64.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\"\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\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\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.5.6\" \"$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\"\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\" && chown -R immich:immich \"$INSTALL_DIR\"\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  $STD uv add --no-sync --optional openvino onnxruntime-openvino==1.24.1 --active -n -p \"${ML_PYTHON}\" --managed-python\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  patchelf --clear-execstack \"${VIRTUAL_ENV}/lib/python3.13/site-packages/onnxruntime/capi/onnxruntime_pybind11_state.cpython-313-x86_64-linux-gnu.so\"\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\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}\nEnvironmentFile=${INSTALL_DIR}/.env\nExecStart=/usr/bin/node ${APP_DIR}/dist/main\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 \"$INSTALL_DIR\" /var/log/immich\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\n$STD apt remove -y dotnet-sdk-8.0\n$STD apt autoremove -y\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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_amd64.deb\" \"${HOME}/chronograf_1.10.8_amd64.deb\"\n  $STD dpkg -i \"${HOME}/chronograf_1.10.8_amd64.deb\"\n  rm -rf \"${HOME}/chronograf_1.10.8_amd64.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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.inspircd.org/ | Github: https://github.com/inspircd/inspircd\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=\"helper-scripts.com\">\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\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/community-scripts/ProxmoxVE/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 \"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/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://invoiceninja.com/ | Github: https://github.com/invoiceninja/invoiceninja\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.iobroker.net/#en/intro | Github: https://github.com/ioBroker/ioBroker.js-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 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=\"22\" 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/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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.LinuxAMDx64.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/community-scripts/ProxmoxVE/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 community-scripts ORG\n# Author: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/x86_64-linux-gnu/libjemalloc.so.2 /usr/lib/libjemalloc.so\nfi\nmsg_ok \"Installed Dependencies\"\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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://joplinapp.org/ | Github: https://github.com/laurent22/joplin\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/community-scripts/ProxmoxVE/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 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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://karakeep.app/ | Github: https://github.com/karakeep-app/karakeep\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-x86_64\"\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/community-scripts/ProxmoxVE/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_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)\nif [[ -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\nelse\n  KASM_VERSION=$(echo \"$KASM_URL\" | sed -E 's/.*kasm_release_([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/')\nfi\n\nif [[ -z \"$KASM_URL\" ]] || [[ -z \"$KASM_VERSION\" ]]; 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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.kavitareader.com/ | Github: https://github.com/Kareadita/Kavita\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-x64.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\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/community-scripts/ProxmoxVE/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-amd64\"\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/community-scripts/ProxmoxVE/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=\"20\" 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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.kimai.org/ | Github: https://github.com/kimai/kimai\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@helper-scripts.com ROLE_SUPER_ADMIN\n\nexpect \"Please enter the password:\"\nsend \"helper-scripts.com\\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/community-scripts/ProxmoxVE/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\nmkdir -p /nltk_data\n$STD uv run python -m nltk.downloader -d /nltk_data averaged_perceptron_tagger_eng\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://koel.dev/ | Github: https://github.com/koel/koel\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://koillection.github.io/ | Github: https://github.com/benjaminjonard/koillection\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/community-scripts/ProxmoxVE/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 -p \"${TAB3}Enter your TMDb API key: \" TMDBKEY\nread -p \"${TAB3}Enter your Plex URL: \" PLEXURL\nread -p \"${TAB3}Enter your Plex token: \" PLEXTOKEN\nsed -i -e \"s#url: http://192.168.1.12:32400#url: $PLEXURL #g\" /opt/kometa/config/config.yml\nsed -i -e \"s/token: ####################/token: $PLEXTOKEN/g\" /opt/kometa/config/config.yml\nsed -i -e \"s/apikey: ################################/apikey: $TMDBKEY/g\" /opt/kometa/config/config.yml\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\nsystemctl enable -q --now kometa\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://komga.org/ | Github: https://github.com/gotson/komga\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/x86_64-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/community-scripts/ProxmoxVE/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-amd64.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\"]'\nmsg_ok \"Configured 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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://leantime.io | Github: https://github.com/Leantime/leantime\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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.librenms.org/ | Github: https://github.com/librenms/librenms\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/community-scripts/ProxmoxVE/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-x86_64-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/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://lidarr.audio/ | Github: https://github.com/Lidarr/Lidarr\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\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"lidarr\" \"Lidarr/Lidarr\" \"prebuild\" \"latest\" \"/opt/Lidarr\" \"Lidarr.master*linux-core-x64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://linkding.link/ | Github: https://github.com/sissbruecker/linkding\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\"\n\nmsg_info \"Building Frontend\"\ncd /opt/linkding\n$STD npm ci\n$STD npm run build\nln -sf /usr/lib/x86_64-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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://linkstack.org/ | Github: https://github.com/linkstackorg/linkstack\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://linkwarden.app/ | Github: https://github.com/linkwarden/linkwarden\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://listmonk.app/ | Github: https://github.com/knadh/listmonk\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_amd64.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/litellm-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: stout01\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/BerriAI/litellm\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\nmsg_ok \"Installed Dependencies\"\n\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"litellm_db\" PG_DB_USER=\"litellm\" setup_postgresql_db\nPYTHON_VERSION=\"3.13\" USE_UVX=\"YES\" setup_uv\n\nmsg_info \"Setting up Virtual Environment\"\nmkdir -p /opt/litellm\ncd /opt/litellm\n$STD uv venv --clear /opt/litellm/.venv\n$STD /opt/litellm/.venv/bin/python -m ensurepip --upgrade\n$STD /opt/litellm/.venv/bin/python -m pip install --upgrade pip\n$STD /opt/litellm/.venv/bin/python -m pip install litellm[proxy] prisma\nmsg_ok \"Installed LiteLLM\"\n\nmsg_info \"Configuring LiteLLM\"\nmkdir -p /opt\ncat <<EOF >/opt/litellm/litellm.yaml\ngeneral_settings:\n  master_key: sk-1234\n  database_url: postgresql://$PG_DB_USER:$PG_DB_PASS@127.0.0.1:5432/$PG_DB_NAME\n  store_model_in_db: true\nEOF\nuv --directory=/opt/litellm run litellm --config /opt/litellm/litellm.yaml --use_prisma_db_push --skip_server_startup\nmsg_ok \"Configured LiteLLM\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >/etc/systemd/system/litellm.service\n[Unit]\nDescription=LiteLLM\n\n[Service]\nType=simple\nExecStart=uv --directory=/opt/litellm run litellm --config /opt/litellm/litellm.yaml\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now litellm\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://lubelogger.com/ | Github: https://github.com/hargata/lubelog\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/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/community-scripts/ProxmoxVE/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[^\"]*amd64\\.deb(?=\"[^>]*>)' | head -n 1)\nRELEASE=$(echo \"$DEB_URL\" | grep -oP 'lyrionmusicserver_\\K[0-9.]+(?=_amd64\\.deb)')\nDEB_FILE=\"/tmp/lyrionmusicserver_${RELEASE}_amd64.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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://mafl.hywax.space/ | Github: https://github.com/hywax/mafl\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://magicmirror.builders/ | Github: https://github.com/MagicMirrorOrg/MagicMirror\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/community-scripts/ProxmoxVE/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\"\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  dotnet-sdk-10.0 \\\n  aspnetcore-runtime-8.0\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://mealie.io | Github: https://github.com/mealie-recipes/mealie\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\" \"latest\" \"/opt/mealie\"\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\n$STD sed -i \"s|https://github.com/mealie-recipes/mealie/commit/|https://github.com/mealie-recipes/mealie/releases/tag/|g\" /opt/mealie/frontend/pages/admin/site-settings.vue\n$STD sed -i \"s|value: data.buildId,|value: \\\"v${MEALIE_VERSION}\\\",|g\" /opt/mealie/frontend/pages/admin/site-settings.vue\n$STD sed -i \"s|value: data.production ? i18n.t(\\\"about.production\\\") : i18n.t(\\\"about.development\\\"),|value: \\\"bare-metal\\\",|g\" /opt/mealie/frontend/pages/admin/site-settings.vue\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\nmsg_info \"Downloading NLTK Data\"\nmkdir -p /nltk_data/\ncd /opt/mealie\n$STD uv run python -m nltk.downloader -d /nltk_data averaged_perceptron_tagger_eng\nmsg_ok \"Downloaded 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/community-scripts/ProxmoxVE/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\nsetup_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/community-scripts/ProxmoxVE/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_amd64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.meilisearch.com/ | Github: https://github.com/meilisearch/meilisearch\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/community-scripts/ProxmoxVE/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\nfetch_and_deploy_gh_release \"memos\" \"usememos/memos\" \"prebuild\" \"latest\" \"/opt/memos\" \"memos*linux_amd64.tar.gz\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://meshcentral.com/ | Github: https://github.com/Ylianst/MeshCentral\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/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: amd64\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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://miniflux.app/ | Github: https://github.com/miniflux/v2\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/community-scripts/ProxmoxVE/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-amd64/minio\"\nelse\n  RELEASE=\"$FEATURE_RICH_VERSION\"\n  DOWNLOAD_URL=\"https://dl.min.io/server/minio/release/linux-amd64/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/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/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.monicahq.com/ | Github: https://github.com/monicahq/monica\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@helper-scripts.com --password=helper-scripts.com --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/community-scripts/ProxmoxVE/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\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://ipcheck.ing/ | Github: https://github.com/jason5ng32/MyIP\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 \"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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster) | Co-Author: CrazyWolf13\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://n8n.io/ | Github: https://github.com/n8n-io/n8n\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/navidrome-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/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/community-scripts/ProxmoxVE/main/tools/addon/filebrowser.sh)\"\nfi\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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://netboxlabs.com/ | Github: https://github.com/netbox-community/netbox\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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://nginxui.com | Github: https://github.com/0xJacky/nginx-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 \\\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-64.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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://nginxproxymanager.com/ | Github: https://github.com/NginxProxyManager/nginx-proxy-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 update\n$STD apt -y install \\\n  ca-certificates \\\n  apache2-utils \\\n  logrotate \\\n  build-essential \\\n  git\nmsg_ok \"Installed Dependencies\"\n\nmsg_info \"Installing Python Dependencies\"\n$STD apt install -y \\\n  python3 \\\n  python3-dev \\\n  python3-pip \\\n  python3-venv \\\n  python3-cffi\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\nmsg_info \"Installing Openresty\"\ncurl -fsSL \"https://openresty.org/package/pubkey.gpg\" | gpg --dearmor -o /etc/apt/trusted.gpg.d/openresty.gpg\ncat <<'EOF' >/etc/apt/sources.list.d/openresty.sources\nTypes: deb\nURIs: http://openresty.org/package/debian/\nSuites: bookworm\nComponents: openresty\nSigned-By: /etc/apt/trusted.gpg.d/openresty.gpg\nEOF\n$STD apt update\n$STD apt -y install openresty\nmsg_ok \"Installed Openresty\"\n\nNODE_VERSION=\"22\" NODE_MODULE=\"yarn\" setup_nodejs\n\nRELEASE=$(curl -fsSL https://api.github.com/repos/NginxProxyManager/nginx-proxy-manager/releases/latest |\n  grep \"tag_name\" |\n  awk '{print substr($2, 3, length($2)-4) }')\n\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 \"s|\\\"version\\\": \\\"2.0.0\\\"|\\\"version\\\": \\\"$RELEASE\\\"|\" /opt/nginxproxymanager/backend/package.json\nsed -i \"s|\\\"version\\\": \\\"2.0.0\\\"|\\\"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\nsystemctl restart openresty\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.nocodb.com/ | Github: https://github.com/nocodb/nocodb\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\" \"latest\" \"/opt/nocodb/\" \"Noco-linux-x64\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://nodered.org/ | Github: https://github.com/node-red/node-red\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/community-scripts/ProxmoxVE/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 \"helper-scripts\\r\"\n}\nexpect \"Administrator email address\" {\n    send \"helper-scripts@local.com\\r\"\n}\nexpect \"Password\" {\n    send \"helper-scripts\\r\"\n}\nexpect \"Confirm Password\" {\n    send \"helper-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/community-scripts/ProxmoxVE/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\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-x86_64 -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=helper-scripts.com\\\"])\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/linux/nxwitness-server-[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+-linux_x64\\.deb\" | head -n 1)\ncurl -fsSL \"$DOWNLOAD_URL\" -o \"\"nxwitness-server-$RELEASE-linux_x64.deb\"\"\nexport DEBIAN_FRONTEND=noninteractive\n$STD dpkg -i nxwitness-server-$RELEASE-linux_x64.deb\nrm -f /tmp/nxwitness-server-$RELEASE-linux_x64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 \"Setting up Intel® Repositories\"\nmkdir -p /usr/share/keyrings\ncurl -fsSL https://repositories.intel.com/gpu/intel-graphics.key | gpg --dearmor -o /usr/share/keyrings/intel-graphics.gpg\ncat <<EOF >/etc/apt/sources.list.d/intel-gpu.sources\nTypes: deb\nURIs: https://repositories.intel.com/gpu/ubuntu\nSuites: jammy\nComponents: client\nArchitectures: amd64 i386\nSigned-By: /usr/share/keyrings/intel-graphics.gpg\nEOF\ncurl -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\ncat <<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\nmsg_ok \"Set up Intel® Repositories\"\n\nmsg_info \"Installing Intel® Level Zero\"\n# Debian 13+ has newer Level Zero packages in system repos that conflict with Intel repo packages\nif 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  }\nelse\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  }\nfi\nmsg_ok \"Installed Intel® Level Zero\"\n\nmsg_info \"Installing Intel® oneAPI Base Toolkit (Patience)\"\n$STD apt install -y --no-install-recommends intel-basekit-2024.1\nmsg_ok \"Installed Intel® oneAPI Base Toolkit\"\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}')\nOLLAMA_INSTALL_DIR=\"/usr/local/lib/ollama\"\nBINDIR=\"/usr/local/bin\"\nmkdir -p $OLLAMA_INSTALL_DIR\nOLLAMA_URL=\"https://github.com/ollama/ollama/releases/download/${RELEASE}/ollama-linux-amd64.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/community-scripts/ProxmoxVE/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+deb11u5_amd64.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\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://ombi.io/ | Github: https://github.com/Ombi-app/Ombi\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-x64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://openarchiver.com/ | Github: https://github.com/LogicLabs-OU/OpenArchiver\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://opencloud.eu | Github: https://github.com/opencloud-eu/opencloud\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.2.0\" \"/usr/bin\" \"opencloud-*-linux-amd64\"\nmv /usr/bin/OpenCloud /usr/bin/opencloud\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://opengist.io/ | Github: https://github.com/thomiceli/opengist\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-amd64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/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: amd64 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-amd64.tar.zst\n  tar --zstd -C /usr -xf ollama-linux-amd64.tar.zst\n  rm -rf ollama-linux-amd64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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_amd64.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/community-scripts/ProxmoxVE/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=\"22\" 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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://owncast.online/ | Github: https://github.com/owncast/owncast\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-64bit.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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://pairdrop.net/ | Github: https://github.com/schlagmichdoch/PairDrop\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://pangolin.net/ | Github: https://github.com/fosrl/pangolin\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_amd64\"\nfetch_and_deploy_gh_release \"traefik\" \"traefik/traefik\" \"prebuild\" \"latest\" \"/usr/bin\" \"traefik_v*_linux_amd64.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\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://docs.paperless-ngx.com/ | Github: https://github.com/paperless-ngx/paperless-ngx\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\nmsg_info \"Installing Natural Language Toolkit (Patience)\"\ncd /opt/paperless\n$STD uv run python -m nltk.downloader -d /usr/share/nltk_data snowball_data\n$STD uv run python -m nltk.downloader -d /usr/share/nltk_data stopwords\n$STD uv run python -m nltk.downloader -d /usr/share/nltk_data punkt_tab ||\n  $STD uv run python -m nltk.downloader -d /usr/share/nltk_data punkt\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\nmsg_ok \"Installed Natural Language Toolkit\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://docs.part-db.de/ | Github: https://github.com/Part-DB/Part-DB-server\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@latest\" setup_nodejs\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\nmsg_info \"Installing Part-DB (Patience)\"\ncd /opt\nRELEASE=$(get_latest_github_release \"Part-DB/Part-DB-server\")\ncurl -fsSL \"https://github.com/Part-DB/Part-DB-server/archive/refs/tags/v${RELEASE}.zip\" -o \"/opt/v${RELEASE}.zip\"\n$STD unzip \"v${RELEASE}.zip\"\nmv /opt/Part-DB-server-${RELEASE}/ /opt/partdb\n\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\n\nexport COMPOSER_ALLOW_SUPERUSER=1\n$STD composer install --no-dev -o --no-interaction\n$STD yarn install\n$STD yarn build\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\nrm -rf \"/opt/v${RELEASE}.zip\"\necho \"${RELEASE}\" >~/.partdb\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 \\\n  build-essential \\\n  nginx \\\n  redis-server\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"24\" setup_nodejs\nPG_VERSION=\"17\" setup_postgresql\nPG_DB_NAME=\"patchmon_db\" PG_DB_USER=\"patchmon_usr\" setup_postgresql_db\n\nfetch_and_deploy_gh_release \"PatchMon\" \"PatchMon/PatchMon\" \"tarball\" \"v1.4.2\" \"/opt/patchmon\"\n\nmsg_info \"Configuring PatchMon\"\nVERSION=$(get_latest_github_release \"PatchMon/PatchMon\")\nexport NODE_ENV=production\ncd /opt/patchmon\n$STD npm install --no-audit --no-fund --no-save --ignore-scripts\n\ncd /opt/patchmon/frontend\ncat <<EOF >./.env\nVITE_APP_NAME=PatchMon\nVITE_APP_VERSION=${VERSION}\nEOF\n$STD npm install --no-audit --no-fund --no-save --ignore-scripts --include=dev\n$STD npm run build\n\nJWT_SECRET=\"$(openssl rand -hex 64)\"\nmv /opt/patchmon/backend/env.example /opt/patchmon/backend/.env\nsed -i -e \"s|DATABASE_URL=.*|DATABASE_URL=\\\"postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME\\\"|\" \\\n  -e \"/JWT_SECRET/s/[=$].*/=$JWT_SECRET/\" \\\n  -e \"\\|CORS_ORIGIN|s|localhost|$LOCAL_IP|\" \\\n  -e \"/PORT=3001/aSERVER_PROTOCOL=http \\\\\n  SERVER_HOST=$LOCAL_IP \\\\\n  SERVER_PORT=3000\" \\\n  -e '/_ENV=production/aTRUST_PROXY=1' \\\n  -e '/REDIS_USER=.*/,+1d' /opt/patchmon/backend/.env\n\ncd /opt/patchmon/backend\n$STD npm run db:generate\n$STD npx prisma migrate deploy\nmsg_ok \"Configured PatchMon\"\n\nmsg_info \"Configuring Nginx\"\ncp /opt/patchmon/docker/nginx.conf.template /etc/nginx/sites-available/patchmon.conf\nsed -i -e 's|proxy_pass .*|proxy_pass http://127.0.0.1:3001;|' \\\n  -e '\\|try_files |i\\        root /opt/patchmon/frontend/dist;' \\\n  -e 's|alias.*|alias /opt/patchmon/frontend/dist/assets;|' \\\n  -e '\\|expires 1y|i\\        root /opt/patchmon/frontend/dist;' /etc/nginx/sites-available/patchmon.conf\nln -sf /etc/nginx/sites-available/patchmon.conf /etc/nginx/sites-enabled/\nrm -f /etc/nginx/sites-enabled/default\n$STD nginx -t\nsystemctl restart nginx\nmsg_ok \"Configured Nginx\"\n\nmsg_info \"Creating service\"\ncat <<EOF >/etc/systemd/system/patchmon-server.service\n[Unit]\nDescription=PatchMon Service\nAfter=network.target postgresql.service\n\n[Service]\nType=simple\nWorkingDirectory=/opt/patchmon/backend\nExecStart=/usr/bin/npm run start\nRestart=always\nRestartSec=10\nEnvironment=NODE_ENV=production\nEnvironment=PATH=/usr/bin:/usr/local/bin\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.paymenter.org | Github: https://github.com/paymenter/paymenter\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\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/community-scripts/ProxmoxVE/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\ncat <<EOF >/etc/peanut/settings.yml\nWEB_HOST: 0.0.0.0\nWEB_PORT: 8080\nNUT_HOST: 0.0.0.0\nNUT_PORT: 3493\nEOF\nln -sf /etc/peanut/settings.yml /opt/peanut/.next/standalone/config/settings.yml\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\nEnvironment=\"NODE_ENV=production\"\n#Environment=\"NUT_HOST=localhost\"\n#Environment=\"NUT_PORT=3493\"\n#Environment=\"WEB_HOST=0.0.0.0\"\n#Environment=\"WEB_PORT=8080\"\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/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/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_amd64\"\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/pf2etools-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: TheRealVira\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://pf2etools.com/ | Github: https://github.com/Pf2eToolsOrg/Pf2eTools\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  ca-certificates \\\n  git\nmsg_ok \"Installed Dependencies\"\n\nNODE_VERSION=\"22\" setup_nodejs\nfetch_and_deploy_gh_release \"pf2etools\" \"Pf2eToolsOrg/Pf2eTools\" \"tarball\" \"latest\" \"/opt/Pf2eTools\"\n\nmsg_info \"Configuring Pf2eTools\"\ncd /opt/Pf2eTools\n$STD npm install\n$STD npm run build\nmsg_ok \"Configured Pf2eTools\"\n\nmsg_info \"Creating Service\"\ncat <<EOF >>/etc/apache2/apache2.conf\n<Location /server-status>\n    SetHandler server-status\n    Order deny,allow\n    Allow from all\n</Location>\nEOF\nrm -rf /var/www/html\nln -s \"/opt/Pf2eTools\" /var/www/html\nchown -R www-data: \"/opt/Pf2eTools\"\nchmod -R 755 \"/opt/Pf2eTools\"\nmsg_ok \"Created Service\"\ncleanup_lxc\nmotd_ssh\ncustomize\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.photoprism.app/ | Github: https://github.com/photoprism/photoprism\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-amd64.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-amd64-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://Helper-Scripts.com'\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/community-scripts/ProxmoxVE/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\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/x86/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://plant-it.org/ | Github: https://github.com/MDeLuise/plant-it\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 community-scripts ORG\n# Author: tteck (tteckster) | MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://pocketbase.io/ | Github: https://github.com/pocketbase/pocketbase\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_amd64.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/community-scripts/ProxmoxVE/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-amd64\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://privatebin.info/ | Github: https://github.com/PrivateBin/PrivateBin\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.projectsend.org/ | Github: https://github.com/projectsend/projectsend\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://prometheus.io/ | Github: https://github.com/prometheus/alertmanager\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-amd64.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/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://prometheus.io/ | Github: https://github.com/prometheus/prometheus\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-amd64.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\nExecReload=/bin/kill -HUP $MAINPID\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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://prowlarr.com/ | Github: https://github.com/Prowlarr/Prowlarr\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\nfetch_and_deploy_gh_release \"prowlarr\" \"Prowlarr/Prowlarr\" \"prebuild\" \"latest\" \"/opt/Prowlarr\" \"Prowlarr.master*linux-core-x64.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/community-scripts/ProxmoxVE/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 Proxmox Backup Server\"\ncurl -fsSL \"https://enterprise.proxmox.com/debian/proxmox-release-trixie.gpg\" -o \"/etc/apt/trusted.gpg.d/proxmox-release-trixie.gpg\"\ncat <<EOF >>/etc/apt/sources.list\ndeb http://download.proxmox.com/debian/pbs trixie pbs-no-subscription\nEOF\n$STD apt update\nexport DEBIAN_FRONTEND=noninteractive\nexport IFUPDOWN2_NO_IFRELOAD=1\n$STD apt install -y proxmox-backup-server\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/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_amd64\"\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/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/community-scripts/ProxmoxVE-Local\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\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.qbittorrent.org/ | Github: https://github.com/qbittorrent/qBittorrent\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\" \"x86_64-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/community-scripts/ProxmoxVE/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\" \"binary\" \"latest\" \"/usr/bin/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/community-scripts/ProxmoxVE/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_x86_64.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/community-scripts/ProxmoxVE/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 apt-transport-https\nmsg_ok \"Installed Dependencies\"\n\nsetup_deb822_repo \\\n  \"rabbitmq\" \\\n  \"https://keys.openpgp.org/vks/v1/by-fingerprint/0A9AF2115F4687BD29803A206B73A36E6026DFCA\" \\\n  \"https://deb1.rabbitmq.com/rabbitmq-server/debian/trixie\" \\\n  \"trixie\"\n\nmsg_info \"Setting up RabbitMQ\"\n$STD apt install -y \\\n  erlang-base erlang-asn1 erlang-crypto erlang-eldap erlang-ftp \\\n  erlang-inets erlang-mnesia erlang-os-mon erlang-parsetools \\\n  erlang-public-key erlang-runtime-tools erlang-snmp erlang-ssl \\\n  erlang-syntax-tools erlang-tftp erlang-tools erlang-xmerl\n$STD apt install -y --fix-missing rabbitmq-server\nmsg_ok \"Setup RabbitMQ \"\n\nmsg_info \"Starting 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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://radarr.video/ | Github: https://github.com/Radarr/Radarr\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\nfetch_and_deploy_gh_release \"Radarr\" \"Radarr/Radarr\" \"prebuild\" \"latest\" \"/opt/Radarr\" \"Radarr.master*linux-core-x64.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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://radicale.org/ | Github: https://github.com/Kozea/Radicale\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/community-scripts/ProxmoxVE/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-amd64.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/community-scripts/ProxmoxVE/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\"\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$STD apt install -y aspnetcore-runtime-10.0\nmsg_ok \"Installed Dependencies\"\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 | Rewrite: MickLesk (CanbiZ)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://rxresume.org | Github: https://github.com/amruthpillai/reactive-resume\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\"\n$STD apt install -y chromium\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 \"Configuring Reactive Resume\"\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/community-scripts/ProxmoxVE/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\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=x64' -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/community-scripts/ProxmoxVE/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-amd64\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://recyclarr.dev/wiki/ | Github: https://github.com/recyclarr/recyclarr\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 \"recyclarr\" \"recyclarr/recyclarr\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"recyclarr-linux-x64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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  rabbitmq-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\nmsg_info \"Configuring RabbitMQ\"\nRABBIT_USER=\"reitti\"\nRABBIT_PASS=\"$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)\"\nRABBIT_VHOST=\"/\"\n$STD rabbitmqctl add_user \"$RABBIT_USER\" \"$RABBIT_PASS\"\n$STD rabbitmqctl add_vhost \"$RABBIT_VHOST\"\n$STD rabbitmqctl set_permissions -p \"$RABBIT_VHOST\" \"$RABBIT_USER\" \".*\" \".*\" \".*\"\n$STD rabbitmqctl set_user_tags \"$RABBIT_USER\" administrator\n{\n  echo \"\"\n  echo \"Reitti Credentials\"\n  echo \"RabbitMQ User: $RABBIT_USER\"\n  echo \"RabbitMQ Password: $RABBIT_PASS\"\n} >>~/reitti.creds\nmsg_ok \"Configured RabbitMQ\"\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\nUSE_ORIGINAL_FILENAME=\"true\" fetch_and_deploy_gh_release \"photon\" \"komoot/photon\" \"singlefile\" \"latest\" \"/opt/photon\" \"photon-*.jar\"\nmv /opt/photon/photon-*.jar /opt/photon/photon.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_info \"Installed Nginx Tile Cache\"\n\nmsg_info \"Creating Reitti Configuration-File\"\nmkdir -p /opt/reitti/data\ncat <<EOF >/opt/reitti/application.properties\n# Reitti Server Base URI\nreitti.server.advertise-uri=http://127.0.0.1:8080\n\n# PostgreSQL Database Connection\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.driver-class-name=org.postgresql.Driver\n\n# Flyway Database Migrations\nspring.flyway.enabled=true\nspring.flyway.locations=classpath:db/migration\nspring.flyway.baseline-on-migrate=true\n\n# RabbitMQ (Message Queue)\nspring.rabbitmq.host=127.0.0.1\nspring.rabbitmq.port=5672\nspring.rabbitmq.username=$RABBIT_USER\nspring.rabbitmq.password=$RABBIT_PASS\n\n# Redis (Cache)\nspring.data.redis.host=127.0.0.1\nspring.data.redis.port=6379\n\n# Server Port\nserver.port=8080\n\n# Optional: Logging & Performance\nlogging.level.root=INFO\nspring.jpa.hibernate.ddl-auto=none\nspring.datasource.hikari.maximum-pool-size=10\n\n# OIDC / Security Settings\nreitti.security.oidc.registration.enabled=false\n\n# Photon (Geocoding)\nPHOTON_BASE_URL=http://127.0.0.1:2322\nPROCESSING_WAIT_TIME=15\nPROCESSING_BATCH_SIZE=1000\nPROCESSING_WORKERS_PER_QUEUE=4-16\n\n# Disable potentially dangerous features unless needed\nDANGEROUS_LIFE=false\n\n# Tiles Cache\nreitti.ui.tiles.cache.url=http://127.0.0.1\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 rabbitmq-server.service photon.service\nWants=postgresql.service redis-server.service rabbitmq-server.service photon.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\ncat <<EOF >/etc/systemd/system/photon.service\n[Unit]\nDescription=Photon Geocoding Service (Germany, OpenSearch)\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=/opt/photon\nExecStart=/usr/bin/java -Xmx4g -jar photon.jar \\\n  -data-dir /opt/photon \\\n  -listen-port 2322 \\\n  -listen-ip 0.0.0.0 \\\n  -cors-any\nRestart=on-failure\nTimeoutStopSec=20\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsystemctl enable -q --now photon\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 '25s/localhost/0.0.0.0/g' /opt/revealjs/gulpfile.js\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://romm.app | Github: https://github.com/rommapp/romm\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\"\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\nln -sfn /var/lib/romm/resources /opt/romm/frontend/dist/assets/romm/resources\nln -sfn /var/lib/romm/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\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/community-scripts/ProxmoxVE/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\" \"lejianwen/rustdesk-server\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-server-hbbr*amd64.deb\"\nfetch_and_deploy_gh_release \"rustdesk-hbbs\" \"lejianwen/rustdesk-server\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-server-hbbs*amd64.deb\"\nfetch_and_deploy_gh_release \"rustdesk-utils\" \"lejianwen/rustdesk-server\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-server-utils*amd64.deb\"\nfetch_and_deploy_gh_release \"rustdesk-api\" \"lejianwen/rustdesk-api\" \"binary\" \"latest\" \"/opt/rustdesk\" \"rustdesk-api-server*amd64.deb\"\nsystemctl enable -q --now rustdesk-hbbr\nsystemctl enable -q --now rustdesk-hbbs\nsystemctl enable -q --now rustdesk-api\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/community-scripts/ProxmoxVE/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\" \"*x86_64-unknown-linux-gnu.tar.gz\"\nfetch_and_deploy_gh_release \"rustypaste-cli\" \"orhun/rustypaste-cli\" \"prebuild\" \"latest\" \"/usr/local/bin\" \"*x86_64-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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://sabnzbd.org/ | Github: https://github.com/sabnzbd/sabnzbd\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-amd64.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\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/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://docs.seerr.dev/ | Github: https://github.com/seerr-team/seerr\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://semaphoreui.com/ | Github: https://github.com/semaphoreui/semaphore\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_amd64.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@helper-scripts.com --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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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=\"22\" setup_nodejs\nPYTHON_VERSION=\"3.12\" 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\"\ncd /opt/shelfmark\n$STD uv venv --clear ./venv\n$STD source ./venv/bin/activate\n$STD uv pip install -r ./requirements-base.txt\n[[ \"$DEPLOYMENT_TYPE\" == \"1\" ]] && $STD uv pip install -r ./requirements-shelfmark.txt\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/community-scripts/ProxmoxVE/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 777 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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://signoz.io/ | Github: https://github.com/SigNoz/signoz\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: amd64\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_amd64.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_amd64.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_amd64.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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://silverbullet.md | Github: https://github.com/silverbulletmd/silverbullet\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-x86_64.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/community-scripts/ProxmoxVE/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-x64.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...\"\n  exit 1\nelse\n  source /opt/soularr/venv/bin/activate\n  uv run python3 -u /opt/soularr/soularr.py --config-dir /opt/soularr\nfi\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://snipeitapp.com/ | Github: https://github.com/grokability/snipe-it\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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/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\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-x86-64/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-x86-64/sonar.sh start\nExecStop=/opt/sonarqube/bin/linux-x86-64/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://sonarr.tv/ | Github: https://github.com/Sonarr/Sonarr\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\nfetch_and_deploy_gh_release \"Sonarr\" \"Sonarr/Sonarr\" \"prebuild\" \"latest\" \"/opt/Sonarr\" \"Sonarr.main.*.linux-x64.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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/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|^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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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-x64-*.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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.stirlingpdf.com/ | Github: https://github.com/Stirling-Tools/Stirling-PDF\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/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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_amd64\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://sure.am | Github: https://github.com/we-promise/sure\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\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://tandoor.dev/ | Github: https://github.com/TandoorRecipes/recipes\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\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/community-scripts/ProxmoxVE/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 777 /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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://tautulli.com/ | Github: https://github.com/Tautulli/Tautulli\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/community-scripts/ProxmoxVE/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_with_retry \"https://f000.backblazeb2.com/file/tdarrs/versions.json\" \"-\" | grep -oP '(?<=\"Tdarr_Updater\": \")[^\"]+' | grep linux_x64 | head -n 1)\ncurl_with_retry \"$RELEASE\" \"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/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/community-scripts/ProxmoxVE/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_amd64-\\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_amd64-${RELEASE}.tar.bz2\" -o ts3server.tar.bz2\ntar -xf ./ts3server.tar.bz2\nmv teamspeak3-server_linux_amd64/ /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/community-scripts/ProxmoxVE/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 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-9.0\nmsg_ok \"Installed Dependencies\"\n\nRELEASE=$(curl -fsSL https://technitium.com/dns/ | grep -oP 'Version \\K[\\d.]+')\nmsg_info \"Installing Technitium DNS\"\nmkdir -p /opt/technitium/dns\ncurl -fsSL \"https://download.technitium.com/dns/DnsServerPortable.tar.gz\" -o /opt/DnsServerPortable.tar.gz\n$STD tar zxvf /opt/DnsServerPortable.tar.gz -C /opt/technitium/dns/\nrm -f /opt/DnsServerPortable.tar.gz\necho \"${RELEASE}\" >~/.technitium\nmsg_ok \"Installed Technitium DNS\"\n\nmsg_info \"Creating 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/community-scripts/ProxmoxVE/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  ca-certificates\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_gh_release \"teddycloud\" \"toniebox-reverse-engineering/teddycloud\" \"prebuild\" \"latest\" \"/opt/teddycloud\" \"teddycloud.amd64.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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/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\"\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\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://thelounge.chat/ | Github: https://github.com/thelounge/thelounge\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\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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_amd64\"\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/community-scripts/ProxmoxVE/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 \"Setting up AppRise\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.traccar.org/ | Github: https://github.com/traccar/traccar\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-64*.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/community-scripts/ProxmoxVE/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)\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://tracktor.bytedge.in | Github: https://github.com/javedh-dev/tracktor\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://traefik.io/ | Github: https://github.com/traefik/traefik\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_amd64.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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/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-x64.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/community-scripts/ProxmoxVE/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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://tududi.com/ | Github: https://github.com/chrisvel/tududi\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://tunarr.com/ | Github: https://github.com/chrisbenincasa/tunarr\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-x64.tar.gz\"\ncd /opt/tunarr\nmv tunarr* tunarr\nfetch_and_deploy_gh_release \"ersatztv-ffmpeg\" \"ErsatzTV/ErsatzTV-ffmpeg\" \"prebuild\" \"latest\" \"/opt/ErsatzTV-ffmpeg\" \"*-linux64-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/community-scripts/ProxmoxVE/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/typesense-install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tlissak\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://typesense.org/\n\nsource /dev/stdin <<<\"$FUNCTIONS_FILE_PATH\"\ncolor\nverb_ip6\ncatch_errors\nsetting_up_container\nnetwork_check\nupdate_os\n\nmsg_info \"Installing TypeSense\"\nRELEASE=$(curl -fsSL https://api.github.com/repos/typesense/typesense/releases/latest | grep \"tag_name\" | awk '{print substr($2, 3, length($2)-4) }')\ncd /opt\ncurl -fsSL \"https://dl.typesense.org/releases/${RELEASE}/typesense-server-${RELEASE}-amd64.deb\" -o \"/opt/typesense-server-${RELEASE}-amd64.deb\"\n$STD apt install -y /opt/typesense-server-${RELEASE}-amd64.deb\necho 'enable-cors = true' >>/etc/typesense/typesense-server.ini\nrm -rf /opt/typesense-server-${RELEASE}-amd64.deb\necho \"${RELEASE}\" >\"/opt/${APPLICATION}_version.txt\"\nmsg_ok \"Installed TypeSense\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.uhfapp.com/server | Github: https://github.com/swapplications/uhf-server-dist\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\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-x64-*.zip\"\nfetch_and_deploy_gh_release \"uhf-server\" \"swapplications/uhf-server-dist\" \"prebuild\" \"latest\" \"/opt/uhf-server\" \"UHF.Server-linux-x64-*.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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://umami.is/ | Github: https://github.com/umami-software/umami\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 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/community-scripts/ProxmoxVE/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-x64\"))\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/community-scripts/ProxmoxVE/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\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/community-scripts/ProxmoxVE/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_amd64.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/community-scripts/ProxmoxVE/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_amd64.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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://uptime.kuma.pet/ | Github: https://github.com/louislam/uptime-kuma\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://verdaccio.org/ | Github: https://github.com/verdaccio/verdaccio\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/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/community-scripts/ProxmoxVE/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-amd64-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-amd64-v[0-9.]+\\.tar\\.gz$')\nmsg_ok \"Got latest version of VictoriaMetrics\"\n\nfetch_and_deploy_gh_release \"victoriametrics\" \"VictoriaMetrics/VictoriaMetrics\" \"prebuild\" \"latest\" \"/opt/victoriametrics\" \"$victoriametrics_filename\"\nfetch_and_deploy_gh_release \"vmutils\" \"VictoriaMetrics/VictoriaMetrics\" \"prebuild\" \"latest\" \"/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-amd64-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-amd64-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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://vikunja.io/ | Github: https://github.com/go-vikunja/vikunja\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://wallabag.org/ | Github: https://github.com/wallabag/wallabag\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/community-scripts/ProxmoxVE/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\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://wanderer.to | Github: https://github.com/open-wanderer/wanderer\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\" \"binary\" \"latest\" \"/opt/wanderer/source/search\"\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 >/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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}_x86_64-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\"\n\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]\nWorkingDirectory=/opt/wastebin\nExecStart=/opt/wastebin/wastebin\nEnvironmentFile=/opt/wastebin-data/.env\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://wealthfolio.app/ | Github: https://github.com/afadil/wealthfolio\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=\"20\" NODE_MODULE=\"pnpm\" setup_nodejs\nfetch_and_deploy_gh_release \"wealthfolio\" \"afadil/wealthfolio\" \"tarball\" \"v3.0.3\"\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/community-scripts/ProxmoxVE/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  make \\\n  g++ \\\n  traceroute \\\n  xvfb \\\n  dbus \\\n  xorg \\\n  xvfb \\\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: amd64\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\" \"CrazyWolf13/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\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\nmsg_ok \"Installed Dependencies\"\n\nfetch_and_deploy_from_url \"https://whisparr.servarr.com/v1/update/nightly/updatefile?os=linux&runtime=netcore&arch=x64\" /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/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://js.wiki/ | Github: https://github.com/requarks/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\" 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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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_amd64.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/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/community-scripts/ProxmoxVE/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-amd64\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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  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/community-scripts/ProxmoxVE/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_amd64.deb\ndpkg -i ztncui_0.8.14_amd64.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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.zigbee2mqtt.io/ | Github: https://github.com/Koenkk/zigbee2mqtt\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://zitadel.com/ | Github: https://github.com/zitadel/zitadel\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\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-amd64.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": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://zoraxy.aroz.org/ | Github: https://github.com/tobychui/zoraxy\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_amd64\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://zotregistry.dev/ | Github: https://github.com/project-zot/zot\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-amd64\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://zwave-js.github.io/zwave-js-ui/#/ | Github: https://github.com/zwave-js/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-linux\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/alpine-install.func",
    "content": "# Copyright (c) 2021-2026 community-scripts ORG\n# Author: tteck (tteckster)\n# Co-Author: MickLesk\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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  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    ipv4_status=\"${GN}✔${CL} IPv4\"\n  else\n    ipv4_status=\"${RD}✖${CL} IPv4\"\n    read -r -p \"Internet NOT connected. 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  RESOLVEDIP=$(getent hosts github.com | awk '{ print $1 }')\n  if [[ -z \"$RESOLVEDIP\" ]]; then\n    msg_error \"Internet: ${ipv4_status}  DNS Failed\"\n  else\n    msg_ok \"Internet: ${ipv4_status}  DNS: ${BL}${RESOLVEDIP}${CL}\"\n  fi\n  set -e\n  trap 'error_handler $LINENO \"$BASH_COMMAND\"' ERR\n}\n\n# This function updates the Container OS by running apt-get update and upgrade\nupdate_os() {\n  msg_info \"Updating Container OS\"\n  $STD apk -U upgrade\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 ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/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 1\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 1\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 1\n    }\n  else\n    curl -fL# -o \"$out\" \"$url\" || {\n      msg_error \"Download failed: $url\"\n      return 1\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 1\n  }\n  need_tool curl jq || return 1\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 1\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 1\n  }\n  need_tool curl jq tar || return 1\n  [ \"$mode\" = \"prebuild\" ] || [ \"$mode\" = \"singlefile\" ] && need_tool unzip >/dev/null 2>&1 || true\n\n  tmpd=\"$(mktemp -d)\" || return 1\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 1\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 1\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 1\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 1\n    }\n    tar -xzf \"$tmpd/$filename\" -C \"$tmpd\" || {\n      msg_error \"tar extract failed\"\n      rm -rf \"$tmpd\"\n      return 1\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 1\n    }\n    ;;\n  prebuild)\n    [ -n \"$pattern\" ] || {\n      msg_error \"prebuild requires asset pattern\"\n      rm -rf \"$tmpd\"\n      return 1\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 1\n    }\n    filename=\"${url##*/}\"\n    download_with_progress \"$url\" \"$tmpd/$filename\" || {\n      rm -rf \"$tmpd\"\n      return 1\n    }\n    # unpack archive (Zip or tarball)\n    case \"$filename\" in\n    *.zip)\n      need_tool unzip || {\n        rm -rf \"$tmpd\"\n        return 1\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 1\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 1\n      }\n    else\n      (cd \"$tmpd/unp\" && tar -cf - .) | (cd \"$target\" && tar -xf -) || {\n        msg_error \"copy failed\"\n        rm -rf \"$tmpd\"\n        return 1\n      }\n    fi\n    ;;\n  singlefile)\n    [ -n \"$pattern\" ] || {\n      msg_error \"singlefile requires asset pattern\"\n      rm -rf \"$tmpd\"\n      return 1\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 1\n    }\n    filename=\"${url##*/}\"\n    download_with_progress \"$url\" \"$target/$app\" || {\n      rm -rf \"$tmpd\"\n      return 1\n    }\n    chmod +x \"$target/$app\"\n    ;;\n  *)\n    msg_error \"Unknown mode: $mode\"\n    rm -rf \"$tmpd\"\n    return 1\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 1\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 1\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 1\n  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 1\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 1\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 1\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 1\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 1\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 1\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 1\n  }\n  tar -xzf \"$tmpd/uv.tar.gz\" -C \"$tmpd\" || {\n    msg_error \"uv: extract failed\"\n    rm -rf \"$tmpd\"\n    return 1\n  }\n\n  # tar contains ./uv\n  if [ -x \"$tmpd/uv\" ]; then\n    install -m 0755 \"$tmpd/uv\" \"$UV_BIN\"\n  else\n    # fallback: in subfolder\n    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 1\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 1\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 1\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 1\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 1\n    }\n    msg_ok \"Go ready: $(go version 2>/dev/null)\"\n    return 0\n  fi\n\n  need_tool curl tar || return 1\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 1\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 1\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 1\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 1\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 1\n  curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php || {\n    msg_error \"composer installer download failed\"\n    return 1\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 1\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# ------------------------------------------------------------------------------\njson_escape() {\n  # Escape a string for safe JSON embedding using awk (handles any input size).\n  # Pipeline: strip ANSI → remove control chars → escape \\ \" TAB → join lines with \\n\n  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    | awk '\n        BEGIN { ORS = \"\" }\n        {\n          gsub(/\\\\/, \"\\\\\\\\\")   # backslash → \\\\\n          gsub(/\"/, \"\\\\\\\"\")    # double quote → \\\"\n          gsub(/\\t/, \"\\\\t\")   # tab → \\t\n          if (NR > 1) printf \"\\\\n\"\n          printf \"%s\", $0\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)\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 ' ')\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)\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\n  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\": \"lxc\",\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  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    [[ \"$http_code\" =~ ^2[0-9]{2}$ ]] && break\n    [[ \"$attempt\" -lt 3 ]] && sleep 1\n  done\n\n  POST_TO_API_DONE=true\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  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    [[ \"$http_code\" =~ ^2[0-9]{2}$ ]] && break\n    [[ \"$attempt\" -lt 3 ]] && sleep 1\n  done\n\n  POST_TO_API_DONE=true\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  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 '\"')\n    os_version=$(grep \"^VERSION_ID=\" /etc/os-release | cut -d= -f2 | tr -d '\"')\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/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/community-scripts/ProxmoxVE/main/misc/api.func)\n\nif command -v curl >/dev/null 2>&1; then\n  source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)\n  source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/main/misc/core.func)\n  source <(wget -qO- https://raw.githubusercontent.com/community-scripts/ProxmoxVE/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)\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)\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    # 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 1\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 1\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 \"$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  # 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 1\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 1\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  IPV6_STATIC=${var_ipv6_static:-\"\"}\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    if ! curl -s --connect-timeout 2 \"http://${APT_CACHER_IP}:3142\" >/dev/null 2>&1; then\n      msg_warn \"APT Cacher configured but not reachable at ${APT_CACHER_IP}:3142\"\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 ${APT_CACHER_IP}:3142\"\n    fi\n  fi\n\n  MTU=${var_mtu:-\"\"}\n  SD=${var_searchdomain:-\"\"}\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,${var_tags:-}\"\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  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_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  )\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_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_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_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  )\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 1\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 - with example)\n# var_apt_cacher=yes\n# var_apt_cacher_ip=192.168.1.10\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=\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 1\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) 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_gpu\n    var_gateway var_hostname var_ipv6_method var_mac var_mtu\n    var_net var_ns var_os var_pw var_ram var_tags var_tun var_unprivileged\n    var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage\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  } >\"$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      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      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-)\n  ct=$(grep -E '^var_container_storage=' \"$vf\" | cut -d= -f2-)\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  TAGS=\"community-script;${var_tags:-}\"\n  local STEP=1\n  local MAX_STEP=28\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\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/^[- ]*//')\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      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\\nNote: Automatically enabled for unprivileged containers.\\n\\n(App default: ${var_keyctl:-0})\" 16 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 server IP address:\" 10 58 \"$_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        _mount_fs=\"$result\"\n        ((STEP++))\n      else\n        ((STEP--))\n      fi\n      ;;\n\n    # ═══════════════════════════════════════════════════════════════════════════\n    # STEP 28: Verbose Mode & Confirmation\n    # ═══════════════════════════════════════════════════════════════════════════\n    28)\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 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  GPU: $_enable_gpu | Protection: $protect_desc\n\nAdvanced:\n  Timezone: $tz_display\n  APT Cacher: $apt_display\n  Verbose: $_verbose\"\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\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  # Alpine UDHCPC fix\n  if [ \"$var_os\" == \"alpine\" ] && [ \"$NET\" == \"dhcp\" ] && [ -n \"$_ns\" ]; then\n    UDHCPC_FIX=\"yes\"\n  else\n    UDHCPC_FIX=\"no\"\n  fi\n  export UDHCPC_FIX\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  [[ \"${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 \"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    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    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-)\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-)\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# 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/community-scripts/ProxmoxVE/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    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    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=*) 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    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  # 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  # Build PCT_OPTIONS as string for export\n  TEMP_DIR=$(mktemp -d)\n  pushd \"$TEMP_DIR\" >/dev/null\n  local _func_url\n  if [ \"$var_os\" == \"alpine\" ]; then\n    _func_url=\"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/alpine-install.func\"\n  else\n    _func_url=\"https://raw.githubusercontent.com/community-scripts/ProxmoxVE/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..20}; 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      sleep 1\n    done\n\n    if [ -z \"$ip_in_lxc\" ]; then\n      msg_error \"No IP assigned to CT $CTID after 20s\"\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)\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\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\" >>\"$BUILD_LOG\" 2>&1 || {\n      msg_error \"Failed to install base packages in Alpine container\"\n      install_exit_code=1\n    }\n  else\n    sleep 3\n    LANG=${LANG:-en_US.UTF-8}\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 \"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    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      msg_error \"apt-get base packages installation failed\"\n      install_exit_code=1\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/community-scripts/ProxmoxVE/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    # 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    # 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    # 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    # Detect error type for smart recovery options\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    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    # Exit 1 subclassification: analyze logs to identify actual root cause\n    # Many exit 1 errors are actually APT, OOM, network, or command-not-found issues\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    # 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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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            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 1\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 1 ;;\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 1\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  # 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 1\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 2\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 2\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 1\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  # Offer upgrade for pve-container/lxc-pve if candidate > installed; optional auto-retry pct create\n  # Returns:\n  #   0 = no upgrade needed\n  #   1 = upgraded (and if do_retry=yes and retry succeeded, creation done)\n  #   2 = user declined\n  #   3 = upgrade attempted but failed OR retry failed\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\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_info \"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\n    read -rp \"Do you want to upgrade now? [y/N] \" _ans </dev/tty\n    case \"${_ans,,}\" in\n    y | yes)\n      msg_info \"Upgrading Proxmox LXC stack (pve-container, lxc-pve)\"\n      apt_update_safe\n      if $STD apt-get install -y --only-upgrade pve-container lxc-pve; then\n        msg_ok \"LXC stack upgraded.\"\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  # ------------------------------------------------------------------------------\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    unset CTID\n    msg_error \"Cannot use ID that is already in use.\"\n    exit 206\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  STORAGE_TYPE=$(grep -E \"^[^:]+: $CONTAINER_STORAGE$\" /etc/pve/storage.cfg | cut -d: -f1 | head -1)\n\n  if [[ -z \"$STORAGE_TYPE\" ]]; then\n    msg_error \"Storage '$CONTAINER_STORAGE' not found in /etc/pve/storage.cfg\"\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    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)\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\n  # ------------------------------------------------------------------------------\n  TEMPLATE_SEARCH=\"${PCT_OSTYPE}-${PCT_OSVERSION:-}\"\n  case \"$PCT_OSTYPE\" in\n  debian | ubuntu) TEMPLATE_PATTERN=\"-standard_\" ;;\n  alpine | fedora | rocky | centos) TEMPLATE_PATTERN=\"-default_\" ;;\n  *) TEMPLATE_PATTERN=\"\" ;;\n  esac\n\n  msg_info \"Searching for template '$TEMPLATE_SEARCH'\"\n\n  # Initialize variables\n  ONLINE_TEMPLATE=\"\"\n  ONLINE_TEMPLATES=()\n\n  # Step 1: Check local templates first (instant)\n  mapfile -t LOCAL_TEMPLATES < <(\n    pveam list \"$TEMPLATE_STORAGE\" 2>/dev/null |\n      awk -v search=\"${TEMPLATE_SEARCH}\" -v pattern=\"${TEMPLATE_PATTERN}\" '$1 ~ search && $1 ~ pattern {print $1}' |\n      sed 's|.*/||' | sort -t - -k 2 -V\n  )\n\n  # Step 2: If local template found, use it immediately (skip pveam update)\n  if [[ ${#LOCAL_TEMPLATES[@]} -gt 0 ]]; then\n    TEMPLATE=\"${LOCAL_TEMPLATES[-1]}\"\n    TEMPLATE_SOURCE=\"local\"\n    msg_ok \"Template search completed\"\n  else\n    # Step 3: No local template - need to check online (this may be slow)\n    msg_info \"No local template found, checking online catalog...\"\n\n    # Update catalog with timeout to prevent long hangs\n    if command -v timeout &>/dev/null; then\n      if ! timeout 30 pveam update >/dev/null 2>&1; then\n        msg_warn \"Template catalog update timed out (possible network/DNS issue). Run 'pveam update' manually to diagnose.\"\n      fi\n    else\n      pveam update >/dev/null 2>&1 || msg_warn \"Could not update template catalog (pveam update failed)\"\n    fi\n\n    ONLINE_TEMPLATES=()\n    mapfile -t ONLINE_TEMPLATES < <(pveam available -section system 2>/dev/null | grep -E '\\.(tar\\.zst|tar\\.xz|tar\\.gz)$' | awk '{print $2}' | grep -E \"^${TEMPLATE_SEARCH}.*${TEMPLATE_PATTERN}\" | sort -t - -k 2 -V 2>/dev/null || true)\n    [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE=\"${ONLINE_TEMPLATES[-1]}\"\n\n    TEMPLATE=\"$ONLINE_TEMPLATE\"\n    TEMPLATE_SOURCE=\"online\"\n    msg_ok \"Template search completed\"\n  fi\n\n  # If still no template, try to find alternatives\n  if [[ -z \"$TEMPLATE\" ]]; then\n    msg_warn \"No template found for ${PCT_OSTYPE} ${PCT_OSVERSION}, searching for alternatives...\"\n\n    # Get all available versions for this OS type\n    AVAILABLE_VERSIONS=()\n    mapfile -t AVAILABLE_VERSIONS < <(\n      pveam available -section system 2>/dev/null |\n        grep -E '\\.(tar\\.zst|tar\\.xz|tar\\.gz)$' |\n        awk -F'\\t' '{print $1}' |\n        grep \"^${PCT_OSTYPE}-\" |\n        sed -E \"s/.*${PCT_OSTYPE}-([0-9]+(\\.[0-9]+)?).*/\\1/\" |\n        sort -u -V 2>/dev/null\n    )\n\n    if [[ ${#AVAILABLE_VERSIONS[@]} -gt 0 ]]; then\n      echo \"\"\n      echo \"${BL}Available ${PCT_OSTYPE} versions:${CL}\"\n      for i in \"${!AVAILABLE_VERSIONS[@]}\"; do\n        echo \"  [$((i + 1))] ${AVAILABLE_VERSIONS[$i]}\"\n      done\n      echo \"\"\n      read -p \"Select version [1-${#AVAILABLE_VERSIONS[@]}] or press Enter to cancel: \" choice </dev/tty\n\n      if [[ \"$choice\" =~ ^[0-9]+$ ]] && [[ \"$choice\" -ge 1 ]] && [[ \"$choice\" -le ${#AVAILABLE_VERSIONS[@]} ]]; then\n        PCT_OSVERSION=\"${AVAILABLE_VERSIONS[$((choice - 1))]}\"\n        TEMPLATE_SEARCH=\"${PCT_OSTYPE}-${PCT_OSVERSION}\"\n\n        ONLINE_TEMPLATES=()\n        mapfile -t ONLINE_TEMPLATES < <(\n          pveam available -section system 2>/dev/null |\n            grep -E '\\.(tar\\.zst|tar\\.xz|tar\\.gz)$' |\n            awk '{print $2}' |\n            grep -E \"^${TEMPLATE_SEARCH}-.*${TEMPLATE_PATTERN}\" |\n            sort -t - -k 2 -V 2>/dev/null || true\n        )\n\n        if [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]]; then\n          TEMPLATE=\"${ONLINE_TEMPLATES[-1]}\"\n          TEMPLATE_SOURCE=\"online\"\n        else\n          msg_error \"No templates available for ${PCT_OSTYPE} ${PCT_OSVERSION}\"\n          exit 225\n        fi\n      else\n        msg_custom \"🚫\" \"${YW}\" \"Installation cancelled\"\n        exit 0\n      fi\n    else\n      msg_error \"No ${PCT_OSTYPE} templates available at all\"\n      exit 225\n    fi\n  fi\n\n  TEMPLATE_PATH=\"$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)\"\n  if [[ -z \"$TEMPLATE_PATH\" ]]; then\n    TEMPLATE_BASE=$(awk -v s=\"$TEMPLATE_STORAGE\" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg)\n    [[ -n \"$TEMPLATE_BASE\" ]] && TEMPLATE_PATH=\"$TEMPLATE_BASE/template/cache/$TEMPLATE\"\n  fi\n\n  # If we still don't have a path but have a valid template name, construct it\n  if [[ -z \"$TEMPLATE_PATH\" && -n \"$TEMPLATE\" ]]; then\n    TEMPLATE_PATH=\"/var/lib/vz/template/cache/$TEMPLATE\"\n  fi\n\n  [[ -n \"$TEMPLATE_PATH\" ]] || {\n    if [[ -z \"$TEMPLATE\" ]]; then\n      msg_error \"Template ${PCT_OSTYPE} ${PCT_OSVERSION} not available\"\n\n      # Get available versions\n      mapfile -t AVAILABLE_VERSIONS < <(\n        pveam available -section system 2>/dev/null |\n          grep \"^${PCT_OSTYPE}-\" |\n          sed -E 's/.*'\"${PCT_OSTYPE}\"'-([0-9]+\\.[0-9]+).*/\\1/' |\n          grep -E '^[0-9]+\\.[0-9]+$' |\n          sort -u -V 2>/dev/null || sort -u\n      )\n\n      if [[ ${#AVAILABLE_VERSIONS[@]} -gt 0 ]]; then\n        echo -e \"\\n${BL}Available versions:${CL}\"\n        for i in \"${!AVAILABLE_VERSIONS[@]}\"; do\n          echo \"  [$((i + 1))] ${AVAILABLE_VERSIONS[$i]}\"\n        done\n\n        echo \"\"\n        read -p \"Select version [1-${#AVAILABLE_VERSIONS[@]}] or Enter to exit: \" choice </dev/tty\n\n        if [[ \"$choice\" =~ ^[0-9]+$ ]] && [[ \"$choice\" -ge 1 ]] && [[ \"$choice\" -le ${#AVAILABLE_VERSIONS[@]} ]]; then\n          export var_version=\"${AVAILABLE_VERSIONS[$((choice - 1))]}\"\n          export PCT_OSVERSION=\"$var_version\"\n          msg_ok \"Switched to ${PCT_OSTYPE} ${var_version}\"\n\n          # Retry template search with new version\n          TEMPLATE_SEARCH=\"${PCT_OSTYPE}-${PCT_OSVERSION:-}\"\n\n          mapfile -t LOCAL_TEMPLATES < <(\n            pveam list \"$TEMPLATE_STORAGE\" 2>/dev/null |\n              awk -v search=\"${TEMPLATE_SEARCH}-\" -v pattern=\"${TEMPLATE_PATTERN}\" '$1 ~ search && $1 ~ pattern {print $1}' |\n              sed 's|.*/||' | sort -t - -k 2 -V\n          )\n          mapfile -t ONLINE_TEMPLATES < <(\n            pveam available -section system 2>/dev/null |\n              grep -E '\\.(tar\\.zst|tar\\.xz|tar\\.gz)$' |\n              awk '{print $2}' |\n              grep -E \"^${TEMPLATE_SEARCH}-.*${TEMPLATE_PATTERN}\" |\n              sort -t - -k 2 -V 2>/dev/null || true\n          )\n          ONLINE_TEMPLATE=\"\"\n          [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE=\"${ONLINE_TEMPLATES[-1]}\"\n\n          if [[ ${#LOCAL_TEMPLATES[@]} -gt 0 ]]; then\n            TEMPLATE=\"${LOCAL_TEMPLATES[-1]}\"\n            TEMPLATE_SOURCE=\"local\"\n          else\n            TEMPLATE=\"$ONLINE_TEMPLATE\"\n            TEMPLATE_SOURCE=\"online\"\n          fi\n\n          TEMPLATE_PATH=\"$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)\"\n          if [[ -z \"$TEMPLATE_PATH\" ]]; then\n            TEMPLATE_BASE=$(awk -v s=\"$TEMPLATE_STORAGE\" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg)\n            [[ -n \"$TEMPLATE_BASE\" ]] && TEMPLATE_PATH=\"$TEMPLATE_BASE/template/cache/$TEMPLATE\"\n          fi\n\n          # If we still don't have a path but have a valid template name, construct it\n          if [[ -z \"$TEMPLATE_PATH\" && -n \"$TEMPLATE\" ]]; then\n            TEMPLATE_PATH=\"/var/lib/vz/template/cache/$TEMPLATE\"\n          fi\n\n          [[ -n \"$TEMPLATE_PATH\" ]] || {\n            msg_error \"Template still not found after version change\"\n            exit 220\n          }\n        else\n          msg_custom \"🚫\" \"${YW}\" \"Installation cancelled\"\n          exit 0\n        fi\n      else\n        msg_error \"No ${PCT_OSTYPE} templates available\"\n        exit 220\n      fi\n    fi\n  }\n\n  # Validate that we found a template\n  if [[ -z \"$TEMPLATE\" ]]; then\n    msg_error \"No template found for ${PCT_OSTYPE} ${PCT_OSVERSION}\"\n    msg_custom \"ℹ️\" \"${YW}\" \"Please check:\"\n    msg_custom \"  •\" \"${YW}\" \"Is pveam catalog available? (run: pveam available -section system)\"\n    msg_custom \"  •\" \"${YW}\" \"Does the template exist for your OS version?\"\n    exit 225\n  fi\n\n  msg_ok \"Template ${BL}$TEMPLATE${CL} [$TEMPLATE_SOURCE]\"\n  msg_debug \"Resolved TEMPLATE_PATH=$TEMPLATE_PATH\"\n\n  NEED_DOWNLOAD=0\n  if [[ ! -f \"$TEMPLATE_PATH\" ]]; then\n    msg_info \"Template not present locally – will download.\"\n    NEED_DOWNLOAD=1\n  elif [[ ! -r \"$TEMPLATE_PATH\" ]]; then\n    msg_error \"Template file exists but is not readable – check permissions.\"\n    exit 221\n  elif [[ \"$(stat -c%s \"$TEMPLATE_PATH\")\" -lt 1000000 ]]; then\n    if [[ -n \"$ONLINE_TEMPLATE\" ]]; then\n      msg_warn \"Template file too small (<1MB) – re-downloading.\"\n      NEED_DOWNLOAD=1\n    else\n      msg_warn \"Template looks too small, but no online version exists. Keeping local file.\"\n    fi\n  elif ! tar -tf \"$TEMPLATE_PATH\" &>/dev/null; then\n    if [[ -n \"$ONLINE_TEMPLATE\" ]]; then\n      msg_warn \"Template appears corrupted – re-downloading.\"\n      NEED_DOWNLOAD=1\n    else\n      msg_warn \"Template appears corrupted, but no online version exists. Keeping local file.\"\n    fi\n  else\n    $STD msg_ok \"Template $TEMPLATE is present and valid.\"\n  fi\n\n  if [[ \"$TEMPLATE_SOURCE\" == \"local\" && -n \"$ONLINE_TEMPLATE\" && \"$TEMPLATE\" != \"$ONLINE_TEMPLATE\" ]]; then\n    msg_warn \"Local template is outdated: $TEMPLATE (latest available: $ONLINE_TEMPLATE)\"\n    if whiptail --yesno \"A newer template is available:\\n$ONLINE_TEMPLATE\\n\\nDo you want to download and use it instead?\" 12 70; then\n      TEMPLATE=\"$ONLINE_TEMPLATE\"\n      NEED_DOWNLOAD=1\n    else\n      msg_custom \"ℹ️\" \"${BL}\" \"Continuing with local template $TEMPLATE\"\n    fi\n  fi\n\n  if [[ \"$NEED_DOWNLOAD\" -eq 1 ]]; then\n    [[ -f \"$TEMPLATE_PATH\" ]] && rm -f \"$TEMPLATE_PATH\"\n    for attempt in {1..3}; do\n      msg_info \"Attempt $attempt: Downloading template $TEMPLATE to $TEMPLATE_STORAGE\"\n      if pveam download \"$TEMPLATE_STORAGE\" \"$TEMPLATE\" >>\"${BUILD_LOG:-/dev/null}\" 2>&1; then\n        msg_ok \"Template download successful.\"\n        break\n      fi\n      if [[ $attempt -eq 3 ]]; then\n        msg_error \"Failed after 3 attempts. Please check network access, permissions, or manually run:\\n  pveam download $TEMPLATE_STORAGE $TEMPLATE\"\n        exit 222\n      fi\n      sleep $((attempt * 5))\n    done\n  fi\n\n  if ! pveam list \"$TEMPLATE_STORAGE\" 2>/dev/null | grep -q \"$TEMPLATE\"; then\n    msg_error \"Template $TEMPLATE not available in storage $TEMPLATE_STORAGE after download.\"\n    exit 223\n  fi\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    pveam download \"$TEMPLATE_STORAGE\" \"$TEMPLATE\" >>\"${BUILD_LOG:-/dev/null}\" 2>&1 || {\n      msg_error \"Failed to download template '$TEMPLATE' to storage '$TEMPLATE_STORAGE'\"\n      exit 222\n    }\n    msg_ok \"Template downloaded\"\n  elif ! tar -tf \"$TEMPLATE_PATH\" &>/dev/null; then\n    if [[ -n \"$ONLINE_TEMPLATE\" ]]; then\n      msg_info \"Template appears corrupted – re-downloading\"\n      rm -f \"$TEMPLATE_PATH\"\n      pveam download \"$TEMPLATE_STORAGE\" \"$TEMPLATE\" >>\"${BUILD_LOG:-/dev/null}\" 2>&1 || {\n        msg_error \"Failed to re-download template '$TEMPLATE'\"\n        exit 222\n      }\n      msg_ok \"Template re-downloaded\"\n    else\n      msg_warn \"Template appears corrupted, but no online version exists. Skipping re-download.\"\n    fi\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  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 if template issue - 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      pveam download \"$TEMPLATE_STORAGE\" \"$TEMPLATE\" >>\"${BUILD_LOG:-/dev/null}\" 2>&1\n      msg_ok \"Template re-downloaded\"\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_ok \"Trying local storage fallback\"\n          msg_info \"Downloading template to local\"\n          pveam download local \"$TEMPLATE\" >>\"${BUILD_LOG:-/dev/null}\" 2>&1\n          msg_ok \"Template downloaded to local\"\n        else\n          msg_ok \"Trying local storage fallback\"\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) : ;; # success - container created, continue\n            2)\n              msg_error \"Upgrade declined. Please update and re-run: apt update && apt install --only-upgrade pve-container lxc-pve\"\n              _flush_pct_log\n              exit 231\n              ;;\n            3)\n              msg_error \"Upgrade and/or retry failed. Please inspect: $LOGFILE\"\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 \"Upgrade declined. Please update and re-run: apt update && apt install --only-upgrade pve-container lxc-pve\"\n            _flush_pct_log\n            exit 231\n            ;;\n          3)\n            msg_error \"Upgrade and/or retry failed. Please inspect: $LOGFILE\"\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\n\n  # Verify container exists\n  pct list | awk '{print $1}' | grep -qx \"$CTID\" || {\n    msg_error \"Container ID $CTID not listed in 'pct list'. See $LOGFILE\"\n    _flush_pct_log\n    exit 215\n  }\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\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/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;'>${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/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  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 1\n    fi\n    if [ -n \"$gateway\" ] && ! validate_ip \"$gateway\"; then\n      _ci_msg_error \"Invalid gateway IP format: $gateway\"\n      return 1\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 1\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 1\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 1\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 1\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/community-scripts/ProxmoxVE/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\"\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)\" != \"amd64\" ]; then\n    msg_error \"This script will not work with PiMox (ARM architecture detected).\"\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/community-scripts/ProxmoxVE/main/misc/error_handler.func); then\n        explain_exit_code() { echo \"unknown (error_handler.func download failed)\"; }\n      fi\n    fi\n\n    local explanation\n    explanation=\"$(explain_exit_code \"$rc\")\"\n\n    printf \"\\e[?25h\"\n    msg_error \"in line ${caller_line}: exit code ${rc} (${explanation})\"\n    msg_custom \"→\" \"${YWB}\" \"${cmd}\"\n\n    if [[ -s \"$logfile\" ]]; then\n      echo -e \"\\n${TAB}--- Last 20 lines of log ---\"\n      tail -n 20 \"$logfile\"\n      echo -e \"${TAB}-----------------------------------\"\n      echo -e \"${TAB}📋 Full log: ${logfile}\\n\"\n    fi\n\n    exit \"$rc\"\n  fi\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/community-scripts/ProxmoxVE/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 1\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  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 1\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 1\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 1\n  fi\n  if ! chmod 600 \"$swap_file\"; then\n    msg_error \"Failed to set permissions on $swap_file\"\n    return 1\n  fi\n  if ! mkswap \"$swap_file\"; then\n    msg_error \"Failed to format swap file (mkswap failed)\"\n    return 1\n  fi\n  if ! swapon \"$swap_file\"; then\n    msg_error \"Failed to activate swap (swapon failed)\"\n    return 1\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 1\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 1\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/community-scripts/ProxmoxVE/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 [[ \"$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  local active_log=\"\"\n  if 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 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 \"installing\" records to telemetry API\n  # Catches ALL exit paths: errors, signals, AND clean exits where\n  # post_to_api was called but post_update_to_api was never called\n  if [[ \"${POST_TO_API_DONE:-}\" == \"true\" && \"${POST_UPDATE_DONE:-}\" != \"true\" ]]; then\n    if [[ $exit_code -ne 0 ]]; then\n      _send_abort_telemetry \"$exit_code\"\n    elif declare -f post_update_to_api >/dev/null 2>&1; then\n      post_update_to_api \"done\" \"0\" 2>/dev/null || true\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/community-scripts/ProxmoxVE/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/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# ==============================================================================\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# 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    cat <<EOF >/usr/local/bin/apt-proxy-detect.sh\n#!/bin/bash\nif nc -w1 -z \"${CACHER_IP}\" 3142; then\n  echo -n \"http://${CACHER_IP}:3142\"\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  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/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}\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 ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/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/community-scripts/ProxmoxVE/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/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 1\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 1\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 1\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; 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 1\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 1\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 1\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 1\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)\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)\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]+')\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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# 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    return 1\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    break\n  done\n\n  export GITHUB_TOKEN=\"$token\"\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 1\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 1\n      ;;\n    404)\n      msg_error \"GitHub repository or release not found (HTTP 404): $url\"\n      return 1\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 1\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 1\n      ;;\n    esac\n    ((attempt++))\n  done\n\n  msg_error \"GitHub API call failed after ${max_retries} attempts: ${url}\"\n  return 1\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 1\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 1\n      ;;\n    404)\n      msg_error \"Codeberg repository or release not found (HTTP 404): $url\"\n      return 1\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 1\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 1\n      ;;\n    esac\n  done\n\n  msg_error \"Codeberg API call failed after ${max_retries} attempts: ${url}\"\n  return 1\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 1\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 1\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 1\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 1\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 1\n  }\n\n  # Import GPG key (auto-detect binary vs ASCII-armored format)\n  local tmp_gpg\n  tmp_gpg=$(mktemp) || return 1\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 1\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 1\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 1\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=\"./\" or absolute path) 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 1\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 1\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 1\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 1\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  if ! github_api_call \"https://api.github.com/repos/${repo}/tags?per_page=50\" \"$temp_file\"; then\n    rm -f \"$temp_file\"\n    return 1\n  fi\n\n  local tag=\"\"\n  if [[ -n \"$prefix\" ]]; then\n    tag=$(jq -r --arg p \"$prefix\" '[.[] | select(.name | startswith($p))][0].name // empty' \"$temp_file\")\n  else\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 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\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\n    else\n      msg_error \"Unable to fetch releases for ${app} (HTTP ${http_code})\"\n      rm -f /tmp/gh_check.json\n      return 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\n    fi\n  else\n    if ! curl -fsSL \"$url\" | pv -s \"$content_length\" >\"$output\"; then\n      msg_error \"Download failed: $url\"\n      return 1\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 1\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 1\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 1\n    fi\n\n    local tmpdir\n    tmpdir=$(mktemp -d) || return 1\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 1\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 1\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 1\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++))\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 1\n  fi\n\n  http_code=\"${resp:(-3)}\"\n  [[ \"$http_code\" != \"200\" ]] && {\n    msg_error \"Codeberg API returned HTTP $http_code\"\n    return 1\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 1\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 1\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 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    # 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 1\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 1\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 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 $(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 1\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 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 ]]; 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_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 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 $(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 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 \"$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# 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 1\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 1\n      fi\n    fi\n  done\n\n  return 1\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 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_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 1\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=0\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 1\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 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=$(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|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    # 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 1\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 1\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 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 $(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 1\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 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_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 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      # 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 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 $(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 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 \"$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# 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 1\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 1\n    }\n    $STD systemctl reload apache2 || {\n      msg_error \"Failed to reload Apache\"\n      return 1\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 1\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 1\n  }\n  rm -f /tmp/composer-setup.php\n\n  if [[ ! -x \"$COMPOSER_BIN\" ]]; then\n    msg_error \"Composer installation failed\"\n    return 1\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 1\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 1\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 libsvtav1-dev zlib1g-dev libnuma-dev\n      libva-dev libdrm-dev\n    )\n    ;;\n  *)\n    msg_error \"Invalid FFMPEG_TYPE: $TYPE\"\n    rm -rf \"$TMP_DIR\"\n    return 1\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 1\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 1\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 1\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 1\n  }\n\n  cd \"$TMP_DIR/FFmpeg-\"* || {\n    msg_error \"Source extraction failed\"\n    rm -rf \"$TMP_DIR\"\n    return 1\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 1\n  fi\n\n  $STD ./configure \"${args[@]}\" || {\n    msg_error \"FFmpeg configure failed\"\n    rm -rf \"$TMP_DIR\"\n    return 1\n  }\n  $STD make -j\"$(nproc)\" || {\n    msg_error \"FFmpeg compilation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 1\n  }\n  $STD make install || {\n    msg_error \"FFmpeg installation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 1\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 1\n  }\n\n  if ! command -v ffmpeg &>/dev/null; then\n    msg_error \"FFmpeg installation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\n  }\n  $STD make -j\"$(nproc)\" || {\n    msg_error \"Ghostscript compilation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 1\n  }\n  $STD make install || {\n    msg_error \"Ghostscript installation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 1\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# 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      # libigdgmm - bundled in compute-runtime releases\n      fetch_and_deploy_gh_release \"libigdgmm12\" \"intel/compute-runtime\" \"binary\" \"latest\" \"\" \"libigdgmm12_*_amd64.deb\" || true\n\n      # Intel Graphics Compiler (note: packages have -2 suffix)\n      fetch_and_deploy_gh_release \"intel-igc-core\" \"intel/intel-graphics-compiler\" \"binary\" \"latest\" \"\" \"intel-igc-core-2_*_amd64.deb\" || true\n      fetch_and_deploy_gh_release \"intel-igc-opencl\" \"intel/intel-graphics-compiler\" \"binary\" \"latest\" \"\" \"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      # 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      # Intel Graphics Compiler (note: packages have -2 suffix)\n      fetch_and_deploy_gh_release \"intel-igc-core\" \"intel/intel-graphics-compiler\" \"binary\" \"latest\" \"\" \"intel-igc-core-2_*_amd64.deb\" || true\n      fetch_and_deploy_gh_release \"intel-igc-opencl\" \"intel/intel-graphics-compiler\" \"binary\" \"latest\" \"\" \"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)\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 666 \"$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 666 \"$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    chmod 666 /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 1\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 1\n  }\n\n  cd \"$TMP_DIR\"/ImageMagick-* || {\n    msg_error \"Source extraction failed\"\n    rm -rf \"$TMP_DIR\"\n    return 1\n  }\n\n  $STD ./configure --disable-static || {\n    msg_error \"ImageMagick configure failed\"\n    rm -rf \"$TMP_DIR\"\n    return 1\n  }\n  $STD make -j\"$(nproc)\" || {\n    msg_error \"ImageMagick compilation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 1\n  }\n  $STD make install || {\n    msg_error \"ImageMagick installation failed\"\n    rm -rf \"$TMP_DIR\"\n    return 1\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 1\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 1\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 1\n    upgrade_packages_with_retry \"$DESIRED_PACKAGE\" || {\n      msg_error \"Failed to update Temurin JDK\"\n      return 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\n        }\n      fi\n    fi\n\n    # Perform upgrade with retry logic\n    ensure_apt_working || return 1\n    upgrade_packages_with_retry \"mariadb-server\" \"mariadb-client\" || {\n      msg_error \"Failed to upgrade MariaDB packages\"\n      return 1\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 1\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 1\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 1\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 1\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 major version.\n#\n# Description:\n#   - Preserves data across installations\n#   - Adds official MongoDB repo\n#\n# Variables:\n#   MONGO_VERSION  - MongoDB major version to install (e.g. 7.0, 8.0)\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 1\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 1\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 1\n\n    # Perform upgrade with retry logic\n    upgrade_packages_with_retry \"mongodb-org\" || {\n      msg_error \"Failed to upgrade MongoDB\"\n      return 1\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 1\n  }\n\n  # Setup repository\n  manage_tool_repository \"mongodb\" \"$MONGO_VERSION\" \"$MONGO_BASE_URL\" \\\n    \"https://www.mongodb.org/static/pgp/server-${MONGO_VERSION}.asc\" || {\n    msg_error \"Failed to setup MongoDB repository\"\n    return 1\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 1\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 1\n  }\n\n  if ! command -v mongod >/dev/null 2>&1; then\n    msg_error \"MongoDB binary not found after installation\"\n    return 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\n  }\n\n  ensure_apt_working || return 1\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 1\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 1\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 1\n    }\n  fi\n\n  # Scenario 1: Already installed at target version - just 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 1\n\n    # Just update npm to latest\n    $STD npm install -g npm@latest 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 1\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 1\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 1\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 1\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 1\n    fi\n\n    # Update to latest npm (with version check to avoid incompatibility)\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@latest 2>/dev/null || {\n        msg_warn \"Failed to update npm to latest version (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  export NODE_OPTIONS=\"--max-old-space-size=4096\"\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 1\n  }\n\n  # Install global Node modules\n  if [[ -n \"$NODE_MODULE\" ]]; then\n    IFS=',' read -ra MODULES <<<\"$NODE_MODULE\"\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 1\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 1\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 1\n    }\n  fi\n  ensure_apt_working || return 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 ' ')\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 ' ')\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 ' ')\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\" &\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          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 1\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 1\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 1\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 1\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 1\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 1\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 1\n  }\n\n  install_packages_with_retry \"clickhouse-server\" \"clickhouse-client\" || {\n    msg_error \"Failed to install ClickHouse packages\"\n    return 1\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 1\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 1\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 1\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 1\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 1\n      }\n      $STD rustup default \"$RUST_TOOLCHAIN\" || {\n        msg_error \"Failed to set default Rust toolchain\"\n        return 1\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 1\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 1\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 1\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 1\n          }\n          msg_ok \"Installed $NAME v$VER\"\n        else\n          $STD cargo install \"$NAME\" || {\n            msg_error \"Failed to install $NAME\"\n            return 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\n  fi\n\n  $STD install -m 755 \"$UV_BINARY\" \"$UV_BIN\" || {\n    msg_error \"Failed to install uv binary\"\n    return 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\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 1\n  }\n\n  curl -fsSL -o \"$tmpdir/$filename\" \"$url\" || {\n    msg_error \"Download failed: $url\"\n    rm -rf \"$tmpdir\"\n    return 1\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 1\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 1\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 1\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 1\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 1\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 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\"/* \"$directory/\" || {\n        msg_error \"Failed to copy contents to $directory\"\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  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"
  },
  {
    "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 1\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    # Source explain_exit_code if needed\n    if ! declare -f explain_exit_code >/dev/null 2>&1; then\n      source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/error_handler.func) 2>/dev/null || true\n    fi\n\n    local explanation=\"\"\n    if declare -f explain_exit_code >/dev/null 2>&1; then\n      explanation=\"$(explain_exit_code \"$rc\")\"\n    fi\n\n    printf \"\\e[?25h\"\n    if [[ -n \"$explanation\" ]]; then\n      msg_error \"in line ${caller_line}: exit code ${rc} (${explanation})\"\n    else\n      msg_error \"in line ${caller_line}: exit code ${rc}\"\n    fi\n    msg_custom \"→\" \"${YWB}\" \"${cmd}\"\n\n    if [[ -s \"$logfile\" ]]; then\n      echo -e \"\\n${TAB}--- Last 20 lines of log ---\"\n      tail -n 20 \"$logfile\"\n      echo -e \"${TAB}----------------------------\\n\"\n    fi\n\n    exit \"$rc\"\n  fi\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  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/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='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  )\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://tailscale.com/ | Github: https://github.com/tailscale/tailscale\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/community-scripts/ProxmoxVE/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/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 \"adguardhome-sync\" \"addon\"\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 ifconfig | grep -v '127.0.0.1' | grep -Eo 'inet (addr:)?([0-9]*\\.){3}[0-9]*' | grep -m1 -Eo '([0-9]*\\.){3}[0-9]*' || 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_amd64.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_amd64.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/community-scripts/ProxmoxVE/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 proxmox-helper-scripts\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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}\"\n\nif [ -f ~/.config/code-server/config.yaml ]; then\n  existing_config=true\nfi\n\ncurl -fOL https://github.com/coder/code-server/releases/download/v\"$VERSION\"/code-server_\"${VERSION}\"_amd64.deb &>/dev/null\ndpkg -i code-server_\"${VERSION}\"_amd64.deb &>/dev/null\nrm -rf code-server_\"${VERSION}\"_amd64.deb\nmkdir -p ~/.config/code-server/\nsystemctl enable -q --now code-server@\"$USER\"\n\nif [ $existing_config = false ]; then\ncat <<EOF >~/.config/code-server/config.yaml\nbind-addr: 0.0.0.0:8680\nauth: none\npassword: \ncert: false\nEOF\nfi\nsystemctl restart code-server@\"$USER\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 [helper-scripts.com]: \" admin_pass\n    echo \"\"\n    admin_pass=${admin_pass:-helper-scripts.com}\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://github.com/filebrowserspace/quantum\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# 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-amd64-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-amd64-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: helper-scripts.com\nEOF\n  msg_ok \"Configured with default admin (admin / helper-scripts.com)\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://filebrowser.org/ | Github: https://github.com/filebrowser/filebrowser\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# 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-amd64-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-amd64-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 init --auth.method=noauth &>/dev/null\n    filebrowser config set --auth.method=noauth &>/dev/null\n    filebrowser users add ID 1 --perm.admin &>/dev/null\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 helper-scripts.com --perm.admin --database \"$DB_PATH\" &>/dev/null\n    msg_ok \"Default authentication configured (admin:helper-scripts.com)\"\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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://nicolargo.github.io/glances/ | Github: https://github.com/nicolargo/glances\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/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/community-scripts/ProxmoxVE/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/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 \"immich-public-proxy\" \"addon\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/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 \"jellystat\" \"addon\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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\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  if ! grep -qxF 'COMPOSE_KOMODO_BACKUPS_PATH=/etc/komodo/backups' \"$COMPOSE_ENV\"; then\n    sed -i '/^COMPOSE_KOMODO_IMAGE_TAG=latest$/a 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 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  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  PASSKEY=$(openssl rand -base64 24 | 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_DB_USERNAME=.*/KOMODO_DB_USERNAME=komodo_admin/\" \"$COMPOSE_ENV\"\n  sed -i \"s/^KOMODO_DB_PASSWORD=.*/KOMODO_DB_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_PASSKEY=.*/KOMODO_PASSKEY=${PASSKEY}/\" \"$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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/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 \"nextcloud-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=\"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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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_amd64.deb\" -o $(basename \"https://github.com/OliveTin/OliveTin/releases/latest/download/OliveTin_linux_amd64.deb\")\ndpkg -i OliveTin_linux_amd64.deb &>/dev/null\nsystemctl enable --now OliveTin &>/dev/null\nrm OliveTin_linux_amd64.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/community-scripts/ProxmoxVE/raw/main/LICENSE\n# Source: https://www.phpmyadmin.net/ | Github: https://github.com/phpmyadmin/phpmyadmin\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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    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 \"Enter URL of qBittorrent, example: (http://127.0.0.1:8080): \" QBITTORRENT_BASE_URL\n  read -erp \"Enter qBittorrent username: \" QBITTORRENT_USERNAME\n  read -rsp \"Enter qBittorrent password: \" QBITTORRENT_PASSWORD\n  printf \"\\n\"\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_USERNAME=\"${QBITTORRENT_USERNAME}\"\nQBITTORRENT_PASSWORD=\"${QBITTORRENT_PASSWORD}\"\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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 666 /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/webmin.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2021-2026 tteck\n# Author: tteck (tteckster)\n# License: MIT | https://github.com/community-scripts/ProxmoxVE/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/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/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/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# 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\n    if [ -f \"/etc/pve/lxc/${container_id}.conf\" ] || [ -f \"/etc/pve/qemu-server/${container_id}.conf\" ]; 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 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n# bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/cron-update-lxcs.sh)\"\n\nclear\ncat <<\"EOF\"\n   ______                    __  __          __      __          __   _  ________\n  / ____/________  ____     / / / /___  ____/ /___ _/ /____     / /  | |/ / ____/____\n / /   / ___/ __ \\/ __ \\   / / / / __ \\/ __  / __ `/ __/ _ \\   / /   |   / /   / ___/\n/ /___/ /  / /_/ / / / /  / /_/ / /_/ / /_/ / /_/ / /_/  __/  / /___/   / /___(__  )\n\\____/_/   \\____/_/ /_/   \\____/ .___/\\__,_/\\__,_/\\__/\\___/  /_____/_/|_\\____/____/\n                              /_/\nEOF\n\nadd() {\n  while true; do\n    read -p \"This script will add a crontab schedule that updates all LXCs every Sunday at midnight. 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  sh -c '(crontab -l -u root 2>/dev/null; echo \"0 0 * * 0 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /bin/bash -c \\\"\\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-lxcs-cron.sh)\\\" >>/var/log/update-lxcs-cron.log 2>/dev/null\") | crontab -u root -'\n  clear\n  echo -e \"\\n To view Cron Update LXCs logs: cat /var/log/update-lxcs-cron.log\"\n}\n\nremove() {\n  (crontab -l | grep -v \"update-lxcs-cron.sh\") | crontab -\n  rm -rf /var/log/update-lxcs-cron.log\n  echo \"Removed Crontab Schedule from Proxmox VE\"\n}\n\nOPTIONS=(Add \"Add Crontab Schedule\"\n  Remove \"Remove Crontab Schedule\")\n\nCHOICE=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"Cron Update LXCs\" --menu \"Select an option:\" 10 58 2 \\\n  \"${OPTIONS[@]}\" 3>&1 1>&2 2>&3)\n\ncase $CHOICE in\n\"Add\")\n  add\n  ;;\n\"Remove\")\n  remove\n  ;;\n*)\n  echo \"Exiting...\"\n  exit 0\n  ;;\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/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/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() { 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    sed -i '/proxmox/d;/bookworm/d' /etc/apt/sources.list || true\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-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/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() { 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\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) start_routines_3 ;;\n  trixie) start_routines_4 ;;\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    sed -i '/proxmox/d;/bookworm/d' /etc/apt/sources.list || true\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/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-pmg-install\" \"pve\"\n\nif ! grep -q \"Proxmox Mail Gateway\" /etc/issue 2>/dev/null; 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; 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\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\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  # ---- 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      sed -i \"s/^[^#].*pmg-enterprise/# &/\" \"$file\"\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      sed -i \"s/^#.*pmg-enterprise/deb/\" \"$file\"\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.list <<EOF\ndeb https://enterprise.proxmox.com/debian/pmg ${VERSION} pmg-enterprise\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      sed -i \"s/^[^#].*pmg-no-subscription/# &/\" \"$file\"\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      sed -i \"s/^#.*pmg-no-subscription/deb/\" \"$file\"\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-install-repo.list <<EOF\ndeb http://download.proxmox.com/debian/pmg ${VERSION} pmg-no-subscription\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-for-beta.list <<EOF\n# deb http://download.proxmox.com/debian/pmg ${VERSION} pmgtest\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\n      sed -i '/proxmox/d;/bookworm/d' /etc/apt/sources.list || true\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/community-scripts/ProxmoxVE/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/community-scripts/ProxmoxVE/raw/main/LICENSE\n\nsource <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/refs/heads/main/misc/core.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 \"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_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_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_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  # 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 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\nheader_info\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[@]} 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\nif [[ -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\nif [[ -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\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    continue\n  else\n    echo -e \"${BL}[INFO]${CL} Detected service: ${GN}${service}${CL}\"\n  fi\n\n  #2) Extract service build/update resource requirements from config/installation file\n  script=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/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\" ]; then\n    pct set \"$container\" --cores \"$build_cpu\" --memory \"$build_ram\"\n  fi\n\n  #4) Update service, using the update command\n  case \"$os\" in\n  alpine) pct exec \"$container\" -- ash -c \"$UPDATE_CMD\" ;;\n  archlinux) pct exec \"$container\" -- bash -c \"$UPDATE_CMD\" ;;\n  fedora | rocky | centos | alma) pct exec \"$container\" -- bash -c \"$UPDATE_CMD\" ;;\n  ubuntu | debian | devuan) pct exec \"$container\" -- bash -c \"$UPDATE_CMD\" ;;\n  opensuse) pct exec \"$container\" -- bash -c \"$UPDATE_CMD\" ;;\n  esac\n  exit_code=$?\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 &\n  fi\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 [ $exit_code -eq 0 ]; then\n    msg_ok \"Updated container $container\"\n  elif [ $exit_code -eq 75 ]; then\n    echo -e \"${YW}[WARN]${CL} Container $container skipped (requires interactive mode)\"\n  elif [ \"$BACKUP_CHOICE\" == \"yes\" ]; then\n    msg_error \"Update failed for container $container (exit code: $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      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    else\n      msg_error \"Restore failed for container $container\"\n      exit 235\n    fi\n  else\n    msg_error \"Update failed for container $container. Exiting\"\n    exit \"$exit_code\"\n  fi\ndone\n\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\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 tteck\n# Author: tteck (tteckster)\n# License: MIT\n# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE\n\necho -e \"\\n $(date)\"\nexcluded_containers=(\"$@\")\nfunction update_container() {\n  container=$1\n  name=$(pct exec \"$container\" hostname)\n  echo -e \"\\n [Info] Updating $container : $name \\n\"\n  os=$(pct config \"$container\" | awk '/^ostype/ {print $2}')\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  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    template=$(pct config \"$container\" | grep -q \"template:\" && echo \"true\" || echo \"false\")\n    if [ \"$template\" == \"false\" ] && [ \"$status\" == \"status: stopped\" ]; then\n      echo -e \"[Info] Starting $container\"\n      pct start \"$container\"\n      sleep 5\n      update_container \"$container\"\n      echo -e \"[Info] Shutting down $container\"\n      pct shutdown \"$container\" &\n    elif [ \"$status\" == \"status: running\" ]; then\n      update_container \"$container\"\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 && 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 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\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 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 -euo 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}\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\nwhiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"TurnKey LXCs\" --yesno \"This will allow for the creation of one of the many TurnKey LXC Containers. Proceed?\" 10 68\nTURNKEY_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  TURNKEY_MENU+=(\"$TAG\" \"$ITEM \" \"OFF\")\ndone < <(\n  cat <<EOF\nansible Ansible\nbookstack BookStack\ncore Core\nfaveo-helpdesk Faveo Helpdesk\nfileserver File Server\ngallery Gallery\ngameserver Game Server\ngitea Gitea\ngitlab GitLab\ninvoice-ninja Invoice Ninja\nmediaserver Media Server\nnextcloud Nextcloud\nobservium Observium\nodoo Odoo\nopenldap OpenLDAP\nopenvpn OpenVPN\nowncloud ownCloud\nphpbb phpBB\ntorrentserver Torrent Server\nwireguard WireGuard\nwordpress Wordpress\nzoneminder ZoneMinder\nEOF\n)\nturnkey=$(whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"TurnKey LXCs\" --radiolist \"\\nSelect a TurnKey LXC to create:\\n\" 16 $((MSG_MAX_LENGTH + 58)) 6 \"${TURNKEY_MENU[@]}\" 3>&1 1>&2 2>&3 | tr -d '\"')\n[ -z \"$turnkey\" ] && {\n  whiptail --backtitle \"Proxmox VE Helper Scripts\" --title \"No TurnKey LXC Selected\" --msgbox \"It appears that no TurnKey LXC container was selected\" 10 68\n  msg \"Done\"\n  exit\n}\n\n# Setup script environment\nPASS=\"$(openssl rand -base64 8)\"\n# Prompt user to confirm container ID\nwhile true; do\n  CTID=$(whiptail --backtitle \"Container ID\" --title \"Choose the Container ID\" --inputbox \"Enter the container ID...\" 8 40 $(pvesh get /cluster/nextid) 3>&1 1>&2 2>&3)\n\n  # Check if user cancelled\n  [ -z \"$CTID\" ] && die \"No Container ID selected\"\n\n  # Validate Container ID\n  if ! validate_container_id \"$CTID\"; then\n    SUGGESTED_ID=$(get_valid_container_id \"$CTID\")\n    if whiptail --backtitle \"Container ID\" --title \"ID Already In Use\" --yesno \"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    # User declined, loop back to input\n  else\n    break\n  fi\ndone\n# Prompt user to confirm Hostname\nHOST_NAME=$(whiptail --backtitle \"Hostname\" --title \"Choose the Hostname\" --inputbox \"Enter the containers Hostname...\" 8 40 \"turnkey-${turnkey}\" 3>&1 1>&2 2>&3)\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  \"\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}\n\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# Update LXC template list\nmsg \"Updating LXC template list...\"\npveam update >/dev/null\n\n# Get LXC template string\nmapfile -t TEMPLATES < <(pveam available -section turnkeylinux | awk -v turnkey=\"${turnkey}\" '$0 ~ turnkey {print $2}' | sort -t - -k 2 -V)\n[ ${#TEMPLATES[@]} -gt 0 ] || die \"Unable to find a template when searching for '${turnkey}'.\"\nTEMPLATE=\"${TEMPLATES[-1]}\"\n\n# Download LXC template\nif ! pveam list $TEMPLATE_STORAGE | grep -q $TEMPLATE; then\n  msg \"Downloading LXC template (Patience)...\"\n  pveam download $TEMPLATE_STORAGE $TEMPLATE >/dev/null ||\n    die \"A problem occured while downloading the LXC template.\"\nfi\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 \"TurnKey ${turnkey} password: ${PASS}\" >>~/turnkey-${turnkey}.creds # file is located in the Proxmox root directory\n\n# If turnkey is \"OpenVPN\", add access to the tun device\nTUN_DEVICE_REQUIRED=(\"openvpn\") # Setup this way in case future turnkeys also need tun access\nif printf '%s\\n' \"${TUN_DEVICE_REQUIRED[@]}\" | grep -qw \"${turnkey}\"; then\n  info \"${turnkey} requires access to /dev/net/tun on the host. Modifying the container configuration to allow this.\"\n  echo \"lxc.cgroup2.devices.allow: c 10:200 rwm\" >>/etc/pve/lxc/${CTID}.conf\n  echo \"lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file 0 0\" >>/etc/pve/lxc/${CTID}.conf\n  sleep 5\nfi\n\n# Start container\nmsg \"Starting LXC Container...\"\npct start \"$CTID\"\nsleep 10\n\n# Get container IP\nset +euo pipefail # Turn off error checking\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\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\"\ninfo \"(credentials also stored in the root user's root directory in the 'turnkey-${turnkey}.creds' file.)\"\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/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 \"\\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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\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 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z \"$RAM_SIZE\" ]; then\n      RAM_SIZE=\"2048\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${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\" --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  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    else\n      MAC=\"$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 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    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  else\n    exit-script\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 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\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 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://Helper-Scripts.com' 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/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                                              (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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\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 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z \"$RAM_SIZE\" ]; then\n      RAM_SIZE=\"2048\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${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\" --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  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    else\n      MAC=\"$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 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    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  else\n    exit-script\n  fi\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://Helper-Scripts.com' 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/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 \"\\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)\" != \"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=\"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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\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 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $RAM_SIZE ]; then\n      RAM_SIZE=\"2048\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${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\" --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  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    else\n      MAC=\"$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 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    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  else\n    exit-script\n  fi\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-amd64.qcow2\nelse\n  URL=https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-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\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 4M 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://Helper-Scripts.com' 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 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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\n    fi\n    echo -e \"${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}\"\n  else\n    exit_script\n  fi\n\n  # RAM Size\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\n      RAM_SIZE=\"4096\"\n    fi\n    echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n  else\n    exit_script\n  fi\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  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    else\n      MAC=\"$MAC1\"\n    fi\n    echo -e \"${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}\"\n  else\n    exit_script\n  fi\n\n  # VLAN\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    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  # MTU\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    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  # 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\narch_check\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/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 \"\\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\"\n\nfor version in \"${VERSIONS[@]}\"; do\n  eval \"$version=$(curl -fsSL https://raw.githubusercontent.com/home-assistant/version/master/stable.json | grep '\"ova\"' | cut -d '\"' -f 4)\"\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)\" != \"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  BRANCH=\"$stable\"\n  VMID=$(get_valid_nextid)\n  MACHINE=\"q35\"\n  FORMAT=\"\"\n  DISK_SIZE=\"32G\"\n  HN=\"haos-${BRANCH}\"\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 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 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\" 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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\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 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $RAM_SIZE ]; then\n      RAM_SIZE=\"4096\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${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\" --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  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    else\n      MAC=\"$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 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    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  else\n    exit-script\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 \"${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\"\nif [ \"$BRANCH\" == \"$dev\" ]; then\n  URL=\"https://os-artifacts.home-assistant.io/${BRANCH}/haos_ova-${BRANCH}.qcow2.xz\"\nelse\n  URL=\"https://github.com/home-assistant/operating-system/releases/download/${BRANCH}/haos_ova-${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/${CACHE_FILE##*/%.xz}\" # .qcow2\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 -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\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\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\" | 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[[ -z \"$DISK_REF\" ]] && {\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}${CL})\"\n\nrm -f \"$FILE_IMG\"\n\nmsg_info \"Attaching EFI and root disk\"\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\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://Helper-Scripts.com' 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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\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 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $RAM_SIZE ]; then\n      RAM_SIZE=\"2048\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${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\" --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  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    else\n      MAC=\"$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 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    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  else\n    exit-script\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 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://Helper-Scripts.com' 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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\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 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $RAM_SIZE ]; then\n      RAM_SIZE=\"2048\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${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\" --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  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    else\n      MAC=\"$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 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    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  else\n    exit-script\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 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://Helper-Scripts.com' 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 -d ' ')\n    fi\n    echo -e \"${DGN}Using Hostname: ${BGN}$HN${CL}\"\n  else\n    exit-script\n  fi\n\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\n      CORE_COUNT=\"1\"\n    fi\n    echo -e \"${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}\"\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 256 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $RAM_SIZE ]; then\n      RAM_SIZE=\"256\"\n    fi\n    echo -e \"${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}\"\n  else\n    exit-script\n  fi\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  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    else\n      VLAN=\",tag=$VLAN1\"\n    fi\n    echo -e \"${DGN}Using WAN Vlan: ${BGN}$VLAN1${CL}\"\n  else\n    exit-script\n  fi\n\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    else\n      LAN_VLAN=\",tag=$VLAN2\"\n    fi\n    echo -e \"${DGN}Using LAN Vlan: ${BGN}$VLAN2${CL}\"\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n    fi\n    echo -e \"${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}\"\n  else\n    exit-script\n  fi\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://Helper-Scripts.com' 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 \"halt\"\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/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...\"\n#API VARIABLES\nRANDOM_UUID=\"$(cat /proc/sys/kernel/random/uuid)\"\nMETHOD=\"\"\nNSAPP=\"opnsense-vm\"\nvar_os=\"opnsense\"\nvar_version=\"25.7\"\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)\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 -d ' ')\n    fi\n    echo -e \"${DGN}Using Hostname: ${BGN}$HN${CL}\"\n  else\n    exit-script\n  fi\n\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\n      CORE_COUNT=\"2\"\n    fi\n    echo -e \"${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}\"\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 8192 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $RAM_SIZE ]; then\n      RAM_SIZE=\"8192\"\n    fi\n    echo -e \"${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}\"\n  else\n    exit-script\n  fi\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}$\")\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 proxmox-helper-scripts -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 ${FILE} $STORAGE ${DISK_IMPORT:-} 1>&/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://Helper-Scripts.com' 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 25.7\"\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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\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 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $RAM_SIZE ]; then\n      RAM_SIZE=\"2048\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${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\" --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  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    else\n      MAC=\"$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 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    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  else\n    exit-script\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 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://Helper-Scripts.com' 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 \"\\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=\"pimox-haos-vm\"\nvar_os=\"pimox-haos\"\nDISK_SIZE=\"32G\"\n\nfor version in \"${VERSIONS[@]}\"; do\n  eval \"$version=$(curl -fsSL https://raw.githubusercontent.com/home-assistant/version/master/stable.json | grep '\"ova\"' | cut -d '\"' -f 4)\"\ndone\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 \"Pimox Homeassistant OS VM\" --yesno \"This will create a New Pimox 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)\" != \"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  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 -d ' ')\n      echo -e \"${DGN}Using Hostname: ${BGN}$HN${CL}\"\n    fi\n  fi\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 [ -z $CORE_COUNT ]; then\n    CORE_COUNT=\"2\"\n    echo -e \"${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}\"\n  else\n    if [ $exitstatus = 0 ]; then echo -e \"${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}\"; fi\n  fi\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 [ -z $RAM_SIZE ]; then\n    RAM_SIZE=\"4096\"\n    echo -e \"${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}\"\n  else\n    if [ $exitstatus = 0 ]; then echo -e \"${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}\"; fi\n  fi\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  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 [ -z $MAC1 ]; then\n    MAC=\"$GEN_MAC\"\n    echo -e \"${DGN}Using MAC Address: ${BGN}$MAC${CL}\"\n  else\n    if [ $exitstatus = 0 ]; then\n      MAC=\"$MAC1\"\n      echo -e \"${DGN}Using MAC Address: ${BGN}$MAC1${CL}\"\n    fi\n  fi\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 = 0 ]; then\n    if [ -z $VLAN1 ]; then\n      VLAN1=\"Default\" VLAN=\"\"\n      echo -e \"${DGN}Using Vlan: ${BGN}$VLAN1${CL}\"\n    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${DGN}Using Vlan: ${BGN}$VLAN1${CL}\"\n    fi\n  fi\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 = 0 ]; then\n    if [ -z $MTU1 ]; then\n      MTU1=\"Default\" MTU=\"\"\n      echo -e \"${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}\"\n    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  fi\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 >/dev/null\n\nDESCRIPTION=$(\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/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 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 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/community-scripts/ProxmoxVE/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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\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 \"$RAM_SIZE\" --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $RAM_SIZE ]; then\n      RAM_SIZE=\"8192\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${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 \"$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  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    else\n      MAC=\"$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 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    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  else\n    exit-script\n  fi\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://Helper-Scripts.com' 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/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 \"\\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)\" != \"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=\"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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\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 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $RAM_SIZE ]; then\n      RAM_SIZE=\"2048\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${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\" --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  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    else\n      MAC=\"$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 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    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  else\n    exit-script\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 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-amd64.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 4M 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://Helper-Scripts.com' 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\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 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 <<<$(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=\"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)\" != \"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=\"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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\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 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $RAM_SIZE ]; then\n      RAM_SIZE=\"2048\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${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\" --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  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    else\n      MAC=\"$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 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    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  else\n    exit-script\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 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-amd64.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 4M 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://Helper-Scripts.com' 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\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 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/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\\____/_.___/\\__,_/_/ /_/\\__/\\__,_/   /____/_____(_)____/  /_/       |___/_/  /_/ (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)\" != \"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=\"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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\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 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $RAM_SIZE ]; then\n      RAM_SIZE=\"2048\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${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\" --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  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    else\n      MAC=\"$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 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    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  else\n    exit-script\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 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-amd64.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 4M 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://Helper-Scripts.com' 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\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 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/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 \"\\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 -d ' ')\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  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\n      CORE_COUNT=\"2\"\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 2048 --title \"RAM\" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then\n    if [ -z $RAM_SIZE ]; then\n      RAM_SIZE=\"2048\"\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}\"\n    else\n      echo -e \"${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${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\" --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  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    else\n      MAC=\"$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 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    else\n      VLAN=\",tag=$VLAN1\"\n      echo -e \"${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}\"\n    fi\n  else\n    exit-script\n  fi\n\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    else\n      MTU=\",mtu=$MTU1\"\n      echo -e \"${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}\"\n    fi\n  else\n    exit-script\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 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://Helper-Scripts.com' 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"
  }
]