diff --git a/.github/workflows/auto-update-app-headers.yml b/.github/workflows/auto-update-app-headers.yml index 18941379..30bc5f42 100644 --- a/.github/workflows/auto-update-app-headers.yml +++ b/.github/workflows/auto-update-app-headers.yml @@ -20,17 +20,22 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: community-scripts + repositories: ProxmoxVED + - name: Generate a token for PR approval and merge id: generate-token-merge - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: - app-id: ${{ secrets.APP_ID_APPROVE_AND_MERGE }} - private-key: ${{ secrets.APP_KEY_APPROVE_AND_MERGE }} + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: community-scripts + repositories: ProxmoxVED # Step 1: Checkout repository - name: Checkout repository diff --git a/.github/workflows/backup/update_json_date.yml.bak b/.github/workflows/backup/update_json_date.yml.bak index cb9bc855..71012528 100644 --- a/.github/workflows/backup/update_json_date.yml.bak +++ b/.github/workflows/backup/update_json_date.yml.bak @@ -17,10 +17,12 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: community-scripts + repositories: ProxmoxVED - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/changelog-pr.yaml b/.github/workflows/changelog-pr.yaml index ca187dd5..80959d54 100644 --- a/.github/workflows/changelog-pr.yaml +++ b/.github/workflows/changelog-pr.yaml @@ -19,17 +19,21 @@ jobs: steps: - name: Generate a token for PR creation id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: community-scripts + repositories: ProxmoxVED - name: Generate a token for PR approval and merge id: generate-token-merge - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ secrets.APP_ID_APPROVE_AND_MERGE }} private-key: ${{ secrets.APP_KEY_APPROVE_AND_MERGE }} + owner: community-scripts + repositories: ProxmoxVED - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/delete-discord-thread.yml b/.github/workflows/delete-discord-thread.yml index 19e36081..1c36059e 100644 --- a/.github/workflows/delete-discord-thread.yml +++ b/.github/workflows/delete-discord-thread.yml @@ -9,12 +9,12 @@ jobs: close_discord_thread: if: github.repository == 'community-scripts/ProxmoxVED' runs-on: ubuntu-latest + env: + ISSUE_TITLE: ${{ github.event.issue.title }} steps: - name: Get thread-ID op and close thread run: | - ISSUE_TITLE="${{ github.event.issue.title }}" - THREAD_ID=$(curl -s -X GET "https://discord.com/api/v10/guilds/${{ secrets.DISCORD_GUILD_ID }}/threads/active" \ -H "Authorization: Bot ${{ secrets.DISCORD_BOT_TOKEN }}" \ -H "Content-Type: application/json" | \ diff --git a/.github/workflows/delete_new_script.yaml b/.github/workflows/delete_new_script.yaml index 9d9f97d6..d538437a 100644 --- a/.github/workflows/delete_new_script.yaml +++ b/.github/workflows/delete_new_script.yaml @@ -16,10 +16,12 @@ jobs: - name: Generate a token for PR approval and merge id: generate-token-merge - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ secrets.APP_ID_APPROVE_AND_MERGE }} private-key: ${{ secrets.APP_KEY_APPROVE_AND_MERGE }} + owner: community-scripts + repositories: ProxmoxVED - name: Extract Issue Title (Lowercase & Underscores) id: extract_title diff --git a/.github/workflows/get-versions-from-gh.yaml b/.github/workflows/get-versions-from-gh.yaml index d3cd7e36..0cf632d1 100644 --- a/.github/workflows/get-versions-from-gh.yaml +++ b/.github/workflows/get-versions-from-gh.yaml @@ -24,10 +24,12 @@ jobs: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: community-scripts + repositories: ProxmoxVED - name: Crawl from Github API env: diff --git a/.github/workflows/get-versions-from-newreleases.yaml b/.github/workflows/get-versions-from-newreleases.yaml index aabfae35..634a0c1b 100644 --- a/.github/workflows/get-versions-from-newreleases.yaml +++ b/.github/workflows/get-versions-from-newreleases.yaml @@ -24,17 +24,21 @@ jobs: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: community-scripts + repositories: ProxmoxVED - name: Generate a token for PR approval and merge id: generate-token-merge - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ secrets.APP_ID_APPROVE_AND_MERGE }} private-key: ${{ secrets.APP_KEY_APPROVE_AND_MERGE }} + owner: community-scripts + repositories: ProxmoxVED - name: Crawl from newreleases.io env: diff --git a/.github/workflows/live/changelog-pr.yml b/.github/workflows/live/changelog-pr.yml index dc5bcd3d..87da5514 100644 --- a/.github/workflows/live/changelog-pr.yml +++ b/.github/workflows/live/changelog-pr.yml @@ -18,10 +18,12 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: community-scripts + repositories: ProxmoxVED - name: Checkout code uses: actions/checkout@v4 @@ -109,7 +111,7 @@ jobs: ); if (updateScriptsCategory) { - + const subCategory = updateScriptsCategory.subCategories.find(sub => sub.labels.some(label => prLabels.includes(label)) ); @@ -121,7 +123,7 @@ jobs: } } }); - + console.log(JSON.stringify(categorizedPRs, null, 2)); return categorizedPRs; @@ -147,30 +149,30 @@ jobs: const hasSubcategories = subCategories && subCategories.length > 0; const hasMainNotes = notes.length > 0; const hasSubNotes = hasSubcategories && subCategories.some(sub => sub.notes && sub.notes.length > 0); - - + + if (hasMainNotes || hasSubNotes) { newReleaseNotes += `### ${title}\n\n`; } - + if (hasMainNotes) { newReleaseNotes += ` ${notes.join("\n")}\n\n`; } if (hasSubcategories) { for (const { title: subTitle, notes: subNotes } of subCategories) { if (subNotes && subNotes.length > 0) { - newReleaseNotes += ` - #### ${subTitle}\n\n`; - newReleaseNotes += ` ${subNotes.join("\n ")}\n\n`; + newReleaseNotes += ` - #### ${subTitle}\n\n`; + newReleaseNotes += ` ${subNotes.join("\n ")}\n\n`; } } } - } - + } + const changelogContent = await fs.readFile(changelogPath, 'utf-8'); const changelogIncludesTodaysReleaseNotes = changelogContent.includes(`\n## ${today}`); - const regex = changelogIncludesTodaysReleaseNotes - ? new RegExp(`## ${today}.*(?=## ${latestDateInChangelog})`, "gs") + const regex = changelogIncludesTodaysReleaseNotes + ? new RegExp(`## ${today}.*(?=## ${latestDateInChangelog})`, "gs") : new RegExp(`(?=## ${latestDateInChangelog})`, "gs"); const newChangelogContent = changelogContent.replace(regex, newReleaseNotes); @@ -223,4 +225,4 @@ jobs: PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number') if [ -n "$PR_NUMBER" ]; then gh pr review $PR_NUMBER --approve - fi \ No newline at end of file + fi diff --git a/.github/workflows/live/update-json-date.yml b/.github/workflows/live/update-json-date.yml index 7e9c2497..1bf965a4 100644 --- a/.github/workflows/live/update-json-date.yml +++ b/.github/workflows/live/update-json-date.yml @@ -6,7 +6,7 @@ on: - main paths: - 'json/**.json' - workflow_dispatch: + workflow_dispatch: jobs: update-app-files: @@ -19,15 +19,17 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: community-scripts + repositories: ProxmoxVED - name: Generate dynamic branch name id: timestamp run: echo "BRANCH_NAME=pr-update-json-$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV - + - name: Set up GH_TOKEN env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -102,8 +104,8 @@ jobs: - name: Commit and create PR if changes exist if: env.changed == 'true' run: | - - + + git commit -m "Update date in json" git checkout -b ${{ env.BRANCH_NAME }} git push origin ${{ env.BRANCH_NAME }} diff --git a/.github/workflows/move-to-main-repo.yaml b/.github/workflows/move-to-main-repo.yaml index 9c9fb65d..dfddeff7 100644 --- a/.github/workflows/move-to-main-repo.yaml +++ b/.github/workflows/move-to-main-repo.yaml @@ -18,7 +18,7 @@ jobs: steps: - name: Generate a token id: app-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.PUSH_MAIN_APP_ID }} private-key: ${{ secrets.PUSH_MAIN_APP_SECRET }} diff --git a/.vscode/settings.json b/.vscode/settings.json index 8f17c7ff..47009d64 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,7 +18,7 @@ "editor.minimap.enabled": false, "terminal.integrated.scrollback": 10000, "[shellscript]": { - "editor.defaultFormatter": "foxundermoon.shell-format", + "editor.defaultFormatter": "mads-hartmann.bash-ide-vscode", "editor.tabSize": 4, "editor.insertSpaces": true, }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cddfbd3..0de5171d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ ### πŸ†• New Scripts - - odoo ([#4477](https://github.com/community-scripts/ProxmoxVE/pull/4477)) +- odoo ([#4477](https://github.com/community-scripts/ProxmoxVE/pull/4477)) - alpine-transmission ([#4277](https://github.com/community-scripts/ProxmoxVE/pull/4277)) - alpine-tinyauth ([#4264](https://github.com/community-scripts/ProxmoxVE/pull/4264)) - alpine-rclone ([#4265](https://github.com/community-scripts/ProxmoxVE/pull/4265)) @@ -24,7 +24,7 @@ ### πŸš€ Updated Scripts - - fix: fetch_release_and_deploy function [@CrazyWolf13](https://github.com/CrazyWolf13) ([#4478](https://github.com/community-scripts/ProxmoxVE/pull/4478)) +- fix: fetch_release_and_deploy function [@CrazyWolf13](https://github.com/CrazyWolf13) ([#4478](https://github.com/community-scripts/ProxmoxVE/pull/4478)) - Website: re-add documenso & some little bugfixes [@MickLesk](https://github.com/MickLesk) ([#4456](https://github.com/community-scripts/ProxmoxVE/pull/4456)) - update some improvements from dev (tools.func) [@MickLesk](https://github.com/MickLesk) ([#4430](https://github.com/community-scripts/ProxmoxVE/pull/4430)) - Alpine: Use onliner for updates [@tremor021](https://github.com/tremor021) ([#4414](https://github.com/community-scripts/ProxmoxVE/pull/4414)) @@ -56,26 +56,26 @@ ### 🧰 Maintenance - - #### πŸ’Ύ Core +- #### πŸ’Ύ Core - - fix: improve bridge detection in all network interface configuration files [@filippolauria](https://github.com/filippolauria) ([#4413](https://github.com/community-scripts/ProxmoxVE/pull/4413)) - - Config file Function in build.func [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4411](https://github.com/community-scripts/ProxmoxVE/pull/4411)) - - fix: detect all bridge types, not just vmbr prefix [@filippolauria](https://github.com/filippolauria) ([#4351](https://github.com/community-scripts/ProxmoxVE/pull/4351)) + - fix: improve bridge detection in all network interface configuration files [@filippolauria](https://github.com/filippolauria) ([#4413](https://github.com/community-scripts/ProxmoxVE/pull/4413)) + - Config file Function in build.func [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4411](https://github.com/community-scripts/ProxmoxVE/pull/4411)) + - fix: detect all bridge types, not just vmbr prefix [@filippolauria](https://github.com/filippolauria) ([#4351](https://github.com/community-scripts/ProxmoxVE/pull/4351)) - - #### πŸ“‚ Github +- #### πŸ“‚ Github - - Add Github app for auto PR merge [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4461](https://github.com/community-scripts/ProxmoxVE/pull/4461)) + - Add Github app for auto PR merge [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#4461](https://github.com/community-scripts/ProxmoxVE/pull/4461)) ### 🌐 Website - - FAQ: Explanation "updatable" [@tremor021](https://github.com/tremor021) ([#4300](https://github.com/community-scripts/ProxmoxVE/pull/4300)) +- FAQ: Explanation "updatable" [@tremor021](https://github.com/tremor021) ([#4300](https://github.com/community-scripts/ProxmoxVE/pull/4300)) - - #### πŸ“ Script Information +- #### πŸ“ Script Information - - Jellyfin Media Server: Update configuration path [@tremor021](https://github.com/tremor021) ([#4434](https://github.com/community-scripts/ProxmoxVE/pull/4434)) - - 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)) - - pingvin.json: fix typo [@warmbo](https://github.com/warmbo) ([#4426](https://github.com/community-scripts/ProxmoxVE/pull/4426)) - - Navidrome - Fix config path (use /etc/ instead of /var/lib) [@quake1508](https://github.com/quake1508) ([#4406](https://github.com/community-scripts/ProxmoxVE/pull/4406)) + - Jellyfin Media Server: Update configuration path [@tremor021](https://github.com/tremor021) ([#4434](https://github.com/community-scripts/ProxmoxVE/pull/4434)) + - 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)) + - pingvin.json: fix typo [@warmbo](https://github.com/warmbo) ([#4426](https://github.com/community-scripts/ProxmoxVE/pull/4426)) + - Navidrome - Fix config path (use /etc/ instead of /var/lib) [@quake1508](https://github.com/quake1508) ([#4406](https://github.com/community-scripts/ProxmoxVE/pull/4406)) ## 2025-03-24 @@ -86,6 +86,7 @@ - yt-dlp-webui [@CrazyWolf13](https://github.com/CrazyWolf13) ([#3364](https://github.com/community-scripts/ProxmoxVE/pull/3364)) - Extension/New Script: Redis Alpine Installation [@MickLesk](https://github.com/MickLesk) ([#3367](https://github.com/community-scripts/ProxmoxVE/pull/3367)) - Fluid Calendar [@vhsdream](https://github.com/vhsdream) ([#2869](ht + ### πŸš€ Updated Scripts - License url VED to VE [@bvdberg01](https://github.com/bvdberg01) ([#3258](https://github.com/community-scripts/ProxmoxVE/pull/3258)) @@ -125,7 +126,6 @@ - #### ✨ New Features - - [core] install core deps (debian / ubuntu) [@MickLesk](https://github.com/MickLesk) ([#3366](https://github.com/community-scripts/ProxmoxVE/pull/3366)) - #### πŸ’Ύ Core @@ -145,7 +145,7 @@ - #### 🐞 Bug Fixes - - Better Text for Version Date [@michelroegl-brunner](https:er) ([#3388](https://github.com/community-scripts/ProxmoxVE/pull/3388)) + - Better Text for Version Date [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#3388](https://github.com/community-scripts/ProxmoxVE/pull/3388)) - JSON editor note fix [@bvdberg01](https://github.com/bvdberg01) ([#3260](https://github.com/community-scripts/ProxmoxVE/pull/3260)) - Move cryptpad files to right folders [@bvdberg01](https://github.com/bvdberg01) ([#3242](https://github.com/community-scripts/ProxmoxVE/pull/3242)) diff --git a/ct/alpine-syncthing.sh b/ct/alpine-syncthing.sh deleted file mode 100644 index 97f2f226..00000000 --- a/ct/alpine-syncthing.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -source <(curl -s https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func) -# Copyright (c) 2021-2025 community-scripts ORG -# Author: MickLesk (CanbiZ) -# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE -# Source: https://syncthing.net/ - -APP="Alpine-Syncthing" -var_tags="${var_tags:-alpine;networking}" -var_cpu="${var_cpu:-1}" -var_ram="${var_ram:-256}" -var_disk="${var_disk:-1}" -var_os="${var_os:-alpine}" -var_version="${var_version:-3.21}" -var_unprivileged="${var_unprivileged:-1}" - -header_info "$APP" -variables -color -catch_errors - -function update_script() { - msg_info "Updating Alpine Packages" - $STD apk -U upgrade - msg_ok "Updated Alpine Packages" - - msg_info "Updating Syncthing" - $STD apk upgrade syncthing - msg_ok "Updated Syncthing" - - msg_info "Restarting Syncthing" - $STD rc-service syncthing restart - msg_ok "Restarted Syncthing" - - exit 1 -} - -start -build_container -description - -msg_ok "Completed Successfully!\n" -echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" -echo -e "${INFO}${YW} Access it using the following URL:${CL}" -echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8384${CL}" diff --git a/ct/alpine-teamspeak-server.sh b/ct/alpine-teamspeak-server.sh new file mode 100644 index 00000000..1fee6cdc --- /dev/null +++ b/ct/alpine-teamspeak-server.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) +# Copyright (c) 2021-2025 community-scripts ORG +# Author: tremor021 (SlaviΕ‘a AreΕΎina) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://teamspeak.com/en/ + +APP="Alpine-TeamSpeak-Server" +var_tags="${var_tags:-alpine;communication}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-256}" +var_disk="${var_disk:-2}" +var_os="${var_os:-alpine}" +var_version="${var_version:-3.22}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + + if [[ ! -d /opt/teamspeak-server ]]; then + msg_error "No ${APP} Installation Found!" + exit 1 + fi + + RELEASE=$(curl -fsSL https://teamspeak.com/en/downloads/#server | sed -n 's/.*teamspeak3-server_linux_amd64-\([0-9]\+\.[0-9]\+\.[0-9]\+\).*/\1/p' | head -1) + if [ "${RELEASE}" != "$(cat ~/.teamspeak-server)" ] || [ ! -f ~/.teamspeak-server ]; then + msg_info "Updating ${APP} LXC" + $STD apk -U upgrade + $STD service teamspeak stop + curl -fsSL "https://files.teamspeak-services.com/releases/server/${RELEASE}/teamspeak3-server_linux_amd64-${RELEASE}.tar.bz2" -o ts3server.tar.bz2 + tar -xf ./ts3server.tar.bz2 + cp -ru teamspeak3-server_linux_amd64/* /opt/teamspeak-server/ + rm -f ~/ts3server.tar.bz* + rm -rf teamspeak3-server_linux_amd64 + echo "${RELEASE}" >~/.teamspeak-server + $STD service teamspeak start + msg_ok "Updated Successfully" + else + msg_ok "No update required. ${APP} is already at ${RELEASE}" + fi + + exit 0 +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following IP:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}${IP}:9987${CL}" diff --git a/ct/alpine.sh b/ct/alpine.sh index 8d09d7b6..11972a3c 100644 --- a/ct/alpine.sh +++ b/ct/alpine.sh @@ -11,7 +11,7 @@ var_cpu="${var_cpu:-1}" var_ram="${var_ram:-512}" var_disk="${var_disk:-1}" var_os="${var_os:-alpine}" -var_version="${var_version:-3.21}" +var_version="${var_version:-3.22}" var_unprivileged="${var_unprivileged:-1}" header_info "$APP" diff --git a/ct/bar-assistant.sh b/ct/bar-assistant.sh deleted file mode 100644 index 68f895de..00000000 --- a/ct/bar-assistant.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env bash -source <(curl -s https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func) -# Copyright (c) 2021-2025 community-scripts ORG -# Author: bvdberg01 -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/karlomikus/bar-assistant -# Source: https://github.com/karlomikus/vue-salt-rim -# Source: https://www.meilisearch.com/ - -APP="Bar-Assistant" -var_tags="${var_tags:-inventory;drinks}" -var_cpu="${var_cpu:-2}" -var_ram="${var_ram:-2048}" -var_disk="${var_disk:-4}" -var_os="${var_os:-ubuntu}" -var_version="${var_version:-24.10}" -var_unprivileged="${var_unprivileged:-1}" - -header_info "$APP" -variables -color -catch_errors - -function update_script() { - header_info - check_container_storage - check_container_resources - if [[ ! -d /opt/bar-assistant ]]; then - msg_error "No ${APP} Installation Found!" - exit - fi - RELEASE_MEILISEARCH=$(curl -s https://api.github.com/repos/meilisearch/meilisearch/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') - RELEASE_BARASSISTANT=$(curl -s https://api.github.com/repos/karlomikus/bar-assistant/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }') - RELEASE_SALTRIM=$(curl -s https://api.github.com/repos/karlomikus/vue-salt-rim/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }') - - if [[ ! -f /opt/${APP}_version.txt ]] || [[ "${RELEASE_BARASSISTANT}" != "$(cat /opt/${APP}_version.txt)" ]]; then - msg_info "Stopping nginx" - systemctl stop nginx - msg_ok "Stopped nginx" - - msg_info "Updating ${APP} to v${RELEASE_BARASSISTANT}" - cd /opt - mv /opt/bar-assistant /opt/bar-assistant-backup - curl -fsSL "https://github.com/karlomikus/bar-assistant/archive/refs/tags/v${RELEASE_BARASSISTANT}.zip" -o barassistant.zip - unzip -q barassistant.zip - mv /opt/bar-assistant-${RELEASE_BARASSISTANT}/ /opt/bar-assistant - cp -r /opt/bar-assistant-backup/.env /opt/bar-assistant/.env - cp -r /opt/bar-assistant-backup/storage/bar-assistant /opt/bar-assistant/storage/bar-assistant - cd /opt/bar-assistant - $STD composer install --no-interaction - $STD php artisan migrate --force - $STD php artisan storage:link - $STD php artisan bar:setup-meilisearch - $STD php artisan scout:sync-index-settings - $STD php artisan config:cache - $STD php artisan route:cache - $STD php artisan event:cache - chown -R www-data:www-data /opt/bar-assistant - echo "${RELEASE_BARASSISTANT}" >/opt/${APP}_version.txt - msg_ok "Updated $APP to v${RELEASE_BARASSISTANT}" - - msg_info "Starting nginx" - systemctl start nginx - msg_ok "Started nginx" - - msg_info "Cleaning up" - rm -rf /opt/barassistant.zip - rm -rf /opt/bar-assistant-backup - msg_ok "Cleaned" - else - msg_ok "No update required. ${APP} is already at v${RELEASE_BARASSISTANT}" - fi - - if [[ ! -f /opt/vue-salt-rim_version.txt ]] || [[ "${RELEASE_SALTRIM}" != "$(cat /opt/vue-salt-rim_version.txt)" ]]; then - msg_info "Stopping nginx" - systemctl stop nginx - msg_ok "Stopped nginx" - - msg_info "Updating Salt Rim to v${RELEASE_SALTRIM}" - cd /opt - mv /opt/vue-salt-rim /opt/vue-salt-rim-backup - curl -fsSL "https://github.com/karlomikus/vue-salt-rim/archive/refs/tags/v${RELEASE_SALTRIM}.zip" -o saltrim.zip - unzip -q saltrim.zip - mv /opt/vue-salt-rim-${RELEASE_SALTRIM}/ /opt/vue-salt-rim - cp /opt/vue-salt-rim-backup/public/config.js /opt/vue-salt-rim/public/config.js - cd /opt/vue-salt-rim - $STD npm install - $STD npm run build - echo "${RELEASE_SALTRIM}" >/opt/vue-salt-rim_version.txt - msg_ok "Updated $APP to v${RELEASE_SALTRIM}" - - msg_info "Starting nginx" - systemctl start nginx - msg_ok "Started nginx" - - msg_info "Cleaning up" - rm -rf /opt/saltrim.zip - rm -rf /opt/vue-salt-rim-backup - msg_ok "Cleaned" - msg_ok "Updated" - else - msg_ok "No update required. Salt Rim is already at v${RELEASE_SALTRIM}" - fi - - if [[ ! -f /opt/meilisearch_version.txt ]] || [[ "${RELEASE_MEILISEARCH}" != "$(cat /opt/meilisearch_version.txt)" ]]; then - msg_info "Stopping Meilisearch" - systemctl stop meilisearch - msg_ok "Stopped Meilisearch" - - msg_info "Updating Meilisearch to ${RELEASE_MEILISEARCH}" - cd /opt - RELEASE=$(curl -s https://api.github.com/repos/meilisearch/meilisearch/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') - curl -fsSL https://github.com/meilisearch/meilisearch/releases/latest/download/meilisearch.deb -o meilisearch.deb - $STD dpkg -i meilisearch.deb - echo "${RELEASE_MEILISEARCH}" >/opt/meilisearch_version.txt - msg_ok "Updated Meilisearch to ${RELEASE_MEILISEARCH}" - - msg_info "Starting Meilisearch" - systemctl start meilisearch - msg_ok "Started Meilisearch" - - msg_info "Cleaning up" - rm -rf "/opt/meilisearch.deb" - msg_ok "Cleaned" - msg_ok "Updated Meilisearch" - else - msg_ok "No update required. Meilisearch is already at ${RELEASE_MEILISEARCH}" - fi - exit -} - -start -build_container -description - -msg_ok "Completed Successfully!\n" -echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" -echo -e "${INFO}${YW} Access it using the following URL:${CL}" -echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL}" diff --git a/ct/debian.sh b/ct/debian.sh index c3816577..c525e5e2 100644 --- a/ct/debian.sh +++ b/ct/debian.sh @@ -40,5 +40,5 @@ start build_container description -msg_ok "Completed Successfully!\n" -echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +msg_ok "Completed Successfully!" +msg_custom "πŸš€" "${GN}" "${APP} setup has been successfully initialized!" diff --git a/ct/docspell.sh b/ct/docspell.sh index 7fcdc014..3ff1cd3b 100644 --- a/ct/docspell.sh +++ b/ct/docspell.sh @@ -3,7 +3,7 @@ source <(curl -s https://git.community-scripts.org/community-scripts/ProxmoxVED/ # Copyright (c) 2021-2025 community-scripts ORG # Author: MickLesk (Canbiz) # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: +# Source: https://github.com/community-scripts/ProxmoxVE APP="Docspell" var_tags="${var_tags:-document}" diff --git a/ct/garmin-grafana.sh b/ct/garmin-grafana.sh index fb0a5afa..7351ad00 100644 --- a/ct/garmin-grafana.sh +++ b/ct/garmin-grafana.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/raw/main/garmin-grafana/misc/build.func) +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/raw/main/misc/build.func) # Copyright (c) 2021-2025 community-scripts ORG # Author: aliaksei135 # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE @@ -21,74 +21,74 @@ catch_errors # this only updates garmin-grafana, not influxdb or grafana, which are upgraded with apt function update_script() { - header_info - check_container_storage - check_container_resources + header_info + check_container_storage + check_container_resources - if [[ ! -d /opt/garmin-grafana/ ]]; then - msg_error "No ${APP} Installation Found!" - exit - fi - - RELEASE=$(curl -fsSL https://api.github.com/repos/arpanghosh8453/garmin-grafana/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') - if [[ ! -d /opt/garmin-grafana/ ]] || [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]] || [[ ! -f /opt/${APP}_version.txt ]]; then - msg_info "Stopping $APP" - systemctl stop garmin-grafana - systemctl stop grafana-server - systemctl stop influxdb - msg_ok "Stopped $APP" - - if [[ ! -f /opt/garmin-grafana/.env ]]; then - msg_error "No .env file found in /opt/garmin-grafana/.env" - exit - fi - source /opt/garmin-grafana/.env - if [[ -z "${INFLUXDB_USER}" || -z "${INFLUXDB_PASSWORD}" || -z "${INFLUXDB_NAME}" ]]; then - msg_error "INFLUXDB_USER, INFLUXDB_PASSWORD, or INFLUXDB_NAME not set in .env file" - exit - fi - - msg_info "Creating Backup" - tar -czf "/opt/${APP}_backup_$(date +%F).tar.gz" /opt/garmin-grafana/.garminconnect /opt/garmin-grafana/.env - mv /opt/garmin-grafana/ /opt/garmin-grafana-backup/ - msg_ok "Backup Created" - - msg_info "Updating $APP to v${RELEASE}" - curl -fsSL -o "${RELEASE}.zip" "https://github.com/arpanghosh8453/garmin-grafana/archive/refs/tags/${RELEASE}.zip" - unzip -q "${RELEASE}.zip" - mv "garmin-grafana-${RELEASE}/" "/opt/garmin-grafana" - rm -f "${RELEASE}.zip" - $STD uv sync --locked --project /opt/garmin-grafana/ - # shellcheck disable=SC2016 - sed -i 's/\${DS_GARMIN_STATS}/garmin_influxdb/g' /opt/garmin-grafana/Grafana_Dashboard/Garmin-Grafana-Dashboard.json - sed -i 's/influxdb:8086/localhost:8086/' /opt/garmin-grafana/Grafana_Datasource/influxdb.yaml - sed -i "s/influxdb_user/${INFLUXDB_USER}/" /opt/garmin-grafana/Grafana_Datasource/influxdb.yaml - sed -i "s/influxdb_secret_password/${INFLUXDB_PASSWORD}/" /opt/garmin-grafana/Grafana_Datasource/influxdb.yaml - sed -i "s/GarminStats/${INFLUXDB_NAME}/" /opt/garmin-grafana/Grafana_Datasource/influxdb.yaml - # Copy across grafana data - cp -r /opt/garmin-grafana/Grafana_Datasource/* /etc/grafana/provisioning/datasources - cp -r /opt/garmin-grafana/Grafana_Dashboard/* /etc/grafana/provisioning/dashboards - # Copy back the env and token files - cp /opt/garmin-grafana-backup/.env /opt/garmin-grafana/.env - cp -r /opt/garmin-grafana-backup/.garminconnect /opt/garmin-grafana/.garminconnect - msg_ok "Updated $APP to v${RELEASE}" - - msg_info "Starting $APP" - systemctl start garmin-grafana - systemctl start grafana-server - systemctl start influxdb - msg_ok "Started $APP" - - msg_info "Cleaning Up" - rm -rf /opt/garmin-grafana-backup - msg_ok "Cleanup Completed" - - echo "${RELEASE}" >/opt/${APP}_version.txt - msg_ok "Update Successful" - else - msg_ok "No update required. ${APP} is already at v${RELEASE}" - fi + if [[ ! -d /opt/garmin-grafana/ ]]; then + msg_error "No ${APP} Installation Found!" exit + fi + + RELEASE=$(curl -fsSL https://api.github.com/repos/arpanghosh8453/garmin-grafana/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') + if [[ ! -d /opt/garmin-grafana/ ]] || [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]] || [[ ! -f /opt/${APP}_version.txt ]]; then + msg_info "Stopping $APP" + systemctl stop garmin-grafana + systemctl stop grafana-server + systemctl stop influxdb + msg_ok "Stopped $APP" + + if [[ ! -f /opt/garmin-grafana/.env ]]; then + msg_error "No .env file found in /opt/garmin-grafana/.env" + exit + fi + source /opt/garmin-grafana/.env + if [[ -z "${INFLUXDB_USER}" || -z "${INFLUXDB_PASSWORD}" || -z "${INFLUXDB_NAME}" ]]; then + msg_error "INFLUXDB_USER, INFLUXDB_PASSWORD, or INFLUXDB_NAME not set in .env file" + exit + fi + + msg_info "Creating Backup" + tar -czf "/opt/${APP}_backup_$(date +%F).tar.gz" /opt/garmin-grafana/.garminconnect /opt/garmin-grafana/.env + mv /opt/garmin-grafana/ /opt/garmin-grafana-backup/ + msg_ok "Backup Created" + + msg_info "Updating $APP to v${RELEASE}" + curl -fsSL -o "${RELEASE}.zip" "https://github.com/arpanghosh8453/garmin-grafana/archive/refs/tags/${RELEASE}.zip" + unzip -q "${RELEASE}.zip" + mv "garmin-grafana-${RELEASE}/" "/opt/garmin-grafana" + rm -f "${RELEASE}.zip" + $STD uv sync --locked --project /opt/garmin-grafana/ + # shellcheck disable=SC2016 + sed -i 's/\${DS_GARMIN_STATS}/garmin_influxdb/g' /opt/garmin-grafana/Grafana_Dashboard/Garmin-Grafana-Dashboard.json + sed -i 's/influxdb:8086/localhost:8086/' /opt/garmin-grafana/Grafana_Datasource/influxdb.yaml + sed -i "s/influxdb_user/${INFLUXDB_USER}/" /opt/garmin-grafana/Grafana_Datasource/influxdb.yaml + sed -i "s/influxdb_secret_password/${INFLUXDB_PASSWORD}/" /opt/garmin-grafana/Grafana_Datasource/influxdb.yaml + sed -i "s/GarminStats/${INFLUXDB_NAME}/" /opt/garmin-grafana/Grafana_Datasource/influxdb.yaml + # Copy across grafana data + cp -r /opt/garmin-grafana/Grafana_Datasource/* /etc/grafana/provisioning/datasources + cp -r /opt/garmin-grafana/Grafana_Dashboard/* /etc/grafana/provisioning/dashboards + # Copy back the env and token files + cp /opt/garmin-grafana-backup/.env /opt/garmin-grafana/.env + cp -r /opt/garmin-grafana-backup/.garminconnect /opt/garmin-grafana/.garminconnect + msg_ok "Updated $APP to v${RELEASE}" + + msg_info "Starting $APP" + systemctl start garmin-grafana + systemctl start grafana-server + systemctl start influxdb + msg_ok "Started $APP" + + msg_info "Cleaning Up" + rm -rf /opt/garmin-grafana-backup + msg_ok "Cleanup Completed" + + echo "${RELEASE}" >/opt/${APP}_version.txt + msg_ok "Update Successful" + else + msg_ok "No update required. ${APP} is already at v${RELEASE}" + fi + exit } start diff --git a/ct/gitea-mirror.sh b/ct/gitea-mirror.sh index 16a497c2..ce0e21ee 100644 --- a/ct/gitea-mirror.sh +++ b/ct/gitea-mirror.sh @@ -3,7 +3,7 @@ source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxV # Copyright (c) 2021-2025 community-scripts ORG # Author: CrazyWolf13 # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/arunavo4/gitea-mirror +# Source: https://github.com/RayLabsHQ/gitea-mirror APP="gitea-mirror" var_tags="${var_tags:-mirror;gitea}" @@ -49,7 +49,7 @@ function update_script() { rm -rf /opt/gitea-mirror fi - RELEASE=$(curl -fsSL https://api.github.com/repos/arunavo4/gitea-mirror/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }') + RELEASE=$(curl -fsSL https://api.github.com/repos/RayLabsHQ/gitea-mirror/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }') if [[ "${RELEASE}" != "$(cat ~/.${APP} 2>/dev/null || cat /opt/${APP}_version.txt 2>/dev/null)" ]]; then msg_info "Stopping Services" @@ -71,7 +71,7 @@ function update_script() { msg_ok "Installed Bun" rm -rf /opt/gitea-mirror - fetch_and_deploy_gh_release "gitea-mirror" "arunavo4/gitea-mirror" "tarball" "v3.0.2" + fetch_and_deploy_gh_release "gitea-mirror" "RayLabsHQ/gitea-mirror" "tarball" "v3.0.2" msg_info "Updating and rebuilding ${APP} to v${RELEASE}" cd /opt/gitea-mirror diff --git a/ct/headers/alpine-syncthing b/ct/headers/alpine-syncthing deleted file mode 100644 index 8d684d61..00000000 --- a/ct/headers/alpine-syncthing +++ /dev/null @@ -1,6 +0,0 @@ - ___ __ _ _____ __ __ _ - / | / /___ (_)___ ___ / ___/__ ______ _____/ /_/ /_ (_)___ ____ _ - / /| | / / __ \/ / __ \/ _ \______\__ \/ / / / __ \/ ___/ __/ __ \/ / __ \/ __ `/ - / ___ |/ / /_/ / / / / / __/_____/__/ / /_/ / / / / /__/ /_/ / / / / / / / /_/ / -/_/ |_/_/ .___/_/_/ /_/\___/ /____/\__, /_/ /_/\___/\__/_/ /_/_/_/ /_/\__, / - /_/ /____/ /____/ diff --git a/ct/headers/alpine-teamspeak-server b/ct/headers/alpine-teamspeak-server new file mode 100644 index 00000000..cfa5aeb9 --- /dev/null +++ b/ct/headers/alpine-teamspeak-server @@ -0,0 +1,6 @@ + ___ __ _ ______ _____ __ _____ + / | / /___ (_)___ ___ /_ __/__ ____ _____ ___ / ___/____ ___ ____ _/ /__ / ___/___ ______ _____ _____ + / /| | / / __ \/ / __ \/ _ \______/ / / _ \/ __ `/ __ `__ \\__ \/ __ \/ _ \/ __ `/ //_/_____\__ \/ _ \/ ___/ | / / _ \/ ___/ + / ___ |/ / /_/ / / / / / __/_____/ / / __/ /_/ / / / / / /__/ / /_/ / __/ /_/ / ,< /_____/__/ / __/ / | |/ / __/ / +/_/ |_/_/ .___/_/_/ /_/\___/ /_/ \___/\__,_/_/ /_/ /_/____/ .___/\___/\__,_/_/|_| /____/\___/_/ |___/\___/_/ + /_/ /_/ diff --git a/ct/headers/bar-assistant b/ct/headers/bar-assistant deleted file mode 100644 index 714e3854..00000000 --- a/ct/headers/bar-assistant +++ /dev/null @@ -1,6 +0,0 @@ - ____ ___ _ __ __ - / __ )____ ______ / | __________(_)____/ /_____ _____ / /_ - / __ / __ `/ ___/_____/ /| | / ___/ ___/ / ___/ __/ __ `/ __ \/ __/ - / /_/ / /_/ / / /_____/ ___ |(__ |__ ) (__ ) /_/ /_/ / / / / /_ -/_____/\__,_/_/ /_/ |_/____/____/_/____/\__/\__,_/_/ /_/\__/ - diff --git a/ct/headers/itsmng b/ct/headers/itsmng deleted file mode 100644 index bbb5bb64..00000000 --- a/ct/headers/itsmng +++ /dev/null @@ -1,6 +0,0 @@ - _ __ - (_) /__________ ___ ____ ____ _ - / / __/ ___/ __ `__ \/ __ \/ __ `/ - / / /_(__ ) / / / / / / / / /_/ / -/_/\__/____/_/ /_/ /_/_/ /_/\__, / - /____/ diff --git a/ct/headers/jellyfin b/ct/headers/jellyfin deleted file mode 100644 index d905c4db..00000000 --- a/ct/headers/jellyfin +++ /dev/null @@ -1,6 +0,0 @@ - __ ____ _____ - / /__ / / /_ __/ __(_)___ - __ / / _ \/ / / / / / /_/ / __ \ -/ /_/ / __/ / / /_/ / __/ / / / / -\____/\___/_/_/\__, /_/ /_/_/ /_/ - /____/ diff --git a/ct/headers/kapowarr b/ct/headers/kapowarr deleted file mode 100644 index e127f364..00000000 --- a/ct/headers/kapowarr +++ /dev/null @@ -1,6 +0,0 @@ - __ __ - / //_/___ _____ ____ _ ______ ___________ - / ,< / __ `/ __ \/ __ \ | /| / / __ `/ ___/ ___/ - / /| / /_/ / /_/ / /_/ / |/ |/ / /_/ / / / / -/_/ |_\__,_/ .___/\____/|__/|__/\__,_/_/ /_/ - /_/ diff --git a/ct/headers/leantime b/ct/headers/leantime new file mode 100644 index 00000000..048d615d --- /dev/null +++ b/ct/headers/leantime @@ -0,0 +1,6 @@ + __ __ _ + / / ___ ____ _____ / /_(_)___ ___ ___ + / / / _ \/ __ `/ __ \/ __/ / __ `__ \/ _ \ + / /___/ __/ /_/ / / / / /_/ / / / / / / __/ +/_____/\___/\__,_/_/ /_/\__/_/_/ /_/ /_/\___/ + diff --git a/ct/headers/mealie b/ct/headers/mealie deleted file mode 100644 index a5d36d54..00000000 --- a/ct/headers/mealie +++ /dev/null @@ -1,6 +0,0 @@ - __ ___ ___ - / |/ /__ ____ _/ (_)__ - / /|_/ / _ \/ __ `/ / / _ \ - / / / / __/ /_/ / / / __/ -/_/ /_/\___/\__,_/_/_/\___/ - diff --git a/ct/headers/nginxproxymanager b/ct/headers/nginxproxymanager new file mode 100644 index 00000000..d68d0c9d --- /dev/null +++ b/ct/headers/nginxproxymanager @@ -0,0 +1,6 @@ + _ __ _ ____ __ ___ + / | / /___ _(_)___ _ __ / __ \_________ _ ____ __ / |/ /___ _____ ____ _____ ____ _____ + / |/ / __ `/ / __ \| |/_/ / /_/ / ___/ __ \| |/_/ / / / / /|_/ / __ `/ __ \/ __ `/ __ `/ _ \/ ___/ + / /| / /_/ / / / / /> < / ____/ / / /_/ /> "/opt/${APP}_db_backup_$(date +%F).sql" + tar -czf "/opt/${APP}_backup_$(date +%F).tar.gz" "/opt/${APP}" + msg_ok "Backup Created" + fetch_and_deploy_gh_release "$APP" "Leantime/leantime" "prebuild" "latest" "/opt/${APP}" Leantime-v[0-9].[0-9].[0-9].tar.gz exit } @@ -44,4 +45,4 @@ description msg_ok "Completed Successfully!\n" echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" echo -e "${INFO}${YW} Access it using the following URL:${CL}" -echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}/install${CL}" diff --git a/ct/linkstack.sh b/ct/linkstack.sh index 2a88277d..e745545f 100644 --- a/ct/linkstack.sh +++ b/ct/linkstack.sh @@ -1,15 +1,15 @@ #!/usr/bin/env bash -source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) # Copyright (c) 2021-2025 community-scripts ORG -# Author: Omar Minaya +# Author: Omar Minaya | MickLesk (CanbiZ) # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # Source: https://linkstack.org/ APP="LinkStack" var_tags="${var_tags:-os}" var_cpu="${var_cpu:-1}" -var_ram="${var_ram:-2048}" -var_disk="${var_disk:-10}" +var_ram="${var_ram:-1024}" +var_disk="${var_disk:-5}" var_os="${var_os:-debian}" var_version="${var_version:-12}" var_unprivileged="${var_unprivileged:-1}" @@ -23,17 +23,13 @@ function update_script() { header_info check_container_storage check_container_resources - - if [[ ! -d /var ]]; then + + if [[ ! -f ~/.linkstack ]]; then msg_error "No ${APP} Installation Found!" exit fi - - msg_info "Updating $APP LXC" - $STD apt-get update - $STD apt-get -y upgrade - msg_ok "Updated $APP LXC" - + PHP_VERSION="8.3" PHP_MODULE="sqlite3" PHP_APACHE="YES" setup_php + msg_error "Adguard Home should be updated via the user interface." exit } @@ -43,5 +39,5 @@ description msg_ok "Completed Successfully!\n" echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" -echo -e "${INFO}${YW} Complete setup at:${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL}" diff --git a/ct/nginxproxymanager.sh b/ct/nginxproxymanager.sh new file mode 100644 index 00000000..eb7259de --- /dev/null +++ b/ct/nginxproxymanager.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) +# Copyright (c) 2021-2025 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://nginxproxymanager.com/ + +APP="Nginx Proxy Manager" +var_tags="${var_tags:-proxy}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-1024}" +var_disk="${var_disk:-4}" +var_os="${var_os:-debian}" +var_version="${var_version:-12}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -f /lib/systemd/system/npm.service ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + if ! command -v pnpm &>/dev/null; then + msg_info "Installing pnpm" + #export NODE_OPTIONS=--openssl-legacy-provider + $STD npm install -g pnpm@8.15 + msg_ok "Installed pnpm" + fi + RELEASE=$(curl -fsSL https://api.github.com/repos/NginxProxyManager/nginx-proxy-manager/releases/latest | + grep "tag_name" | + awk '{print substr($2, 3, length($2)-4) }') + msg_info "Stopping Services" + systemctl stop openresty + systemctl stop npm + msg_ok "Stopped Services" + + msg_info "Cleaning Old Files" + rm -rf /app \ + /var/www/html \ + /etc/nginx \ + /var/log/nginx \ + /var/lib/nginx \ + "$STD" /var/cache/nginx + msg_ok "Cleaned Old Files" + + msg_info "Downloading NPM v${RELEASE}" + curl -fsSL "https://codeload.github.com/NginxProxyManager/nginx-proxy-manager/tar.gz/v${RELEASE}" | tar -xz + cd nginx-proxy-manager-"${RELEASE}" + msg_ok "Downloaded NPM v${RELEASE}" + + msg_info "Setting up Enviroment" + ln -sf /usr/bin/python3 /usr/bin/python + ln -sf /usr/bin/certbot /opt/certbot/bin/certbot + ln -sf /usr/local/openresty/nginx/sbin/nginx /usr/sbin/nginx + ln -sf /usr/local/openresty/nginx/ /etc/nginx + sed -i "s|\"version\": \"0.0.0\"|\"version\": \"$RELEASE\"|" backend/package.json + sed -i "s|\"version\": \"0.0.0\"|\"version\": \"$RELEASE\"|" frontend/package.json + sed -i 's+^daemon+#daemon+g' docker/rootfs/etc/nginx/nginx.conf + NGINX_CONFS=$(find "$(pwd)" -type f -name "*.conf") + for NGINX_CONF in $NGINX_CONFS; do + sed -i 's+include conf.d+include /etc/nginx/conf.d+g' "$NGINX_CONF" + done + mkdir -p /var/www/html /etc/nginx/logs + cp -r docker/rootfs/var/www/html/* /var/www/html/ + cp -r docker/rootfs/etc/nginx/* /etc/nginx/ + cp docker/rootfs/etc/letsencrypt.ini /etc/letsencrypt.ini + cp docker/rootfs/etc/logrotate.d/nginx-proxy-manager /etc/logrotate.d/nginx-proxy-manager + ln -sf /etc/nginx/nginx.conf /etc/nginx/conf/nginx.conf + rm -f /etc/nginx/conf.d/dev.conf + mkdir -p /tmp/nginx/body \ + /run/nginx \ + /data/nginx \ + /data/custom_ssl \ + /data/logs \ + /data/access \ + /data/nginx/default_host \ + /data/nginx/default_www \ + /data/nginx/proxy_host \ + /data/nginx/redirection_host \ + /data/nginx/stream \ + /data/nginx/dead_host \ + /data/nginx/temp \ + /var/lib/nginx/cache/public \ + /var/lib/nginx/cache/private \ + /var/cache/nginx/proxy_temp + chmod -R 777 /var/cache/nginx + chown root /tmp/nginx + echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" {print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf);" >/etc/nginx/conf.d/include/resolvers.conf + if [ ! -f /data/nginx/dummycert.pem ] || [ ! -f /data/nginx/dummykey.pem ]; then + $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 + fi + mkdir -p /app/global /app/frontend/images + cp -r backend/* /app + cp -r global/* /app/global + $STD python3 -m pip install --no-cache-dir --break-system-packages certbot-dns-cloudflare + msg_ok "Setup Enviroment" + + msg_info "Building Frontend" + cd ./frontend + $STD pnpm install + $STD pnpm upgrade + $STD pnpm run build + cp -r dist/* /app/frontend + cp -r app-images/* /app/frontend/images + msg_ok "Built Frontend" + + msg_info "Initializing Backend" + $STD rm -rf /app/config/default.json + if [ ! -f /app/config/production.json ]; then + cat <<'EOF' >/app/config/production.json +{ + "database": { + "engine": "knex-native", + "knex": { + "client": "sqlite3", + "connection": { + "filename": "/data/database.sqlite" + } + } + } +} +EOF + fi + cd /app + $STD pnpm install + msg_ok "Initialized Backend" + + msg_info "Starting Services" + sed -i 's/user npm/user root/g; s/^pid/#pid/g' /usr/local/openresty/nginx/conf/nginx.conf + sed -i 's/su npm npm/su root root/g' /etc/logrotate.d/nginx-proxy-manager + sed -i 's/include-system-site-packages = false/include-system-site-packages = true/g' /opt/certbot/pyvenv.cfg + systemctl enable -q --now openresty + systemctl enable -q --now npm + msg_ok "Started Services" + + msg_info "Cleaning up" + rm -rf ~/nginx-proxy-manager-* + msg_ok "Cleaned" + + msg_ok "Updated Successfully" + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:81${CL}" diff --git a/ct/npmplus.sh b/ct/npmplus.sh deleted file mode 100644 index 9e3b0917..00000000 --- a/ct/npmplus.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env bash -source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) -# Copyright (c) 2021-2025 community-scripts ORG -# Author: MickLesk (CanbiZ) -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/ZoeyVid/NPMplus - -APP="NPMplus" -var_tags="${var_tags:-proxy;nginx}" -var_cpu="${var_cpu:-1}" -var_ram="${var_ram:-512}" -var_disk="${var_disk:-3}" -var_os="${var_os:-alpine}" -var_version="${var_version:-3.21}" -var_unprivileged="${var_unprivileged:-1}" - -header_info "$APP" -variables -color -catch_errors - -function update_script() { - UPD=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "UPDATE MODE" --radiolist --cancel-button Exit-Script "Spacebar = Select" 14 60 2 \ - "1" "Check for Alpine Updates" OFF \ - "2" "Update NPMplus Docker Container" ON \ - 3>&1 1>&2 2>&3) - - header_info "$APP" - - case "$UPD" in - "1") - msg_info "Updating Alpine OS" - $STD apk -U upgrade - msg_ok "System updated" - exit - ;; - "2") - msg_info "Updating NPMplus Container" - cd /opt - msg_info "Pulling latest container image" - $STD docker compose pull - msg_info "Recreating container" - $STD docker compose up -d - msg_ok "NPMplus container updated" - exit - ;; - esac - exit 0 -} - -start -build_container -description - -msg_ok "Completed Successfully!\n" -echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" -echo -e "${INFO}${YW} Access it using the following URL:${CL}" -echo -e "${TAB}${GATEWAY}${BGN}https://${IP}:81${CL}" diff --git a/ct/ots.sh b/ct/ots.sh new file mode 100644 index 00000000..e3e03df9 --- /dev/null +++ b/ct/ots.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +source <(curl -s https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func) +# Copyright (c) 2021-2025 community-scripts ORG +# Author: bvdberg01 +# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE +# Source: https://github.com/Luzifer/ots + +APP="OTS" +var_tags="${var_tags:-secrets-sharer}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-512}" +var_disk="${var_disk:-3}" +var_os="${var_os:-debian}" +var_version="${var_version:-12}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -d /opt/ots ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + RELEASE=$(curl -fsSL https://api.github.com/repos/Luzifer/ots/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') + if [[ ! -f /opt/${APP}_version.txt ]] || [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]]; then + + msg_info "Stopping ${APP} Service" + systemctl stop ots + msg_ok "Stopped ${APP} Service" + + msg_info "Updating ${APP} to v${RELEASE}" + fetch_and_deploy_gh_release "ots" "Luzifer/ots" "prebuild" "latest" "/opt/ots" "ots_linux_amd64.tgz" + msg_ok "Updated ${APP} to v${RELEASE}" + + msg_info "Stopping ${APP} Service" + systemctl start ots + msg_ok "Stopped ${APP} Service" + + else + msg_ok "No update required. ${APP} is already at ${RELEASE}" + fi + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}" diff --git a/ct/mealie.sh b/ct/rybbit.sh similarity index 67% rename from ct/mealie.sh rename to ct/rybbit.sh index d233296b..e9164be4 100644 --- a/ct/mealie.sh +++ b/ct/rybbit.sh @@ -3,13 +3,13 @@ source <(curl -s https://git.community-scripts.org/community-scripts/ProxmoxVED/ # Copyright (c) 2021-2025 community-scripts ORG # Author: MickLesk (CanbiZ) # License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE -# Source: https://mealie.io +# Source: https://github.com/rybbit-io/rybbit -APP="Mealie" -var_tags="${var_tags:-recipes}" -var_cpu="${var_cpu:-4}" -var_ram="${var_ram:-4096}" -var_disk="${var_disk:-10}" +APP="Rybbit" +var_tags="${var_tags:-analytics}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-2048}" +var_disk="${var_disk:-5}" var_os="${var_os:-debian}" var_version="${var_version:-12}" var_unprivileged="${var_unprivileged:-1}" @@ -38,7 +38,5 @@ start build_container description -msg_ok "Completed Successfully!\n" -echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" -echo -e "${INFO}${YW} Access it using the following URL:${CL}" -echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:9000${CL}" +msg_ok "Completed Successfully!" +msg_custom "πŸš€" "${GN}" "${APP} setup has been successfully initialized!" diff --git a/ct/saltmaster.sh b/ct/saltmaster.sh deleted file mode 100644 index 9cf36a9a..00000000 --- a/ct/saltmaster.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -source <(curl -s https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func) -# Copyright (c) 2021-2025 community-scripts ORG -# Author: bvdberg01 -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/saltstack/salt - -APP="saltmaster" -var_tags="${var_tags:-automations}" -var_cpu="${var_cpu:-1}" -var_ram="${var_ram:-1024}" -var_disk="${var_disk:-3}" -var_os="${var_os:-debian}" -var_version="${var_version:-12}" -var_unprivileged="${var_unprivileged:-1}" - -header_info "$APP" -variables -color -catch_errors - -function update_script() { - header_info - check_container_storage - check_container_resources - - if [[ ! -d /etc/salt ]]; then - msg_error "No ${APP} Installation Found!" - exit - fi - - RELEASE=$(curl -fsSL https://api.github.com/repos/saltstack/salt/releases/latest | jq -r .tag_name | sed 's/^v//') - if [[ ! -f /opt/${APP}_version.txt ]] || [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]]; then - msg_info "Updating $APP to ${RELEASE}" - sed -i "s/^\(Pin: version \).*/\1${RELEASE}/" /etc/apt/preferences.d/salt-pin-1001 - $STD apt-get update - $STD apt-get upgrade -y - echo "${RELEASE}" >/opt/${APP}_version.txt - msg_ok "Updated ${APP} to ${RELEASE}" - else - msg_ok "${APP} is already up to date (${RELEASE})" - fi -} - -start -build_container -description - -msg_ok "Completed Successfully!\n" -echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" diff --git a/ct/scraparr.sh b/ct/scraparr.sh new file mode 100644 index 00000000..b7eeee7e --- /dev/null +++ b/ct/scraparr.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) +# Copyright (c) 2021-2025 community-scripts ORG +# Author: JasonGreenC +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/thecfu/scraparr + +APP="Scraparr" +var_tags="${var_tags:-arr;monitoring}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-1024}" +var_disk="${var_disk:-4}" +var_os="${var_os:-debian}" +var_version="${var_version:-12}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + if [[ ! -d /opt/scraparr/ ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + RELEASE=$(curl -fsSL https://api.github.com/repos/thecfu/scraparr/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }') + if [[ ! -f "${HOME}/.scrappar" ]] || [[ "${RELEASE}" != "$(cat "${HOME}"/.scrappar)" ]]; then + msg_info "Stopping Services" + systemctl stop scraparr + msg_ok "Services Stopped" + + PYTHON_VERSION="3.12" setup_uv + fetch_and_deploy_gh_release "scrappar" "thecfu/scraparr" "tarball" "latest" "/opt/scraparr" + + msg_info "Updating ${APP} to ${RELEASE}" + cd /opt/scraparr || exit + $STD uv venv /opt/scraparr/.venv + $STD /opt/scraparr/.venv/bin/python -m ensurepip --upgrade + $STD /opt/scraparr/.venv/bin/python -m pip install --upgrade pip + $STD /opt/scraparr/.venv/bin/python -m pip install -r /opt/scraparr/src/scraparr/requirements.txt + chmod -R 755 /opt/scraparr + msg_ok "Updated ${APP} to v${RELEASE}" + + msg_info "Starting Services" + systemctl start scraparr + msg_ok "Services Started" + else + msg_ok "No update required. ${APP} is already at v${RELEASE}" + fi + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:7100${CL}" diff --git a/ct/tududi.sh b/ct/tududi.sh new file mode 100644 index 00000000..a4d6a4d4 --- /dev/null +++ b/ct/tududi.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) +# Copyright (c) 2021-2025 community-scripts ORG +# Author: vhsdream +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://tududi.com + +APP="Tududi" +var_tags="${var_tags:-todo-app}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-2048}" +var_disk="${var_disk:-4}" +var_os="${var_os:-debian}" +var_version="${var_version:-12}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -d /opt/tududi ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + RELEASE=$(curl -fsSL https://api.github.com/repos/chrisvel/tududi/releases/latest | yq '.tag_name' | sed 's/^v//') + if [[ "${RELEASE}" != "$(cat ~/.tududi 2>/dev/null)" ]] || [[ ! -f ~/.tududi ]]; then + msg_info "Stopping Service" + systemctl stop tududi + msg_ok "Stopped Service" + + msg_info "Updating ${APP}" + cp /opt/"$APP"/backend/.env /opt/"$APP".env + rm -rf /opt/"$APP" + fetch_and_deploy_gh_release "tududi" "chrisvel/tududi" + + cd /opt/"$APP" + $STD npm install + export NODE_ENV=production + $STD npm run frontend:build + cp -r ./dist ./backend/dist + cp -r ./public/locales ./backend/dist/locales + cp ./public/favicon.* ./backend/dist + mv /opt/"$APP".env /opt/"$APP"/.env + msg_ok "Updated $APP" + + msg_info "Starting Service" + systemctl start tududi + msg_ok "Started Service" + + msg_ok "Updated Successfully" + else + msg_ok "Already up to date" + fi + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${ADVANCED}${BL}Create your initial user in${CL} ${BGN}/opt/${APP}${CL}${BL} in the LXC:${CL} ${RD}npm run user:create ${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3002${CL}" diff --git a/ct/vikunja.sh b/ct/vikunja.sh new file mode 100644 index 00000000..792e3b1e --- /dev/null +++ b/ct/vikunja.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) +# Copyright (c) 2021-2025 community-scripts ORG +# Author: MickLesk (Canbiz) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://vikunja.io/ + +APP="Vikunja" +var_tags="${var_tags:-todo-app}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-1024}" +var_disk="${var_disk:-4}" +var_os="${var_os:-debian}" +var_version="${var_version:-12}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -d /opt/vikunja ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + if whiptail --backtitle "Vikunja Update" --title "πŸ”„ VERSION SELECTION" --yesno \ + "Choose the version type to update to:\n\nβ€’ STABLE: Recommended for production use\nβ€’ UNSTABLE: Latest development version\n\n⚠️ WARNING: Unstable versions may contain bugs,\nbe incomplete, or cause system instability.\nOnly use for testing purposes.\n\nDo you want to use the UNSTABLE version?\n(No = Stable, Yes = Unstable)" 16 70 --defaultno + then + msg_info "Selecting version" + RELEASE="unstable" + FILENAME="vikunja-${RELEASE}-x86_64.deb" + msg_ok "Selected UNSTABLE version" + else + msg_info "Selecting version" + RELEASE=$(curl -fsSL https://dl.vikunja.io/vikunja/ | grep -oP 'href="/vikunja/\K[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -n 1) + FILENAME="vikunja-${RELEASE}-amd64.deb" + msg_ok "Selected STABLE version: ${RELEASE}" + fi + + if [[ ! -f /opt/${APP}_version.txt ]] || [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]]; then + msg_info "Stopping ${APP}" + systemctl stop vikunja + msg_ok "Stopped ${APP}" + msg_info "Updating ${APP} to ${RELEASE}" + cd /opt + rm -rf /opt/vikunja/vikunja + rm -rf "/opt/$FILENAME" + curl -fsSL "https://dl.vikunja.io/vikunja/$RELEASE/$FILENAME" -o $(basename "https://dl.vikunja.io/vikunja/$RELEASE/$FILENAME") + export DEBIAN_FRONTEND=noninteractive + $STD dpkg -i $FILENAME + echo "${RELEASE}" >/opt/${APP}_version.txt + msg_ok "Updated ${APP}" + msg_info "Starting ${APP}" + systemctl start vikunja + msg_ok "Started ${APP}" + msg_info "Cleaning Up" + rm -rf /opt/$FILENAME + msg_ok "Cleaned" + msg_ok "Updated Successfully" + else + msg_ok "No update required. ${APP} is already at ${RELEASE}" + fi + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3456${CL}" diff --git a/ct/viseron.sh b/ct/viseron.sh new file mode 100644 index 00000000..32cb237e --- /dev/null +++ b/ct/viseron.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +source <(curl -s https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func) + +APP="Viseron" +var_tags="${var_tags:-nvr}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-2048}" +var_disk="${var_disk:-10}" +var_os="${var_os:-debian}" +var_version="${var_version:-12}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" + +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -f /etc/systemd/system/viseron.service ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + msg_error "To update Viseron, create a new container and transfer your configuration." + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8888${CL}" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9c8b7c97..fe7ecb63 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4082,6 +4082,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4694,6 +4708,21 @@ "dev": true, "license": "MIT" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -4804,14 +4833,11 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -4860,9 +4886,9 @@ "license": "MIT" }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, "license": "MIT", "dependencies": { @@ -4873,15 +4899,16 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -5728,14 +5755,16 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -5851,17 +5880,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5879,6 +5913,20 @@ "node": ">=6" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-symbol-description": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", @@ -5982,13 +6030,13 @@ "license": "MIT" }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6055,9 +6103,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", "engines": { @@ -7037,6 +7085,16 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", diff --git a/frontend/public/json/bar-assistant.json b/frontend/public/json/bar-assistant.json deleted file mode 100644 index 9084595d..00000000 --- a/frontend/public/json/bar-assistant.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Bar-Assistant", - "slug": "bar-assistant", - "categories": [ - 24 - ], - "date_created": "2025-05-25", - "type": "ct", - "updateable": true, - "privileged": false, - "interface_port": 80, - "documentation": "https://docs.barassistant.app/", - "website": "https://barassistant.app/", - "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/bar-assistant.webp", - "config_path": "/opt/bar-assistant", - "description": "Bar assistant is a all-in-one solution for managing your home bar.", - "install_methods": [ - { - "type": "default", - "script": "ct/bar-assistant.sh", - "resources": { - "cpu": 2, - "ram": 2048, - "hdd": 4, - "os": "ubuntu", - "version": "24.10" - } - } - ], - "default_credentials": { - "username": null, - "password": null - }, - "notes": [] -} diff --git a/frontend/public/json/docspell.json b/frontend/public/json/docspell.json new file mode 100644 index 00000000..dbf8e95a --- /dev/null +++ b/frontend/public/json/docspell.json @@ -0,0 +1,35 @@ +{ + "name": "Docspell", + "slug": "docspell", + "categories": [ + 12 + ], + "date_created": "2025-07-02", + "type": "ct", + "updateable": true, + "privileged": false, + "config_path": "/opt/docspell/.env", + "interface_port": 3000, + "documentation": "https://docspell.io/", + "website": "https://docspell.io/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/docspell.svg", + "description": "Docspell is an open-source self-hosted application.", + "install_methods": [ + { + "type": "default", + "script": "ct/docspell.sh", + "resources": { + "cpu": 1, + "ram": 1024, + "hdd": 2, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} \ No newline at end of file diff --git a/frontend/public/json/frigate.json b/frontend/public/json/frigate.json new file mode 100644 index 00000000..b5a8d7d6 --- /dev/null +++ b/frontend/public/json/frigate.json @@ -0,0 +1,35 @@ +{ + "name": "Frigate", + "slug": "frigate", + "categories": [ + 15 + ], + "date_created": "2025-07-02", + "type": "ct", + "updateable": true, + "privileged": false, + "config_path": "/opt/frigate/.env", + "interface_port": 3000, + "documentation": "https://frigate.io/", + "website": "https://frigate.io/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/frigate.svg", + "description": "Frigate is an open-source self-hosted application.", + "install_methods": [ + { + "type": "default", + "script": "ct/frigate.sh", + "resources": { + "cpu": 1, + "ram": 1024, + "hdd": 2, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} \ No newline at end of file diff --git a/frontend/public/json/hanko.json b/frontend/public/json/hanko.json new file mode 100644 index 00000000..d8628ad8 --- /dev/null +++ b/frontend/public/json/hanko.json @@ -0,0 +1,35 @@ +{ + "name": "Hanko", + "slug": "hanko", + "categories": [ + 21 + ], + "date_created": "2025-07-02", + "type": "ct", + "updateable": true, + "privileged": false, + "config_path": "/opt/hanko/.env", + "interface_port": 3000, + "documentation": "https://docs.hanko.io/", + "website": "https://hanko.io/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/hanko.svg", + "description": "Hanko is an open-source authentication solution providing passkey-first login with support for WebAuthn/FIDO2, biometrics and modern identity flows. Easy to self-host and integrate via API or widget.", + "install_methods": [ + { + "type": "default", + "script": "ct/hanko.sh", + "resources": { + "cpu": 1, + "ram": 1024, + "hdd": 2, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} diff --git a/frontend/public/json/healthchecks.json b/frontend/public/json/healthchecks.json new file mode 100644 index 00000000..ac5f3fe2 --- /dev/null +++ b/frontend/public/json/healthchecks.json @@ -0,0 +1,35 @@ +{ + "name": "Healthchecks", + "slug": "healthchecks", + "categories": [ + 9 + ], + "date_created": "2025-07-02", + "type": "ct", + "updateable": true, + "privileged": false, + "config_path": "/opt/healthchecks/.env", + "interface_port": 3000, + "documentation": "https://healthchecks.io/", + "website": "https://healthchecks.io/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/healthchecks.svg", + "description": "Healthchecks is an open-source self-hosted application.", + "install_methods": [ + { + "type": "default", + "script": "ct/healthchecks.sh", + "resources": { + "cpu": 1, + "ram": 1024, + "hdd": 2, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} \ No newline at end of file diff --git a/frontend/public/json/itsmng.json b/frontend/public/json/itsmng.json deleted file mode 100644 index a3bef7af..00000000 --- a/frontend/public/json/itsmng.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "ITSMNG", - "slug": "itsmng", - "categories": [ - 25 - ], - "date_created": "2025-06-20", - "type": "ct", - "updateable": true, - "privileged": false, - "interface_port": 80, - "documentation": "https://wiki.itsm-ng.org/en/home", - "website": "https://itsm-ng.com", - "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/itsm-ng.svg", - "config_path": "/etc/itsm-ng", - "description": "ITSM-NG is a Free Asset and IT Management Software package, Data center management, ITIL Service Desk, licenses tracking and software auditing.", - "install_methods": [ - { - "type": "default", - "script": "ct/itsmng.sh", - "resources": { - "cpu": 2, - "ram": 2048, - "hdd": 10, - "os": "debian", - "version": "12" - } - } - ], - "default_credentials": { - "username": "itsm", - "password": "itsm" - }, - "notes": [] -} diff --git a/frontend/public/json/kapowarr.json b/frontend/public/json/kapowarr.json deleted file mode 100644 index e5d69d07..00000000 --- a/frontend/public/json/kapowarr.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Kapowarr", - "slug": "kapowarr", - "categories": [ - 14 - ], - "date_created": "2025-06-09", - "type": "ct", - "updateable": true, - "privileged": false, - "interface_port": 5656, - "documentation": "https://casvt.github.io/Kapowarr/general_info/workings/", - "website": "https://casvt.github.io/Kapowarr/", - "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/kapowarr.webp", - "config_path": "", - "description": "Kapowarr allows you to build a digital library of comics. You can add volumes, map them to a folder and start managing! Download, rename, move and convert issues of the volume (including TPB's, One Shots, Hard Covers, and more). The whole process is automated and can be customised in the settings.", - "install_methods": [ - { - "type": "default", - "script": "ct/kapowarr.sh", - "resources": { - "cpu": 1, - "ram": 256, - "hdd": 2, - "os": "debian", - "version": "12" - } - } - ], - "default_credentials": { - "username": null, - "password": null - }, - "notes": [] -} diff --git a/frontend/public/json/leantime.json b/frontend/public/json/leantime.json new file mode 100644 index 00000000..e5e0fec8 --- /dev/null +++ b/frontend/public/json/leantime.json @@ -0,0 +1,35 @@ +{ + "name": "Leantime", + "slug": "leantime", + "categories": [ + 12 + ], + "date_created": "2025-06-27", + "type": "ct", + "updateable": true, + "privileged": false, + "interface_port": 80, + "documentation": "https://docs.leantime.io/", + "config_path": "/opt/Leantime/config/.env", + "website": "https://leantime.io", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/leantime.webp", + "description": "Leantime is a goals focused project management system for non-project managers. Building with ADHD, Autism, and dyslexia in mind. ", + "install_methods": [ + { + "type": "default", + "script": "ct/leantime.sh", + "resources": { + "cpu": 2, + "ram": 2048, + "hdd": 20, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} diff --git a/frontend/public/json/linkstack.json b/frontend/public/json/linkstack.json index 7c706345..33c6d980 100644 --- a/frontend/public/json/linkstack.json +++ b/frontend/public/json/linkstack.json @@ -1,44 +1,44 @@ { - "name": "LinkStack", - "slug": "linkstack", - "categories": [ - 9 - ], - "date_created": "2025-05-21", - "type": "ct", - "updateable": false, - "privileged": false, - "config_path": "/var/www/html/linkstack/linkstack/.env", - "interface_port": 80, - "documentation": "https://docs.linkstack.org/", - "website": "https://linkstack.org/", - "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/linkstack.webp", - "description": "LinkStack is an open-source, self-hosted alternative to Linktree, allowing users to create a customizable profile page to share multiple links, hosted on their own server.", - "install_methods": [ - { - "type": "default", - "script": "ct/linkstack.sh", - "resources": { - "cpu": 1, - "ram": 2048, - "hdd": 10, - "os": "Debian", - "version": "12" - } - } - ], - "default_credentials": { - "username": null, - "password": null + "name": "LinkStack", + "slug": "linkstack", + "categories": [ + 9 + ], + "date_created": "2025-07-22", + "type": "ct", + "updateable": true, + "privileged": false, + "config_path": "/var/www/html/linkstack/.env", + "interface_port": 80, + "documentation": "https://docs.linkstack.org/", + "website": "https://linkstack.org/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/linkstack.webp", + "description": "LinkStack is an open-source, self-hosted alternative to Linktree, allowing users to create a customizable profile page to share multiple links, hosted on their own server.", + "install_methods": [ + { + "type": "default", + "script": "ct/linkstack.sh", + "resources": { + "cpu": 1, + "ram": 1024, + "hdd": 5, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [ + { + "text": "LinkStack can be updated via the user interface or with the command `update`.", + "type": "info" }, - "notes": [ - { - "text": "LinkStack can be updated via the user interface.", - "type": "info" - }, - { - "text": "Complete setup via the web interface at http:///. Check installation logs: `cat ~/linkstack-install.log`", - "type": "info" - } - ] + { + "text": "Complete setup via the web interface at http:///. Check installation logs: `cat ~/linkstack-install.log`", + "type": "info" + } + ] } diff --git a/frontend/public/json/maxun.json b/frontend/public/json/maxun.json new file mode 100644 index 00000000..7ed83e0d --- /dev/null +++ b/frontend/public/json/maxun.json @@ -0,0 +1,35 @@ +{ + "name": "Maxun", + "slug": "maxun", + "categories": [ + 0 + ], + "date_created": "2025-07-02", + "type": "ct", + "updateable": true, + "privileged": false, + "config_path": "/opt/maxun/.env", + "interface_port": 3000, + "documentation": "https://maxun.io/", + "website": "https://maxun.io/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/maxun.svg", + "description": "Maxun is an open-source self-hosted application.", + "install_methods": [ + { + "type": "default", + "script": "ct/maxun.sh", + "resources": { + "cpu": 1, + "ram": 1024, + "hdd": 2, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} \ No newline at end of file diff --git a/frontend/public/json/postiz.json b/frontend/public/json/postiz.json new file mode 100644 index 00000000..dfcb0f04 --- /dev/null +++ b/frontend/public/json/postiz.json @@ -0,0 +1,35 @@ +{ + "name": "Postiz", + "slug": "postiz", + "categories": [ + 20 + ], + "date_created": "2025-07-02", + "type": "ct", + "updateable": true, + "privileged": false, + "config_path": "/opt/postiz/.env", + "interface_port": 3000, + "documentation": "https://postiz.io/", + "website": "https://postiz.io/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/postiz.svg", + "description": "Postiz is an open-source self-hosted application.", + "install_methods": [ + { + "type": "default", + "script": "ct/postiz.sh", + "resources": { + "cpu": 1, + "ram": 1024, + "hdd": 2, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} \ No newline at end of file diff --git a/frontend/public/json/salt.json b/frontend/public/json/salt.json new file mode 100644 index 00000000..41750a2f --- /dev/null +++ b/frontend/public/json/salt.json @@ -0,0 +1,35 @@ +{ + "name": "Salt", + "slug": "salt", + "categories": [ + 19 + ], + "date_created": "2025-07-02", + "type": "ct", + "updateable": true, + "privileged": false, + "config_path": "/opt/salt/.env", + "interface_port": 3000, + "documentation": "https://docs.saltproject.io/salt/install-guide/en/latest/", + "website": "https://saltproject.io/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/saltmaster.svg", + "description": "SaltStack Salt is a software for automating the management and configuration of IT infrastructure and applications. It is an event-driven automation tool and framework used to deploy, configure, and manage complex IT systems. Its primary functions include configuration management, where it ensures consistent configurations and manages operating system deployment and software installation. It also automates and orchestrates routine IT processes and can create self-aware, self-healing systems.", + "install_methods": [ + { + "type": "default", + "script": "ct/salt.sh", + "resources": { + "cpu": 1, + "ram": 1024, + "hdd": 2, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} diff --git a/frontend/public/json/scraparr.json b/frontend/public/json/scraparr.json new file mode 100644 index 00000000..731618e3 --- /dev/null +++ b/frontend/public/json/scraparr.json @@ -0,0 +1,40 @@ +{ + "name": "Scraparr", + "slug": "scraparr", + "categories": [ + 14 + ], + "date_created": "2024-05-02", + "type": "ct", + "updateable": true, + "privileged": false, + "interface_port": 7100, + "documentation": "https://github.com/thecfu/scraparr/blob/main/README.md", + "website": "https://github.com/thecfu/scraparr", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/scraparr-dark.svg", + "config_path": "/scraparr/config/config.yaml", + "description": "Scraparr is a Prometheus exporter for the *arr suite (Sonarr, Radarr, Lidarr, etc.). It provides metrics that can be scraped by Prometheus to monitor and visualize the health and performance of your *arr applications.", + "install_methods": [ + { + "type": "default", + "script": "ct/scraparr.sh", + "resources": { + "cpu": 2, + "ram": 1024, + "hdd": 4, + "os": "debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [ + { + "text": "Edit config file then restart the scraparr service: `systemctl restart scraparr`", + "type": "info" + } + ] +} diff --git a/frontend/public/json/teamspeak-server.json b/frontend/public/json/teamspeak-server.json new file mode 100644 index 00000000..98dbec48 --- /dev/null +++ b/frontend/public/json/teamspeak-server.json @@ -0,0 +1,39 @@ +{ + "name": "Teamspeak Server", + "slug": "teamspeak-server", + "categories": [ + 24 + ], + "date_created": "2025-07-21", + "type": "ct", + "updateable": true, + "privileged": false, + "interface_port": 9987, + "documentation": "https://support.teamspeak.com/hc/en-us/categories/360000302017-TeamSpeak-3", + "website": "https://teamspeak.com/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/teamspeak-light.svgp", + "config_path": "", + "description": "TeamSpeak is a voice‑over‑IP (VoIP) application, primarily used by gamers and teams to chat in real‑time on dedicated servers. It delivers crystal‑clear, low‑latency voice communication.""install_methods": [ + { + "type": "default", + "script": "ct/teamspeak-server.sh", + "resources": { + "cpu": 1, + "ram": 512, + "hdd": 2, + "os": "debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [ + { + "text": "Use `journalctl -u teamspeak-server.service` inside LXC console to check for admin credentials!", + "type": "info" + } + ] +} diff --git a/frontend/public/json/tududi.json b/frontend/public/json/tududi.json new file mode 100644 index 00000000..fd884ef5 --- /dev/null +++ b/frontend/public/json/tududi.json @@ -0,0 +1,40 @@ +{ + "name": "Tududi", + "slug": "tududi", + "categories": [ + 12 + ], + "date_created": "2025-07-07", + "type": "ct", + "updateable": true, + "privileged": false, + "interface_port": 3002, + "documentation": null, + "config_path": "/opt/tududi/backend/.env", + "website": "https://tududi.com/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/tududi.webp", + "description": "Self-hosted task management with functional programming architecture, hierarchical organization, and multi-language support.", + "install_methods": [ + { + "type": "default", + "script": "ct/tududi.sh", + "resources": { + "cpu": 2, + "ram": 2048, + "hdd": 4, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [ + { + "text": "Create users like this: `cd /opt/tududi` => `npm run user:create `", + "type": "info" + } + ] +} diff --git a/frontend/public/json/versions.json b/frontend/public/json/versions.json index 4dae1a1d..ccd34e86 100644 --- a/frontend/public/json/versions.json +++ b/frontend/public/json/versions.json @@ -1,108 +1,48 @@ [ { - "name": "Jackett/Jackett", - "version": "v0.22.2075", - "date": "2025-06-28T10:16:17Z" + "name": "theonedev/onedev", + "version": "v12.0.0", + "date": "2025-07-14T06:19:59Z" + }, + { + "name": "wazuh/wazuh", + "version": "coverity-w28-4.13.0", + "date": "2025-07-08T11:25:24Z" }, { "name": "Luligu/matterbridge", - "version": "3.1.0", - "date": "2025-06-28T09:02:38Z" + "version": "3.1.3", + "date": "2025-07-14T07:14:31Z" + }, + { + "name": "morpheus65535/bazarr", + "version": "v1.5.3-beta.9", + "date": "2025-07-14T06:07:20Z" + }, + { + "name": "Jackett/Jackett", + "version": "v0.22.2154", + "date": "2025-07-14T06:00:33Z" }, { "name": "firefly-iii/firefly-iii", - "version": "v6.2.19", - "date": "2025-06-28T06:53:45Z" + "version": "v6.2.20", + "date": "2025-07-02T04:03:37Z" }, { - "name": "esphome/esphome", - "version": "2025.6.2", - "date": "2025-06-28T03:47:16Z" + "name": "steveiliop56/tinyauth", + "version": "v3.6.1", + "date": "2025-07-12T13:41:57Z" }, { - "name": "plexguide/Huntarr.io", - "version": "8.1.11", - "date": "2025-06-28T03:42:46Z" + "name": "homebridge/homebridge", + "version": "v1.11.0", + "date": "2025-07-13T19:22:47Z" }, { - "name": "tobychui/zoraxy", - "version": "v3.2.4", - "date": "2025-06-28T02:47:31Z" - }, - { - "name": "pocket-id/pocket-id", - "version": "v1.5.0", - "date": "2025-06-27T22:04:32Z" - }, - { - "name": "linkwarden/linkwarden", - "version": "v2.11.1", - "date": "2025-06-27T21:21:59Z" - }, - { - "name": "homarr-labs/homarr", - "version": "v1.26.0", - "date": "2025-06-27T19:15:24Z" - }, - { - "name": "ollama/ollama", - "version": "v0.9.4-rc1", - "date": "2025-06-27T18:45:33Z" - }, - { - "name": "home-assistant/core", - "version": "2025.6.3", - "date": "2025-06-24T13:00:12Z" - }, - { - "name": "mattermost/mattermost", - "version": "preview-v0.1", - "date": "2025-06-27T14:35:47Z" - }, - { - "name": "goauthentik/authentik", - "version": "version/2025.6.3", - "date": "2025-06-27T14:01:06Z" - }, - { - "name": "keycloak/keycloak", - "version": "26.2.5", - "date": "2025-05-28T06:49:43Z" - }, - { - "name": "rclone/rclone", - "version": "v1.70.2", - "date": "2025-06-27T13:21:17Z" - }, - { - "name": "documenso/documenso", - "version": "v1.12.0-rc.7", - "date": "2025-06-27T12:17:45Z" - }, - { - "name": "sabnzbd/sabnzbd", - "version": "4.5.1", - "date": "2025-04-11T09:57:47Z" - }, - { - "name": "FlowiseAI/Flowise", - "version": "flowise@3.0.3", - "date": "2025-06-27T09:53:57Z" - }, - { - "name": "nzbgetcom/nzbget", - "version": "v25.1", - "date": "2025-06-27T09:14:14Z" - }, - { - "name": "cockpit-project/cockpit", - "version": "341.1", - "date": "2025-06-27T08:50:16Z" - }, - { - "name": "zabbix/zabbix", - "version": "7.2.10", - "date": "2025-06-27T06:40:00Z" + "name": "wavelog/wavelog", + "version": "2.0.6", + "date": "2025-07-13T19:10:15Z" }, { "name": "MediaBrowser/Emby.Releases", @@ -110,289 +50,284 @@ "date": "2025-06-26T22:08:00Z" }, { - "name": "prometheus/prometheus", - "version": "v3.4.2", - "date": "2025-06-26T21:45:21Z" - }, - { - "name": "home-assistant/operating-system", - "version": "15.2", - "date": "2025-04-14T15:37:12Z" - }, - { - "name": "netbox-community/netbox", - "version": "v4.3.3", - "date": "2025-06-26T18:42:56Z" - }, - { - "name": "apache/tika", - "version": "3.2.1-rc2", - "date": "2025-06-26T17:10:25Z" - }, - { - "name": "tailscale/tailscale", - "version": "v1.84.3", - "date": "2025-06-26T16:31:57Z" - }, - { - "name": "fuma-nama/fumadocs", - "version": "fumadocs-ui@15.5.5", - "date": "2025-06-26T15:54:17Z" - }, - { - "name": "traefik/traefik", - "version": "v3.5.0-rc1", - "date": "2025-06-26T15:08:43Z" - }, - { - "name": "meilisearch/meilisearch", - "version": "prototype-no-simd-x86-arroy-0", - "date": "2025-06-26T14:54:18Z" - }, - { - "name": "AdguardTeam/AdGuardHome", - "version": "v0.107.63", - "date": "2025-06-26T14:34:19Z" - }, - { - "name": "node-red/node-red", - "version": "4.1.0-beta.2", - "date": "2025-06-26T14:23:26Z" - }, - { - "name": "Dolibarr/dolibarr", - "version": "18.0.7", - "date": "2025-06-26T09:16:33Z" - }, - { - "name": "mongodb/mongo", - "version": "r8.1.2-rc1", - "date": "2025-06-25T22:42:04Z" - }, - { - "name": "rcourtman/Pulse", - "version": "v3.32.0", - "date": "2025-06-25T22:27:01Z" - }, - { - "name": "gristlabs/grist-core", - "version": "v1.6.1", - "date": "2025-06-25T21:19:25Z" - }, - { - "name": "coder/code-server", - "version": "v4.101.2", - "date": "2025-06-25T21:18:52Z" + "name": "openhab/openhab-core", + "version": "5.0.0.M4", + "date": "2025-07-13T16:04:39Z" }, { "name": "msgbyte/tianji", - "version": "v1.22.4", - "date": "2025-06-25T20:46:20Z" + "version": "v1.23.5", + "date": "2025-07-13T15:23:02Z" }, { - "name": "influxdata/influxdb", - "version": "v3.2.0", - "date": "2025-06-25T17:31:48Z" - }, - { - "name": "wavelog/wavelog", - "version": "2.0.5", - "date": "2025-06-25T14:53:31Z" - }, - { - "name": "jenkinsci/jenkins", - "version": "jenkins-2.504.3", - "date": "2025-06-25T14:43:01Z" - }, - { - "name": "bunkerity/bunkerweb", - "version": "testing", - "date": "2025-06-16T18:10:42Z" - }, - { - "name": "n8n-io/n8n", - "version": "n8n@1.100.0", - "date": "2025-06-23T12:48:35Z" - }, - { - "name": "moghtech/komodo", - "version": "v1.18.4", - "date": "2025-06-25T00:06:56Z" - }, - { - "name": "duplicati/duplicati", - "version": "v2.1.0.120-2.1.0.120_canary_2025-06-24", - "date": "2025-06-24T22:39:50Z" + "name": "mayswind/AriaNg", + "version": "1.3.11", + "date": "2025-07-13T13:33:48Z" }, { "name": "evcc-io/evcc", - "version": "0.204.5", - "date": "2025-06-24T19:17:16Z" + "version": "0.205.0", + "date": "2025-07-13T12:27:31Z" }, { - "name": "ErsatzTV/ErsatzTV", - "version": "v25.2.0", - "date": "2025-06-24T17:06:31Z" + "name": "rcourtman/Pulse", + "version": "v3.41.1", + "date": "2025-07-10T17:10:46Z" }, { - "name": "arunavo4/gitea-mirror", - "version": "v2.18.0", - "date": "2025-06-24T08:29:55Z" + "name": "authelia/authelia", + "version": "v4.39.5", + "date": "2025-07-13T06:12:47Z" }, { - "name": "element-hq/synapse", - "version": "v1.132.0", - "date": "2025-06-17T13:49:30Z" + "name": "henrygd/beszel", + "version": "v0.11.1", + "date": "2025-04-29T01:14:35Z" }, { - "name": "docker/compose", - "version": "v2.37.3", - "date": "2025-06-24T14:05:33Z" - }, - { - "name": "Checkmk/checkmk", - "version": "v2.4.0p5", - "date": "2025-06-24T13:06:53Z" - }, - { - "name": "fallenbagel/jellyseerr", - "version": "preview-fix-proxy-axios", - "date": "2025-06-24T08:50:22Z" - }, - { - "name": "wazuh/wazuh", - "version": "coverity-w26-4.13.0", - "date": "2025-06-24T02:02:34Z" - }, - { - "name": "minio/minio", - "version": "RELEASE.2025-06-13T11-33-47Z", - "date": "2025-06-23T20:58:42Z" - }, - { - "name": "runtipi/runtipi", - "version": "v4.2.1", - "date": "2025-06-03T20:04:28Z" - }, - { - "name": "VictoriaMetrics/VictoriaMetrics", - "version": "pmm-6401-v1.120.0", - "date": "2025-06-23T15:12:12Z" - }, - { - "name": "Graylog2/graylog2-server", - "version": "6.3.0-rc.2", - "date": "2025-06-23T11:31:38Z" - }, - { - "name": "gotson/komga", - "version": "1.22.0", - "date": "2025-06-23T03:11:37Z" + "name": "esphome/esphome", + "version": "2025.6.3", + "date": "2025-07-03T01:07:26Z" }, { "name": "OliveTin/OliveTin", - "version": "2025.6.22", - "date": "2025-06-22T22:41:11Z" + "version": "2025.7.13", + "date": "2025-07-12T23:32:05Z" }, { - "name": "qbittorrent/qBittorrent", - "version": "release-5.1.1", - "date": "2025-06-22T21:41:17Z" + "name": "Ombi-app/Ombi", + "version": "v4.47.1", + "date": "2025-01-05T21:14:23Z" }, { - "name": "clusterzx/paperless-ai", - "version": "v3.0.7", - "date": "2025-06-22T17:49:29Z" + "name": "dgtlmoon/changedetection.io", + "version": "0.50.6", + "date": "2025-07-12T19:52:52Z" + }, + { + "name": "advplyr/audiobookshelf", + "version": "v2.26.0", + "date": "2025-07-12T19:31:21Z" + }, + { + "name": "jellyfin/jellyfin", + "version": "v10.10.7", + "date": "2025-04-05T19:14:59Z" + }, + { + "name": "cross-seed/cross-seed", + "version": "v6.13.0", + "date": "2025-07-12T15:52:03Z" + }, + { + "name": "blakeblackshear/frigate", + "version": "v0.14.1", + "date": "2024-08-29T22:32:51Z" + }, + { + "name": "homarr-labs/homarr", + "version": "v1.28.1", + "date": "2025-07-12T08:50:59Z" + }, + { + "name": "fallenbagel/jellyseerr", + "version": "preview-seerr", + "date": "2025-07-12T08:15:55Z" + }, + { + "name": "leiweibau/Pi.Alert", + "version": "v2025-07-12", + "date": "2025-07-12T07:53:52Z" + }, + { + "name": "keycloak/keycloak", + "version": "26.3.1", + "date": "2025-07-09T15:41:43Z" + }, + { + "name": "fuma-nama/fumadocs", + "version": "fumadocs-openapi@9.1.2", + "date": "2025-07-12T03:58:47Z" + }, + { + "name": "eclipse-mosquitto/mosquitto", + "version": "v2.0.22", + "date": "2025-07-11T21:34:20Z" + }, + { + "name": "mongodb/mongo", + "version": "r8.2.0-alpha0", + "date": "2025-07-11T21:06:26Z" + }, + { + "name": "duplicati/duplicati", + "version": "v2.1.0.124-2.1.0.124_canary_2025-07-11", + "date": "2025-07-11T20:09:08Z" }, { "name": "TandoorRecipes/recipes", "version": "1.5.35", "date": "2025-06-22T08:30:10Z" }, - { - "name": "inventree/InvenTree", - "version": "0.17.14", - "date": "2025-06-21T23:43:04Z" - }, - { - "name": "HabitRPG/habitica", - "version": "v5.37.0", - "date": "2025-06-21T14:05:12Z" - }, - { - "name": "rogerfar/rdt-client", - "version": "v2.0.114", - "date": "2025-06-21T11:20:21Z" - }, - { - "name": "theonedev/onedev", - "version": "v11.11.1", - "date": "2025-06-21T09:23:39Z" - }, - { - "name": "pocketbase/pocketbase", - "version": "v0.28.4", - "date": "2025-06-21T08:29:04Z" - }, - { - "name": "dgtlmoon/changedetection.io", - "version": "0.50.4", - "date": "2025-06-21T07:47:02Z" - }, - { - "name": "go-gitea/gitea", - "version": "v1.24.2", - "date": "2025-06-20T20:37:55Z" - }, - { - "name": "immich-app/immich", - "version": "v1.135.3", - "date": "2025-06-20T20:19:20Z" - }, - { - "name": "Sonarr/Sonarr", - "version": "v4.0.15.2941", - "date": "2025-06-20T17:20:54Z" - }, - { - "name": "syncthing/syncthing", - "version": "2.0.0-rc.19", - "date": "2025-06-02T17:56:25Z" - }, - { - "name": "benzino77/tasmocompiler", - "version": "v12.7.0", - "date": "2025-06-20T08:31:16Z" - }, - { - "name": "paperless-ngx/paperless-ngx", - "version": "v2.17.1", - "date": "2025-06-19T19:35:01Z" - }, - { - "name": "icereed/paperless-gpt", - "version": "v0.21.0", - "date": "2025-06-19T11:54:59Z" - }, { "name": "neo4j/neo4j", - "version": "2025.05.1", - "date": "2025-06-19T11:28:36Z" + "version": "2025.06.2", + "date": "2025-07-11T18:03:51Z" }, { - "name": "redis/redis", - "version": "8.2-m01-int2", - "date": "2025-06-12T08:52:10Z" + "name": "n8n-io/n8n", + "version": "n8n@1.101.2", + "date": "2025-07-11T12:03:41Z" }, { - "name": "prometheus-pve/prometheus-pve-exporter", - "version": "v3.5.5", - "date": "2025-06-19T05:43:47Z" + "name": "FlowiseAI/Flowise", + "version": "flowise@3.0.4", + "date": "2025-07-11T13:26:54Z" }, { - "name": "docmost/docmost", - "version": "v0.21.0", - "date": "2025-06-18T21:43:27Z" + "name": "zwave-js/zwave-js-ui", + "version": "v10.9.0", + "date": "2025-07-11T12:57:54Z" + }, + { + "name": "zitadel/zitadel", + "version": "v3.3.1", + "date": "2025-07-11T11:51:48Z" + }, + { + "name": "prometheus/prometheus", + "version": "v2.53.5", + "date": "2025-06-30T11:01:12Z" + }, + { + "name": "Paymenter/Paymenter", + "version": "v1.2.2", + "date": "2025-07-11T10:09:47Z" + }, + { + "name": "traefik/traefik", + "version": "v3.4.4", + "date": "2025-07-11T08:41:34Z" + }, + { + "name": "mattermost/mattermost", + "version": "preview-v0.1", + "date": "2025-06-27T14:35:47Z" + }, + { + "name": "documenso/documenso", + "version": "v1.12.2-rc.1", + "date": "2025-07-11T02:55:56Z" + }, + { + "name": "outline/outline", + "version": "v0.85.1", + "date": "2025-07-11T01:17:53Z" + }, + { + "name": "jenkinsci/jenkins", + "version": "jenkins-2.518", + "date": "2025-07-08T13:52:55Z" + }, + { + "name": "LibreTranslate/LibreTranslate", + "version": "v1.7.2", + "date": "2025-07-10T19:29:26Z" + }, + { + "name": "binwiederhier/ntfy", + "version": "v2.13.0", + "date": "2025-07-10T19:27:54Z" + }, + { + "name": "ollama/ollama", + "version": "v0.9.6", + "date": "2025-07-08T01:26:29Z" + }, + { + "name": "forgejo/forgejo", + "version": "v11.0.3", + "date": "2025-07-10T13:12:00Z" + }, + { + "name": "crowdsecurity/crowdsec", + "version": "v1.6.10", + "date": "2025-07-10T12:04:30Z" + }, + { + "name": "meilisearch/meilisearch", + "version": "prototype-incremental-vector-store-3", + "date": "2025-07-07T10:27:19Z" + }, + { + "name": "pocket-id/pocket-id", + "version": "v1.6.2", + "date": "2025-07-09T22:14:10Z" + }, + { + "name": "NginxProxyManager/nginx-proxy-manager", + "version": "v2.12.6", + "date": "2025-07-09T21:52:15Z" + }, + { + "name": "apache/tika", + "version": "3.2.1", + "date": "2025-07-09T20:47:29Z" + }, + { + "name": "sabnzbd/sabnzbd", + "version": "4.5.2", + "date": "2025-07-09T19:08:28Z" + }, + { + "name": "raydak-labs/configarr", + "version": "v1.13.6", + "date": "2025-07-09T17:23:01Z" + }, + { + "name": "hargata/lubelog", + "version": "v1.4.9", + "date": "2025-07-09T16:27:46Z" + }, + { + "name": "nicolargo/glances", + "version": "v4.3.3", + "date": "2025-07-09T15:35:44Z" + }, + { + "name": "rclone/rclone", + "version": "v1.70.3", + "date": "2025-07-09T15:06:31Z" + }, + { + "name": "home-assistant/operating-system", + "version": "16.0", + "date": "2025-07-09T13:28:43Z" + }, + { + "name": "element-hq/synapse", + "version": "v1.133.0", + "date": "2025-07-01T15:13:42Z" + }, + { + "name": "AdguardTeam/AdGuardHome", + "version": "v0.107.63", + "date": "2025-06-26T14:34:19Z" + }, + { + "name": "cockpit-project/cockpit", + "version": "342", + "date": "2025-07-09T08:48:21Z" + }, + { + "name": "Prowlarr/Prowlarr", + "version": "v1.37.0.5076", + "date": "2025-06-04T11:04:53Z" + }, + { + "name": "Radarr/Radarr", + "version": "v5.26.2.10099", + "date": "2025-06-11T20:10:39Z" }, { "name": "ipfs/kubo", @@ -400,68 +335,153 @@ "date": "2025-05-21T18:00:32Z" }, { - "name": "pterodactyl/panel", - "version": "v1.11.11", - "date": "2025-06-18T18:04:50Z" + "name": "grokability/snipe-it", + "version": "v8.1.18", + "date": "2025-07-08T20:36:37Z" }, { - "name": "NodeBB/NodeBB", - "version": "v3.12.7", - "date": "2025-06-18T14:22:53Z" + "name": "Stirling-Tools/Stirling-PDF", + "version": "v1.0.2", + "date": "2025-07-08T19:14:31Z" }, { - "name": "openhab/openhab-core", - "version": "5.0.0.M3", - "date": "2025-06-18T14:18:12Z" + "name": "TwiN/gatus", + "version": "v5.20.0", + "date": "2025-07-08T16:27:11Z" }, { - "name": "Bubka/2FAuth", - "version": "v5.6.0", - "date": "2025-06-18T12:19:54Z" + "name": "bunkerity/bunkerweb", + "version": "v1.6.2", + "date": "2025-07-08T13:52:33Z" }, { - "name": "zwave-js/zwave-js-ui", - "version": "v10.7.0", - "date": "2025-06-18T11:57:05Z" + "name": "docker/compose", + "version": "v2.38.2", + "date": "2025-07-08T09:35:14Z" }, { - "name": "forgejo/forgejo", - "version": "v11.0.2", - "date": "2025-06-18T09:38:19Z" + "name": "Checkmk/checkmk", + "version": "v2.4.0p7", + "date": "2025-07-08T05:51:08Z" }, { - "name": "silverbulletmd/silverbullet", - "version": "2.0.0-pre3", - "date": "2025-06-18T08:01:24Z" + "name": "VictoriaMetrics/VictoriaMetrics", + "version": "pmm-6401-v1.121.0", + "date": "2025-07-07T16:16:13Z" }, { - "name": "cross-seed/cross-seed", - "version": "v6.12.7", - "date": "2025-06-18T03:44:24Z" + "name": "photoprism/photoprism", + "version": "250707-d28b3101e", + "date": "2025-07-07T15:15:21Z" }, { - "name": "grafana/grafana", - "version": "v11.5.6", - "date": "2025-06-17T22:00:40Z" - }, - { - "name": "project-zot/zot", - "version": "v2.1.5", - "date": "2025-06-17T18:04:11Z" + "name": "traccar/traccar", + "version": "v6.8.1", + "date": "2025-07-07T14:40:11Z" }, { "name": "BookStackApp/BookStack", - "version": "v25.05.1", - "date": "2025-06-17T14:38:04Z" + "version": "v25.05.2", + "date": "2025-07-07T14:08:25Z" + }, + { + "name": "nzbgetcom/nzbget", + "version": "v25.2", + "date": "2025-07-04T08:21:42Z" + }, + { + "name": "slskd/slskd", + "version": "0.23.1", + "date": "2025-07-06T23:57:52Z" + }, + { + "name": "pelican-dev/panel", + "version": "v1.0.0-beta22", + "date": "2025-07-06T21:16:00Z" + }, + { + "name": "pelican-dev/wings", + "version": "v1.0.0-beta14", + "date": "2025-07-06T21:07:07Z" + }, + { + "name": "bluenviron/mediamtx", + "version": "v1.13.0", + "date": "2025-07-06T19:23:55Z" + }, + { + "name": "syncthing/syncthing", + "version": "v1.30.0", + "date": "2025-07-01T11:29:11Z" + }, + { + "name": "Part-DB/Part-DB-server", + "version": "v1.17.2", + "date": "2025-07-06T12:21:52Z" + }, + { + "name": "redis/redis", + "version": "8.0.3", + "date": "2025-07-06T12:19:24Z" + }, + { + "name": "hyperion-project/hyperion.ng", + "version": "2.1.1", + "date": "2025-06-14T17:45:06Z" + }, + { + "name": "Kareadita/Kavita", + "version": "v0.8.7", + "date": "2025-07-05T20:08:58Z" + }, + { + "name": "runtipi/runtipi", + "version": "v4.3.0", + "date": "2025-07-05T12:14:52Z" + }, + { + "name": "linkwarden/linkwarden", + "version": "v2.11.3", + "date": "2025-07-05T04:34:46Z" + }, + { + "name": "home-assistant/core", + "version": "2025.7.1", + "date": "2025-07-04T20:02:52Z" + }, + { + "name": "emqx/emqx", + "version": "e6.0.0-M1.202507-alpha.1", + "date": "2025-07-04T14:58:23Z" + }, + { + "name": "kimai/kimai", + "version": "2.37.0", + "date": "2025-07-04T14:49:43Z" + }, + { + "name": "Graylog2/graylog2-server", + "version": "6.3.1", + "date": "2025-07-04T11:20:48Z" }, { "name": "cloudflare/cloudflared", - "version": "2025.6.1", - "date": "2025-06-17T12:45:39Z" + "version": "2025.7.0", + "date": "2025-07-03T17:08:15Z" }, { - "name": "crowdsecurity/crowdsec", - "version": "v1.6.9", - "date": "2025-06-17T11:54:50Z" + "name": "rabbitmq/rabbitmq-server", + "version": "v4.1.2", + "date": "2025-07-03T16:59:29Z" + }, + { + "name": "influxdata/influxdb", + "version": "v3.2.1", + "date": "2025-07-03T16:09:19Z" + }, + { + "name": "Dolibarr/dolibarr", + "version": "18.0.7", + "date": "2025-07-03T08:57:21Z" } ] diff --git a/frontend/public/json/vikunja.json b/frontend/public/json/vikunja.json new file mode 100644 index 00000000..ea171140 --- /dev/null +++ b/frontend/public/json/vikunja.json @@ -0,0 +1,35 @@ +{ + "name": "Vikunja", + "slug": "vikunja", + "categories": [ + 12 + ], + "date_created": "2024-11-05", + "type": "ct", + "updateable": true, + "privileged": false, + "interface_port": 3456, + "documentation": null, + "website": "https://vikunja.io/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/vikunja.webp", + "config_path": "/etc/vikunja/config.yml", + "description": "Vikunja is a powerful self-hosted todo app. It allows you to create and manage to-do lists. You can plan tasks, set priorities and collaborate with others. The best part is that your data is safe with you and you can customize the app to your liking. It's like a personal assistant that helps you stay organized.", + "install_methods": [ + { + "type": "default", + "script": "ct/vikunja.sh", + "resources": { + "cpu": 1, + "ram": 1024, + "hdd": 4, + "os": "debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} diff --git a/frontend/public/json/viseron.json b/frontend/public/json/viseron.json new file mode 100644 index 00000000..3e611c30 --- /dev/null +++ b/frontend/public/json/viseron.json @@ -0,0 +1,46 @@ +{ + "name": "Viseron", + "slug": "viseron", + "categories": [15], + "date_created": "2025-01-15", + "type": "ct", + "updateable": true, + "privileged": false, + "config_path": "/config/viseron.yaml", + "interface_port": 8888, + "documentation": "https://github.com/roflcoopter/viseron", + "website": "https://github.com/roflcoopter/viseron", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/viseron.svg", + "description": "Viseron is an open-source NVR (Network Video Recorder) with object detection capabilities. It can detect objects in video streams and record events when motion is detected.", + "install_methods": [ + { + "type": "default", + "script": "ct/viseron.sh", + "resources": { + "cpu": 2, + "ram": 2048, + "hdd": 10, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [ + { + "text": "Configuration file: /config/viseron.yaml", + "type": "info" + }, + { + "text": "Logs: /config/logs/viseron.log", + "type": "info" + }, + { + "text": "Recordings: /config/recordings", + "type": "info" + } + ] +} diff --git a/frontend/public/json/wallabag.json b/frontend/public/json/wallabag.json new file mode 100644 index 00000000..203595a1 --- /dev/null +++ b/frontend/public/json/wallabag.json @@ -0,0 +1,35 @@ +{ + "name": "Wallabag", + "slug": "wallabag", + "categories": [ + 12 + ], + "date_created": "2025-07-02", + "type": "ct", + "updateable": true, + "privileged": false, + "config_path": "/opt/wallabag/.env", + "interface_port": 3000, + "documentation": "https://wallabag.io/", + "website": "https://wallabag.io/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/wallabag.svg", + "description": "Wallabag is an open-source self-hosted application.", + "install_methods": [ + { + "type": "default", + "script": "ct/wallabag.sh", + "resources": { + "cpu": 1, + "ram": 1024, + "hdd": 2, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} \ No newline at end of file diff --git a/install/alpine-syncthing-install.sh b/install/alpine-syncthing-install.sh deleted file mode 100644 index 701c052f..00000000 --- a/install/alpine-syncthing-install.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2021-2025 community-scripts ORG -# Author: MickLesk (CanbiZ) -# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE -# Source: https://syncthing.net/ - -source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" -color -verb_ip6 -catch_errors -setting_up_container -network_check -update_os - -msg_info "Setup Syncthing" -$STD apk add --no-cache syncthing -msg_ok "Setup Syncthing" - -msg_info "Enabling Syncthing Service" -$STD rc-update add syncthing default -msg_ok "Enabled Syncthing Service" - -msg_info "Starting Syncthing" -$STD rc-service syncthing start -msg_ok "Started Syncthing" - -motd_ssh -customize diff --git a/install/alpine-teamspeak-server-install.sh b/install/alpine-teamspeak-server-install.sh new file mode 100644 index 00000000..af311467 --- /dev/null +++ b/install/alpine-teamspeak-server-install.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: tremor021 (SlaviΕ‘a AreΕΎina) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://teamspeak.com/en/ + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +msg_info "Installing dependencies" +$STD apk add --no-cache \ + ca-certificates \ + libstdc++ \ + libc6-compat +msg_ok "Installed dependencies" + +RELEASE=$(curl -fsSL https://teamspeak.com/en/downloads/#server | sed -n 's/.*teamspeak3-server_linux_amd64-\([0-9]\+\.[0-9]\+\.[0-9]\+\).*/\1/p' | head -1) + +msg_info "Installing Teamspeak Server v${RELEASE}" +mkdir -p /opt/teamspeak-server +cd /opt/teamspeak-server +curl -fsSL "https://files.teamspeak-services.com/releases/server/${RELEASE}/teamspeak3-server_linux_amd64-${RELEASE}.tar.bz2" -o ts3server.tar.bz2 +tar xf ts3server.tar.bz2 --strip-components=1 +mkdir -p logs data lib +mv *.so lib +touch data/ts3server.sqlitedb data/query_ip_blacklist.txt data/query_ip_whitelist.txt .ts3server_license_accepted +echo "${RELEASE}" >~/.teamspeak-server +msg_ok "Installed TeamSpeak Server v${RELEASE}" + +msg_info "Enabling TeamSpeak Server Service" +cat </etc/init.d/teamspeak +#!/sbin/openrc-run + +name="TeamSpeak Server" +description="TeamSpeak 3 Server" +command="/opt/teamspeak-server/ts3server_startscript.sh" +command_args="start" +output_log="/var/log/teamspeak.out.log" +error_log="/var/log/teamspeak.err.log" +command_background=true +pidfile="/run/teamspeak-server.pid" +directory="/opt/teamspeak-server" + +depend() { + need net + use dns +} +EOF +chmod +x /etc/init.d/teamspeak +$STD rc-update add teamspeak default +msg_ok "Enabled TeamSpeak Server Service" + +msg_info "Starting TeamSpeak Server" +$STD service teamspeak start +msg_ok "Started TeamSpeak Server" + +motd_ssh +customize + +msg_info "Cleaning up" +rm -r ts3server.tar.bz* LICENSE* CHANGELOG doc serverquerydocs tsdns redist +$STD apk cache clean +msg_ok "Cleaned" diff --git a/install/bar-assistant-install.sh b/install/bar-assistant-install.sh deleted file mode 100644 index 6f8ffa36..00000000 --- a/install/bar-assistant-install.sh +++ /dev/null @@ -1,209 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2021-2025 community-scripts ORG -# Author: bvdberg01 -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/karlomikus/bar-assistant -# Source: https://github.com/karlomikus/vue-salt-rim -# Source: https://www.meilisearch.com/ - -source /dev/stdin <<< "$FUNCTIONS_FILE_PATH" -color -verb_ip6 -catch_errors -setting_up_container -network_check -update_os - -msg_info "Installing Dependencies" -$STD apt-get install -y \ - composer \ - redis-server \ - npm \ - nginx \ - lsb-release \ - libvips \ - php-{ffi,opcache,redis,zip,pdo-sqlite,bcmath,pdo,curl,dom,fpm} -msg_ok "Installed Dependencies" - -msg_info "Configuring PHP" -PHPVER=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "\n";') -sed -i.bak -E 's/^\s*;?\s*ffi\.enable\s*=.*/ffi.enable=true/' /etc/php/${PHPVER}/fpm/php.ini -$STD systemctl reload php${PHPVER}-fpm -msg_info "configured PHP" - -msg_info "Installing MeiliSearch" -cd /opt -RELEASE_MEILISEARCH=$(curl -s https://api.github.com/repos/meilisearch/meilisearch/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') -curl -fsSL https://github.com/meilisearch/meilisearch/releases/latest/download/meilisearch.deb -o meilisearch.deb -$STD dpkg -i meilisearch.deb -curl -fsSL https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml -o /etc/meilisearch.toml -MASTER_KEY=$(openssl rand -base64 12) -sed -i \ - -e 's|^env =.*|env = "production"|' \ - -e "s|^# master_key =.*|master_key = \"$MASTER_KEY\"|" \ - -e 's|^db_path =.*|db_path = "/var/lib/meilisearch/data"|' \ - -e 's|^dump_dir =.*|dump_dir = "/var/lib/meilisearch/dumps"|' \ - -e 's|^snapshot_dir =.*|snapshot_dir = "/var/lib/meilisearch/snapshots"|' \ - -e 's|^# no_analytics = true|no_analytics = true|' \ - -e 's|^http_addr =.*|http_addr = "127.0.0.1:7700"|' \ - /etc/meilisearch.toml -echo "${RELEASE_MEILISEARCH}" >/opt/meilisearch_version.txt -msg_ok "Installed MeiliSearch" - -msg_info "Creating MeiliSearch service" -cat </etc/systemd/system/meilisearch.service -[Unit] -Description=Meilisearch -After=network.target - -[Service] -ExecStart=/usr/bin/meilisearch --config-file-path /etc/meilisearch.toml -Restart=always - -[Install] -WantedBy=multi-user.target -EOF -systemctl enable -q --now meilisearch -msg_ok "Created Service MeiliSearch" - -msg_info "Installing Bar Assistant" -RELEASE_BARASSISTANT=$(curl -s https://api.github.com/repos/karlomikus/bar-assistant/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }') -cd /opt -curl -fsSL "https://github.com/karlomikus/bar-assistant/archive/refs/tags/v${RELEASE_BARASSISTANT}.zip" -o barassistant.zip -unzip -q barassistant.zip -mv /opt/bar-assistant-${RELEASE_BARASSISTANT}/ /opt/bar-assistant -cd /opt/bar-assistant -cp /opt/bar-assistant/.env.dist /opt/bar-assistant/.env -MeiliSearch_API_KEY=$(curl -s -X GET 'http://127.0.0.1:7700/keys' -H "Authorization: Bearer $MASTER_KEY" | grep -o '"key":"[^"]*"' | head -n 1 | sed 's/"key":"//;s/"//') -MeiliSearch_API_KEY_UID=$(curl -s -X GET 'http://127.0.0.1:7700/keys' -H "Authorization: Bearer $MASTER_KEY" | grep -o '"uid":"[^"]*"' | head -n 1 | sed 's/"uid":"//;s/"//') -LOCAL_IP=$(hostname -I | awk '{print $1}') -sed -i -e "s|^APP_URL=|APP_URL=http://${LOCAL_IP}/bar/|" \ - -e "s|^MEILISEARCH_HOST=|MEILISEARCH_HOST=http://127.0.0.1:7700|" \ - -e "s|^MEILISEARCH_KEY=|MEILISEARCH_KEY=${MASTER_KEY}|" \ - -e "s|^MEILISEARCH_API_KEY=|MEILISEARCH_API_KEY=${MeiliSearch_API_KEY}|" \ - -e "s|^MEILISEARCH_API_KEY_UID=|MEILISEARCH_API_KEY_UID=${MeiliSearch_API_KEY_UID}|" \ - /opt/bar-assistant/.env -$STD composer install --no-interaction -$STD php artisan key:generate -touch storage/bar-assistant/database.ba3.sqlite -$STD php artisan migrate --force -$STD php artisan storage:link -$STD php artisan bar:setup-meilisearch -$STD php artisan scout:sync-index-settings -$STD php artisan config:cache -$STD php artisan route:cache -$STD php artisan event:cache -mkdir /opt/bar-assistant/storage/bar-assistant/uploads/temp -chown -R www-data:www-data /opt/bar-assistant -echo "${RELEASE_BARASSISTANT}" >/opt/${APPLICATION}_version.txt -msg_ok "Installed Bar Assistant" - -msg_info "Installing Salt Rim" -RELEASE_SALTRIM=$(curl -s https://api.github.com/repos/karlomikus/vue-salt-rim/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }') -cd /opt -curl -fsSL "https://github.com/karlomikus/vue-salt-rim/archive/refs/tags/v${RELEASE_SALTRIM}.zip" -o saltrim.zip -unzip -q saltrim.zip -mv /opt/vue-salt-rim-${RELEASE_SALTRIM}/ /opt/vue-salt-rim -cd /opt/vue-salt-rim -cat </opt/vue-salt-rim/public/config.js -window.srConfig = {} -window.srConfig.API_URL = "http://${LOCAL_IP}/bar" -window.srConfig.MEILISEARCH_URL = "http://${LOCAL_IP}/search" -EOF -$STD npm install -$STD npm run build -echo "${RELEASE_SALTRIM}" >/opt/vue-salt-rim_version.txt -msg_ok "Installed Salt Rim" - -msg_info "Creating Service" -cat </etc/nginx/sites-available/barassistant.conf -server { - listen 80 default_server; - listen [::]:80 default_server; - server_name _; - - location = /favicon.ico { access_log off; log_not_found off; } - location = /robots.txt { access_log off; log_not_found off; } - - client_max_body_size 100M; - - location /bar/ { - proxy_pass http://127.0.0.1:8080/; - proxy_set_header Host \$host; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - } - - location /search/ { - proxy_pass http://127.0.0.1:7700/; - } - - location / { - proxy_pass http://127.0.0.1:8081/; - proxy_set_header Host \$host; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - } -} - -server { - listen 127.0.0.1:8080; - server_name example.com; - root /opt/bar-assistant/public; - - add_header X-Frame-Options "SAMEORIGIN"; - add_header X-Content-Type-Options "nosniff"; - - index index.php; - charset utf-8; - - location / { - try_files \$uri \$uri/ /index.php?\$query_string; - } - - location = /favicon.ico { access_log off; log_not_found off; } - location = /robots.txt { access_log off; log_not_found off; } - - error_page 404 /index.php; - - location ~ ^/index\.php(/|$) { - fastcgi_pass unix:/var/run/php/php$PHPVER-fpm.sock; - fastcgi_param SCRIPT_FILENAME \$realpath_root\$fastcgi_script_name; - include fastcgi_params; - fastcgi_hide_header X-Powered-By; - } - - location ~ /\.(?!well-known).* { - deny all; - } -} - -server { - listen 127.0.0.1:8081; - server_name _; - root /opt/vue-salt-rim/dist; - - location / { - try_files \$uri \$uri/ /index.html; - } -} -EOF - -ln -s /etc/nginx/sites-available/barassistant.conf /etc/nginx/sites-enabled/ -rm -f /etc/nginx/sites-enabled/default -$STD systemctl reload nginx -msg_ok "Created Service" - -motd_ssh -customize - -msg_info "Cleaning up" -rm -rf /opt/meilisearch.deb -rm -rf "/opt/barassistant.zip" -rm -rf "/opt/saltrim.zip" -$STD apt-get -y autoremove -$STD apt-get -y autoclean -msg_ok "Cleaned" diff --git a/install/debian-install.sh b/install/debian-install.sh index 052fd10f..e6259184 100644 --- a/install/debian-install.sh +++ b/install/debian-install.sh @@ -17,21 +17,15 @@ msg_info "Installing Dependencies" $STD apt-get install -y gpg msg_ok "Installed Dependencies" +#setup_mariadb + +#FFMPEG_VERSION="n7.1.1" FFMPEG_TYPE="full" setup_ffmpeg + #fetch_and_deploy_gh_release "argus" "release-argus/Argus" "singlefile" "latest" "/opt/argus" "Argus-.*linux-amd64" #fetch_and_deploy_gh_release "planka" "plankanban/planka" "prebuild" "latest" "/opt/planka" "planka-prebuild.zip" #PYTHON_VERSION="3.12" setup_uv -#echo -e "fetching healthchecks" -#fetch_and_deploy_gh_release "healthchecks" "healthchecks/healthchecks" "tarball" "latest" "/opt/healthchecks" -# minimal call: fetch_and_deploy_gh_release "healthchecks" "healthchecks/healthchecks" "tarball" -#echo -e "healthchecks done" - -#echo -e "fetching defguard" -#fetch_and_deploy_gh_release "defguard" "DefGuard/defguard" "binary" "latest" "/opt/defguard" -# minimal call: fetch_and_deploy_gh_release "defguard" "DefGuard/defguard" "binary" -#echo -e "defguard done" - #PHP_VERSION=8.2 PHP_FPM=YES setup_php #setup_composer @@ -39,20 +33,9 @@ msg_ok "Installed Dependencies" #NODE_MODULE="pnpm@10.1,yarn" #RELEASE=$(curl_handler -fsSL https://api.github.com/repos/babybuddy/babybuddy/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }') #msg_ok "Get Release $RELEASE" -#NODE_VERSION="22" NODE_MODULE="yarn" setup_nodejs +#NODE_VERSION="24" NODE_MODULE="yarn" setup_nodejs #PG_VERSION="16" setup_postgresql -#MARIADB_VERSION="11.8" -#MYSQL_VERSION="8.0" - -#install_mongodb -#setup_postgresql -#setup_mariadb -#install_mysql - -# msg_info "Setup DISTRO env" -# DISTRO="$(awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release)" -# msg_ok "Setup DISTRO" motd_ssh customize diff --git a/install/docspell-install.sh b/install/docspell-install.sh index b9bb74ad..8b963d18 100644 --- a/install/docspell-install.sh +++ b/install/docspell-install.sh @@ -20,27 +20,24 @@ msg_ok "Setup Functions" msg_info "Installing Dependencies (Patience)" $STD apt-get install -y \ htop \ - gnupg2 \ ca-certificates \ - default-jdk \ apt-transport-https \ - ghostscript \ - tesseract-ocr \ - tesseract-ocr-deu \ - tesseract-ocr-eng \ - unpaper \ - unoconv \ - wkhtmltopdf \ - ocrmypdf + tesseract-ocr + #tesseract-ocr-deu \ + #tesseract-ocr-eng \ + #unpaper \ + #unoconv \ + #wkhtmltopdf \ + #ocrmypdf msg_ok "Installed Dependencies" -msg_info "Setting up PostgreSQL Repository" -curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg -echo "deb https://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" >/etc/apt/sources.list.d/pgdg.list -$STD apt-get update -msg_ok "Set up PostgreSQL Repository" -msg_info "Install/Set up PostgreSQL Database" +setup_gs +JAVA_VERSION="21" setup_java +POSTGRES_VERSION="16" setup_postgresql +setup_yq + +msg_info "Set up PostgreSQL Database" $STD apt-get install -y postgresql-16 DB_NAME=docspell_db DB_USER=docspell_usr @@ -58,23 +55,15 @@ $STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET timezone TO 'UTC';" } >>~/docspell.creds msg_ok "Set up PostgreSQL Database" -msg_info "Setup Docspell (Patience)" -mkdir -p /opt/docspell -Docspell=$(curl -fsSL https://github.com/eikek/docspell/releases/latest -o - | grep "title>Release" | cut -d " " -f 5) -DocspellDSC=$(curl -fsSL https://github.com/docspell/dsc/releases/latest -o - | grep "title>Release" | cut -d " " -f 4 | sed 's/^v//') -cd /opt -curl -fsSL https://github.com/eikek/docspell/releases/download/v${Docspell}/docspell-joex_${Docspell}_all.deb -o docspell-joex_${Docspell}_all.deb -curl -fsSL https://github.com/eikek/docspell/releases/download/v${Docspell}/docspell-restserver_${Docspell}_all.deb -o docspell-restserver_${Docspell}_all.deb -$STD dpkg -i docspell-*.deb -curl -fsSL https://github.com/docspell/dsc/releases/download/v${DocspellDSC}/dsc_amd64-musl-${DocspellDSC} -o dsc_amd64-musl-${DocspellDSC} -mv dsc_amd* dsc -chmod +x dsc -mv dsc /usr/bin + +fetch_and_deploy_gh_release "docspell-joex" "eikek/docspell" "binary" "latest" "/opt/docspell-joex" "docspell-joex_*all.deb" +fetch_and_deploy_gh_release "docspell-restserver" "eikek/docspell" "binary" "latest" "/opt/docspell-restserver" "docspell-restserver_*all.deb" +fetch_and_deploy_gh_release "docspell-dsc" "docspell/dsc" "singlefile" "latest" "/usr/bin" "dsc" +fetch_and_deploy_gh_release "apache-solr" "apache/solr" "tarball" "latest" "/opt/docspell" + + +msg_info "Setup Docspell" ln -s /etc/docspell-joex /opt/docspell/docspell-joex && ln -s /etc/docspell-restserver /opt/docspell/docspell-restserver && ln -s /usr/bin/dsc /opt/docspell/dsc -curl -fsSL https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -o /usr/bin/yq -chmod +x /usr/bin/yq -#JOEX_CONF="/usr/share/docspell-joex/conf/docspell-joex.conf" -#SERVER_CONF="/usr/share/docspell-restserver/conf/docspell-server.conf" sed -i \ -e '11s|localhost|'"$LOCAL_IP"'|' \ -e '17s|localhost|'"$LOCAL_IP"'|' \ @@ -94,27 +83,6 @@ sed -i \ -e '358s|password = .*|password = "'"$DB_PASS"'"|' \ -e '401s|url = .*|url = "jdbc:postgresql://localhost:5432/'"$DB_NAME"'"|' \ /usr/share/docspell-restserver/conf/docspell-server.conf - -# sed -i 's|address = "localhost"|address = "0.0.0.0"|' "$JOEX_CONF" "$SERVER_CONF" -# sed -i -E '/backend\s*\{/,/\}/ { -# /jdbc\s*\{/,/\}/ { -# s|(url\s*=\s*).*|\1"jdbc:postgresql://localhost:5432/'"$DB_NAME"'"|; -# s|(user\s*=\s*).*|\1"'"$DB_USER"'"|; -# s|(password\s*=\s*).*|\1"'"$DB_PASS"'"|; -# } -# }' "$SERVER_CONF" -# sed -i -E '/postgresql\s*\{/,/\}/ { -# /jdbc\s*\{/,/\}/ { -# s|(url\s*=\s*).*|\1"jdbc:postgresql://localhost:5432/'"$DB_NAME"'"|; -# s|(user\s*=\s*).*|\1"'"$DB_USER"'"|; -# s|(password\s*=\s*).*|\1"'"$DB_PASS"'"|; -# } -# }' "$SERVER_CONF" -# sed -i -E '/jdbc\s*\{/,/\}/ { -# s|(url\s*=\s*).*|\1"jdbc:postgresql://localhost:5432/'"$DB_NAME"'"|; -# s|(user\s*=\s*).*|\1"'"$DB_USER"'"|; -# s|(password\s*=\s*).*|\1"'"$DB_PASS"'"|; -# }' "$JOEX_CONF" msg_ok "Setup Docspell" msg_info "Setup Apache Solr" diff --git a/install/frigate-install.sh b/install/frigate-install.sh index 557fd9d3..daf95b43 100644 --- a/install/frigate-install.sh +++ b/install/frigate-install.sh @@ -134,7 +134,7 @@ msg_info "Installing Coral Object Detection Model (Patience)" cd /opt/frigate export CCACHE_DIR=/root/.ccache export CCACHE_MAXSIZE=2G -curl -fsSL https://github.com/libusb/libusb/archive/v1.0.26.zip +curl -L -o v1.0.26.zip https://github.com/libusb/libusb/archive/v1.0.26.zip unzip -q v1.0.26.zip rm v1.0.26.zip cd libusb-1.0.26 diff --git a/install/gitea-mirror-install.sh b/install/gitea-mirror-install.sh index e8a00cac..a1c68c17 100644 --- a/install/gitea-mirror-install.sh +++ b/install/gitea-mirror-install.sh @@ -3,7 +3,7 @@ # Copyright (c) 2021-2025 community-scripts ORG # Author: CrazyWolf13 # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/arunavo4/gitea-mirror +# Source: https://github.com/RayLabsHQ/gitea-mirror source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" color @@ -28,7 +28,7 @@ ln -sf /opt/bun/bin/bun /usr/local/bin/bun ln -sf /opt/bun/bin/bun /usr/local/bin/bunx msg_ok "Installed Bun" -fetch_and_deploy_gh_release "gitea-mirror" "arunavo4/gitea-mirror" "tarball" "v3.0.2" +fetch_and_deploy_gh_release "gitea-mirror" "RayLabsHQ/gitea-mirror" "tarball" "v3.0.2" msg_info "Installing gitea-mirror" cd /opt/gitea-mirror diff --git a/install/hanko-install.sh b/install/hanko-install.sh index 5ca0d69c..96f2308c 100644 --- a/install/hanko-install.sh +++ b/install/hanko-install.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash -# Copyright (c) 2021-2025 tteck -# Author: tteck (tteckster) -# License: MIT -# https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE +# Copyright (c) 2021-2025 community-scripts ORG +# Author: MickLesk (CanbiZ) +# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE +# Source: https://hanko.io/ source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" color diff --git a/install/healthchecks-install.sh b/install/healthchecks-install.sh index 84ed2a52..5ce285fe 100644 --- a/install/healthchecks-install.sh +++ b/install/healthchecks-install.sh @@ -15,15 +15,15 @@ update_os msg_info "Installing Dependencies" $STD apt-get install -y \ - gcc \ - libpq-dev \ - libcurl4-openssl-dev \ - libssl-dev + gcc \ + libpq-dev \ + libcurl4-openssl-dev \ + libssl-dev msg_ok "Installed Dependencies" msg_info "Setup Python3" $STD apt-get install -y \ - python3 python3-dev python3-pip + python3 python3-dev python3-pip $STD pip install --upgrade pip msg_ok "Setup Python3" @@ -42,23 +42,25 @@ $STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET client_encoding TO 'utf8' $STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET default_transaction_isolation TO 'read committed';" $STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET timezone TO 'UTC'" { - echo "healthchecks-Credentials" - echo "healthchecks Database User: $DB_USER" - echo "healthchecks Database Password: $DB_PASS" - echo "healthchecks Database Name: $DB_NAME" + echo "healthchecks-Credentials" + echo "healthchecks Database User: $DB_USER" + echo "healthchecks Database Password: $DB_PASS" + echo "healthchecks Database Name: $DB_NAME" } >>~/healthchecks.creds msg_ok "Set up Database" msg_info "Setup healthchecks" fetch_and_deploy_gh_release "healthchecks" "healthchecks/healthchecks" "source" cd /opt/healthchecks +mkdir -p /opt/healthchecks/static-collected/ $STD uv venv .venv $STD source .venv/bin/activate $STD uv pip install wheel $STD uv pip install gunicorn $STD uv pip install -r requirements.txt +LOCAL_IP=$(hostname -I | awk '{print $1}') cat </opt/healthchecks/.env -ALLOWED_HOSTS=0.0.0.0 +ALLOWED_HOSTS=localhost,127.0.0.1,${LOCAL_IP},healthchecks DB=postgres DB_HOST=localhost DB_PORT=5432 @@ -68,6 +70,7 @@ DB_PASSWORD=${DB_PASS} DB_CONN_MAX_AGE=0 DB_SSLMODE=prefer DB_TARGET_SESSION_ATTRS=read-write +DATABASE_URL=postgres://${DB_USER}:${DB_PASS}@localhost:5432/${DB_NAME}?sslmode=prefer DEFAULT_FROM_EMAIL=healthchecks@example.org EMAIL_HOST=localhost @@ -79,32 +82,25 @@ EMAIL_USE_VERIFICATION=True # Django & Healthchecks Konfiguration SECRET_KEY=${SECRET_KEY} -DEBUG=False +DEBUG=True + +SITE_ROOT=http://${LOCAL_IP}:8000 +SITE_NAME=MyChecks +STATIC_ROOT=/opt/healthchecks/static-collected -SITE_ROOT=http://0.0.0.0:8000 -SITE_NAME=Mychecks -SITE_ROOT=http://0.0.0.0:8000 EOF $STD .venv/bin/python3 manage.py makemigrations -$STD .venv/bin/python3 manage.py migrate +$STD .venv/bin/python3 manage.py migrate --noinput +$STD .venv/bin/python3 manage.py collectstatic --noinput ADMIN_EMAIL="admin@helper-scripts.local" ADMIN_PASSWORD="$DB_PASS" cat < /etc/apt/sources.list.d/itsm-ng.list -$STD apt update -msg_ok "Installed Repository" - -msg_info "Installing ITSM-NG" -$STD apt install -y itsm-ng -msg_ok "Installed ITSM-NG" - -msg_info "Setting up database" -DB_NAME=itsmng_db -DB_USER=itsmng -DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13) -mariadb-tzinfo-to-sql /usr/share/zoneinfo | mysql mysql -mariadb -u root -e "CREATE DATABASE $DB_NAME;" -mariadb -u root -e "CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';" -mariadb -u root -e "GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost';" -mariadb -u root -e "GRANT SELECT ON \`mysql\`.\`time_zone_name\` TO '$DB_USER'@'localhost'; FLUSH PRIVILEGES;" -{ - echo "ITSM-NG Database Credentials" - echo "Database: $DB_NAME" - echo "Username: $DB_USER" - echo "Password: $DB_PASS" -} >>~/itsmng_db.creds -msg_ok "Set up database" - -msg_info "Installing ITSM-NG" -cd /usr/share/itsm-ng -$STD php bin/console db:install --db-name=$DB_NAME --db-user=$DB_USER --db-password=$DB_PASS --no-interaction -msg_ok "Installed ITSM-NG" - -msg_info "Configuring webserver" -$STD a2dissite 000-default.conf -msg_ok "Setup Service" - -msg_info "Setup Cronjob" -echo "* * * * * php /usr/share/itsm-ng/front/cron.php" | crontab - -msg_ok "Setup Cronjob" - -msg_info "Update PHP Params" -PHP_VERSION=$(ls /etc/php/ | grep -E '^[0-9]+\.[0-9]+$' | head -n 1) -PHP_INI="/etc/php/$PHP_VERSION/apache2/php.ini" -sed -i 's/^upload_max_filesize = .*/upload_max_filesize = 20M/' $PHP_INI -sed -i 's/^post_max_size = .*/post_max_size = 20M/' $PHP_INI -sed -i 's/^max_execution_time = .*/max_execution_time = 60/' $PHP_INI -sed -i 's/^max_input_vars = .*/max_input_vars = 5000/' $PHP_INI -sed -i 's/^memory_limit = .*/memory_limit = 256M/' $PHP_INI -sed -i 's/^;\?\s*session.cookie_httponly\s*=.*/session.cookie_httponly = On/' $PHP_INI -systemctl restart apache2 -msg_ok "Update PHP Params" - -motd_ssh -customize - -msg_info "Cleaning up" -rm -rf /usr/share/itsm-ng/install -$STD apt-get -y autoremove -$STD apt-get -y autoclean -msg_ok "Cleaned" diff --git a/install/jellyfin-install.sh b/install/jellyfin-install.sh deleted file mode 100644 index 1c459305..00000000 --- a/install/jellyfin-install.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2021-2025 tteck -# Author: tteck (tteckster) -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://jellyfin.org/ - -source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" -color -verb_ip6 -catch_errors -setting_up_container -network_check -update_os - -msg_info "Setting Up Hardware Acceleration" -$STD apt-get -y install {va-driver-all,ocl-icd-libopencl1,intel-opencl-icd,vainfo,intel-gpu-tools} -if [[ "$CTTYPE" == "0" ]]; then - chgrp video /dev/dri - chmod 755 /dev/dri - chmod 660 /dev/dri/* - $STD adduser $(id -u -n) video - $STD adduser $(id -u -n) render -fi -msg_ok "Set Up Hardware Acceleration" - -msg_info "Installing Jellyfin" -VERSION="$(awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release)" -# If the keyring directory is absent, create it -if [[ ! -d /etc/apt/keyrings ]]; then - mkdir -p /etc/apt/keyrings -fi -# Download the repository signing key and install it to the keyring directory -curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor --yes --output /etc/apt/keyrings/jellyfin.gpg -# Install the Deb822 format jellyfin.sources entry -cat </etc/apt/sources.list.d/jellyfin.sources -Types: deb -URIs: https://repo.jellyfin.org/${PCT_OSTYPE} -Suites: ${VERSION} -Components: main -Architectures: amd64 -Signed-By: /etc/apt/keyrings/jellyfin.gpg -EOF -# Install Jellyfin using the metapackage (which will fetch jellyfin-server, jellyfin-web, and jellyfin-ffmpeg5) -$STD apt-get update -$STD apt-get install -y jellyfin -sed -i 's/"MinimumLevel": "Information"/"MinimumLevel": "Error"/g' /etc/jellyfin/logging.json -chown -R jellyfin:adm /etc/jellyfin -sleep 10 -systemctl restart jellyfin -if [[ "$CTTYPE" == "0" ]]; then - sed -i -e 's/^ssl-cert:x:104:$/render:x:104:root,jellyfin/' -e 's/^render:x:108:root,jellyfin$/ssl-cert:x:108:/' /etc/group -else - sed -i -e 's/^ssl-cert:x:104:$/render:x:104:jellyfin/' -e 's/^render:x:108:jellyfin$/ssl-cert:x:108:/' /etc/group -fi -msg_ok "Installed Jellyfin" - -motd_ssh -customize - -msg_info "Cleaning up" -$STD apt-get -y autoremove -$STD apt-get -y autoclean -msg_ok "Cleaned" diff --git a/install/kapowarr-install.sh b/install/kapowarr-install.sh deleted file mode 100644 index c5f29a7b..00000000 --- a/install/kapowarr-install.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2021-2025 community-scripts ORG -# Author: SlaviΕ‘a AreΕΎina (tremor021) -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/Casvt/Kapowarr - -source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" -color -verb_ip6 -catch_errors -setting_up_container -network_check -update_os - -msg_info "Setup Python3" -$STD apt-get install -y python3-pip -msg_ok "Setup Python3" - -setup_uv -fetch_and_deploy_gh_release "kapowarr" "Casvt/Kapowarr" - -msg_info "Setup Kapowarr" -cd /opt/kapowarr -$STD uv venv .venv -$STD source .venv/bin/activate -$STD uv pip install --upgrade pip -$STD uv pip install --no-cache-dir -r requirements.txt -msg_ok "Installed Kapowarr" - -msg_info "Creating Service" -cat </etc/systemd/system/kapowarr.service -[Unit] -Description=Kapowarr Service -After=network.target - -[Service] -WorkingDirectory=/opt/kapowarr/ -ExecStart=/opt/kapowarr/.venv/bin/python3 Kapowarr.py -Restart=always - -[Install] -WantedBy=multi-user.target -EOF -systemctl enable -q --now kapowarr -msg_ok "Created Service" - -motd_ssh -customize - -msg_info "Cleaning up" -$STD apt-get -y autoremove -$STD apt-get -y autoclean -msg_ok "Cleaned" diff --git a/install/leantime-install.sh b/install/leantime-install.sh new file mode 100644 index 00000000..ff358010 --- /dev/null +++ b/install/leantime-install.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: Stroopwafe1 +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://leantime.io + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +PHP_VERSION=8.4 PHP_MODULE="mysql" PHP_APACHE="YES" PHP_FPM="YES" setup_php +setup_mariadb + +msg_info "Setting up Database" +DB_NAME=leantime +DB_USER=leantime +DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13) +$STD mysql -u root -e "CREATE DATABASE $DB_NAME;" +$STD mysql -u root -e "CREATE USER '$DB_USER'@'localhost' IDENTIFIED WITH mysql_native_password AS PASSWORD('$DB_PASS');" +$STD mysql -u root -e "GRANT ALL ON $DB_NAME.* TO '$DB_USER'@'localhost'; FLUSH PRIVILEGES;" +{ + echo "${APPLICATION} Credentials" + echo "Database User: $DB_USER" + echo "Database Password: $DB_PASS" + echo "Database Name: $DB_NAME" +} >>~/"$APPLICATION".creds +msg_ok "Set up Database" + +fetch_and_deploy_gh_release "leantime" "Leantime/leantime" "prebuild" "latest" "/opt/leantime" Leantime*.tar.gz + +msg_info "Setup ${APPLICATION}" +APACHE_LOG_DIR=/var/log/apache2 +chown -R www-data:www-data "/opt/leantime" +chmod -R 750 "/opt/leantime" + +cat </etc/apache2/sites-enabled/000-default.conf + + ServerAdmin webmaster@localhost + DocumentRoot /opt/leantime/public + DirectoryIndex index.php index.html index.cgi index.pl index.xhtml + Options +ExecCGI + + + Options FollowSymLinks + Require all granted + AllowOverride All + + + + Require all granted + + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + +EOF + +mv "/opt/leantime/config/sample.env" "/opt/leantime/config/.env" +sed -i -e "s|^LEAN_DB_DATABASE.*|LEAN_DB_DATABASE = '$DB_NAME'|" \ + -e "s|^LEAN_DB_USER.*|LEAN_DB_USER = '$DB_USER'|" \ + -e "s|^LEAN_DB_PASSWORD.*|LEAN_DB_PASSWORD = '$DB_PASS'|" \ + -e "s|^LEAN_SESSION_PASSWORD.*|LEAN_SESSION_PASSWORD = '$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)'|" \ + "/opt/leantime/config/.env" + +a2enmod -q proxy_fcgi setenvif rewrite +a2enconf -q "php${PHP_VERSION}-fpm" + +sed -i -e "s/^;extension.\(curl\|fileinfo\|gd\|intl\|ldap\|mbstring\|exif\|mysqli\|odbc\|openssl\|pdo_mysql\)/extension=\1/g" "/etc/php/${PHP_VERSION}/apache2/php.ini" + +systemctl restart apache2 + +msg_ok "Setup ${APPLICATION}" + +motd_ssh +customize + +msg_info "Cleaning up" +$STD apt-get -y autoremove +$STD apt-get -y autoclean +msg_ok "Cleaned" diff --git a/install/linkstack-install.sh b/install/linkstack-install.sh index 1d6ce833..8801b2c3 100644 --- a/install/linkstack-install.sh +++ b/install/linkstack-install.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Copyright (c) 2021-2025 community-scripts ORG -# Author: Omar Minaya +# Author: Omar Minaya | MickLesk (CanbiZ) # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # Source: https://linkstack.org/ @@ -13,35 +13,21 @@ setting_up_container network_check update_os -msg_info "Installing dependencies" -$STD apt-get install -y \ - software-properties-common \ - ca-certificates \ - lsb-release \ - apt-transport-https \ - apache2 - unzip -msg_ok "Installed dependencies" +PHP_VERSION="8.3" PHP_MODULE="sqlite3,mysql,ronny" PHP_APACHE="YES" setup_php +fetch_and_deploy_gh_release "linkstack" "linkstackorg/linkstack" "prebuild" "latest" "/var/www/html/linkstack" "linkstack.zip" -PHP_VERSION="8.2" PHP_MODULE="sqlite3, mysql, fileinfo" PHP_APACHE="YES" install_php - -msg_info "Installing LinkStack" +msg_info "Configuring LinkStack" $STD a2enmod rewrite - -ZIP_URL="https://github.com/linkstackorg/linkstack/releases/latest/download/linkstack.zip" -ZIP_FILE="/tmp/linkstack.zip" -curl -fsSL -o "$ZIP_FILE" "$ZIP_URL" -unzip -q "$ZIP_FILE" -d /var/www/html/linkstack chown -R www-data:www-data /var/www/html/linkstack chmod -R 755 /var/www/html/linkstack -cat < /etc/apache2/sites-available/linkstack.conf +cat </etc/apache2/sites-available/linkstack.conf ServerAdmin webmaster@localhost - DocumentRoot /var/www/html/linkstack/linkstack + DocumentRoot /var/www/html/linkstack ErrorLog /var/log/apache2/linkstack-error.log CustomLog /var/log/apache2/linkstack-access.log combined - + Options Indexes FollowSymLinks AllowOverride All Require all granted @@ -51,13 +37,12 @@ EOF $STD a2dissite 000-default.conf $STD a2ensite linkstack.conf $STD systemctl restart apache2 -msg_ok "Installed LinkStack" +msg_ok "Configured LinkStack" motd_ssh customize msg_info "Cleaning up" -$STD rm -f "$ZIP_FILE" $STD apt-get -y autoremove $STD apt-get -y autoclean msg_ok "Cleaned" diff --git a/install/maxun-install.sh b/install/maxun-install.sh index f68515a1..e0caea03 100644 --- a/install/maxun-install.sh +++ b/install/maxun-install.sh @@ -15,37 +15,34 @@ update_os msg_info "Installing Dependencies" $STD apt-get install -y \ - gpg \ - openssl \ - redis \ - libgbm1 \ - libnss3 \ - libatk1.0-0 \ - libatk-bridge2.0-0 \ - libdrm2 \ - libxkbcommon0 \ - libglib2.0-0 \ - libdbus-1-3 \ - libx11-xcb1 \ - libxcb1 \ - libxcomposite1 \ - libxcursor1 \ - libxdamage1 \ - libxext6 \ - libxi6 \ - libxtst6 \ - ca-certificates \ - libxrandr2 \ - libasound2 \ - libxss1 \ - libxinerama1 \ - nginx + openssl \ + redis \ + libgbm1 \ + libnss3 \ + libatk1.0-0 \ + libatk-bridge2.0-0 \ + libdrm2 \ + libxkbcommon0 \ + libglib2.0-0 \ + libdbus-1-3 \ + libx11-xcb1 \ + libxcb1 \ + libxcomposite1 \ + libxcursor1 \ + libxdamage1 \ + libxext6 \ + libxi6 \ + libxtst6 \ + ca-certificates \ + libxrandr2 \ + libasound2 \ + libxss1 \ + libxinerama1 \ + nginx msg_ok "Installed Dependencies" -#configure_lxc "Semantic Search requires a dedicated GPU and at least 16GB RAM. Would you like to install it?" 100 "memory" "16000" - PG_VERSION=17 setup_postgresql -NODE_VERSION="22" setup_nodejs +NODE_VERSION="18" setup_nodejs msg_info "Setup Variables" DB_NAME=maxun_db @@ -56,6 +53,7 @@ MINIO_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13) JWT_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32) ENCRYPTION_KEY=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32) LOCAL_IP=$(hostname -I | awk '{print $1}') +SESSION_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32) msg_ok "Set up Variables" msg_info "Setup Database" @@ -65,12 +63,13 @@ $STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET client_encoding TO 'utf8' $STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET default_transaction_isolation TO 'read committed';" $STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET timezone TO 'UTC'" { - echo "Maxun-Credentials" - echo "Maxun Database User: $DB_USER" - echo "Maxun Database Password: $DB_PASS" - echo "Maxun Database Name: $DB_NAME" - echo "Maxun JWT Secret: $JWT_SECRET" - echo "Maxun Encryption Key: $ENCRYPTION_KEY" + echo "Maxun-Credentials" + echo "Maxun Database User: $DB_USER" + echo "Maxun Database Password: $DB_PASS" + echo "Maxun Database Name: $DB_NAME" + echo "Maxun JWT Secret: $JWT_SECRET" + echo "Maxun Encryption Key: $ENCRYPTION_KEY" + echo "Maxun Session Secret: $SESSION_SECRET" } >>~/maxun.creds msg_ok "Set up Database" @@ -99,9 +98,9 @@ LimitNOFILE=65536 WantedBy=multi-user.target EOF { - echo "__________________" - echo "MinIO Admin User: $MINIO_USER" - echo "MinIO Admin Password: $MINIO_PASS" + echo "__________________" + echo "MinIO Admin User: $MINIO_USER" + echo "MinIO Admin Password: $MINIO_PASS" } >>~/maxun.creds cat </etc/default/minio MINIO_ROOT_USER=${MINIO_USER} @@ -110,8 +109,9 @@ EOF systemctl enable -q --now minio msg_ok "Setup MinIO" -msg_info "Installing Maxun (Patience)" fetch_and_deploy_gh_release "maxun" "getmaxun/maxun" "source" + +msg_info "Installing Maxun (Patience)" cat </opt/maxun/.env NODE_ENV=development JWT_SECRET=${JWT_SECRET} @@ -137,6 +137,7 @@ VITE_BACKEND_URL=http://${LOCAL_IP}:8080 VITE_PUBLIC_URL=http://${LOCAL_IP}:5173 MAXUN_TELEMETRY=false +SESSION_SECRET=${SESSION_SECRET} EOF cat <<'EOF' >/usr/local/bin/update-env-ip.sh @@ -162,19 +163,27 @@ msg_info "Setting up nginx with CORS Proxy" cat <<'EOF' >/etc/nginx/sites-available/maxun server { listen 80; + server_name _; + # Frontend ausliefern + root /usr/share/nginx/html; + index index.html; location / { - root /usr/share/nginx/html; try_files $uri $uri/ /index.html; } - location ~ ^/(api|record|workflow|storage|auth|integration|proxy|api-docs) { - proxy_pass http://localhost:8080; - proxy_set_header Host $host; + # Backend Proxy + location ~ ^/(auth|storage|record|workflow|robot|proxy|api-docs|api|webhook)(/|$) { + proxy_pass http://127.0.0.1:8080; + proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; - proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + # CORS add_header Access-Control-Allow-Origin "$http_origin" always; add_header Access-Control-Allow-Credentials true always; add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS always; @@ -193,7 +202,6 @@ server { } } EOF - ln -sf /etc/nginx/sites-available/maxun /etc/nginx/sites-enabled/maxun rm -f /etc/nginx/sites-enabled/default msg_ok "nginx with CORS Proxy set up" diff --git a/install/mealie-install.sh b/install/mealie-install.sh deleted file mode 100644 index 3e5d954f..00000000 --- a/install/mealie-install.sh +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2021-2025 community-scripts ORG -# Author: MickLesk (CanbiZ) -# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE -# Source: https://mealie.io - -source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" -color -verb_ip6 -catch_errors -setting_up_container -network_check -update_os - -msg_info "Installing Dependencies" -$STD apt-get install -y build-essential libpq-dev libwebp-dev libsasl2-dev libldap2-dev libssl-dev git -msg_ok "Installed Dependencies" - -#fetch_and_deploy_gh_release "mealie" "mealie-recipes/mealie" "tarball" "latest" "/opt/mealie" - deactivated for now - -PYTHON_VERSION="3.12" setup_uv -POSTGRES_VERSION="16" setup_postgresql -NODE_MODULE="yarn" NODE_VERSION="20" setup_nodejs - -msg_info "Setup Variables" -DB_NAME=mealie_db -DB_USER=mealie__user -DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13) -msg_ok "Set up Variables" - -msg_info "Setup Database" -$STD sudo -u postgres psql -c "CREATE ROLE $DB_USER WITH LOGIN PASSWORD '$DB_PASS';" -$STD sudo -u postgres psql -c "CREATE DATABASE $DB_NAME WITH OWNER $DB_USER ENCODING 'UTF8' TEMPLATE template0;" -$STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET client_encoding TO 'utf8';" -$STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET default_transaction_isolation TO 'read committed';" -$STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET timezone TO 'UTC'" -$STD sudo -u postgres psql -c "ALTER USER $DB_USER WITH SUPERUSER;" -{ - echo "Mealie-Credentials" - echo "Mealie Database User: $DB_USER" - echo "Mealie Database Password: $DB_PASS" - echo "Mealie Database Name: $DB_NAME" -} >>~/mealie.creds -msg_ok "Set up Database" - -msg_info "Get Mealie Repository" -cd /opt -$STD git clone https://github.com/mealie-recipes/mealie -msg_ok "Get Mealie Repository" - -msg_info "Building Frontend" -export NUXT_TELEMETRY_DISABLED=1 -cd /opt/mealie/frontend -$STD yarn install --prefer-offline --frozen-lockfile --non-interactive --production=false --network-timeout 1000000 -$STD yarn generate -msg_ok "Built Frontend" - -msg_info "Copying Built Frontend into Backend Package" -cp -r /opt/mealie/frontend/dist /opt/mealie/mealie/frontend -msg_ok "Copied Frontend" - -msg_info "Preparing Backend (Poetry)" -$STD uv venv /opt/mealie/.venv -$STD /opt/mealie/.venv/bin/python -m ensurepip --upgrade -$STD /opt/mealie/.venv/bin/python -m pip install --upgrade pip -$STD /opt/mealie/.venv/bin/pip install uv -cd /opt/mealie -$STD /opt/mealie/.venv/bin/uv pip install poetry==2.0.1 -$STD /opt/mealie/.venv/bin/poetry self add "poetry-plugin-export>=1.9" -msg_ok "Prepared Poetry" - -msg_info "Writing Environment File" -cat </opt/mealie/mealie.env -HOST=0.0.0.0 -PORT=9000 -DB_ENGINE=postgres -POSTGRES_SERVER=localhost -POSTGRES_PORT=5432 -POSTGRES_USER=${DB_USER} -POSTGRES_PASSWORD=${DB_PASS} -POSTGRES_DB=${DB_NAME} -NLTK_DATA=/nltk_data -PRODUCTION=true -STATIC_FILES=/opt/mealie/frontend/dist -EOF -msg_ok "Wrote Environment File" - -msg_info "Creating Start Script" -cat <<'EOF' >/opt/mealie/start.sh -#!/bin/bash -set -a -source /opt/mealie/mealie.env -set +a -exec /opt/mealie/.venv/bin/mealie -EOF -chmod +x /opt/mealie/start.sh -msg_ok "Created Start Script" - -msg_info "Building Mealie Backend Wheel" -cd /opt/mealie -$STD /opt/mealie/.venv/bin/poetry build --output dist - -MEALIE_VERSION=$(/opt/mealie/.venv/bin/poetry version --short) -$STD /opt/mealie/.venv/bin/poetry export --only=main --extras=pgsql --output=dist/requirements.txt -echo "mealie[pgsql]==$MEALIE_VERSION \\" >>dist/requirements.txt -/opt/mealie/.venv/bin/poetry run pip hash dist/mealie-$MEALIE_VERSION*.whl | tail -n1 | tr -d '\n' >>dist/requirements.txt -echo " \\" >>dist/requirements.txt -/opt/mealie/.venv/bin/poetry run pip hash dist/mealie-$MEALIE_VERSION*.tar.gz | tail -n1 >>dist/requirements.txt -msg_ok "Built Wheel + Requirements" - -msg_info "Installing Mealie via uv" -cd /opt/mealie -/opt/mealie/.venv/bin/uv pip install --require-hashes -r dist/requirements.txt --find-links dist -msg_ok "Installed Mealie" - -msg_info "Downloading NLTK Data" -mkdir -p /nltk_data/ -$STD /opt/mealie/.venv/bin/python -m nltk.downloader -d /nltk_data averaged_perceptron_tagger_eng -msg_ok "Downloaded NLTK Data" - -msg_info "Set Symbolic Links for Mealie" -ln -sf /opt/mealie/.venv/bin/mealie /usr/local/bin/mealie -ln -sf /opt/mealie/.venv/bin/poetry /usr/local/bin/poetry -msg_ok "Set Symbolic Links" - -msg_info "Creating Systemd Service" -cat </etc/systemd/system/mealie.service -[Unit] -Description=Mealie Backend Server -After=network.target postgresql.service - -[Service] -User=root -WorkingDirectory=/opt/mealie -ExecStart=/opt/mealie/start.sh -Restart=always - -[Install] -WantedBy=multi-user.target -EOF -systemctl enable -q --now mealie -msg_ok "Created Service" - -motd_ssh -customize - -msg_info "Cleaning up" -$STD apt-get -y autoremove -$STD apt-get -y autoclean -msg_ok "Cleaned" diff --git a/install/nginxproxymanager-install.sh b/install/nginxproxymanager-install.sh new file mode 100644 index 00000000..9aaf4cc4 --- /dev/null +++ b/install/nginxproxymanager-install.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://nginxproxymanager.com/ + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +msg_info "Installing Dependencies" +$STD apt-get update +$STD apt-get -y install \ + ca-certificates \ + apache2-utils \ + logrotate \ + build-essential \ + jq \ + git +msg_ok "Installed Dependencies" + +NODE_VERSION="16" NODE_MODULE="yarn" setup_nodejs +PYTHON_VERSION="3.12" setup_uv +fetch_and_deploy_gh_release "nginxproxymanager" "NginxProxyManager/nginx-proxy-manager" "tarball" "latest" "/tmp/nginxproxymanager" + +msg_info "Installing Python Dependencies" +$STD apt-get install -y \ + python3 \ + python3-dev \ + python3-venv +msg_ok "Installed Python Dependencies" + +msg_info "Setting up Certbot Environment" +$STD uv venv /opt/certbot +$STD uv pip install --python \ + certbot \ + certbot-dns-cloudflare \ + certbot-dns-multi +msg_ok "Certbot Environment Ready" + +msg_info "Installing Openresty" +VERSION="$(awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release)" +curl -fsSL "https://openresty.org/package/pubkey.gpg" | gpg --dearmor -o /etc/apt/trusted.gpg.d/openresty-archive-keyring.gpg +echo -e "deb http://openresty.org/package/debian $VERSION openresty" >/etc/apt/sources.list.d/openresty.list +$STD apt-get update +$STD apt-get -y install openresty +msg_ok "Installed Openresty" + +msg_info "Setting up Environment" +ln -sf /usr/bin/python3 /usr/bin/python +ln -sf /opt/certbot/bin/certbot /usr/bin/certbot +ln -sf /usr/local/openresty/nginx/sbin/nginx /usr/sbin/nginx +ln -sf /usr/local/openresty/nginx/ /etc/nginx +sed -i 's+^daemon+#daemon+g' /tmp/nginxproxymanager/docker/rootfs/etc/nginx/nginx.conf +NGINX_CONFS=$(find "$(pwd)" -type f -name "*.conf") +for NGINX_CONF in $NGINX_CONFS; do + sed -i 's+include conf.d+include /etc/nginx/conf.d+g' "$NGINX_CONF" +done + +mkdir -p /var/www/html /etc/nginx/logs +cd /tmp/nginxproxymanager +cp -r docker/rootfs/var/www/html/* /var/www/html/ +cp -r docker/rootfs/etc/nginx/* /etc/nginx/ +cp docker/rootfs/etc/letsencrypt.ini /etc/letsencrypt.ini +cp docker/rootfs/etc/logrotate.d/nginx-proxy-manager /etc/logrotate.d/nginx-proxy-manager +ln -sf /etc/nginx/nginx.conf /etc/nginx/conf/nginx.conf +rm -f /etc/nginx/conf.d/dev.conf + +mkdir -p /tmp/nginx/body \ + /run/nginx \ + /data/nginx \ + /data/custom_ssl \ + /data/logs \ + /data/access \ + /data/nginx/default_host \ + /data/nginx/default_www \ + /data/nginx/proxy_host \ + /data/nginx/redirection_host \ + /data/nginx/stream \ + /data/nginx/dead_host \ + /data/nginx/temp \ + /var/lib/nginx/cache/public \ + /var/lib/nginx/cache/private \ + /var/cache/nginx/proxy_temp + +chmod -R 777 /var/cache/nginx +chown root /tmp/nginx + +echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" {print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf);" >/etc/nginx/conf.d/include/resolvers.conf + +if [ ! -f /data/nginx/dummycert.pem ] || [ ! -f /data/nginx/dummykey.pem ]; then + 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 &>/dev/null +fi + +mkdir -p /app/global /app/frontend/images +cd /tmp/nginxproxymanager +cp -r backend/* /app +cp -r global/* /app/global +msg_ok "Set up Environment" + +msg_info "Building Frontend" +cd /tmp/nginxproxymanager/frontend +$STD yarn install --frozen-lockfile +$STD yarn build +cp -r dist/* /app/frontend +cp -r app-images/* /app/frontend/images +msg_ok "Built Frontend" + +msg_info "Initializing Backend" +rm -rf /app/config/default.json +if [ ! -f /app/config/production.json ]; then + cat <<'EOF' >/app/config/production.json +{ + "database": { + "engine": "knex-native", + "knex": { + "client": "sqlite3", + "connection": { + "filename": "/data/database.sqlite" + } + } + } +} +EOF +fi +cd /app +$STD yarn install --production +msg_ok "Initialized Backend" + +msg_info "Creating Service" +cat <<'EOF' >/lib/systemd/system/npm.service +[Unit] +Description=Nginx Proxy Manager +After=network.target +Wants=openresty.service + +[Service] +Type=simple +Environment=NODE_ENV=production +Environment=NODE_OPTIONS=--openssl-legacy-provider +ExecStartPre=-mkdir -p /tmp/nginx/body /data/letsencrypt-acme-challenge +ExecStart=/usr/bin/node index.js --abort_on_uncaught_exception --max_old_space_size=1024 +WorkingDirectory=/app +Restart=on-failure + +[Install] +WantedBy=multi-user.target +EOF +msg_ok "Created Service" + +motd_ssh +customize + +msg_info "Starting Services" +sed -i 's/user npm/user root/g; s/^pid/#pid/g' /usr/local/openresty/nginx/conf/nginx.conf +sed -r -i 's/^([[:space:]]*)su npm npm/\1#su npm npm/g;' /etc/logrotate.d/nginx-proxy-manager +systemctl enable -q --now openresty +systemctl enable -q --now npm +msg_ok "Started Services" + +msg_info "Cleaning up" +rm -rf /tmp/* +systemctl restart openresty +$STD apt-get -y autoremove +$STD apt-get -y autoclean +msg_ok "Cleaned" diff --git a/install/npmplus-install.sh b/install/npmplus-install.sh deleted file mode 100644 index ef355bc0..00000000 --- a/install/npmplus-install.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2021-2025 community-scripts ORG -# Author: MickLesk (CanbiZ) -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/ZoeyVid/NPMplus - -source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" -color -verb_ip6 -catch_errors -setting_up_container -network_check -update_os - -msg_info "Installing Dependencies" -$STD apk add \ - tzdata \ - gawk \ - yq -msg_ok "Installed Dependencies" - -msg_info "Installing Docker & Compose" -$STD apk add docker -$STD rc-service docker start -$STD rc-update add docker default - -get_latest_release() { - curl -fsSL https://api.github.com/repos/$1/releases/latest | grep '"tag_name":' | cut -d'"' -f4 -} -DOCKER_COMPOSE_LATEST_VERSION=$(get_latest_release "docker/compose") -DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker} -mkdir -p $DOCKER_CONFIG/cli-plugins -curl -fsSL https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_LATEST_VERSION/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose -chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose -msg_ok "Installed Docker & Compose" - -msg_info "Fetching NPMplus" -cd /opt -curl -fsSL "https://raw.githubusercontent.com/ZoeyVid/NPMplus/refs/heads/develop/compose.yaml" -o compose.yaml -msg_ok "Fetched NPMplus" - -attempts=0 -while true; do - read -r -p "${TAB3}Enter your TZ Identifier (e.g., Europe/Berlin): " TZ_INPUT - if validate_tz "$TZ_INPUT"; then - break - fi - msg_error "Invalid timezone! Please enter a valid TZ identifier." - - attempts=$((attempts + 1)) - if [[ "$attempts" -ge 3 ]]; then - msg_error "Maximum attempts reached. Exiting." - exit 1 - fi -done - -read -r -p "${TAB3}Enter your ACME Email: " ACME_EMAIL_INPUT - -yq -i " - .services.npmplus.environment |= - (map(select(. != \"TZ=*\" and . != \"ACME_EMAIL=*\")) + - [\"TZ=$TZ_INPUT\", \"ACME_EMAIL=$ACME_EMAIL_INPUT\"]) -" /opt/compose.yaml - -msg_info "Building and Starting NPMplus (Patience)" -$STD docker compose up -d -CONTAINER_ID="" -for i in {1..60}; do - CONTAINER_ID=$(docker ps --filter "name=npmplus" --format "{{.ID}}") - if [[ -n "$CONTAINER_ID" ]]; then - STATUS=$(docker inspect --format '{{.State.Health.Status}}' "$CONTAINER_ID" 2>/dev/null || echo "starting") - if [[ "$STATUS" == "healthy" ]]; then - msg_ok "NPMplus is running and healthy" - break - elif [[ "$STATUS" == "unhealthy" ]]; then - msg_error "NPMplus container is unhealthy! Check logs." - docker logs "$CONTAINER_ID" - exit 1 - fi - fi - sleep 2 - [[ $i -eq 60 ]] && msg_error "NPMplus container did not become healthy within 120s." && docker logs "$CONTAINER_ID" && exit 1 -done -msg_ok "Builded and started NPMplus" - -motd_ssh -customize - -msg_info "Retrieving Default Login (Patience)" -LOGFILE="/tmp/npmplus.log" -docker logs "$CONTAINER_ID" &>"$LOGFILE" & - -PASSWORD_FOUND=0 -for i in {1..60}; do - if grep -q "Creating a new user:" "$LOGFILE"; then - PASSWORD_LINE=$(grep "Creating a new user:" "$LOGFILE" | head -n1) - PASSWORD=$(echo "$PASSWORD_LINE" | awk -F 'password: ' '{print $2}') - if [[ -n "$PASSWORD" ]]; then - echo -e "username: admin@example.org\npassword: $PASSWORD" >/opt/.npm_pwd - msg_ok "Saved default login to /opt/.npm_pwd" - PASSWORD_FOUND=1 - break - fi - fi - sleep 2 -done - -if [[ $PASSWORD_FOUND -eq 0 ]]; then - msg_error "Could not retrieve default login after 60 seconds." - echo -e "\nYou can manually try:\n docker logs $CONTAINER_ID | grep 'Creating a new user:'\n" -fi - -rm -f "$LOGFILE" diff --git a/install/ots-install.sh b/install/ots-install.sh new file mode 100644 index 00000000..da8e1d55 --- /dev/null +++ b/install/ots-install.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: bvberg01 +# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE +# Source: https://github.com/Luzifer/ots + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +msg_info "Installing Dependencies" +$STD apt-get install -y \ + redis-server +msg_ok "Installed Dependencies" + +msg_info "Installing OTS" +fetch_and_deploy_gh_release "ots" "Luzifer/ots" "prebuild" "latest" "/opt/ots" "ots_linux_amd64.tgz" +cat </opt/ots/env +LISTEN=0.0.0.0:3000 +REDIS_URL=redis://127.0.0.1:6379 +SECRET_EXPIRY=604800 +STORAGE_TYPE=redis +EOF +msg_ok "Installed OTS" + +msg_info "Creating Services" +cat </etc/systemd/system/ots.service +[Unit] +Description=One-Time-Secret Service +After=network-online.target +Requires=network-online.target + +[Service] +EnvironmentFile=/opt/ots/env +ExecStart=/opt/ots/ots +Restart=Always +RestartSecs=5 + +[Install] +WantedBy=multi-user.target +EOF +systemctl enable -q --now ots +msg_ok "Created Services" + +motd_ssh +customize + +msg_info "Cleaning up" +$STD apt-get -y autoremove +$STD apt-get -y autoclean +msg_ok "Cleaned" diff --git a/install/rybbit-install.sh b/install/rybbit-install.sh new file mode 100644 index 00000000..62a825b9 --- /dev/null +++ b/install/rybbit-install.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: MickLesk (CanbiZ) +# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE +# Source: https://github.com/rybbit-io/rybbit + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +msg_info "Installing Dependencies" +$STD apt-get install -y \ + caddy \ + apt-transport-https \ + ca-certificates +msg_ok "Installed Dependencies" + + +setup_clickhouse +PG_VERSION=17 setup_postgresql +NODE_VERSION="20" NODE_MODULE="next" setup_nodejs + +#sed -i 's|default|read_only|' /etc/clickhouse-server/users.xml +#sed -i 's||DISABLED|' /etc/clickhouse-server/users.xml + +msg_info "Setting up PostgreSQL Database" +DB_NAME=rybbit_db +DB_USER=rybbit +DB_PASS="$(openssl rand -base64 18 | cut -c1-13)" +$STD sudo -u postgres psql -c "CREATE ROLE $DB_USER WITH LOGIN PASSWORD '$DB_PASS';" +$STD sudo -u postgres psql -c "CREATE DATABASE $DB_NAME WITH OWNER $DB_USER ENCODING 'UTF8' TEMPLATE template0;" +$STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET client_encoding TO 'utf8';" +$STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET default_transaction_isolation TO 'read committed';" +$STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET timezone TO 'UTC'" +{ + echo "Rybbit-Credentials" + echo "Rybbit Database User: $DB_USER" + echo "Rybbit Database Password: $DB_PASS" + echo "Rybbit Database Name: $DB_NAME" +} >>~/rybbit.creds +msg_ok "Set up PostgreSQL Database" + +fetch_and_deploy_gh_release "rybbit" "rybbit-io/rybbit" "tarball" "latest" "/opt/rybbit" + +cd /opt/rybbit/shared +npm install +npm run build + +cd /opt/rybbit/server +npm ci +npm run build + +cd /opt/rybbit/client +npm ci --legacy-peer-deps +npm run build + +mv /opt/rybbit/.env.example /opt/rybbit/.env +sed -i "s|^POSTGRES_DB=.*|POSTGRES_DB=$DB_NAME|g" /opt/rybbit/.env +sed -i "s|^POSTGRES_USER=.*|POSTGRES_USER=$DB_USER|g" /opt/rybbit/.env +sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$DB_PASS|g" /opt/rybbit/.env +sed -i "s|^DOMAIN_NAME=.*|DOMAIN_NAME=localhost|g" /opt/rybbit/.env +sed -i "s|^BASE_URL=.*|BASE_URL=\"http://localhost\"|g" /opt/rybbit/.env +msg_ok "Rybbit Installed" + +msg_info "Setting up Caddy" +mkdir -p /etc/caddy +cp /opt/rybbit/Caddyfile /etc/caddy/Caddyfile +systemctl enable -q --now caddy +msg_ok "Caddy Setup" + +motd_ssh +customize + +msg_info "Cleaning up" +$STD apt-get -y autoremove +$STD apt-get -y autoclean +msg_ok "Cleaned" diff --git a/install/saltmaster-install.sh b/install/saltmaster-install.sh deleted file mode 100644 index 24293933..00000000 --- a/install/saltmaster-install.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2021-2025 community-scripts ORG -# Author: bvdberg01 -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# Source: https://github.com/saltstack/salt - -source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" -color -verb_ip6 -catch_errors -setting_up_container -network_check -update_os - -msg_info "Installing Dependencies" -$STD apt-get install -y \ - jq -msg_ok "Installed Dependencies" - -msg_info "Setup Salt repo" -mkdir -p /etc/apt/keyrings -curl -fsSL https://packages.broadcom.com/artifactory/api/security/keypair/SaltProjectKey/public -o /etc/apt/keyrings/salt-archive-keyring.pgp -curl -fsSL https://github.com/saltstack/salt-install-guide/releases/latest/download/salt.sources -o /etc/apt/sources.list.d/salt.sources -$STD apt-get update -msg_ok "Setup Salt repo" - -msg_info "Installing Salt Master" -RELEASE=$(curl -fsSL https://api.github.com/repos/saltstack/salt/releases/latest | jq -r .tag_name | sed 's/^v//') -cat </etc/apt/preferences.d/salt-pin-1001 -Package: salt-* -Pin: version ${RELEASE} -Pin-Priority: 1001 -EOF -$STD apt-get install -y salt-master -echo "${RELEASE}" >/opt/${APPLICATION}_version.txt -msg_ok "Installed Salt Master" - -motd_ssh -customize - -msg_info "Cleaning up" -$STD apt-get -y autoremove -$STD apt-get -y autoclean -msg_ok "Cleaned" diff --git a/install/scraparr-install.sh b/install/scraparr-install.sh new file mode 100644 index 00000000..4eafe0a6 --- /dev/null +++ b/install/scraparr-install.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: JasonGreenC +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/thecfu/scraparr + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +msg_info "Installing Scraparr" +PYTHON_VERSION="3.12" setup_uv +fetch_and_deploy_gh_release "scrappar" "thecfu/scraparr" "tarball" "latest" "/opt/scraparr" +cd /opt/scraparr || exit +$STD uv venv /opt/scraparr/.venv +$STD /opt/scraparr/.venv/bin/python -m ensurepip --upgrade +$STD /opt/scraparr/.venv/bin/python -m pip install --upgrade pip +$STD /opt/scraparr/.venv/bin/python -m pip install -r /opt/scraparr/src/scraparr/requirements.txt +chmod -R 755 /opt/scraparr +mkdir -p /scraparr/config +mv /opt/scraparr/config.yaml /scraparr/config/config.yaml +chmod -R 755 /scraparr +msg_ok "Installed Scraparr" + +msg_info "Creating Service" +cat </etc/systemd/system/scraparr.service +[Unit] +Description=Scraparr +Wants=network-online.target +After=network.target + +[Service] +Type=simple +WorkingDirectory=/opt/scraparr/src +ExecStart=/opt/scraparr/.venv/bin/python -m scraparr.scraparr +Restart=always + +[Install] +WantedBy=multi-user.target +EOF +systemctl daemon-reload +systemctl enable -q --now scraparr +msg_ok "Configured Service" + +motd_ssh +customize + +msg_info "Cleaning up" +$STD apt-get -y autoremove +$STD apt-get -y autoclean +msg_ok "Cleaned" diff --git a/install/tududi-install.sh b/install/tududi-install.sh new file mode 100644 index 00000000..17cd07a0 --- /dev/null +++ b/install/tududi-install.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# Copyright (c) 2025 Community Scripts ORG +# Author: vhsdream +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://tududi.com/ + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +msg_info "Installing Dependencies" +$STD apt-get install -y sqlite3 +msg_ok "Installed Dependencies" + +NODE_VERSION="20" setup_nodejs + +msg_info "Installing Tududi" +fetch_and_deploy_gh_release "tududi" "chrisvel/tududi" +cd /opt/tududi +$STD npm install +export NODE_ENV=production +$STD npm run frontend:build +cp -r ./dist ./backend/dist +cp -r ./public/locales ./backend/dist/locales +cp ./public/favicon.* ./backend/dist +msg_ok "Installed Tududi" + +msg_info "Creating config and database" +DB_LOCATION="/opt/tududi-db" +UPLOAD_DIR="/opt/tududi-uploads" +mkdir -p {"$DB_LOCATION","$UPLOAD_DIR"} +SECRET="$(openssl rand -hex 64)" +sed -e 's/^GOOGLE/# &/' \ + -e '/TUDUDI_SESSION/s/^# //' \ + -e '/NODE_ENV/s/^# //' \ + -e "s/your_session_secret_here/$SECRET/" \ + -e 's/development/production/' \ + -e "\$a\DB_FILE=$DB_LOCATION/production.sqlite3" \ + -e "\$a\UPLOAD_LOCATION=$UPLOAD_DIR" \ + /opt/tududi/backend/.env.example >/opt/tududi/backend/.env +export DB_FILE="$DB_LOCATION/production.sqlite3" +$STD npm run db:init +msg_ok "Created config and database" + +msg_info "Creating service" +cat </etc/systemd/system/tududi.service +[Unit] +Description=Tududi Service +After=network.target + +[Service] +Type=simple +WorkingDirectory=/opt/tududi +EnvironmentFile=/opt/tududi/backend/.env +ExecStart=/usr/bin/npm run start + +[Install] +WantedBy=multi-user.target +EOF +systemctl enable -q --now tududi +msg_ok "Created service" + +motd_ssh +customize + +msg_info "Cleaning up" +$STD apt-get -y autoremove +$STD apt-get -y autoclean +msg_ok "Cleaned" diff --git a/install/vikunja-install.sh b/install/vikunja-install.sh new file mode 100644 index 00000000..f56d073d --- /dev/null +++ b/install/vikunja-install.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: MickLesk (Canbiz) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://vikunja.io/ + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +msg_info "Installing Dependencies" +$STD apt-get install -y make +msg_ok "Installed Dependencies" + +msg_info "Setup Vikunja (Patience)" +cd /opt +RELEASE=$(curl -fsSL https://dl.vikunja.io/vikunja/ | grep -oP 'href="/vikunja/\K[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -n 1) +curl -fsSL "https://dl.vikunja.io/vikunja/$RELEASE/vikunja-$RELEASE-amd64.deb" -o vikunja-$RELEASE-amd64.deb +$STD dpkg -i vikunja-$RELEASE-amd64.deb +sed -i 's|^ timezone: .*| timezone: UTC|' /etc/vikunja/config.yml +sed -i 's|"./vikunja.db"|"/etc/vikunja/vikunja.db"|' /etc/vikunja/config.yml +sed -i 's|./files|/etc/vikunja/files|' /etc/vikunja/config.yml +systemctl start vikunja.service +echo "${RELEASE}" >/opt/${APPLICATION}_version.txt +msg_ok "Installed Vikunja" + +motd_ssh +customize + +msg_info "Cleaning up" +rm -rf /opt/vikunja-$RELEASE-amd64.deb +$STD apt-get autoremove +$STD apt-get autoclean +msg_ok "Cleaned" diff --git a/install/viseron-install.sh b/install/viseron-install.sh new file mode 100644 index 00000000..13aea1b3 --- /dev/null +++ b/install/viseron-install.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: jetonr +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/roflcoopter/viseron + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +PYTHON_VERSION="3.12" setup_uv + +msg_info "Installing Dependencies" +$STD apt-get install -y \ + python3 python3-pip python3-venv \ + python3-opencv jq \ + libgl1-mesa-glx libglib2.0-0 \ + libgstreamer1.0-0 libgstreamer-plugins-base1.0-0 \ + gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav +msg_ok "Installed Dependencies" + +msg_info "Setting up Python Environment with uv" +cd /opt +uv venv viseron +source viseron/bin/activate +uv pip install --upgrade pip setuptools wheel +msg_ok "Python Environment Setup (uv)" + +msg_info "Installing Viseron" +RELEASE=$(curl -s https://api.github.com/repos/roflcoopter/viseron/releases/latest | jq -r '.tag_name') +uv pip install https://github.com/roflcoopter/viseron/archive/refs/tags/${RELEASE}.tar.gz +ln -s /opt/viseron/bin/viseron /usr/local/bin/viseron +msg_ok "Installed Viseron $RELEASE" + +msg_info "Creating Configuration Directory" +mkdir -p /config +mkdir -p /config/recordings +mkdir -p /config/logs +msg_ok "Created Configuration Directory" + +msg_info "Creating Default Configuration" +cat </config/viseron.yaml +# Viseron Configuration +# https://github.com/roflcoopter/viseron + +# Logging +logging: + level: INFO + file: /config/logs/viseron.log + +# Web Interface +web: + host: 0.0.0.0 + port: 8888 + +# Cameras +cameras: + # Example camera configuration + # camera_name: + # host: 192.168.1.100 + # port: 554 + # username: admin + # password: password + # path: /stream + # fps: 5 + # width: 1920 + # height: 1080 + +# Object Detection +object_detection: + type: opencv + confidence: 0.5 + labels: + - person + - car + - truck + - bus + - motorcycle + - bicycle + +# Recording +recording: + enabled: true + path: /config/recordings + max_size: 10GB + max_age: 7d + +# Motion Detection +motion_detection: + enabled: true + threshold: 25 + sensitivity: 0.8 +EOF +msg_ok "Created Default Configuration" + +msg_info "Creating Systemd Service" +cat </etc/systemd/system/viseron.service +[Unit] +Description=Viseron NVR Service +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/viseron +Environment=PATH=/opt/viseron/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ExecStart=/opt/viseron/bin/viseron --config /config/viseron.yaml +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +EOF +systemctl enable -q --now viseron +msg_ok "Created Systemd Service" + +msg_info "Setting up Hardware Acceleration" +if [[ "$CTTYPE" == "0" ]]; then + chgrp video /dev/dri + chmod 755 /dev/dri + chmod 660 /dev/dri/* +fi +msg_ok "Hardware Acceleration Configured" + +motd_ssh +customize + +msg_info "Cleaning up" +$STD apt-get autoremove +$STD apt-get autoclean +msg_ok "Cleaned" diff --git a/install/wallabag-install.sh b/install/wallabag-install.sh index b9f1c5d7..351e8537 100644 --- a/install/wallabag-install.sh +++ b/install/wallabag-install.sh @@ -14,52 +14,46 @@ update_os msg_info "Installing Dependencies (Patience)" $STD apt-get install -y \ - curl \ - git \ - unzip \ - sudo \ - make \ - php8.2 \ - php8.2-{cli,common,bcmath,intl,fpm,tidy,xml,mysql,mbstring,zip,gd,curl} \ - composer \ - apache2 \ - libapache2-mod-php \ - redis \ - mariadb-server + make \ + apache2 \ + libapache2-mod-php \ + redis msg_ok "Installed Dependencies" +setup_mariadb +PHP_VERSION="8.3" PHP_APACHE="YES" PHP_FPM="YES" PHP_MODULE="bcmath,bz2,cli,exif,common,curl,tidy,fpm,gd,intl,mbstring,xml,mysql,zip" setup_php +setup_composer + msg_info "Setting up Database" DB_NAME=wallabag_db DB_USER=wallabag DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13) SECRET_KEY="$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32)" -$STD mysql -u root -e "CREATE DATABASE $DB_NAME;" -$STD mysql -u root -e "CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';" -$STD mysql -u root -e "GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost'; FLUSH PRIVILEGES;" +$STD mariadb -u root -e "CREATE DATABASE $DB_NAME;" +$STD mariadb -u root -e "CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';" +$STD mariadb -u root -e "GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost'; FLUSH PRIVILEGES;" { - echo "Wallabag Credentials" - echo "Database User: $DB_USER" - echo "Database Password: $DB_PASS" - echo "Database Name: $DB_NAME" -} >> ~/wallabag.creds + echo "Wallabag Credentials" + echo "Database User: $DB_USER" + echo "Database Password: $DB_PASS" + echo "Database Name: $DB_NAME" +} >>~/wallabag.creds msg_ok "Set up Database" +fetch_and_deploy_gh_release "wallabag" "wallabag/wallabag" "prebuild" "latest" "/opt/wallabag" "wallabag-*.tar.gz" + msg_info "Installing Wallabag (Patience)" -RELEASE=$(curl -s https://api.github.com/repos/wallabag/wallabag/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') -wget -q "https://github.com/wallabag/wallabag/archive/refs/tags/${RELEASE}.zip" -unzip -q ${RELEASE}.zip -mv wallabag-${RELEASE} /opt/wallabag cd /opt/wallabag useradd -d /opt/wallabag -s /bin/bash -M wallabag chown -R wallabag:wallabag /opt/wallabag mv /opt/wallabag/app/config/parameters.yml.dist /opt/wallabag/app/config/parameters.yml sed -i \ - -e 's|database_name: wallabag|database_name: wallabag_db|' \ - -e 's|database_port: ~|database_port: 3306|' \ - -e 's|database_user: root|database_user: wallabag|' \ - -e 's|database_password: ~|database_password: '"$DB_PASS"'|' \ - -e 's|secret: .*|secret: '"$SECRET_KEY"'|' \ - /opt/wallabag/app/config/parameters.yml + -e 's|database_name: wallabag|database_name: wallabag_db|' \ + -e 's|database_port: ~|database_port: 3306|' \ + -e 's|database_user: root|database_user: wallabag|' \ + -e 's|database_password: ~|database_password: '"$DB_PASS"'|' \ + -e 's|secret: .*|secret: '"$SECRET_KEY"'|' \ + /opt/wallabag/app/config/parameters.yml export COMPOSER_ALLOW_SUPERUSER=1 sudo -u wallabag make install --no-interaction @@ -72,7 +66,7 @@ msg_info "Setting up Virtual Host" cat </etc/nginx/conf.d/wallabag.conf server { root /opt/wallabag/web; - server_name $IPADDRESS; + server_name $IPADDRESS; location / { # try to serve file directly, fallback to app.php diff --git a/misc/api.func.bak b/misc/api.func.bak deleted file mode 100644 index 2da17c1b..00000000 --- a/misc/api.func.bak +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright (c) 2021-2025 community-scripts ORG -# Author: michelroegl-brunner -# License: MIT | https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/LICENSE - -get_error_description() { - local exit_code="$1" - case "$exit_code" in - 0) echo " " ;; - 1) echo "General error: An unspecified error occurred." ;; - 2) echo "Incorrect shell usage or invalid command arguments." ;; - 3) echo "Unexecuted function or invalid shell condition." ;; - 4) echo "Error opening a file or invalid path." ;; - 5) echo "I/O error: An input/output failure occurred." ;; - 6) echo "No such device or address." ;; - 7) echo "Insufficient memory or resource exhaustion." ;; - 8) echo "Non-executable file or invalid file format." ;; - 9) echo "Failed child process execution." ;; - 18) echo "Connection to a remote server failed." ;; - 22) echo "Invalid argument or faulty network connection." ;; - 28) echo "No space left on device." ;; - 35) echo "Timeout while establishing a connection." ;; - 56) echo "Faulty TLS connection." ;; - 60) echo "SSL certificate error." ;; - 100) echo "LXC install error: Unexpected error in create_lxc.sh." ;; - 101) echo "LXC install error: No network connection detected." ;; - 200) echo "LXC creation failed." ;; - 201) echo "LXC error: Invalid Storage class." ;; - 202) echo "User aborted menu in create_lxc.sh." ;; - 203) echo "CTID not set in create_lxc.sh." ;; - 204) echo "PCT_OSTYPE not set in create_lxc.sh." ;; - 205) echo "CTID cannot be less than 100 in create_lxc.sh." ;; - 206) echo "CTID already in use in create_lxc.sh." ;; - 207) echo "Template not found in create_lxc.sh." ;; - 208) echo "Error downloading template in create_lxc.sh." ;; - 209) echo "Container creation failed, but template is intact in create_lxc.sh." ;; - 125) echo "Docker error: Container could not start." ;; - 126) echo "Command not executable: Incorrect permissions or missing dependencies." ;; - 127) echo "Command not found: Incorrect path or missing dependency." ;; - 128) echo "Invalid exit signal, e.g., incorrect Git command." ;; - 129) echo "Signal 1 (SIGHUP): Process terminated due to hangup." ;; - 130) echo "Signal 2 (SIGINT): Manual termination via Ctrl+C." ;; - 132) echo "Signal 4 (SIGILL): Illegal machine instruction." ;; - 133) echo "Signal 5 (SIGTRAP): Debugging error or invalid breakpoint signal." ;; - 134) echo "Signal 6 (SIGABRT): Program aborted itself." ;; - 135) echo "Signal 7 (SIGBUS): Memory error, invalid memory address." ;; - 137) echo "Signal 9 (SIGKILL): Process forcibly terminated (OOM-killer or 'kill -9')." ;; - 139) echo "Signal 11 (SIGSEGV): Segmentation fault, possibly due to invalid pointer access." ;; - 141) echo "Signal 13 (SIGPIPE): Pipe closed unexpectedly." ;; - 143) echo "Signal 15 (SIGTERM): Process terminated normally." ;; - 152) echo "Signal 24 (SIGXCPU): CPU time limit exceeded." ;; - 255) echo "Unknown critical error, often due to missing permissions or broken scripts." ;; - *) echo "Unknown error code ($exit_code)." ;; - esac -} - -post_to_api() { - - if ! command -v curl &>/dev/null; then - return - fi - - if [ "$DIAGNOSTICS" = "no" ]; then - return - fi - - if [ -z "$RANDOM_UUID" ]; then - return - fi - - local API_URL="http://api.community-scripts.org/dev/upload" - local pve_version="not found" - pve_version=$(pveversion | awk -F'[/ ]' '{print $2}') - - JSON_PAYLOAD=$( - cat </dev/null; then - return - fi - - if [ "$POST_UPDATE_DONE" = true ]; then - return 0 - fi - exit_code=${2:-1} - local API_URL="http://api.community-scripts.org/dev/upload/updatestatus" - local status="${1:-failed}" - if [[ "$status" == "failed" ]]; then - local exit_code="${2:-1}" - elif [[ "$status" == "success" ]]; then - local exit_code="${2:-0}" - fi - - if [[ -z "$exit_code" ]]; then - exit_code=1 - fi - - error=$(get_error_description "$exit_code") - - if [ -z "$error" ]; then - error="Unknown error" - fi - - JSON_PAYLOAD=$( - cat <, sonst Arbeitsverzeichnis oder APP_DIR gesetzt vom Script - base_dir="${APP_DIR:-/opt/$app_name}" - if [[ ! -d "$base_dir" ]]; then - msg_error "Cannot determine base directory for $app_name" - return 1 - fi - - local snapshot_base="${base_dir}-snapshot" - SNAPSHOT_DIR="${snapshot_base}-$(date +%F_%T | tr ':' '-')" - - msg_info "Creating snapshot for $app_name" - - mkdir -p "$SNAPSHOT_DIR" - cp -a "$base_dir" "$SNAPSHOT_DIR/base" || { - msg_error "Failed to backup base directory" - return 1 - } - - mkdir -p "$SNAPSHOT_DIR/systemd" - cp -a /etc/systemd/system/${app_name}-*.service "$SNAPSHOT_DIR/systemd/" 2>/dev/null || true - - [[ -f "/etc/default/$app_name" ]] && cp "/etc/default/$app_name" "$SNAPSHOT_DIR/" - [[ -f "$base_dir/${app_name}_version.txt" ]] && cp "$base_dir/${app_name}_version.txt" "$SNAPSHOT_DIR/" - - rotate_snapshots "$snapshot_base" - - msg_ok "Snapshot created at $SNAPSHOT_DIR" - return 0 -} - -rotate_snapshots() { - local snapshot_base=$1 - local snapshots - - # Sortiert nach Datum absteigend, behalte nur die 3 neuesten - mapfile -t snapshots < <(ls -dt ${snapshot_base}-* 2>/dev/null) - if ((${#snapshots[@]} > 3)); then - for ((i = 3; i < ${#snapshots[@]}; i++)); do - rm -rf "${snapshots[$i]}" - msg_info "Old snapshot removed: ${snapshots[$i]}" - done - fi -} - -rollback_snapshot() { - local app_name=$1 - local base_dir - - base_dir="${APP_DIR:-/opt/$app_name}" - if [[ -z "$SNAPSHOT_DIR" || ! -d "$SNAPSHOT_DIR" ]]; then - msg_error "No snapshot found. Cannot rollback." - return 1 - fi - - msg_info "Rolling back $app_name from snapshot" - - systemctl stop ${app_name}-* 2>/dev/null || true - - rm -rf "$base_dir" - cp -a "$SNAPSHOT_DIR/base" "$base_dir" || { - msg_error "Failed to restore base directory" - return 1 - } - - if [[ -d "$SNAPSHOT_DIR/systemd" ]]; then - cp "$SNAPSHOT_DIR/systemd/"*.service /etc/systemd/system/ 2>/dev/null || true - systemctl daemon-reload - fi - - [[ -f "$SNAPSHOT_DIR/$app_name" ]] && cp "$SNAPSHOT_DIR/$app_name" "/etc/default/$app_name" - [[ -f "$SNAPSHOT_DIR/${app_name}_version.txt" ]] && cp "$SNAPSHOT_DIR/${app_name}_version.txt" "$base_dir/" - - systemctl start ${app_name}-* 2>/dev/null || true - - msg_ok "Rollback for $app_name completed" - return 0 -} - -cleanup_snapshot() { - if [[ -n "$SNAPSHOT_DIR" && -d "$SNAPSHOT_DIR" ]]; then - rm -rf "$SNAPSHOT_DIR" - msg_ok "Cleaned up snapshot at $SNAPSHOT_DIR" - fi -} - -handle_failure() { - local app_name=$1 - local line=$2 - msg_error "Update failed at line $line. Rolling back..." - rollback_snapshot "$app_name" - exit 1 -} - -safe_run_update_script() { - local app_name="${APP:-paperless}" - - if ! create_snapshot "$app_name"; then - msg_error "Snapshot creation failed. Aborting update." - exit 1 - fi - - trap 'handle_failure "$app_name" $LINENO' ERR - set -eE - - update_script - - cleanup_snapshot -} - -wrap_update_script_with_snapshot() { - local original_func - original_func=$(declare -f update_script) || return 1 - - eval " - original_update_script() { - ${original_func#*\{} - } - update_script() { - local app_name=\"\${APP:-paperless}\" - if ! create_snapshot \"\$app_name\"; then - msg_error \"Snapshot creation failed. Aborting update.\" - exit 1 - fi - trap 'handle_failure \"\$app_name\" \$LINENO' ERR - set -eE - original_update_script - cleanup_snapshot - } - " -} diff --git a/misc/backup_07052025/alpine-install copy.func b/misc/backup_07052025/alpine-install copy.func deleted file mode 100644 index 71e7c1d4..00000000 --- a/misc/backup_07052025/alpine-install copy.func +++ /dev/null @@ -1,277 +0,0 @@ -# Copyright (c) 2021-2025 tteck -# Author: tteck (tteckster) -# Co-Author: MickLesk -# License: MIT -# https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE - -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) -load_functions - -# This function sets color variables for formatting output in the terminal -# color() { -# # Colors -# YW=$(echo "\033[33m") -# YWB=$(echo "\033[93m") -# BL=$(echo "\033[36m") -# RD=$(echo "\033[01;31m") -# GN=$(echo "\033[1;92m") - -# # Formatting -# CL=$(echo "\033[m") -# BFR="\\r\\033[K" -# BOLD=$(echo "\033[1m") -# TAB=" " - -# # System -# RETRY_NUM=10 -# RETRY_EVERY=3 -# i=$RETRY_NUM - -# # Icons -# CM="${TAB}βœ”οΈ${TAB}${CL}" -# CROSS="${TAB}βœ–οΈ${TAB}${CL}" -# INFO="${TAB}πŸ’‘${TAB}${CL}" -# NETWORK="${TAB}πŸ“‘${TAB}${CL}" -# OS="${TAB}πŸ–₯️${TAB}${CL}" -# OSVERSION="${TAB}🌟${TAB}${CL}" -# HOSTNAME="${TAB}🏠${TAB}${CL}" -# GATEWAY="${TAB}🌐${TAB}${CL}" -# DEFAULT="${TAB}βš™οΈ${TAB}${CL}" -# } - -# Function to set STD mode based on verbosity -set_std_mode() { - if [ "$VERBOSE" = "yes" ]; then - STD="" - else - STD="silent" - fi -} - -# Silent execution function -silent() { - "$@" >/dev/null 2>&1 -} - -# This function enables IPv6 if it's not disabled and sets verbose mode -verb_ip6() { - set_std_mode # Set STD mode based on VERBOSE - - if [ "$DISABLEIPV6" == "yes" ]; then - $STD sysctl -w net.ipv6.conf.all.disable_ipv6=1 - echo "net.ipv6.conf.all.disable_ipv6 = 1" >>/etc/sysctl.conf - $STD rc-update add sysctl default - fi -} - -# This function catches errors and handles them with the error handler function -catch_errors() { - unset SPINNER_PID - set -Eeuo pipefail - trap 'error_handler $LINENO "$BASH_COMMAND"' ERR -} - -# This function handles errors -error_handler() { - local exit_code="$?" - local line_number="$1" - local command="$2" - local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" - echo -e "\n$error_message\n" - [[ -n "${SPINNER_PID:-}" ]] && kill "$SPINNER_PID" &>/dev/null || true -} - -# # This function displays an informational message with logging support. -# declare -A MSG_INFO_SHOWN -# SPINNER_ACTIVE=0 -# SPINNER_PID="" -# SPINNER_MSG="" - -# trap 'stop_spinner' EXIT INT TERM HUP - -# start_spinner() { -# local msg="$1" -# local frames=(β ‹ β ™ β Ή β Έ β Ό β ΄ β ¦ β § β ‡ ⠏) -# local spin_i=0 -# local interval=0.1 - -# SPINNER_MSG="$msg" -# printf "\r\e[2K" >&2 - -# { -# while [[ "$SPINNER_ACTIVE" -eq 1 ]]; do -# printf "\r\e[2K%s %b" "${frames[spin_i]}" "${YW}${SPINNER_MSG}${CL}" >&2 -# spin_i=$(((spin_i + 1) % ${#frames[@]})) -# sleep "$interval" -# done -# } & - -# SPINNER_PID=$! -# disown "$SPINNER_PID" -# } - -# stop_spinner() { -# if [[ ${SPINNER_PID+v} && -n "$SPINNER_PID" ]] && kill -0 "$SPINNER_PID" 2>/dev/null; then -# kill "$SPINNER_PID" 2>/dev/null -# sleep 0.1 -# kill -0 "$SPINNER_PID" 2>/dev/null && kill -9 "$SPINNER_PID" 2>/dev/null -# wait "$SPINNER_PID" 2>/dev/null || true -# fi -# SPINNER_ACTIVE=0 -# unset SPINNER_PID -# } - -# spinner_guard() { -# if [[ "$SPINNER_ACTIVE" -eq 1 ]] && [[ -n "$SPINNER_PID" ]]; then -# kill "$SPINNER_PID" 2>/dev/null -# wait "$SPINNER_PID" 2>/dev/null || true -# SPINNER_ACTIVE=0 -# unset SPINNER_PID -# fi -# } - -# msg_info() { -# local msg="$1" -# [[ -n "${MSG_INFO_SHOWN["$msg"]+x}" ]] && return -# MSG_INFO_SHOWN["$msg"]=1 - -# spinner_guard -# SPINNER_ACTIVE=1 -# start_spinner "$msg" -# } - -# msg_ok() { -# local msg="$1" -# stop_spinner -# printf "\r\e[2K%s %b\n" "${CM}" "${GN}${msg}${CL}" >&2 -# unset MSG_INFO_SHOWN["$msg"] -# } - -# msg_error() { -# stop_spinner -# local msg="$1" -# printf "\r\e[2K%s %b\n" "${CROSS}" "${RD}${msg}${CL}" >&2 -# #log_message "ERROR" "$msg" -# } - -# This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection -setting_up_container() { - msg_info "Setting up Container OS" - while [ $i -gt 0 ]; do - if [ "$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)" != "" ]; then - break - fi - echo 1>&2 -en "${CROSS}${RD} No Network! " - sleep $RETRY_EVERY - i=$((i - 1)) - done - - if [ "$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)" = "" ]; then - echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" - echo -e "${NETWORK}Check Network Settings" - exit 1 - fi - msg_ok "Set up Container OS" - msg_ok "Network Connected: ${BL}$(ip addr show | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1 | tail -n1)${CL}" -} - -# This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected -network_check() { - set +e - trap - ERR - 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 - msg_ok "Internet Connected" - else - msg_error "Internet NOT Connected" - read -r -p "Would you like to continue anyway? " prompt - if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then - echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" - else - echo -e "${NETWORK}Check Network Settings" - exit 1 - fi - fi - RESOLVEDIP=$(getent hosts github.com | awk '{ print $1 }') - if [[ -z "$RESOLVEDIP" ]]; then msg_error "DNS Lookup Failure"; else msg_ok "DNS Resolved github.com to ${BL}$RESOLVEDIP${CL}"; fi - set -e - trap 'error_handler $LINENO "$BASH_COMMAND"' ERR -} - -# This function updates the Container OS by running apt-get update and upgrade -update_os() { - msg_info "Updating Container OS" - $STD apk update - $STD apk upgrade - msg_ok "Updated Container OS" - - msg_info "Installing core dependencies" - $STD apk update - $STD apk add newt curl openssh nano mc ncurses - msg_ok "Core dependencies installed" -} - -# This function modifies the message of the day (motd) and SSH settings -motd_ssh() { - echo "export TERM='xterm-256color'" >>/root/.bashrc - IP=$(ip -4 addr show eth0 | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1) - - if [ -f "/etc/os-release" ]; then - OS_NAME=$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"') - OS_VERSION=$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"') - else - OS_NAME="Alpine Linux" - OS_VERSION="Unknown" - fi - - PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" - echo "echo -e \"\"" >"$PROFILE_FILE" - echo -e "echo -e \"${BOLD}${YW}${APPLICATION} LXC Container - DEV Repository${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${RD}WARNING: This is a DEVELOPMENT version (ProxmoxVED). Do NOT use in production!${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${YW} OS: ${GN}${OS_NAME} - Version: ${OS_VERSION}${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${YW} IP Address: ${GN}${IP}${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${YW} Repository: ${GN}https://github.com/community-scripts/ProxmoxVED${CL}\"" >>"$PROFILE_FILE" - echo "echo \"\"" >>"$PROFILE_FILE" - - if [[ "${SSH_ROOT}" == "yes" ]]; then - $STD rc-update add sshd - sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config - $STD /etc/init.d/sshd start - fi -} - -# Validate Timezone for some LXC's -validate_tz() { - [[ -f "/usr/share/zoneinfo/$1" ]] -} - -# This function customizes the container and enables passwordless login for the root user -customize() { - if [[ "$PASSWORD" == "" ]]; then - msg_info "Customizing Container" - passwd -d root >/dev/null 2>&1 - - # Ensure agetty is available - apk add --no-cache --force-broken-world util-linux >/dev/null 2>&1 - - # Create persistent autologin boot script - mkdir -p /etc/local.d - cat <<'EOF' >/etc/local.d/autologin.start -#!/bin/sh -sed -i 's|^tty1::respawn:.*|tty1::respawn:/sbin/agetty --autologin root --noclear tty1 38400 linux|' /etc/inittab -kill -HUP 1 -EOF - touch /root/.hushlogin - - chmod +x /etc/local.d/autologin.start - rc-update add local >/dev/null 2>&1 - - # Apply autologin immediately for current session - /etc/local.d/autologin.start - - msg_ok "Customized Container" - fi - - echo "bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${app}.sh)\"" >/usr/bin/update - chmod +x /usr/bin/update -} diff --git a/misc/backup_07052025/build copy.func b/misc/backup_07052025/build copy.func deleted file mode 100644 index bec59ed4..00000000 --- a/misc/backup_07052025/build copy.func +++ /dev/null @@ -1,1495 +0,0 @@ -# Copyright (c) 2021-2025 tteck -# Author: tteck (tteckster) -# Co-Author: MickLesk -# Co-Author: michelroegl-brunner -# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE - -variables() { - 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. - var_install="${NSAPP}-install" # sets the var_install variable by appending "-install" to the value of NSAPP. - INTEGER='^[0-9]+([.][0-9]+)?$' # it defines the INTEGER regular expression pattern. - PVEHOST_NAME=$(hostname) # gets the Proxmox Hostname and sets it to Uppercase - DIAGNOSTICS="yes" # sets the DIAGNOSTICS variable to "yes", used for the API call. - METHOD="default" # sets the METHOD variable to "default", used for the API call. - RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" # generates a random UUID and sets it to the RANDOM_UUID variable. -} - -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/api.func) - -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) -color - -# # This function sets various color variables using ANSI escape codes for formatting text in the terminal. -# color() { -# # Colors -# YW=$(echo "\033[33m") -# YWB=$(echo "\033[93m") -# BL=$(echo "\033[36m") -# RD=$(echo "\033[01;31m") -# BGN=$(echo "\033[4;92m") -# GN=$(echo "\033[1;92m") -# DGN=$(echo "\033[32m") - -# # Formatting -# CL=$(echo "\033[m") -# BOLD=$(echo "\033[1m") -# HOLD=" " -# TAB=" " - -# # Icons -# CM="${TAB}βœ”οΈ${TAB}" -# CROSS="${TAB}βœ–οΈ${TAB}" -# INFO="${TAB}πŸ’‘${TAB}${CL}" -# OS="${TAB}πŸ–₯️${TAB}${CL}" -# OSVERSION="${TAB}🌟${TAB}${CL}" -# CONTAINERTYPE="${TAB}πŸ“¦${TAB}${CL}" -# DISKSIZE="${TAB}πŸ’Ύ${TAB}${CL}" -# CPUCORE="${TAB}🧠${TAB}${CL}" -# RAMSIZE="${TAB}πŸ› οΈ${TAB}${CL}" -# SEARCH="${TAB}πŸ”${TAB}${CL}" -# VERBOSE_CROPPED="πŸ”${TAB}" -# VERIFYPW="${TAB}πŸ”${TAB}${CL}" -# CONTAINERID="${TAB}πŸ†”${TAB}${CL}" -# HOSTNAME="${TAB}🏠${TAB}${CL}" -# BRIDGE="${TAB}πŸŒ‰${TAB}${CL}" -# NETWORK="${TAB}πŸ“‘${TAB}${CL}" -# GATEWAY="${TAB}🌐${TAB}${CL}" -# DISABLEIPV6="${TAB}🚫${TAB}${CL}" -# DEFAULT="${TAB}βš™οΈ${TAB}${CL}" -# MACADDRESS="${TAB}πŸ”—${TAB}${CL}" -# VLANTAG="${TAB}🏷️${TAB}${CL}" -# ROOTSSH="${TAB}πŸ”‘${TAB}${CL}" -# CREATING="${TAB}πŸš€${TAB}${CL}" -# ADVANCED="${TAB}🧩${TAB}${CL}" -# } - -# This function enables error handling in the script by setting options and defining a trap for the ERR signal. -catch_errors() { - set -Eeuo pipefail - trap 'error_handler $LINENO "$BASH_COMMAND"' ERR -} - -# This function is called when an error occurs. It receives the exit code, line number, and command that caused the error, and displays an error message. -error_handler() { - source /dev/stdin <<<$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/api.func) - if [ -n "$SPINNER_PID" ] && ps -p "$SPINNER_PID" >/dev/null; then kill "$SPINNER_PID" >/dev/null; fi - printf "\e[?25h" - local exit_code="$?" - local line_number="$1" - local command="$2" - local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" - post_update_to_api "failed" "${command}" - echo -e "\n$error_message\n" -} - -# # This function displays an informational message with logging support. -# declare -A MSG_INFO_SHOWN -# SPINNER_ACTIVE=0 -# SPINNER_PID="" -# SPINNER_MSG="" - -# trap 'stop_spinner' EXIT INT TERM HUP - -# start_spinner() { -# local msg="$1" -# local frames=(β ‹ β ™ β Ή β Έ β Ό β ΄ β ¦ β § β ‡ ⠏) -# local spin_i=0 -# local interval=0.1 - -# SPINNER_MSG="$msg" -# printf "\r\e[2K" >&2 - -# { -# while [[ "$SPINNER_ACTIVE" -eq 1 ]]; do -# printf "\r\e[2K%s %b" "${frames[spin_i]}" "${YW}${SPINNER_MSG}${CL}" >&2 -# spin_i=$(((spin_i + 1) % ${#frames[@]})) -# sleep "$interval" -# done -# } & - -# SPINNER_PID=$! -# disown "$SPINNER_PID" -# } - -# stop_spinner() { -# if [[ ${SPINNER_PID+v} && -n "$SPINNER_PID" ]] && kill -0 "$SPINNER_PID" 2>/dev/null; then -# kill "$SPINNER_PID" 2>/dev/null -# sleep 0.1 -# kill -0 "$SPINNER_PID" 2>/dev/null && kill -9 "$SPINNER_PID" 2>/dev/null -# wait "$SPINNER_PID" 2>/dev/null || true -# fi -# SPINNER_ACTIVE=0 -# unset SPINNER_PID -# } - -# spinner_guard() { -# if [[ "$SPINNER_ACTIVE" -eq 1 ]] && [[ -n "$SPINNER_PID" ]]; then -# kill "$SPINNER_PID" 2>/dev/null -# wait "$SPINNER_PID" 2>/dev/null || true -# SPINNER_ACTIVE=0 -# unset SPINNER_PID -# fi -# } - -# msg_info() { -# local msg="$1" -# [[ -n "${MSG_INFO_SHOWN["$msg"]+x}" ]] && return -# MSG_INFO_SHOWN["$msg"]=1 - -# spinner_guard -# SPINNER_ACTIVE=1 -# start_spinner "$msg" -# } - -# msg_ok() { -# local msg="$1" -# stop_spinner -# printf "\r\e[2K%s %b\n" "${CM}" "${GN}${msg}${CL}" >&2 -# unset MSG_INFO_SHOWN["$msg"] -# } - -# msg_error() { -# stop_spinner -# local msg="$1" -# printf "\r\e[2K%s %b\n" "${CROSS}" "${RD}${msg}${CL}" >&2 -# #log_message "ERROR" "$msg" -# } - -# log_message() { -# local level="$1" -# local message="$2" -# local timestamp -# local logdate -# timestamp=$(date '+%Y-%m-%d %H:%M:%S') -# logdate=$(date '+%Y-%m-%d') - -# LOGDIR="/usr/local/community-scripts/logs" -# mkdir -p "$LOGDIR" - -# LOGFILE="${LOGDIR}/${logdate}_${NSAPP}.log" -# echo "$timestamp - $level: $message" >>"$LOGFILE" -# } - -# Check if the shell is using bash -shell_check() { - if [[ "$(basename "$SHELL")" != "bash" ]]; then - clear - msg_error "Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell." - echo -e "\nExiting..." - sleep 2 - exit - fi -} - -# Run as root only -root_check() { - if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then - clear - msg_error "Please run this script as root." - echo -e "\nExiting..." - sleep 2 - exit - fi -} - -# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported. -pve_check() { - if ! pveversion | grep -Eq "pve-manager/8\.[0-4](\.[0-9]+)*"; then - msg_error "${CROSS}${RD}This version of Proxmox Virtual Environment is not supported" - echo -e "Requires Proxmox Virtual Environment Version 8.1 or later." - echo -e "Exiting..." - sleep 2 - exit - fi -} - -# When a node is running tens of containers, it's possible to exceed the kernel's cryptographic key storage allocations. -# These are tuneable, so verify if the currently deployment is approaching the limits, advise the user on how to tune the limits, and exit the script. -# https://cleveruptime.com/docs/files/proc-key-users | https://docs.kernel.org/security/keys/core.html -maxkeys_check() { - # Read kernel parameters - per_user_maxkeys=$(cat /proc/sys/kernel/keys/maxkeys 2>/dev/null || echo 0) - per_user_maxbytes=$(cat /proc/sys/kernel/keys/maxbytes 2>/dev/null || echo 0) - - # Exit if kernel parameters are unavailable - if [[ "$per_user_maxkeys" -eq 0 || "$per_user_maxbytes" -eq 0 ]]; then - echo -e "${CROSS}${RD} Error: Unable to read kernel parameters. Ensure proper permissions.${CL}" - exit 1 - fi - - # Fetch key usage for user ID 100000 (typical for containers) - used_lxc_keys=$(awk '/100000:/ {print $2}' /proc/key-users 2>/dev/null || echo 0) - used_lxc_bytes=$(awk '/100000:/ {split($5, a, "/"); print a[1]}' /proc/key-users 2>/dev/null || echo 0) - - # Calculate thresholds and suggested new limits - threshold_keys=$((per_user_maxkeys - 100)) - threshold_bytes=$((per_user_maxbytes - 1000)) - new_limit_keys=$((per_user_maxkeys * 2)) - new_limit_bytes=$((per_user_maxbytes * 2)) - - # Check if key or byte usage is near limits - failure=0 - if [[ "$used_lxc_keys" -gt "$threshold_keys" ]]; then - echo -e "${CROSS}${RD} Warning: Key usage is near the limit (${used_lxc_keys}/${per_user_maxkeys}).${CL}" - echo -e "${INFO} Suggested action: Set ${GN}kernel.keys.maxkeys=${new_limit_keys}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}." - failure=1 - fi - if [[ "$used_lxc_bytes" -gt "$threshold_bytes" ]]; then - echo -e "${CROSS}${RD} Warning: Key byte usage is near the limit (${used_lxc_bytes}/${per_user_maxbytes}).${CL}" - echo -e "${INFO} Suggested action: Set ${GN}kernel.keys.maxbytes=${new_limit_bytes}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}." - failure=1 - fi - - # Provide next steps if issues are detected - if [[ "$failure" -eq 1 ]]; then - echo -e "${INFO} To apply changes, run: ${BOLD}service procps force-reload${CL}" - exit 1 - fi - - echo -e "${CM}${GN} All kernel key limits are within safe thresholds.${CL}" -} - -# This function checks the system architecture and exits if it's not "amd64". -arch_check() { - if [ "$(dpkg --print-architecture)" != "amd64" ]; then - echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n" - echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n" - echo -e "Exiting..." - sleep 2 - exit - fi -} - -# Function to get the current IP address based on the distribution -get_current_ip() { - if [ -f /etc/os-release ]; then - # Check for Debian/Ubuntu (uses hostname -I) - if grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then - CURRENT_IP=$(hostname -I | awk '{print $1}') - # Check for Alpine (uses ip command) - elif grep -q 'ID=alpine' /etc/os-release; then - CURRENT_IP=$(ip -4 addr show eth0 | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1) - else - CURRENT_IP="Unknown" - fi - fi - echo "$CURRENT_IP" -} - -# Function to update the IP address in the MOTD file -update_motd_ip() { - MOTD_FILE="/etc/motd" - - if [ -f "$MOTD_FILE" ]; then - # Remove existing IP Address lines to prevent duplication - sed -i '/IP Address:/d' "$MOTD_FILE" - - IP=$(get_current_ip) - # Add the new IP address - echo -e "${TAB}${NETWORK}${YW} IP Address: ${GN}${IP}${CL}" >>"$MOTD_FILE" - fi -} - -# Function to download & save header files -get_header() { - local app_name=$(echo "${APP,,}" | tr -d ' ') - local header_url="https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/ct/headers/${app_name}" - local local_header_path="/usr/local/community-scripts/headers/${app_name}" - - mkdir -p "$(dirname "$local_header_path")" - - if [ ! -s "$local_header_path" ]; then - if ! curl -fsSL "$header_url" -o "$local_header_path"; then - return 1 - fi - fi - - cat "$local_header_path" 2>/dev/null || true -} -# This function sets the APP-Name into an ASCII Header in Slant, figlet needed on proxmox main node. -header_info() { - local app_name=$(echo "${APP,,}" | tr -d ' ') - local header_content - - # Download & save Header-File locally - header_content=$(get_header "$app_name") || header_content="" - - # Show ASCII-Header - clear - local term_width - term_width=$(tput cols 2>/dev/null || echo 120) - - if [ -n "$header_content" ]; then - echo "$header_content" - fi -} - -# This function checks if the script is running through SSH and prompts the user to confirm if they want to proceed or exit. -ssh_check() { - if [ -n "${SSH_CLIENT:+x}" ]; then - if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's advisable to utilize the Proxmox shell rather than SSH, as there may be potential complications with variable retrieval. Proceed using SSH?" 10 72; then - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox --title "Proceed using SSH" "You've chosen to proceed using SSH. If any issues arise, please run the script in the Proxmox shell before creating a repository issue." 10 72 - else - clear - echo "Exiting due to SSH usage. Please consider using the Proxmox shell." - exit - fi - fi -} - -base_settings() { - # Default Settings - CT_TYPE="1" - DISK_SIZE="4" - CORE_COUNT="1" - RAM_SIZE="1024" - VERBOSE="${1:-no}" - PW="" - CT_ID=$NEXTID - HN=$NSAPP - BRG="vmbr0" - NET="dhcp" - GATE="" - APT_CACHER="" - APT_CACHER_IP="" - DISABLEIP6="no" - MTU="" - SD="" - NS="" - MAC="" - VLAN="" - SSH="no" - SSH_AUTHORIZED_KEY="" - TAGS="community-script;" - UDHCPC_FIX="" - - # Override default settings with variables from ct script - CT_TYPE=${var_unprivileged:-$CT_TYPE} - DISK_SIZE=${var_disk:-$DISK_SIZE} - CORE_COUNT=${var_cpu:-$CORE_COUNT} - RAM_SIZE=${var_ram:-$RAM_SIZE} - VERB=${var_verbose:-$VERBOSE} - TAGS="${TAGS}${var_tags:-}" - - # 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 - if [ -z "$var_os" ]; then - var_os="debian" - fi - if [ -z "$var_version" ]; then - var_version="12" - fi -} - -# This function displays the default values for various settings. -echo_default() { - # Convert CT_TYPE to description - CT_TYPE_DESC="Unprivileged" - if [ "$CT_TYPE" -eq 0 ]; then - CT_TYPE_DESC="Privileged" - fi - - # Output the selected values with icons - echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}" - echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}" - echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}" - echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" - echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}" - echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}${CT_ID}${CL}" - if [ "$VERB" == "yes" ]; then - echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}Enabled${CL}" - fi - echo -e "${CREATING}${BOLD}${BL}Creating a ${APP} LXC using the above default settings${CL}" - echo -e " " -} - -# This function is called when the user decides to exit the script. It clears the screen and displays an exit message. -exit_script() { - [[ -n "${SPINNER_PID:-}" ]] && kill "$SPINNER_PID" &>/dev/null || true - clear - echo -e "\n${CROSS}${RD}User exited script${CL}\n" - exit -} - -# This function allows the user to configure advanced settings for the script. -advanced_settings() { - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox --title "Here is an instructional tip:" "To make a selection, use the Spacebar." 8 58 - # Setting Default Tag for Advanced Settings - TAGS="community-script;${var_tags:-}" - CT_DEFAULT_TYPE="${CT_TYPE}" - CT_TYPE="" - while [ -z "$CT_TYPE" ]; do - if [ "$CT_DEFAULT_TYPE" == "1" ]; then - if CT_TYPE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "CONTAINER TYPE" --radiolist "Choose Type" 10 58 2 \ - "1" "Unprivileged" ON \ - "0" "Privileged" OFF \ - 3>&1 1>&2 2>&3); then - if [ -n "$CT_TYPE" ]; then - CT_TYPE_DESC="Unprivileged" - if [ "$CT_TYPE" -eq 0 ]; then - CT_TYPE_DESC="Privileged" - fi - echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}" - echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}" - fi - else - exit_script - fi - fi - if [ "$CT_DEFAULT_TYPE" == "0" ]; then - if CT_TYPE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "CONTAINER TYPE" --radiolist "Choose Type" 10 58 2 \ - "1" "Unprivileged" OFF \ - "0" "Privileged" ON \ - 3>&1 1>&2 2>&3); then - if [ -n "$CT_TYPE" ]; then - CT_TYPE_DESC="Unprivileged" - if [ "$CT_TYPE" -eq 0 ]; then - CT_TYPE_DESC="Privileged" - fi - echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}" - echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}" - fi - else - exit_script - fi - fi - done - - while true; do - if PW1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --passwordbox "\nSet Root Password (needed for root ssh access)" 9 58 --title "PASSWORD (leave blank for automatic login)" 3>&1 1>&2 2>&3); then - if [[ ! -z "$PW1" ]]; then - if [[ "$PW1" == *" "* ]]; then - whiptail --msgbox "Password cannot contain spaces. Please try again." 8 58 - elif [ ${#PW1} -lt 5 ]; then - whiptail --msgbox "Password must be at least 5 characters long. Please try again." 8 58 - else - if PW2=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --passwordbox "\nVerify Root Password" 9 58 --title "PASSWORD VERIFICATION" 3>&1 1>&2 2>&3); then - if [[ "$PW1" == "$PW2" ]]; then - PW="-password $PW1" - echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}********${CL}" - break - else - whiptail --msgbox "Passwords do not match. Please try again." 8 58 - fi - else - exit_script - fi - fi - else - PW1="Automatic Login" - PW="" - echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}$PW1${CL}" - break - fi - else - exit_script - fi - done - - if CT_ID=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Container ID" 8 58 "$NEXTID" --title "CONTAINER ID" 3>&1 1>&2 2>&3); then - if [ -z "$CT_ID" ]; then - CT_ID="$NEXTID" - echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}" - else - echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}" - fi - else - exit - fi - - if CT_NAME=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 "$NSAPP" --title "HOSTNAME" 3>&1 1>&2 2>&3); then - if [ -z "$CT_NAME" ]; then - HN="$NSAPP" - else - HN=$(echo "${CT_NAME,,}" | tr -d ' ') - fi - echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" - else - exit_script - fi - - if DISK_SIZE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GB" 8 58 "$var_disk" --title "DISK SIZE" 3>&1 1>&2 2>&3); then - if [ -z "$DISK_SIZE" ]; then - DISK_SIZE="$var_disk" - echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}" - else - if ! [[ $DISK_SIZE =~ $INTEGER ]]; then - echo -e "{INFO}${HOLD}${RD} DISK SIZE MUST BE AN INTEGER NUMBER!${CL}" - advanced_settings - fi - echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}" - fi - else - exit_script - fi - - if CORE_COUNT=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Allocate CPU Cores" 8 58 "$var_cpu" --title "CORE COUNT" 3>&1 1>&2 2>&3); then - if [ -z "$CORE_COUNT" ]; then - CORE_COUNT="$var_cpu" - echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" - else - echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" - fi - else - exit_script - fi - - if RAM_SIZE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 "$var_ram" --title "RAM" 3>&1 1>&2 2>&3); then - if [ -z "$RAM_SIZE" ]; then - RAM_SIZE="$var_ram" - echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}" - else - echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}" - fi - else - exit_script - fi - - if BRG=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a Bridge" 8 58 vmbr0 --title "BRIDGE" 3>&1 1>&2 2>&3); then - if [ -z "$BRG" ]; then - BRG="vmbr0" - echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" - else - echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" - fi - else - exit_script - fi - - while true; do - NET=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a Static IPv4 CIDR Address (/24)" 8 58 dhcp --title "IP ADDRESS" 3>&1 1>&2 2>&3) - exit_status=$? - if [ $exit_status -eq 0 ]; then - if [ "$NET" = "dhcp" ]; then - echo -e "${NETWORK}${BOLD}${DGN}IP Address: ${BGN}$NET${CL}" - break - else - if [[ "$NET" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then - echo -e "${NETWORK}${BOLD}${DGN}IP Address: ${BGN}$NET${CL}" - break - else - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox "$NET is an invalid IPv4 CIDR address. Please enter a valid IPv4 CIDR address or 'dhcp'" 8 58 - fi - fi - else - exit_script - fi - done - - if [ "$NET" != "dhcp" ]; then - while true; do - GATE1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Enter gateway IP address" 8 58 --title "Gateway IP" 3>&1 1>&2 2>&3) - if [ -z "$GATE1" ]; then - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox "Gateway IP address cannot be empty" 8 58 - elif [[ ! "$GATE1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox "Invalid IP address format" 8 58 - else - GATE=",gw=$GATE1" - echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE1${CL}" - break - fi - done - else - GATE="" - echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}Default${CL}" - fi - - if [ "$var_os" == "alpine" ]; then - APT_CACHER="" - APT_CACHER_IP="" - else - if APT_CACHER_IP=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set APT-Cacher IP (leave blank for none)" 8 58 --title "APT-Cacher IP" 3>&1 1>&2 2>&3); then - APT_CACHER="${APT_CACHER_IP:+yes}" - echo -e "${NETWORK}${BOLD}${DGN}APT-Cacher IP Address: ${BGN}${APT_CACHER_IP:-Default}${CL}" - else - exit_script - fi - fi - - if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "IPv6" --yesno "Disable IPv6?" 10 58); then - DISABLEIP6="yes" - else - DISABLEIP6="no" - fi - echo -e "${DISABLEIPV6}${BOLD}${DGN}Disable IPv6: ${BGN}$DISABLEIP6${CL}" - - if MTU1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default [The MTU of your selected vmbr, default is 1500])" 8 58 --title "MTU SIZE" 3>&1 1>&2 2>&3); then - if [ -z "$MTU1" ]; then - MTU1="Default" - MTU="" - else - MTU=",mtu=$MTU1" - fi - echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" - else - exit_script - fi - - if SD=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a DNS Search Domain (leave blank for HOST)" 8 58 --title "DNS Search Domain" 3>&1 1>&2 2>&3); then - if [ -z "$SD" ]; then - SX=Host - SD="" - else - SX=$SD - SD="-searchdomain=$SD" - fi - echo -e "${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}$SX${CL}" - else - exit_script - fi - - if NX=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a DNS Server IP (leave blank for HOST)" 8 58 --title "DNS SERVER IP" 3>&1 1>&2 2>&3); then - if [ -z "$NX" ]; then - NX=Host - NS="" - else - NS="-nameserver=$NX" - fi - echo -e "${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}$NX${CL}" - else - exit_script - fi - - if [ "$var_os" == "alpine" ] && [ "$NET" == "dhcp" ] && [ "$NX" != "Host" ]; then - UDHCPC_FIX="yes" - else - UDHCPC_FIX="no" - fi - export UDHCPC_FIX - - if MAC1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a MAC Address(leave blank for generated MAC)" 8 58 --title "MAC ADDRESS" 3>&1 1>&2 2>&3); then - if [ -z "$MAC1" ]; then - MAC1="Default" - MAC="" - else - MAC=",hwaddr=$MAC1" - echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}" - fi - else - exit_script - fi - - if VLAN1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a Vlan(leave blank for no VLAN)" 8 58 --title "VLAN" 3>&1 1>&2 2>&3); then - if [ -z "$VLAN1" ]; then - VLAN1="Default" - VLAN="" - else - VLAN=",tag=$VLAN1" - fi - echo -e "${VLANTAG}${BOLD}${DGN}Vlan: ${BGN}$VLAN1${CL}" - else - exit_script - fi - - if ADV_TAGS=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Custom Tags?[If you remove all, there will be no tags!]" 8 58 "${TAGS}" --title "Advanced Tags" 3>&1 1>&2 2>&3); then - if [ -n "${ADV_TAGS}" ]; then - ADV_TAGS=$(echo "$ADV_TAGS" | tr -d '[:space:]') - TAGS="${ADV_TAGS}" - else - TAGS=";" - fi - echo -e "${NETWORK}${BOLD}${DGN}Tags: ${BGN}$TAGS${CL}" - else - exit_script - fi - - if [[ "$PW" == -password* ]]; then - if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "SSH ACCESS" --yesno "Enable Root SSH Access?" 10 58); then - SSH="yes" - else - SSH="no" - fi - echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" - else - SSH="no" - echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" - fi - - if [[ "${SSH}" == "yes" ]]; then - SSH_AUTHORIZED_KEY="$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "SSH Authorized key for root (leave empty for none)" 8 58 --title "SSH Key" 3>&1 1>&2 2>&3)" - - if [[ -z "${SSH_AUTHORIZED_KEY}" ]]; then - echo "Warning: No SSH key provided." - fi - else - SSH_AUTHORIZED_KEY="" - fi - if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "VERBOSE MODE" --yesno "Enable Verbose Mode?" 10 58); then - VERB="yes" - else - VERB="no" - fi - echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERB${CL}" - - if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create ${APP} LXC?" 10 58); then - echo -e "${CREATING}${BOLD}${RD}Creating a ${APP} LXC using the above advanced settings${CL}" - else - clear - header_info - echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings on node $PVEHOST_NAME${CL}" - advanced_settings - fi -} - -diagnostics_check() { - if ! [ -d "/usr/local/community-scripts" ]; then - mkdir -p /usr/local/community-scripts - fi - - if ! [ -f "/usr/local/community-scripts/diagnostics" ]; then - if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS" --yesno "Send Diagnostics of LXC Installation?\n\n(This only transmits data without user data, just RAM, CPU, LXC name, ...)" 10 58); then - cat </usr/local/community-scripts/diagnostics -DIAGNOSTICS=yes - -#This file is used to store the diagnostics settings for the Community-Scripts API. -#https://github.com/community-scripts/ProxmoxVED/discussions/1836 -#Your diagnostics will be sent to the Community-Scripts API for troubleshooting/statistical purposes. -#You can review the data at https://community-scripts.github.io/ProxmoxVE/data -#If you do not wish to send diagnostics, please set the variable 'DIAGNOSTICS' to "no" in /usr/local/community-scripts/diagnostics, or use the menue. -#This will disable the diagnostics feature. -#To send diagnostics, set the variable 'DIAGNOSTICS' to "yes" in /usr/local/community-scripts/diagnostics, or use the menue. -#This will enable the diagnostics feature. -#The following information will be sent: -#"ct_type" -#"disk_size" -#"core_count" -#"ram_size" -#"os_type" -#"os_version" -#"disableip6" -#"nsapp" -#"method" -#"pve_version" -#"status" -#If you have any concerns, please review the source code at /misc/build.func -EOF - DIAGNOSTICS="yes" - else - cat </usr/local/community-scripts/diagnostics -DIAGNOSTICS=no - -#This file is used to store the diagnostics settings for the Community-Scripts API. -#https://github.com/community-scripts/ProxmoxVED/discussions/1836 -#Your diagnostics will be sent to the Community-Scripts API for troubleshooting/statistical purposes. -#You can review the data at https://community-scripts.github.io/ProxmoxVE/data -#If you do not wish to send diagnostics, please set the variable 'DIAGNOSTICS' to "no" in /usr/local/community-scripts/diagnostics, or use the menue. -#This will disable the diagnostics feature. -#To send diagnostics, set the variable 'DIAGNOSTICS' to "yes" in /usr/local/community-scripts/diagnostics, or use the menue. -#This will enable the diagnostics feature. -#The following information will be sent: -#"ct_type" -#"disk_size" -#"core_count" -#"ram_size" -#"os_type" -#"os_version" -#"disableip6" -#"nsapp" -#"method" -#"pve_version" -#"status" -#If you have any concerns, please review the source code at /misc/build.func -EOF - DIAGNOSTICS="no" - fi - else - DIAGNOSTICS=$(awk -F '=' '/^DIAGNOSTICS/ {print $2}' /usr/local/community-scripts/diagnostics) - - fi - -} - -config_file() { - - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox --title "Default distribution for $APP" "${var_os} ${var_version} \n \nIf the default Linux distribution is not adhered to, script support will be discontinued. \n" 10 58 - - CONFIG_FILE="/opt/community-scripts/.settings" - - if [[ -f "/opt/community-scripts/${NSAPP}.conf" ]]; then - CONFIG_FILE="/opt/community-scripts/${NSAPP}.conf" - fi - - if CONFIG_FILE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set absolute path to config file" 8 58 "$CONFIG_FILE" --title "CONFIG FILE" 3>&1 1>&2 2>&3); then - if [[ ! -f "$CONFIG_FILE" ]]; then - echo -e "${CROSS}${RD}Config file not found, exiting script!.${CL}" - exit - else - echo -e "${INFO}${BOLD}${DGN}Using config File: ${BGN}$CONFIG_FILE${CL}" - base_settings - source "$CONFIG_FILE" - fi - fi - - if [[ "$var_os" == "debian" ]]; then - echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}" - if [[ "$var_version" == "11" ]]; then - echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" - elif [[ "$var_version" == "12" ]]; then - echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" - else - msg_error "Unknown setting for var_version, should be 11 or 12, was ${var_version}" - exit - fi - elif [[ "$var_os" == "ubuntu" ]]; then - echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}" - if [[ "$var_version" == "20.04" ]]; then - echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" - elif [[ "$var_version" == "22.04" ]]; then - echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" - elif [[ "$var_version" == "24.04" ]]; then - echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" - elif [[ "$var_version" == "24.10" ]]; then - echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" - else - msg_error "Unknown setting for var_version, should be 20.04, 22.04, 24.04 or 24.10, was ${var_version}" - exit - fi - else - msg_error "Unknown setting for var_os! should be debian or ubuntu, was ${var_os}" - exit - fi - - if [[ -n "$CT_ID" ]]; then - - if [[ "$CT_ID" =~ ^([0-9]{3,4})-([0-9]{3,4})$ ]]; then - MIN_ID=${BASH_REMATCH[1]} - MAX_ID=${BASH_REMATCH[2]} - - if ((MIN_ID >= MAX_ID)); then - msg_error "Invalid Container ID range. The first number must be smaller than the second number, was ${CT_ID}" - exit - fi - - LIST_OF_IDS=$(pvesh get /cluster/resources --type vm --output-format json | grep -oP '"vmid":\s*\K\d+') - - for ((ID = MIN_ID; ID <= MAX_ID; ID++)); do - if ! grep -q "^$ID$" <<<"$LIST_OF_IDS"; then - CT_ID=$ID - break - fi - done - - echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}" - - elif [[ "$CT_ID" =~ ^[0-9]+$ ]]; then - - LIST_OF_IDS=$(pvesh get /cluster/resources --type vm --output-format json | grep -oP '"vmid":\s*\K\d+') - if ! grep -q "^$CT_ID$" <<<"$LIST_OF_IDS"; then - echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}" - else - msg_error "Container ID $CT_ID already exists" - exit - fi - else - msg_error "Invalid Container ID format. Needs to be 0000-9999 or 0-9999, was ${CT_ID}" - exit - fi - fi - - if [[ "$CT_TYPE" -eq 0 ]]; then - CT_TYPE_DESC="Privileged" - elif [[ "$CT_TYPE" -eq 1 ]]; then - CT_TYPE_DESC="Unprivileged" - else - msg_error "Unknown setting for CT_TYPE, should be 1 or 0, was ${CT_TYPE}" - exit - fi - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}" - - if [[ ! -z "$PW" ]]; then - - if [[ "$PW" == *" "* ]]; then - msg_error "Password cannot be empty" - exit - elif [[ ${#PW} -lt 5 ]]; then - msg_error "Password must be at least 5 characters long" - exit - else - echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}********${CL}" - fi - PW="-password $PW" - else - PW="" - echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}Automatic Login${CL}" - fi - echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}" - - if [[ ! -z "$HN" ]]; then - echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" - else - msg_error "Hostname cannot be empty" - exit - fi - - if [[ ! -z "$DISK_SIZE" ]]; then - if [[ "$DISK_SIZE" =~ ^-?[0-9]+$ ]]; then - echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}" - else - msg_error "DISK_SIZE must be an integer, was ${DISK_SIZE}" - exit - fi - else - msg_error "DISK_SIZE cannot be empty" - exit - fi - - if [[ ! -z "$CORE_COUNT" ]]; then - if [[ "$CORE_COUNT" =~ ^-?[0-9]+$ ]]; then - echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" - else - msg_error "CORE_COUNT must be an integer, was ${CORE_COUNT}" - exit - fi - else - msg_error "CORE_COUNT cannot be empty" - exit - fi - - if [[ ! -z "$RAM_SIZE" ]]; then - if [[ "$RAM_SIZE" =~ ^-?[0-9]+$ ]]; then - echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}" - else - msg_error "RAM_SIZE must be an integer, was ${RAM_SIZE}" - exit - fi - else - msg_error "RAM_SIZE cannot be empty" - exit - fi - - if [[ ! -z "$BRG" ]]; then - if grep -q "^iface ${BRG}" /etc/network/interfaces; then - echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" - else - msg_error "Bridge '${BRG}' does not exist in /etc/network/interfaces" - exit - fi - else - msg_error "Bridge cannot be empty" - exit - fi - - local ip_cidr_regex='^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/([0-9]{1,2})$' - local ip_regex='^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$' - - if [[ ! -z $NET ]]; then - if [ "$NET" == "dhcp" ]; then - echo -e "${NETWORK}${BOLD}${DGN}IP Address: ${BGN}DHCP${CL}" - echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}Default${CL}" - elif - [[ "$NET" =~ $ip_cidr_regex ]] - then - echo -e "${NETWORK}${BOLD}${DGN}IP Address: ${BGN}$NET${CL}" - else - msg_error "Invalid IP Address format. Needs to be 0.0.0.0/0, was ${NET}" - exit - fi - fi - if [ ! -z "$GATE" ]; then - if [[ "$GATE" =~ $ip_regex ]]; then - echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE${CL}" - GATE=",gw=$GATE" - else - msg_error "Invalid IP Address format for Gateway. Needs to be 0.0.0.0, was ${GATE}" - exit - fi - else - msg_error "Gateway IP Address cannot be empty" - exit - fi - - if [[ ! -z "$APT_CACHER_IP" ]]; then - if [[ "$APT_CACHER_IP" =~ $ip_regex ]]; then - APT_CACHER="yes" - echo -e "${NETWORK}${BOLD}${DGN}APT-CACHER IP Address: ${BGN}$APT_CACHER_IP${CL}" - else - msg_error "Invalid IP Address format for APT-Cacher. Needs to be 0.0.0.0, was ${APT_CACHER_IP}" - exit - fi - fi - - if [[ "$DISABLEIP6" == "yes" ]]; then - echo -e "${DISABLEIPV6}${BOLD}${DGN}Disable IPv6: ${BGN}Yes${CL}" - elif [[ "$DISABLEIP6" == "no" ]]; then - echo -e "${DISABLEIPV6}${BOLD}${DGN}Disable IPv6: ${BGN}No${CL}" - else - msg_error "Disable IPv6 needs to be 'yes' or 'no'" - exit - fi - - if [[ ! -z "$MTU" ]]; then - if [[ "$MTU" =~ ^-?[0-9]+$ ]]; then - echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU${CL}" - MTU=",mtu=$MTU" - else - msg_error "MTU must be an integer, was ${MTU}" - exit - fi - else - MTU="" - echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}" - - fi - - if [[ ! -z "$SD" ]]; then - echo -e "${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}$SD${CL}" - SD="-searchdomain=$SD" - else - SD="" - echo -e "${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}HOST${CL}" - fi - - if [[ ! -z "$NS" ]]; then - if [[ "$NS" =~ $ip_regex ]]; then - echo -e "${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}$NS${CL}" - NS="-nameserver=$NS" - else - msg_error "Invalid IP Address format for DNS Server. Needs to be 0.0.0.0, was ${NS}" - exit - fi - else - NS="" - echo -e "${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}HOST${CL}" - fi - - if [[ ! -z "$MAC" ]]; then - if [[ "$MAC" =~ ^([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}$ ]]; then - echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}" - MAC=",hwaddr=$MAC" - else - msg_error "MAC Address must be in the format xx:xx:xx:xx:xx:xx, was ${MAC}" - exit - fi - fi - - if [[ ! -z "$VLAN" ]]; then - if [[ "$VLAN" =~ ^-?[0-9]+$ ]]; then - echo -e "${VLANTAG}${BOLD}${DGN}Vlan: ${BGN}$VLAN${CL}" - VLAN=",tag=$VLAN" - else - msg_error "VLAN must be an integer, was ${VLAN}" - exit - fi - fi - - if [[ ! -z "$TAGS" ]]; then - echo -e "${NETWORK}${BOLD}${DGN}Tags: ${BGN}$TAGS${CL}" - fi - - if [[ "$SSH" == "yes" ]]; then - echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" - if [[ ! -z "$SSH_AUTHORIZED_KEY" ]]; then - echo -e "${ROOTSSH}${BOLD}${DGN}SSH Authorized Key: ${BGN}********************${CL}" - else - echo -e "${ROOTSSH}${BOLD}${DGN}SSH Authorized Key: ${BGN}None${CL}" - fi - elif [[ "$SSH" == "no" ]]; then - echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" - else - msg_error "SSH needs to be 'yes' or 'no', was ${SSH}" - exit - fi - - if [[ "$VERB" == "yes" ]]; then - echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERB${CL}" - elif [[ "$VERB" == "no" ]]; then - echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}No${CL}" - else - msg_error "Verbose Mode needs to be 'yes' or 'no', was ${VERB}" - exit - fi - - if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS WITH CONFIG FILE COMPLETE" --yesno "Ready to create ${APP} LXC?" 10 58); then - echo -e "${CREATING}${BOLD}${RD}Creating a ${APP} LXC using the above settings${CL}" - else - clear - header_info - echo -e "${INFO}${HOLD} ${GN}Using Config File on node $PVEHOST_NAME${CL}" - config_file - fi - -} - -install_script() { - pve_check - shell_check - root_check - arch_check - ssh_check - maxkeys_check - diagnostics_check - - if systemctl is-active -q ping-instances.service; then - systemctl -q stop ping-instances.service - fi - NEXTID=$(pvesh get /cluster/nextid) - timezone=$(cat /etc/timezone) - header_info - while true; do - - CHOICE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SETTINGS" --menu "Choose an option:" \ - 18 60 6 \ - "1" "Default Settings" \ - "2" "Default Settings (with verbose)" \ - "3" "Advanced Settings" \ - "4" "Use Config File" \ - "5" "Diagnostic Settings" \ - "6" "Exit" --nocancel --default-item "1" 3>&1 1>&2 2>&3) - - if [ $? -ne 0 ]; then - echo -e "${CROSS}${RD} Menu canceled. Exiting.${CL}" - exit 0 - fi - - case $CHOICE in - 1) - header_info - echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME${CL}" - VERB="no" - METHOD="default" - base_settings "$VERB" - echo_default - break - ;; - 2) - header_info - echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME (${VERBOSE_CROPPED}Verbose)${CL}" - VERB="yes" - METHOD="default" - base_settings "$VERB" - echo_default - break - ;; - 3) - header_info - echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings on node $PVEHOST_NAME${CL}" - METHOD="advanced" - base_settings - advanced_settings - break - ;; - 4) - header_info - echo -e "${INFO}${HOLD} ${GN}Using Config File on node $PVEHOST_NAME${CL}" - METHOD="advanced" - base_settings - config_file - break - ;; - 5) - if [[ $DIAGNOSTICS == "yes" ]]; then - if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --yesno "Send Diagnostics of LXC Installation?\n\nCurrent setting: ${DIAGNOSTICS}" 10 58 \ - --yes-button "No" --no-button "Back"; then - DIAGNOSTICS="no" - sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=no/' /usr/local/community-scripts/diagnostics - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --msgbox "Diagnostics settings changed to ${DIAGNOSTICS}." 8 58 - fi - else - if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --yesno "Send Diagnostics of LXC Installation?\n\nCurrent setting: ${DIAGNOSTICS}" 10 58 \ - --yes-button "Yes" --no-button "Back"; then - DIAGNOSTICS="yes" - sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=yes/' /usr/local/community-scripts/diagnostics - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --msgbox "Diagnostics settings changed to ${DIAGNOSTICS}." 8 58 - fi - fi - - ;; - 6) - echo -e "${CROSS}${RD}Exiting.${CL}" - exit 0 - ;; - *) - echo -e "${CROSS}${RD}Invalid option, please try again.${CL}" - ;; - esac - done -} - -check_container_resources() { - # Check actual RAM & Cores - current_ram=$(free -m | awk 'NR==2{print $2}') - current_cpu=$(nproc) - - # Check whether the current RAM is less than the required RAM or the CPU cores are less than required - if [[ "$current_ram" -lt "$var_ram" ]] || [[ "$current_cpu" -lt "$var_cpu" ]]; then - echo -e "\n${INFO}${HOLD} ${GN}Required: ${var_cpu} CPU, ${var_ram}MB RAM ${CL}| ${RD}Current: ${current_cpu} CPU, ${current_ram}MB RAM${CL}" - 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" - echo -ne "${INFO}${HOLD} May cause data loss! ${INFO} Continue update with under-provisioned LXC? " - read -r prompt - # Check if the input is 'yes', otherwise exit with status 1 - if [[ ! ${prompt,,} =~ ^(yes)$ ]]; then - echo -e "${CROSS}${HOLD} ${YWB}Exiting based on user input.${CL}" - exit 1 - fi - else - echo -e "" - fi -} - -check_container_storage() { - # Check if the /boot partition is more than 80% full - total_size=$(df /boot --output=size | tail -n 1) - local used_size=$(df /boot --output=used | tail -n 1) - usage=$((100 * used_size / total_size)) - if ((usage > 80)); then - # Prompt the user for confirmation to continue - echo -e "${INFO}${HOLD} ${YWB}Warning: Storage is dangerously low (${usage}%).${CL}" - echo -ne "Continue anyway? " - read -r prompt - # Check if the input is 'y' or 'yes', otherwise exit with status 1 - if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then - echo -e "${CROSS}${HOLD}${YWB}Exiting based on user input.${CL}" - exit 1 - fi - fi -} - -start() { - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) - if command -v pveversion >/dev/null 2>&1; then - if ! (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "${APP} LXC" --yesno "This will create a New ${APP} LXC. Proceed?" 10 58); then - clear - exit_script - exit - fi - SPINNER_PID="" - install_script - else - CHOICE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "${APP} LXC Update/Setting" --menu \ - "Support/Update functions for ${APP} LXC. Choose an option:" \ - 12 60 3 \ - "1" "YES (Silent Mode)" \ - "2" "YES (Verbose Mode)" \ - "3" "NO (Cancel Update)" --nocancel --default-item "1" 3>&1 1>&2 2>&3) - - case "$CHOICE" in - 1) - VERB="no" - set_std_mode - ;; - 2) - VERB="yes" - set_std_mode - ;; - 3) - clear - exit_script - exit - ;; - esac - - SPINNER_PID="" - update_script - fi -} - -# This function collects user settings and integrates all the collected information. -build_container() { - # if [ "$VERB" == "yes" ]; then set -x; fi - - if [ "$CT_TYPE" == "1" ]; then - FEATURES="keyctl=1,nesting=1" - else - FEATURES="nesting=1" - fi - - if [[ $DIAGNOSTICS == "yes" ]]; then - post_to_api - fi - - TEMP_DIR=$(mktemp -d) - pushd "$TEMP_DIR" >/dev/null - if [ "$var_os" == "alpine" ]; then - export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/alpine-install.func)" - else - export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/install.func)" - fi - export RANDOM_UUID="$RANDOM_UUID" - export CACHER="$APT_CACHER" - export CACHER_IP="$APT_CACHER_IP" - export tz="$timezone" - export DISABLEIPV6="$DISABLEIP6" - export APPLICATION="$APP" - export app="$NSAPP" - export PASSWORD="$PW" - export VERBOSE="$VERB" - export SSH_ROOT="${SSH}" - export SSH_AUTHORIZED_KEY - export CTID="$CT_ID" - export CTTYPE="$CT_TYPE" - export PCT_OSTYPE="$var_os" - export PCT_OSVERSION="$var_version" - export PCT_DISK_SIZE="$DISK_SIZE" - export PCT_OPTIONS=" - -features $FEATURES - -hostname $HN - -tags $TAGS - $SD - $NS - -net0 name=eth0,bridge=$BRG$MAC,ip=$NET$GATE$VLAN$MTU - -onboot 1 - -cores $CORE_COUNT - -memory $RAM_SIZE - -unprivileged $CT_TYPE - $PW - " - # This executes create_lxc.sh and creates the container and .conf file - bash -c "$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/create_lxc.sh)" $? - - LXC_CONFIG=/etc/pve/lxc/${CTID}.conf - if [ "$CT_TYPE" == "0" ]; then - cat <>"$LXC_CONFIG" -# USB passthrough -lxc.cgroup2.devices.allow: a -lxc.cap.drop: -lxc.cgroup2.devices.allow: c 188:* rwm -lxc.cgroup2.devices.allow: c 189:* rwm -lxc.mount.entry: /dev/serial/by-id dev/serial/by-id none bind,optional,create=dir -lxc.mount.entry: /dev/ttyUSB0 dev/ttyUSB0 none bind,optional,create=file -lxc.mount.entry: /dev/ttyUSB1 dev/ttyUSB1 none bind,optional,create=file -lxc.mount.entry: /dev/ttyACM0 dev/ttyACM0 none bind,optional,create=file -lxc.mount.entry: /dev/ttyACM1 dev/ttyACM1 none bind,optional,create=file -EOF - fi - - if [ "$CT_TYPE" == "0" ]; then - if [[ "$APP" == "immich" || "$APP" == "Channels" || "$APP" == "Emby" || "$APP" == "ErsatzTV" || "$APP" == "Frigate" || "$APP" == "Jellyfin" || "$APP" == "Plex" || "$APP" == "Scrypted" || "$APP" == "Tdarr" || "$APP" == "Unmanic" || "$APP" == "Ollama" || "$APP" == "FileFlows" ]]; then - cat <>$LXC_CONFIG -# VAAPI hardware transcoding -lxc.cgroup2.devices.allow: c 226:0 rwm -lxc.cgroup2.devices.allow: c 226:128 rwm -lxc.cgroup2.devices.allow: c 29:0 rwm -lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file -lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir -lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file -EOF - fi - else - if [[ "$APP" == "immich" || "$APP" == "Channels" || "$APP" == "Emby" || "$APP" == "ErsatzTV" || "$APP" == "Frigate" || "$APP" == "Jellyfin" || "$APP" == "Plex" || "$APP" == "Scrypted" || "$APP" == "Tdarr" || "$APP" == "Unmanic" || "$APP" == "Ollama" || "$APP" == "FileFlows" ]]; then - if [[ -e "/dev/dri/renderD128" ]]; then - if [[ -e "/dev/dri/card0" ]]; then - cat <>$LXC_CONFIG -# VAAPI hardware transcoding -dev0: /dev/dri/card0,gid=44 -dev1: /dev/dri/renderD128,gid=104 -EOF - else - cat <>"$LXC_CONFIG" -# VAAPI hardware transcoding -dev0: /dev/dri/card1,gid=44 -dev1: /dev/dri/renderD128,gid=104 -EOF - fi - fi - fi - fi - - # This starts the container and executes -install.sh - msg_info "Starting LXC Container" - pct start "$CTID" - msg_ok "Started LXC Container" - if [ "$var_os" == "alpine" ]; then - sleep 3 - pct exec "$CTID" -- /bin/sh -c 'cat </etc/apk/repositories -http://dl-cdn.alpinelinux.org/alpine/latest-stable/main -http://dl-cdn.alpinelinux.org/alpine/latest-stable/community -EOF' - pct exec "$CTID" -- ash -c "apk add bash >/dev/null" - fi - lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/install/"$var_install".sh)" $? - -} - -# This function sets the description of the container. -description() { - IP=$(pct exec "$CTID" ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1) - - # Generate LXC Description - DESCRIPTION=$( - cat < - - Logo - - -

${APP} LXC

- -

- - spend Coffee - -

- - - - GitHub - - - - Discussions - - - - Issues - - -EOF - ) - - # Set Description in LXC - pct set "$CTID" -description "$DESCRIPTION" - - if [[ -f /etc/systemd/system/ping-instances.service ]]; then - systemctl start ping-instances.service - fi - - post_update_to_api "done" "none" -} - -set_std_mode() { - if [ "$VERB" = "yes" ]; then - STD="" - else - STD="silent" - fi -} - -# Silent execution function -silent() { - if [ "$VERB" = "no" ]; then - "$@" >/dev/null 2>&1 || return 1 - else - "$@" || return 1 - fi -} - -api_exit_script() { - exit_code=$? # Capture the exit status of the last executed command - #200 exit codes indicate error in create_lxc.sh - #100 exit codes indicate error in install.func - - if [ $exit_code -ne 0 ]; then - case $exit_code in - 100) post_update_to_api "failed" "100: Unexpected error in create_lxc.sh" ;; - 101) post_update_to_api "failed" "101: No network connection detected in create_lxc.sh" ;; - 200) post_update_to_api "failed" "200: LXC creation failed in create_lxc.sh" ;; - 201) post_update_to_api "failed" "201: Invalid Storage class in create_lxc.sh" ;; - 202) post_update_to_api "failed" "202: User aborted menu in create_lxc.sh" ;; - 203) post_update_to_api "failed" "203: CTID not set in create_lxc.sh" ;; - 204) post_update_to_api "failed" "204: PCT_OSTYPE not set in create_lxc.sh" ;; - 205) post_update_to_api "failed" "205: CTID cannot be less than 100 in create_lxc.sh" ;; - 206) post_update_to_api "failed" "206: CTID already in use in create_lxc.sh" ;; - 207) post_update_to_api "failed" "207: Template not found in create_lxc.sh" ;; - 208) post_update_to_api "failed" "208: Error downloading template in create_lxc.sh" ;; - 209) post_update_to_api "failed" "209: Container creation failed, but template is intact in create_lxc.sh" ;; - *) post_update_to_api "failed" "Unknown error, exit code: $exit_code in create_lxc.sh" ;; - esac - fi -} - -trap 'api_exit_script' EXIT -trap 'post_update_to_api "failed" "$BASH_COMMAND"' ERR -trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT -trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM diff --git a/misc/backup_07052025/core.func.bak b/misc/backup_07052025/core.func.bak deleted file mode 100644 index de18842a..00000000 --- a/misc/backup_07052025/core.func.bak +++ /dev/null @@ -1,220 +0,0 @@ -# Copyright (c) 2021-2025 community-scripts ORG -# Author: michelroegl-brunner -# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/LICENSE - -color() { - # Colors - YW=$(echo "\033[33m") - YWB=$(echo "\033[93m") - BL=$(echo "\033[36m") - RD=$(echo "\033[01;31m") - BGN=$(echo "\033[4;92m") - GN=$(echo "\033[1;92m") - DGN=$(echo "\033[32m") - - # Formatting - CL=$(echo "\033[m") - BOLD=$(echo "\033[1m") - HOLD=" " - TAB=" " - - # Icons - CM="${TAB}βœ”οΈ${TAB}" - CROSS="${TAB}βœ–οΈ${TAB}${CL}" - INFO="${TAB}πŸ’‘${TAB}${CL}" - OS="${TAB}πŸ–₯️${TAB}${CL}" - OSVERSION="${TAB}🌟${TAB}${CL}" - CONTAINERTYPE="${TAB}πŸ“¦${TAB}${CL}" - DISKSIZE="${TAB}πŸ’Ύ${TAB}${CL}" - CPUCORE="${TAB}🧠${TAB}${CL}" - RAMSIZE="${TAB}πŸ› οΈ${TAB}${CL}" - SEARCH="${TAB}πŸ”${TAB}${CL}" - VERBOSE_CROPPED="πŸ”${TAB}" - VERIFYPW="${TAB}πŸ”${TAB}${CL}" - CONTAINERID="${TAB}πŸ†”${TAB}${CL}" - HOSTNAME="${TAB}🏠${TAB}${CL}" - BRIDGE="${TAB}πŸŒ‰${TAB}${CL}" - NETWORK="${TAB}πŸ“‘${TAB}${CL}" - GATEWAY="${TAB}🌐${TAB}${CL}" - DISABLEIPV6="${TAB}🚫${TAB}${CL}" - DEFAULT="${TAB}βš™οΈ${TAB}${CL}" - MACADDRESS="${TAB}πŸ”—${TAB}${CL}" - VLANTAG="${TAB}🏷️${TAB}${CL}" - ROOTSSH="${TAB}πŸ”‘${TAB}${CL}" - CREATING="${TAB}πŸš€${TAB}${CL}" - ADVANCED="${TAB}🧩${TAB}${CL}" - FUSE="${TAB}πŸ”§${TAB}${CL}" -} - -declare -A MSG_INFO_SHOWN -SPINNER_ACTIVE=0 -SPINNER_PID="" -SPINNER_MSG="" - -start_spinner() { - local msg="$1" - local frames=(β ‹ β ™ β Ή β Έ β Ό β ΄ β ¦ β § β ‡ ⠏) - local spin_i=0 - local interval=0.1 - - SPINNER_MSG="$msg" - printf "\r\e[2K" >&2 - - { - while [[ "$SPINNER_ACTIVE" -eq 1 ]]; do - printf "\r\e[2K%s %b" "${frames[spin_i]}" "${YW}${SPINNER_MSG}${CL}" >&2 - spin_i=$(((spin_i + 1) % ${#frames[@]})) - sleep "$interval" - done - } & - - SPINNER_PID=$! - disown "$SPINNER_PID" -} - -stop_spinner() { - if [[ ${SPINNER_PID+v} && -n "$SPINNER_PID" ]] && kill -0 "$SPINNER_PID" 2>/dev/null; then - kill "$SPINNER_PID" 2>/dev/null - sleep 0.1 - kill -0 "$SPINNER_PID" 2>/dev/null && kill -9 "$SPINNER_PID" 2>/dev/null - wait "$SPINNER_PID" 2>/dev/null || true - fi - SPINNER_ACTIVE=0 - unset SPINNER_PID -} - -spinner_guard() { - if [[ "$SPINNER_ACTIVE" -eq 1 ]] && [[ -n "$SPINNER_PID" ]]; then - kill "$SPINNER_PID" 2>/dev/null - wait "$SPINNER_PID" 2>/dev/null || true - SPINNER_ACTIVE=0 - unset SPINNER_PID - fi -} - -log_message() { - local level="$1" - local message="$2" - local timestamp - local logdate - timestamp=$(date '+%Y-%m-%d %H:%M:%S') - logdate=$(date '+%Y-%m-%d') - - LOGDIR="/usr/local/community-scripts/logs" - mkdir -p "$LOGDIR" - - LOGFILE="${LOGDIR}/${logdate}_${NSAPP}.log" - echo "$timestamp - $level: $message" >>"$LOGFILE" -} - -msg_info() { - local msg="$1" - if [ "${SPINNER_ACTIVE:-0}" -eq 1 ]; then - return - fi - - SPINNER_ACTIVE=1 - start_spinner "$msg" -} - -msg_ok() { - if [ -n "${SPINNER_PID:-}" ] && ps -p "$SPINNER_PID" >/dev/null 2>&1; then - kill "$SPINNER_PID" >/dev/null 2>&1 - wait "$SPINNER_PID" 2>/dev/null || true - fi - - local msg="$1" - printf "\r\e[2K${CM}${GN}%b${CL}\n" "$msg" >&2 - unset SPINNER_PID - SPINNER_ACTIVE=0 - - log_message "OK" "$msg" -} - -msg_error() { - if [ -n "${SPINNER_PID:-}" ] && ps -p "$SPINNER_PID" >/dev/null 2>&1; then - kill "$SPINNER_PID" >/dev/null 2>&1 - wait "$SPINNER_PID" 2>/dev/null || true - fi - - local msg="$1" - printf "\r\e[2K${CROSS}${RD}%b${CL}\n" "$msg" >&2 - unset SPINNER_PID - SPINNER_ACTIVE=0 - log_message "ERROR" "$msg" -} - -shell_check() { - if [[ "$(basename "$SHELL")" != "bash" ]]; then - clear - msg_error "Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell." - echo -e "\nExiting..." - sleep 2 - exit - fi -} - -root_check() { - if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then - clear - msg_error "Please run this script as root." - echo -e "\nExiting..." - sleep 2 - exit - fi -} - -pve_check() { - if ! pveversion | grep -Eq "pve-manager/8\.[1-9](\.[0-9]+)*"; then - msg_error "${CROSS}${RD}This version of Proxmox Virtual Environment is not supported" - echo -e "Requires Proxmox Virtual Environment Version 8.1 or later." - echo -e "Exiting..." - sleep 2 - exit - fi -} - -arch_check() { - if [ "$(dpkg --print-architecture)" != "amd64" ]; then - echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n" - echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n" - echo -e "Exiting..." - sleep 2 - exit - fi -} - -ssh_check() { - if command -v pveversion >/dev/null 2>&1; then - if [ -n "${SSH_CLIENT:+x}" ]; then - 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 - echo "you've been warned" - else - clear - exit - fi - fi - fi -} - -exit-script() { - clear - echo -e "\n${CROSS}${RD}User exited script${CL}\n" - exit -} - -set_std_mode() { - if [ "$VERB" = "yes" ]; then - STD="" - else - STD="silent" - fi -} - -silent() { - if [ "$VERB" = "no" ]; then - "$@" >>"$LOGFILE" 2>&1 - else - "$@" 2>&1 | tee -a "$LOGFILE" - fi -} diff --git a/misc/backup_07052025/dialogue.bak b/misc/backup_07052025/dialogue.bak deleted file mode 100644 index 497b5fe0..00000000 --- a/misc/backup_07052025/dialogue.bak +++ /dev/null @@ -1,850 +0,0 @@ - -# dialog_input() { -# local title="$1" -# local prompt="$2" -# local default="$3" -# local result -# apt-get install -y dialog -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "$title" \ -# --extra-button --extra-label "Back" \ -# --ok-label "Next" --cancel-label "Exit" \ -# --inputbox "$prompt" 10 58 "$default" 2>&1 >/dev/tty) - -# local exitcode=$? - -# case $exitcode in -# 0) -# REPLY_RESULT="$result" -# return 0 -# ;; # OK -# 3) return 2 ;; # Back -# *) return 1 ;; # Cancel/Exit -# esac -# } - -# advanced_settings() { -# local step=1 - -# while true; do -# case $step in -# 1) -# show_intro_messages && ((step++)) -# ;; -# 2) -# select_distribution -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 3) -# select_version -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 4) -# select_container_type -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 5) -# set_root_password -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 6) -# set_container_id -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 7) -# set_hostname -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 8) -# set_disk_size -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 9) -# set_cpu_cores -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 10) -# set_ram_size -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 11) -# set_bridge -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 12) -# set_ip_address -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 13) -# set_gateway -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 14) -# set_apt_cacher -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 15) -# toggle_ipv6 -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 16) -# set_mtu -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 17) -# set_dns_search_domain -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 18) -# set_dns_server -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 19) -# set_mac_address -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 20) -# set_vlan -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 21) -# set_tags -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 22) -# set_ssh_access -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 23) -# set_fuse -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 24) -# set_verbose -# result=$? -# [[ $result -eq 0 ]] && ((step++)) -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# 25) -# confirm_creation -# result=$? -# [[ $result -eq 0 ]] && break -# [[ $result -eq 2 && $step -gt 1 ]] && ((step--)) -# [[ $result -eq 1 ]] && return -# ;; -# esac -# done -# } - -# show_intro_messages() { -# dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "Instructional Tip" \ -# --msgbox "To make a selection, use the Spacebar." 8 58 || return 1 - -# dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "Default distribution for $APP" \ -# --msgbox "Default is: ${var_os} ${var_version}\n\nIf the default Linux distribution is not adhered to, script support will be discontinued." 10 58 || return 1 -# return 0 -# } - -# select_distribution() { -# [[ "$var_os" == "alpine" ]] && return 0 - -# local default result exitcode -# default="${var_os:-debian}" -# var_os="" - -# local debian_flag ubuntu_flag -# [[ "$default" == "debian" ]] && debian_flag="on" || debian_flag="off" -# [[ "$default" == "ubuntu" ]] && ubuntu_flag="on" || ubuntu_flag="off" - -# while [[ -z "$var_os" ]]; do -# exec 3>&1 -# result=$(dialog --clear \ -# --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "DISTRIBUTION" \ -# --radiolist "Choose Distribution:" 15 60 4 \ -# "debian" "" "$debian_flag" \ -# "ubuntu" "" "$ubuntu_flag" \ -# --ok-label "Next" \ -# --cancel-label "Exit" \ -# --extra-button \ -# --extra-label "Back" \ -# 2>&1 1>&3) -# exitcode=$? -# exec 3>&- - -# case "$exitcode" in -# 0) -# if [[ "$result" =~ ^(debian|ubuntu)$ ]]; then -# var_os="$result" -# printf "%bOperating System: %b%s%b\n" "$OS$BOLD$DGN" "$BGN" "$var_os" "$CL" -# return 0 -# else -# printf "[DEBUG] No valid selection made (result='%s'), repeating...\n" "$result" -# fi -# ;; -# 3) -# return 2 -# ;; -# 1 | 255) -# return 1 -# ;; -# *) -# printf "[DEBUG] Unexpected exit code: %s\n" "$exitcode" >&2 -# return 1 -# ;; -# esac -# done -# } - -# select_version() { -# local default="${var_version}" -# var_version="" -# local list result exitcode - -# if [[ "$var_os" == "debian" ]]; then -# case "$default" in -# 11) list=("11" "Bullseye" on "12" "Bookworm" off) ;; -# 12) list=("11" "Bullseye" off "12" "Bookworm" on) ;; -# *) list=("11" "Bullseye" off "12" "Bookworm" off) ;; -# esac -# elif [[ "$var_os" == "ubuntu" ]]; then -# case "$default" in -# 20.04) list=("20.04" "Focal" on "22.04" "Jammy" off "24.04" "Noble" off "24.10" "Oracular" off) ;; -# 22.04) list=("20.04" "Focal" off "22.04" "Jammy" on "24.04" "Noble" off "24.10" "Oracular" off) ;; -# 24.04) list=("20.04" "Focal" off "22.04" "Jammy" off "24.04" "Noble" on "24.10" "Oracular" off) ;; -# 24.10) list=("20.04" "Focal" off "22.04" "Jammy" off "24.04" "Noble" off "24.10" "Oracular" on) ;; -# *) list=("20.04" "Focal" off "22.04" "Jammy" off "24.04" "Noble" off "24.10" "Oracular" off) ;; -# esac -# fi - -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "VERSION" \ -# --radiolist "Choose Version:" 15 58 5 \ -# "${list[@]}" \ -# --ok-label "Next" \ -# --cancel-label "Exit" \ -# --extra-button --extra-label "Back" \ -# 3>&1 1>&2 2>&3) - -# exitcode=$? - -# case $exitcode in -# 0) -# var_version="$result" -# printf "%bVersion: %b%s%b\n" "$OSVERSION$BOLD$DGN" "$BGN" "$var_version" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# select_container_type() { -# local default="${CT_TYPE}" -# CT_TYPE="" -# local list result exitcode - -# [[ "$default" == "1" ]] && list=("1" "Unprivileged" on "0" "Privileged" off) || list=("1" "Unprivileged" off "0" "Privileged" on) - -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "CONTAINER TYPE" \ -# --radiolist "Choose Type:" 10 58 2 "${list[@]}" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) - -# exitcode=$? - -# case $exitcode in -# 0) -# CT_TYPE="$result" -# [[ "$CT_TYPE" == "0" ]] && desc="Privileged" || desc="Unprivileged" -# printf "%bContainer Type: %b%s%b\n" "$CONTAINERTYPE$BOLD$DGN" "$BGN" "$desc" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } -# set_root_password() { -# local pw1 pw2 exitcode - -# while true; do -# pw1=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "PASSWORD (leave blank for automatic login)" \ -# --insecure --passwordbox "\nSet Root Password (needed for root ssh access)" 10 58 \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# if [[ -z "$pw1" ]]; then -# PW1="Automatic Login" -# PW="" -# printf "%bRoot Password: %b%s%b\n" "$VERIFYPW$BOLD$DGN" "$BGN" "$PW1" "$CL" -# return 0 -# fi -# if [[ "$pw1" == *" "* ]]; then -# dialog --msgbox "Password cannot contain spaces. Please try again." 8 58 -# continue -# fi -# if [[ ${#pw1} -lt 5 ]]; then -# dialog --msgbox "Password must be at least 5 characters long. Please try again." 8 58 -# continue -# fi -# pw2=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "PASSWORD VERIFICATION" \ -# --insecure --passwordbox "\nVerify Root Password" 10 58 \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? -# case $exitcode in -# 0) -# if [[ "$pw1" == "$pw2" ]]; then -# PW="-password $pw1" -# printf "%bRoot Password: %b********%b\n" "$VERIFYPW$BOLD$DGN" "$BGN" "$CL" -# return 0 -# else -# dialog --msgbox "Passwords do not match. Please try again." 8 58 -# continue -# fi -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# done -# } - -# set_container_id() { -# local result exitcode -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "CONTAINER ID" \ -# --inputbox "Set Container ID" 8 58 "$NEXTID" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# CT_ID="${result:-$NEXTID}" -# printf "%bContainer ID: %b%s%b\n" "$CONTAINERID$BOLD$DGN" "$BGN" "$CT_ID" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# set_hostname() { -# local result exitcode -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "HOSTNAME" \ -# --inputbox "Set Hostname" 8 58 "$NSAPP" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# if [[ -z "$result" ]]; then -# HN="$NSAPP" -# else -# HN=$(tr -d ' ' <<<"${result,,}") -# fi -# printf "%bHostname: %b%s%b\n" "$HOSTNAME$BOLD$DGN" "$BGN" "$HN" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# set_disk_size() { -# local result exitcode -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "DISK SIZE" \ -# --inputbox "Set Disk Size in GB" 8 58 "$var_disk" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# if [[ -z "$result" ]]; then -# DISK_SIZE="$var_disk" -# elif [[ "$result" =~ ^[0-9]+$ ]]; then -# DISK_SIZE="$result" -# else -# dialog --msgbox "Disk size must be an integer!" 8 58 -# return 2 -# fi -# printf "%bDisk Size: %b%s GB%b\n" "$DISKSIZE$BOLD$DGN" "$BGN" "$DISK_SIZE" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# set_cpu_cores() { -# local result exitcode -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "CORE COUNT" \ -# --inputbox "Allocate CPU Cores" 8 58 "$var_cpu" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# CORE_COUNT="${result:-$var_cpu}" -# printf "%bCPU Cores: %b%s%b\n" "$CPUCORE$BOLD$DGN" "$BGN" "$CORE_COUNT" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# set_ram_size() { -# local result exitcode -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "RAM" \ -# --inputbox "Allocate RAM in MiB" 8 58 "$var_ram" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# RAM_SIZE="${result:-$var_ram}" -# printf "%bRAM Size: %b%s MiB%b\n" "$RAMSIZE$BOLD$DGN" "$BGN" "$RAM_SIZE" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# set_bridge() { -# local result exitcode -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "BRIDGE" \ -# --inputbox "Set a Bridge" 8 58 "vmbr0" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# BRG="${result:-vmbr0}" -# printf "%bBridge: %b%s%b\n" "$BRIDGE$BOLD$DGN" "$BGN" "$BRG" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# set_ip_address() { -# local result exitcode -# while true; do -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "IP ADDRESS" \ -# --inputbox "Set a Static IPv4 CIDR Address (/24)" 8 58 "dhcp" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# if [[ "$result" == "dhcp" ]]; then -# NET="dhcp" -# printf "%bIP Address: %b%s%b\n" "$NETWORK$BOLD$DGN" "$BGN" "$NET" "$CL" -# return 0 -# elif [[ "$result" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then -# NET="$result" -# printf "%bIP Address: %b%s%b\n" "$NETWORK$BOLD$DGN" "$BGN" "$NET" "$CL" -# return 0 -# else -# dialog --msgbox "$result is an invalid IPv4 CIDR address. Please enter a valid address or 'dhcp'." 8 58 -# continue -# fi -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# done -# } - -# set_gateway() { -# local result exitcode -# if [[ "$NET" == "dhcp" ]]; then -# GATE="" -# printf "%bGateway IP Address: %bDefault%b\n" "$GATEWAY$BOLD$DGN" "$BGN" "$CL" -# return 0 -# fi - -# while true; do -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "Gateway IP" \ -# --inputbox "Enter gateway IP address" 8 58 \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# if [[ -z "$result" ]]; then -# dialog --msgbox "Gateway IP address cannot be empty" 8 58 -# continue -# elif [[ "$result" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then -# GATE=",gw=$result" -# printf "%bGateway IP Address: %b%s%b\n" "$GATEWAY$BOLD$DGN" "$BGN" "$result" "$CL" -# return 0 -# else -# dialog --msgbox "Invalid IP address format" 8 58 -# fi -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# done -# } - -# set_apt_cacher() { -# local result exitcode -# if [[ "$var_os" == "alpine" ]]; then -# APT_CACHER="" -# APT_CACHER_IP="" -# return 0 -# fi - -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "APT-Cacher IP" \ -# --inputbox "Set APT-Cacher IP (leave blank for none)" 8 58 \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# APT_CACHER_IP="$result" -# APT_CACHER="${APT_CACHER_IP:+yes}" -# printf "%bAPT-Cacher IP Address: %b%s%b\n" "$NETWORK$BOLD$DGN" "$BGN" "${APT_CACHER_IP:-Default}" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# toggle_ipv6() { -# dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "IPv6" \ -# --yesno "Disable IPv6?" 10 58 \ -# --extra-button --extra-label "Back" --ok-label "Yes" --cancel-label "No" -# case $? in -# 0) DISABLEIP6="yes" ;; -# 1) DISABLEIP6="no" ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# printf "%bDisable IPv6: %b%s%b\n" "$DISABLEIPV6$BOLD$DGN" "$BGN" "$DISABLEIP6" "$CL" -# return 0 -# } -# set_mtu() { -# local result exitcode -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "MTU SIZE" \ -# --inputbox "Set Interface MTU Size (leave blank for default [1500])" 8 58 "" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# if [[ -z "$result" ]]; then -# MTU1="Default" -# MTU="" -# else -# MTU1="$result" -# MTU=",mtu=$MTU1" -# fi -# printf "%bInterface MTU Size: %b%s%b\n" "$DEFAULT$BOLD$DGN" "$BGN" "$MTU1" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# set_dns_search_domain() { -# local result exitcode -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "DNS Search Domain" \ -# --inputbox "Set a DNS Search Domain (leave blank for HOST)" 8 58 "" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# if [[ -z "$result" ]]; then -# SX="Host" -# SD="" -# else -# SX="$result" -# SD="-searchdomain=$result" -# fi -# printf "%bDNS Search Domain: %b%s%b\n" "$SEARCH$BOLD$DGN" "$BGN" "$SX" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# set_dns_server() { -# local result exitcode -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "DNS SERVER IP" \ -# --inputbox "Set a DNS Server IP (leave blank for HOST)" 8 58 "" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# if [[ -z "$result" ]]; then -# NX="Host" -# NS="" -# else -# NX="$result" -# NS="-nameserver=$result" -# fi -# printf "%bDNS Server IP Address: %b%s%b\n" "$NETWORK$BOLD$DGN" "$BGN" "$NX" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# set_mac_address() { -# local result exitcode -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "MAC ADDRESS" \ -# --inputbox "Set a MAC Address (leave blank for generated MAC)" 8 58 "" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# if [[ -z "$result" ]]; then -# MAC1="Default" -# MAC="" -# else -# MAC1="$result" -# MAC=",hwaddr=$MAC1" -# fi -# printf "%bMAC Address: %b%s%b\n" "$MACADDRESS$BOLD$DGN" "$BGN" "$MAC1" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# set_vlan() { -# local result exitcode -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "VLAN" \ -# --inputbox "Set a VLAN (leave blank for no VLAN)" 8 58 "" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# if [[ -z "$result" ]]; then -# VLAN1="Default" -# VLAN="" -# else -# VLAN1="$result" -# VLAN=",tag=$VLAN1" -# fi -# printf "%bVlan: %b%s%b\n" "$VLANTAG$BOLD$DGN" "$BGN" "$VLAN1" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# set_tags() { -# local result exitcode -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "Advanced Tags" \ -# --inputbox "Set Custom Tags? [If you remove all, there will be no tags!]" 8 58 "$TAGS" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? - -# case $exitcode in -# 0) -# if [[ -n "$result" ]]; then -# ADV_TAGS=$(tr -d '[:space:]' <<<"$result") -# TAGS="$ADV_TAGS" -# else -# TAGS=";" -# fi -# printf "%bTags: %b%s%b\n" "$NETWORK$BOLD$DGN" "$BGN" "$TAGS" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } - -# set_ssh_access() { -# local result exitcode - -# if [[ "$PW" == -password* ]]; then -# dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "SSH ACCESS" \ -# --yesno "Enable Root SSH Access?" 10 58 \ -# --extra-button --extra-label "Back" --ok-label "Yes" --cancel-label "No" -# exitcode=$? -# case $exitcode in -# 0) SSH="yes" ;; -# 1) SSH="no" ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# else -# SSH="no" -# fi - -# printf "%bRoot SSH Access: %b%s%b\n" "$ROOTSSH$BOLD$DGN" "$BGN" "$SSH" "$CL" - -# if [[ "$SSH" == "yes" ]]; then -# result=$(dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "SSH Key" \ -# --inputbox "SSH Authorized key for root (leave empty for none)" 8 58 "" \ -# --extra-button --extra-label "Back" --ok-label "Next" --cancel-label "Exit" 3>&1 1>&2 2>&3) -# exitcode=$? -# case $exitcode in -# 0) -# SSH_AUTHORIZED_KEY="$result" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# else -# SSH_AUTHORIZED_KEY="" -# return 0 -# fi -# } - -# set_fuse() { -# dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "FUSE Support" \ -# --yesno "Enable FUSE (Filesystem in Userspace) support in the container?" 10 58 \ -# --extra-button --extra-label "Back" --ok-label "Yes" --cancel-label "No" -# case $? in -# 0) ENABLE_FUSE="yes" ;; -# 1) ENABLE_FUSE="no" ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# printf "%bFUSE (Filesystem in Userspace) Support: %b%s%b\n" "$FUSE$BOLD$DGN" "$BGN" "$ENABLE_FUSE" "$CL" -# return 0 -# } - -# set_verbose() { -# dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "VERBOSE MODE" \ -# --yesno "Enable Verbose Mode?" 10 58 \ -# --extra-button --extra-label "Back" --ok-label "Yes" --cancel-label "No" -# case $? in -# 0) VERB="yes" ;; -# 1) VERB="no" ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# printf "%bVerbose Mode: %b%s%b\n" "$SEARCH$BOLD$DGN" "$BGN" "$VERB" "$CL" -# return 0 -# } - -# confirm_creation() { -# dialog --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "ADVANCED SETTINGS COMPLETE" \ -# --yesno "Ready to create ${APP} LXC?" 10 58 \ -# --extra-button --extra-label "Back" --ok-label "Yes" --cancel-label "No" -# case $? in -# 0) -# printf "%bCreating a %s LXC using the above advanced settings%b\n" "$CREATING$BOLD$RD" "$APP" "$CL" -# return 0 -# ;; -# 3) return 2 ;; -# *) return 1 ;; -# esac -# } diff --git a/misc/backup_07052025/install copy.func b/misc/backup_07052025/install copy.func deleted file mode 100644 index d2042d5d..00000000 --- a/misc/backup_07052025/install copy.func +++ /dev/null @@ -1,281 +0,0 @@ -# Copyright (c) 2021-2025 tteck -# Author: tteck (tteckster) -# Co-Author: MickLesk -# Co-Author: michelroegl-brunner -# License: MIT -# https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE - -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) -load_functions - -# color() { -# # Colors -# YW=$(echo "\033[33m") -# YWB=$(echo "\033[93m") -# BL=$(echo "\033[36m") -# RD=$(echo "\033[01;31m") -# GN=$(echo "\033[1;92m") - -# # Formatting -# CL=$(echo "\033[m") -# BFR="\\r\\033[K" -# BOLD=$(echo "\033[1m") -# HOLD=" " -# TAB=" " - -# # System -# RETRY_NUM=10 -# RETRY_EVERY=3 - -# # Icons -# CM="${TAB}βœ”οΈ${TAB}${CL}" -# CROSS="${TAB}βœ–οΈ${TAB}${CL}" -# INFO="${TAB}πŸ’‘${TAB}${CL}" -# NETWORK="${TAB}πŸ“‘${TAB}${CL}" -# OS="${TAB}πŸ–₯️${TAB}${CL}" -# OSVERSION="${TAB}🌟${TAB}${CL}" -# HOSTNAME="${TAB}🏠${TAB}${CL}" -# GATEWAY="${TAB}🌐${TAB}${CL}" -# DEFAULT="${TAB}βš™οΈ${TAB}${CL}" -# } - -# Function to set STD mode based on verbosity -set_std_mode() { - if [ "$VERBOSE" = "yes" ]; then - STD="" - else - STD="silent" - fi -} - -# Silent execution function -silent() { - "$@" >/dev/null 2>&1 -} - -# This function enables IPv6 if it's not disabled and sets verbose mode -verb_ip6() { - set_std_mode # Set STD mode based on VERBOSE - - if [ "$DISABLEIPV6" == "yes" ]; then - echo "net.ipv6.conf.all.disable_ipv6 = 1" >>/etc/sysctl.conf - $STD sysctl -p - fi -} - -# This function sets error handling options and defines the error_handler function to handle errors -catch_errors() { - set -Eeuo pipefail - trap 'error_handler $LINENO "$BASH_COMMAND"' ERR -} - -# This function handles errors -error_handler() { - source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) - if [ -n "$SPINNER_PID" ] && ps -p "$SPINNER_PID" >/dev/null; then kill "$SPINNER_PID" >/dev/null; fi - printf "\e[?25h" - local exit_code="$?" - local line_number="$1" - local command="$2" - local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" - echo -e "\n$error_message" - [[ -n "${SPINNER_PID:-}" ]] && kill "$SPINNER_PID" &>/dev/null || true - - if [[ "$line_number" -eq 50 ]]; then - echo -e "The silent function has suppressed the error, run the script with verbose mode enabled, which will provide more detailed output.\n" - post_update_to_api "failed" "No error message, script ran in silent mode" - else - post_update_to_api "failed" "${command}" - fi -} - -# # This function displays a spinner. -# spinner() { -# local frames=('β ‹' 'β ™' 'β Ή' 'β Έ' 'β Ό' 'β ΄' 'β ¦' 'β §' 'β ‡' '⠏') -# local spin_i=0 -# local interval=0.1 -# printf "\e[?25l" - -# local color="${YWB}" - -# while true; do -# printf "\r ${color}%s${CL}" "${frames[spin_i]}" -# spin_i=$(((spin_i + 1) % ${#frames[@]})) -# sleep "$interval" -# done -# } - -# # This function displays an informational message with a yellow color. -# msg_info() { -# local msg="$1" -# echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}" -# spinner & -# SPINNER_PID=$! -# } - -# # This function displays a success message with a green color. -# msg_ok() { -# if [ -n "$SPINNER_PID" ] && ps -p "$SPINNER_PID" >/dev/null; then kill "$SPINNER_PID" >/dev/null; fi -# printf "\e[?25h" -# local msg="$1" -# echo -e "${BFR}${CM}${GN}${msg}${CL}" -# } - -# # This function displays a error message with a red color. -# msg_error() { -# if [ -n "$SPINNER_PID" ] && ps -p "$SPINNER_PID" >/dev/null; then kill "$SPINNER_PID" >/dev/null; fi -# printf "\e[?25h" -# local msg="$1" -# echo -e "${BFR}${CROSS}${RD}${msg}${CL}" -# } - -# This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection -setting_up_container() { - msg_info "Setting up Container OS" - sed -i "/$LANG/ s/\(^# \)//" /etc/locale.gen - locale_line=$(grep -v '^#' /etc/locale.gen | grep -E '^[a-zA-Z]' | awk '{print $1}' | head -n 1) - echo "LANG=${locale_line}" >/etc/default/locale - locale-gen >/dev/null - export LANG=${locale_line} - echo $tz >/etc/timezone - ln -sf /usr/share/zoneinfo/$tz /etc/localtime - for ((i = RETRY_NUM; i > 0; i--)); do - if [ "$(hostname -I)" != "" ]; then - break - fi - echo 1>&2 -en "${CROSS}${RD} No Network! " - sleep $RETRY_EVERY - done - if [ "$(hostname -I)" = "" ]; then - echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" - echo -e "${NETWORK}Check Network Settings" - exit 1 - fi - rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED - systemctl disable -q --now systemd-networkd-wait-online.service - msg_ok "Set up Container OS" - msg_ok "Network Connected: ${BL}$(hostname -I)" -} - -# This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected -network_check() { - set +e - trap - ERR - ipv4_connected=false - ipv6_connected=false - sleep 1 - # Check IPv4 connectivity to Google, Cloudflare & Quad9 DNS servers. - 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 - msg_ok "IPv4 Internet Connected" - ipv4_connected=true - else - msg_error "IPv4 Internet Not Connected" - fi - - # Check IPv6 connectivity to Google, Cloudflare & Quad9 DNS servers. - 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 - msg_ok "IPv6 Internet Connected" - ipv6_connected=true - else - msg_error "IPv6 Internet Not Connected" - fi - - # If both IPv4 and IPv6 checks fail, prompt the user - if [[ $ipv4_connected == false && $ipv6_connected == false ]]; then - read -r -p "No Internet detected,would you like to continue anyway? " prompt - if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then - echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" - else - echo -e "${NETWORK}Check Network Settings" - exit 1 - fi - fi - - RESOLVEDIP=$(getent hosts github.com | awk '{ print $1 }') - if [[ -z "$RESOLVEDIP" ]]; then msg_error "DNS Lookup Failure"; else msg_ok "DNS Resolved github.com to ${BL}$RESOLVEDIP${CL}"; fi - set -e - trap 'error_handler $LINENO "$BASH_COMMAND"' ERR -} - -# This function updates the Container OS by running apt-get update and upgrade -update_os() { - msg_info "Updating Container OS" - if [[ "$CACHER" == "yes" ]]; then - echo "Acquire::http::Proxy-Auto-Detect \"/usr/local/bin/apt-proxy-detect.sh\";" >/etc/apt/apt.conf.d/00aptproxy - cat </usr/local/bin/apt-proxy-detect.sh -#!/bin/bash -if nc -w1 -z "${CACHER_IP}" 3142; then - echo -n "http://${CACHER_IP}:3142" -else - echo -n "DIRECT" -fi -EOF - chmod +x /usr/local/bin/apt-proxy-detect.sh - fi - $STD apt-get update - $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade - rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED - msg_ok "Updated Container OS" - - msg_info "Installing core dependencies" - $STD apt-get update - $STD apt-get install -y sudo curl mc gnupg2 - msg_ok "Core dependencies installed" - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) - -} - -# This function modifies the message of the day (motd) and SSH settings -motd_ssh() { - grep -qxF "export TERM='xterm-256color'" /root/.bashrc || echo "export TERM='xterm-256color'" >>/root/.bashrc - - if [ -f "/etc/os-release" ]; then - OS_NAME=$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"') - OS_VERSION=$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"') - elif [ -f "/etc/debian_version" ]; then - OS_NAME="Debian" - OS_VERSION=$(cat /etc/debian_version) - fi - - PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" - echo "echo -e \"\"" >"$PROFILE_FILE" - echo -e "echo -e \"${BOLD}${YW}${APPLICATION} LXC Container - DEV Repository${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${RD}WARNING: This is a DEVELOPMENT version (ProxmoxVED). Do NOT use in production!${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${YW} OS: ${GN}${OS_NAME} - Version: ${OS_VERSION}${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${YW} IP Address: ${GN}\$(hostname -I | awk '{print \$1}')${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${YW} Repository: ${GN}https://github.com/community-scripts/ProxmoxVED${CL}\"" >>"$PROFILE_FILE" - echo "echo \"\"" >>"$PROFILE_FILE" - - chmod -x /etc/update-motd.d/* - - if [[ "${SSH_ROOT}" == "yes" ]]; then - sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config - systemctl restart sshd - fi -} - -# This function customizes the container by modifying the getty service and enabling auto-login for the root user -customize() { - if [[ "$PASSWORD" == "" ]]; then - msg_info "Customizing Container" - GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf" - mkdir -p $(dirname $GETTY_OVERRIDE) - cat <$GETTY_OVERRIDE - [Service] - ExecStart= - ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM -EOF - systemctl daemon-reload - systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') - msg_ok "Customized Container" - fi - echo "bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${app}.sh)\"" >/usr/bin/update - chmod +x /usr/bin/update - if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then - mkdir -p /root/.ssh - echo "${SSH_AUTHORIZED_KEY}" >/root/.ssh/authorized_keys - chmod 700 /root/.ssh - chmod 600 /root/.ssh/authorized_keys - fi -} diff --git a/misc/backup_07052025/msg.func b/misc/backup_07052025/msg.func deleted file mode 100644 index 1257d0d2..00000000 --- a/misc/backup_07052025/msg.func +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env bash - -# Spinner state -declare -A SPINNER_PIDS -declare -A SPINNER_MSGS -declare -A MSG_SHOWN - -# Color definitions (adjust as needed) -RD='\033[0;31m' -GN='\033[0;32m' -YW='\033[0;33m' -CL='\033[0m' -CM='βœ”' -CROSS='✘' - -# Trap cleanup -trap cleanup_spinners EXIT INT TERM HUP - -# Hash function for message ID -msg_hash() { - local input="$1" - echo -n "$input" | sha1sum | awk '{print $1}' -} - -# Start a spinner for a specific message -start_spinner_for_msg() { - local msg="$1" - local id - id=$(msg_hash "$msg") - - [[ -n "${MSG_SHOWN["$id"]+x}" ]] && return - MSG_SHOWN["$id"]=1 - SPINNER_MSGS["$id"]="$msg" - - local frames=(β ‹ β ™ β Ή β Έ β Ό β ΄ β ¦ β § β ‡ ⠏) - local interval=0.1 - local spin_i=0 - - { - while true; do - printf "\r\e[2K%s %b" "${frames[spin_i]}" "${YW}${msg}${CL}" >&2 - spin_i=$(((spin_i + 1) % ${#frames[@]})) - sleep "$interval" - done - } & - - SPINNER_PIDS["$id"]=$! - disown "${SPINNER_PIDS["$id"]}" -} - -# Stop the spinner for a specific message -stop_spinner_for_msg() { - local msg="$1" - local id - id=$(msg_hash "$msg") - - if [[ -n "${SPINNER_PIDS["$id"]+x}" ]] && ps -p "${SPINNER_PIDS["$id"]}" >/dev/null 2>&1; then - kill "${SPINNER_PIDS["$id"]}" 2>/dev/null - wait "${SPINNER_PIDS["$id"]}" 2>/dev/null || true - fi - - unset SPINNER_PIDS["$id"] - unset SPINNER_MSGS["$id"] - unset MSG_SHOWN["$id"] -} - -# Cleanup all active spinners -cleanup_spinners() { - for id in "${!SPINNER_PIDS[@]}"; do - if ps -p "${SPINNER_PIDS[$id]}" >/dev/null 2>&1; then - kill "${SPINNER_PIDS[$id]}" 2>/dev/null - wait "${SPINNER_PIDS[$id]}" 2>/dev/null || true - fi - unset SPINNER_PIDS["$id"] - unset SPINNER_MSGS["$id"] - unset MSG_SHOWN["$id"] - done -} - -# Show info message with spinner -msg_info() { - local msg="$1" - start_spinner_for_msg "$msg" -} - -# End spinner and show success message -msg_ok() { - local msg="$1" - stop_spinner_for_msg "$msg" - printf "\r\e[2K%s %b\n" "${CM}" "${GN}${msg}${CL}" >&2 -} - -# End spinner and show error message -msg_error() { - local msg="$1" - stop_spinner_for_msg "$msg" - printf "\r\e[2K%s %b\n" "${CROSS}" "${RD}${msg}${CL}" >&2 -} diff --git a/misc/build.func b/misc/build.func index ac7d163e..e9247b9d 100644 --- a/misc/build.func +++ b/misc/build.func @@ -1,3 +1,4 @@ +#!/usr/bin/env bash # Copyright (c) 2021-2025 tteck # Author: tteck (tteckster) # Co-Author: MickLesk @@ -30,19 +31,22 @@ fi # This function enables error handling in the script by setting options and defining a trap for the ERR signal. catch_errors() { set -Eeo pipefail + # if [ -n "$BASH_VERSION" ] && command -v shopt >/dev/null 2>&1; then + # shopt -s errtrace + # fi trap 'error_handler $LINENO "$BASH_COMMAND"' ERR } # This function is called when an error occurs. It receives the exit code, line number, and command that caused the error, and displays an error message. error_handler() { - source /dev/stdin <<<$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/api.func) - printf "\e[?25h" local exit_code="$?" local line_number="$1" local command="$2" - local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" - post_update_to_api "failed" "${command}" + printf "\e[?25h" + local error_message="[ERROR] in line $line_number: exit code $exit_code: while executing command $command" + post_update_to_api "failed" "$command" echo -e "\n$error_message\n" + exit "$exit_code" if [[ -n "$CT_ID" ]]; then read -p "Remove this Container? " prompt @@ -53,6 +57,7 @@ error_handler() { fi fi } + # Check if the shell is using bash shell_check() { if [[ "$(basename "$SHELL")" != "bash" ]]; then @@ -77,13 +82,34 @@ root_check() { # This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported. pve_check() { - if ! pveversion | grep -Eq "pve-manager/8\.[0-4](\.[0-9]+)*"; then - msg_error "${CROSS}${RD}This version of Proxmox Virtual Environment is not supported" - echo -e "Requires Proxmox Virtual Environment Version 8.1 or later." - echo -e "Exiting..." - sleep 2 - exit + local PVE_VER + PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" + + # 8 Version Check + if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 1 || MINOR > 4)); then + msg_error "This version of Proxmox Virtual Environment is not supported" + echo -e "Requires Proxmox Virtual Environment Version 8.1 – 8.4" + echo -e "Exiting..." + sleep 2 + exit 1 + fi + return 0 fi + + # 9 Beta Version Check + if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then + msg_custom "Detected Proxmox Virtual Environment $PVE_VER – Beta state, use with caution!" + return 0 + fi + + # All others (unsupported versions) + msg_error "This version of Proxmox Virtual Environment is not supported" + echo -e "Requires Proxmox Virtual Environment Version 8.1 – 8.4 or 9.x (Beta)" + echo -e "Exiting..." + sleep 2 + exit 1 } # When a node is running tens of containers, it's possible to exceed the kernel's cryptographic key storage allocations. @@ -186,92 +212,92 @@ ssh_check() { fi } -select_storage() { - local CLASS=$1 CONTENT CONTENT_LABEL - case $CLASS in - container) - CONTENT='rootdir' - CONTENT_LABEL='Container' - ;; - template) - CONTENT='vztmpl' - CONTENT_LABEL='Template' - ;; - iso) - CONTENT='iso' - CONTENT_LABEL='ISO image' - ;; - images) - CONTENT='images' - CONTENT_LABEL='VM Disk image' - ;; - backup) - CONTENT='backup' - CONTENT_LABEL='Backup' - ;; - snippets) - CONTENT='snippets' - CONTENT_LABEL='Snippets' - ;; - *) - msg_error "Invalid storage class '$CLASS'." - exit 201 - ;; - esac +# select_storage() { +# local CLASS=$1 CONTENT CONTENT_LABEL +# case $CLASS in +# container) +# CONTENT='rootdir' +# CONTENT_LABEL='Container' +# ;; +# template) +# CONTENT='vztmpl' +# CONTENT_LABEL='Template' +# ;; +# iso) +# CONTENT='iso' +# CONTENT_LABEL='ISO image' +# ;; +# images) +# CONTENT='images' +# CONTENT_LABEL='VM Disk image' +# ;; +# backup) +# CONTENT='backup' +# CONTENT_LABEL='Backup' +# ;; +# snippets) +# CONTENT='snippets' +# CONTENT_LABEL='Snippets' +# ;; +# *) +# msg_error "Invalid storage class '$CLASS'." +# exit 201 +# ;; +# esac - command -v whiptail >/dev/null || { - msg_error "whiptail missing." - exit 220 - } - command -v numfmt >/dev/null || { - msg_error "numfmt missing." - exit 221 - } +# command -v whiptail >/dev/null || { +# msg_error "whiptail missing." +# exit 220 +# } +# command -v numfmt >/dev/null || { +# msg_error "numfmt missing." +# exit 221 +# } - local -a MENU - while read -r line; do - local TAG=$(echo "$line" | awk '{print $1}') - local TYPE=$(echo "$line" | awk '{printf "%-10s", $2}') - local FREE=$(echo "$line" | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf "%9sB", $6}') - MENU+=("$TAG" "Type: $TYPE Free: $FREE" "OFF") - done < <(pvesm status -content "$CONTENT" | awk 'NR>1') +# local -a MENU +# while read -r line; do +# local TAG=$(echo "$line" | awk '{print $1}') +# local TYPE=$(echo "$line" | awk '{printf "%-10s", $2}') +# local FREE=$(echo "$line" | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf "%9sB", $6}') +# MENU+=("$TAG" "Type: $TYPE Free: $FREE" "OFF") +# done < <(pvesm status -content "$CONTENT" | awk 'NR>1') - if [ ${#MENU[@]} -eq 0 ]; then - msg_error "No storage found for content type '$CONTENT'." - exit 203 - fi +# if [ ${#MENU[@]} -eq 0 ]; then +# msg_error "No storage found for content type '$CONTENT'." +# exit 203 +# fi - if [ $((${#MENU[@]} / 3)) -eq 1 ]; then - echo "${MENU[0]}" - return - fi +# if [ $((${#MENU[@]} / 3)) -eq 1 ]; then +# echo "${MENU[0]}" +# return +# fi - local STORAGE - STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ - "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \ - 16 70 6 "${MENU[@]}" 3>&1 1>&2 2>&3) || { - msg_error "Storage selection cancelled by user." - exit 202 - } - echo "$STORAGE" -} +# local STORAGE +# STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ +# "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \ +# 16 70 6 "${MENU[@]}" 3>&1 1>&2 2>&3) || { +# msg_error "Storage selection cancelled by user." +# exit 202 +# } +# echo "$STORAGE" +# } -manage_default_storage() { - local file="/usr/local/community-scripts/default_storage" - mkdir -p /usr/local/community-scripts +# manage_default_storage() { +# local file="/usr/local/community-scripts/default_storage" +# mkdir -p /usr/local/community-scripts - local tmpl=$(select_storage template) - local cont=$(select_storage container) +# local tmpl=$(select_storage template) +# local cont=$(select_storage container) - cat <"$file" -TEMPLATE_STORAGE=$tmpl -CONTAINER_STORAGE=$cont -EOF +# cat <"$file" +# TEMPLATE_STORAGE=$tmpl +# CONTAINER_STORAGE=$cont +# EOF - msg_ok "Default Storage set: Template=${BL}$tmpl${CL} ${GN}|${CL} Container=${BL}$cont${CL}" - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --msgbox "Default Storage set:\n\nTemplate: $tmpl\nContainer: $cont" 10 58 -} +# msg_ok "Default Storage set: Template=${BL}$tmpl${CL} ${GN}|${CL} Container=${BL}$cont${CL}" +# whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ +# --msgbox "Default Storage set:\n\nTemplate: $tmpl\nContainer: $cont" 10 58 +# } base_settings() { # Default Settings @@ -312,6 +338,8 @@ base_settings() { TAGS="${TAGS}${var_tags:-}" ENABLE_FUSE="${var_fuse:-$ENABLE_FUSE}" ENABLE_TUN="${var_tun:-$ENABLE_TUN}" + APT_CACHER="${var_apt_cacher:-$APT_CACHER}" + APT_CACHER_IP="${var_apt_cacher_ip:-$APT_CACHER_IP}" # 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 if [ -z "$var_os" ]; then @@ -333,13 +361,13 @@ write_config() { CT_TYPE="${CT_TYPE}" DISK_SIZE="${DISK_SIZE}" -CORE_COUNT="${DISK_SIZE}" +CORE_COUNT="${CORE_COUNT}" RAM_SIZE="${RAM_SIZE}" HN="${HN}" BRG="${BRG}" APT_CACHER_IP="${APT_CACHER_IP:-none}" DISABLEIP6="${DISABLEIP6}" -PW="${PW:-none}" +PW='${PW:-none}' SSH="${SSH}" SSH_AUTHORIZED_KEY="${SSH_AUTHORIZED_KEY}" VERBOSE="${VERBOSE}" @@ -351,6 +379,7 @@ SD="${SD:-none}" MAC="${MAC:-none}" NS="${NS:-none}" NET="${NET}" +FUSE="${ENABLE_FUSE}" EOF echo -e "${INFO}${BOLD}${GN}Writing configuration to ${FILEPATH}${CL}" @@ -364,7 +393,7 @@ EOF CT_TYPE="${CT_TYPE}" DISK_SIZE="${DISK_SIZE}" -CORE_COUNT="${DISK_SIZE}" +CORE_COUNT="${CORE_COUNT}" RAM_SIZE="${RAM_SIZE}" HN="${HN}" BRG="${BRG}" @@ -382,6 +411,7 @@ SD="${SD:-none}" MAC="${MAC:-none}" NS="${NS:-none}" NET="${NET}" +FUSE="${ENABLE_FUSE}" EOF echo -e "${INFO}${BOLD}${GN}Writing configuration to ${FILEPATH}${CL}" @@ -402,9 +432,11 @@ echo_default() { # Output the selected values with icons echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}${CT_ID}${CL}" - echo -e "${OS}${BOLD}${DGN}Operating System: $var_os | Version: $var_version${CL}" + echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os ($var_version)${CL}" echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}" - echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB | ${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT} | ${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}" + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}" if [ "$VERB" == "yes" ]; then echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}Enabled${CL}" fi @@ -861,12 +893,12 @@ advanced_settings() { echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" fi - # if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "FUSE Support" --yesno "Enable FUSE support?\nRequired for tools like rclone, mergerfs, AppImage, etc." 10 58); then - # ENABLE_FUSE="yes" - # else - # ENABLE_FUSE="no" - # fi - # echo -e "${FUSE}${BOLD}${DGN}Enable FUSE Support: ${BGN}$ENABLE_FUSE${CL}" + if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "FUSE Support" --yesno "Enable FUSE support?\nRequired for tools like rclone, mergerfs, AppImage, etc." 10 58); then + ENABLE_FUSE="yes" + else + ENABLE_FUSE="no" + fi + echo -e "${FUSE}${BOLD}${DGN}Enable FUSE Support: ${BGN}$ENABLE_FUSE${CL}" if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "VERBOSE MODE" --yesno "Enable Verbose Mode?" 10 58); then VERBOSE="yes" @@ -966,91 +998,111 @@ install_script() { if systemctl is-active -q ping-instances.service; then systemctl -q stop ping-instances.service fi + NEXTID=$(pvesh get /cluster/nextid) timezone=$(cat /etc/timezone) header_info - while true; do - CHOICE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SETTINGS" --menu "Choose an option:" \ - 20 60 7 \ - "1" "Default Settings" \ - "2" "Default Settings (with verbose)" \ - "3" "Advanced Settings" \ - "4" "Use Config File" \ - "5" "Manage Default Storage" \ - "6" "Diagnostic Settings" \ - "7" "Exit" --nocancel --default-item "1" 3>&1 1>&2 2>&3) - - if [ $? -ne 0 ]; then - echo -e "${CROSS}${RD} Menu canceled. Exiting.${CL}" - exit 0 - fi - - case $CHOICE in - 1) - header_info - echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME${CL}" - VERBOSE="no" - METHOD="default" - base_settings "$VERBOSE" - echo_default - break + # --- PRESET support --- + if [ -n "${PRESET:-}" ]; then + case "$PRESET" in + DEFAULT | default | 1) + CHOICE="1" ;; - 2) - header_info - echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME (${VERBOSE_CROPPED}Verbose)${CL}" - VERBOSE="yes" - METHOD="default" - base_settings "$VERBOSE" - echo_default - break + VERBOSE | verbose | 2) + CHOICE="2" ;; - 3) - header_info - echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings on node $PVEHOST_NAME${CL}" - METHOD="advanced" - base_settings - advanced_settings - break - ;; - 4) - header_info - echo -e "${INFO}${HOLD} ${GN}Using Config File on node $PVEHOST_NAME${CL}" - METHOD="advanced" - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/config-file.func) - config_file - break - ;; - - 5) - manage_default_storage - ;; - 6) - if [[ $DIAGNOSTICS == "yes" ]]; then - if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --yesno "Send Diagnostics of LXC Installation?\n\nCurrent setting: ${DIAGNOSTICS}" 10 58 \ - --yes-button "No" --no-button "Back"; then - DIAGNOSTICS="no" - sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=no/' /usr/local/community-scripts/diagnostics - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --msgbox "Diagnostics settings changed to ${DIAGNOSTICS}." 8 58 - fi - else - if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --yesno "Send Diagnostics of LXC Installation?\n\nCurrent setting: ${DIAGNOSTICS}" 10 58 \ - --yes-button "Yes" --no-button "Back"; then - DIAGNOSTICS="yes" - sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=yes/' /usr/local/community-scripts/diagnostics - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --msgbox "Diagnostics settings changed to ${DIAGNOSTICS}." 8 58 - fi - fi - ;; - 7) - echo -e "${CROSS}${RD}Exiting.${CL}" - exit 0 + ADVANCED | advanced | 3) + CHOICE="3" ;; *) - echo -e "${CROSS}${RD}Invalid option, please try again.${CL}" + echo -e "\n${CROSS}${RD}Invalid PRESET value: ${PRESET}${CL}\n" + exit 1 ;; esac - done + else + while true; do + TMP_CHOICE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --title "SETTINGS" \ + --menu "Choose an option:" 20 60 7 \ + "1" "Default Settings" \ + "2" "Default Settings (with verbose)" \ + "3" "Advanced Settings" \ + "4" "Use Config File" \ + "5" "Manage Default Storage" \ + "6" "Diagnostic Settings" \ + "7" "Exit" \ + --default-item "1" 3>&1 1>&2 2>&3) || true + + if [ -z "$TMP_CHOICE" ]; then + echo -e "\n${CROSS}${RD}Menu canceled. Exiting script.${CL}\n" + exit 0 + fi + + CHOICE="$TMP_CHOICE" + break + done + fi + + case $CHOICE in + 1) + header_info + echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME${CL}" + VERBOSE="no" + METHOD="default" + base_settings "$VERBOSE" + echo_default + ;; + 2) + header_info + echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME (${VERBOSE_CROPPED}Verbose)${CL}" + VERBOSE="yes" + METHOD="default" + base_settings "$VERBOSE" + echo_default + ;; + 3) + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings on node $PVEHOST_NAME${CL}" + METHOD="advanced" + base_settings + advanced_settings + ;; + 4) + header_info + echo -e "${INFO}${HOLD} ${GN}Using Config File on node $PVEHOST_NAME${CL}" + METHOD="advanced" + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/config-file.func) + config_file + ;; + 5) + manage_default_storage + ;; + 6) + if [[ $DIAGNOSTICS == "yes" ]]; then + if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --yesno "Send Diagnostics of LXC Installation?\n\nCurrent setting: ${DIAGNOSTICS}" 10 58 \ + --yes-button "No" --no-button "Back"; then + DIAGNOSTICS="no" + sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=no/' /usr/local/community-scripts/diagnostics + whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --msgbox "Diagnostics settings changed to ${DIAGNOSTICS}." 8 58 + fi + else + if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --yesno "Send Diagnostics of LXC Installation?\n\nCurrent setting: ${DIAGNOSTICS}" 10 58 \ + --yes-button "Yes" --no-button "Back"; then + DIAGNOSTICS="yes" + sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=yes/' /usr/local/community-scripts/diagnostics + whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --msgbox "Diagnostics settings changed to ${DIAGNOSTICS}." 8 58 + fi + fi + ;; + 7) + echo -e "\n${CROSS}${RD}Script terminated. Have a great day!${CL}\n" + exit 0 + ;; + *) + echo -e "${CROSS}${RD}Invalid option, please try again.${CL}" + ;; + esac } check_container_resources() { @@ -1096,6 +1148,10 @@ start() { source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) if command -v pveversion >/dev/null 2>&1; then install_script + elif [ ! -z ${PHS_SILENT+x} ] && [[ "${PHS_SILENT}" == "1" ]]; then + VERBOSE="no" + set_std_mode + update_script else CHOICE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "${APP} LXC Update/Setting" --menu \ "Support/Update functions for ${APP} LXC. Choose an option:" \ @@ -1191,16 +1247,14 @@ build_container() { -unprivileged $CT_TYPE $PW " - # This executes create_lxc.sh and creates the container and .conf file - CREATE_CMD="bash -c \"\$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/create_lxc.sh)\"" - eval "$CREATE_CMD" - RET=$? - if [[ $RET -ne 0 ]]; then - msg_error "in line $LINENO: exit code $RET: while executing command $CREATE_CMD" - exit $RET + bash -c "$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/create_lxc.sh)" + if [ $? -ne 0 ]; then + exit 200 fi - LXC_CONFIG=/etc/pve/lxc/${CTID}.conf + LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" + + # USB passthrough for privileged LXC (CT_TYPE=0) if [ "$CT_TYPE" == "0" ]; then cat <>"$LXC_CONFIG" # USB passthrough @@ -1216,38 +1270,82 @@ lxc.mount.entry: /dev/ttyACM1 dev/ttyACM1 none bind,optional,create= EOF fi - if [ "$CT_TYPE" == "0" ]; then - if [[ "$APP" == "immich" || "$APP" == "Channels" || "$APP" == "Emby" || "$APP" == "ErsatzTV" || "$APP" == "Frigate" || "$APP" == "Jellyfin" || "$APP" == "Plex" || "$APP" == "Scrypted" || "$APP" == "Tdarr" || "$APP" == "Unmanic" || "$APP" == "Ollama" || "$APP" == "FileFlows" ]]; then - cat <>$LXC_CONFIG -# VAAPI hardware transcoding -lxc.cgroup2.devices.allow: c 226:0 rwm -lxc.cgroup2.devices.allow: c 226:128 rwm -lxc.cgroup2.devices.allow: c 29:0 rwm -lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file -lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir -lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file -EOF + # VAAPI passthrough for privileged containers or known apps + VAAPI_APPS=( + "immich" + "Channels" + "Emby" + "ErsatzTV" + "Frigate" + "Jellyfin" + "Plex" + "Scrypted" + "Tdarr" + "Unmanic" + "Ollama" + "FileFlows" + "Open WebUI" + ) + + is_vaapi_app=false + for vaapi_app in "${VAAPI_APPS[@]}"; do + if [[ "$APP" == "$vaapi_app" ]]; then + is_vaapi_app=true + break fi - else - if [[ "$APP" == "immich" || "$APP" == "Channels" || "$APP" == "Emby" || "$APP" == "ErsatzTV" || "$APP" == "Frigate" || "$APP" == "Jellyfin" || "$APP" == "Plex" || "$APP" == "Scrypted" || "$APP" == "Tdarr" || "$APP" == "Unmanic" || "$APP" == "Ollama" || "$APP" == "FileFlows" ]]; then - if [[ -e "/dev/dri/renderD128" ]]; then - if [[ -e "/dev/dri/card0" ]]; then - cat <>$LXC_CONFIG -# VAAPI hardware transcoding -dev0: /dev/dri/card0,gid=44 -dev1: /dev/dri/renderD128,gid=104 -EOF - else - cat <>"$LXC_CONFIG" -# VAAPI hardware transcoding -dev0: /dev/dri/card1,gid=44 -dev1: /dev/dri/renderD128,gid=104 -EOF - fi + done + + if ([ "$CT_TYPE" == "0" ] || [ "$is_vaapi_app" == "true" ]) && + ([[ -e /dev/dri/renderD128 ]] || [[ -e /dev/dri/card0 ]] || [[ -e /dev/fb0 ]]); then + + echo "" + msg_custom "βš™οΈ " "\e[96m" "Configuring VAAPI passthrough for LXC container" + if [ "$CT_TYPE" != "0" ]; then + msg_custom "⚠️ " "\e[33m" "Container is unprivileged – VAAPI passthrough may not work without additional host configuration (e.g., idmap)." + fi + msg_custom "ℹ️ " "\e[96m" "VAAPI enables GPU hardware acceleration (e.g., for video transcoding in Jellyfin or Plex)." + echo "" + read -rp "➀ Automatically mount all available VAAPI devices? [Y/n]: " VAAPI_ALL + + if [[ "$VAAPI_ALL" =~ ^[Yy]$|^$ ]]; then + if [ "$CT_TYPE" == "0" ]; then + # PRV Container β†’ alles zulΓ€ssig + [[ -e /dev/dri/renderD128 ]] && { + echo "lxc.cgroup2.devices.allow: c 226:128 rwm" >>"$LXC_CONFIG" + echo "lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file" >>"$LXC_CONFIG" + } + [[ -e /dev/dri/card0 ]] && { + echo "lxc.cgroup2.devices.allow: c 226:0 rwm" >>"$LXC_CONFIG" + echo "lxc.mount.entry: /dev/dri/card0 dev/dri/card0 none bind,optional,create=file" >>"$LXC_CONFIG" + } + [[ -e /dev/fb0 ]] && { + echo "lxc.cgroup2.devices.allow: c 29:0 rwm" >>"$LXC_CONFIG" + echo "lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file" >>"$LXC_CONFIG" + } + [[ -d /dev/dri ]] && { + echo "lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir" >>"$LXC_CONFIG" + } + else + # UNPRV Container β†’ nur devX fΓΌr UI + [[ -e /dev/dri/card0 ]] && echo "dev0: /dev/dri/card0,gid=44" >>"$LXC_CONFIG" + [[ -e /dev/dri/card1 ]] && echo "dev0: /dev/dri/card1,gid=44" >>"$LXC_CONFIG" + [[ -e /dev/dri/renderD128 ]] && echo "dev1: /dev/dri/renderD128,gid=104" >>"$LXC_CONFIG" fi fi + + fi + if [ "$CT_TYPE" == "1" ] && [ "$is_vaapi_app" == "true" ]; then + if [[ -e /dev/dri/card0 ]]; then + echo "dev0: /dev/dri/card0,gid=44" >>"$LXC_CONFIG" + elif [[ -e /dev/dri/card1 ]]; then + echo "dev0: /dev/dri/card1,gid=44" >>"$LXC_CONFIG" + fi + if [[ -e /dev/dri/renderD128 ]]; then + echo "dev1: /dev/dri/renderD128,gid=104" >>"$LXC_CONFIG" + fi fi + # TUN device passthrough if [ "$ENABLE_TUN" == "yes" ]; then cat <>"$LXC_CONFIG" lxc.cgroup2.devices.allow: c 10:200 rwm @@ -1258,7 +1356,53 @@ EOF # This starts the container and executes -install.sh msg_info "Starting LXC Container" pct start "$CTID" - msg_ok "Started LXC Container" + + # wait for status 'running' + for i in {1..10}; do + if pct status "$CTID" | grep -q "status: running"; then + msg_ok "Startted LXC Container" + break + fi + sleep 1 + if [ "$i" -eq 10 ]; then + msg_error "LXC Container did not reach running state" + exit 1 + fi + done + + if [ "$var_os" != "alpine" ]; then + msg_info "Waiting for network in LXC container" + for i in {1..10}; do + if pct exec "$CTID" -- ping -c1 -W1 deb.debian.org >/dev/null 2>&1; then + msg_ok "Network in LXC is reachable" + break + fi + if [ "$i" -lt 10 ]; then + msg_warn "No network yet in LXC (try $i/10) – waiting..." + sleep 3 + else + msg_error "No network in LXC after waiting." + read -r -p "Set fallback DNS (1.1.1.1/8.8.8.8)? [y/N]: " choice + case "$choice" in + [yY]*) + pct set "$CTID" --nameserver 1.1.1.1 + pct set "$CTID" --nameserver 8.8.8.8 + if pct exec "$CTID" -- ping -c1 -W1 deb.debian.org >/dev/null 2>&1; then + msg_ok "Network reachable after DNS fallback" + else + msg_error "Still no network/DNS in LXC! Aborting customization." + exit 1 + fi + ;; + *) + msg_error "Aborted by user – no DNS fallback set." + exit 1 + ;; + esac + fi + done + fi + msg_info "Customizing LXC Container" if [ "$var_os" == "alpine" ]; then sleep 3 @@ -1269,21 +1413,33 @@ EOF' pct exec "$CTID" -- ash -c "apk add bash newt curl openssh nano mc ncurses >/dev/null" else sleep 3 - # Set locale and timezone before update + pct exec "$CTID" -- bash -c "sed -i '/$LANG/ s/^# //' /etc/locale.gen" pct exec "$CTID" -- bash -c "locale_line=\$(grep -v '^#' /etc/locale.gen | grep -E '^[a-zA-Z]' | awk '{print \$1}' | head -n 1) && \ echo LANG=\$locale_line >/etc/default/locale && \ locale-gen >/dev/null && \ export LANG=\$locale_line" - pct exec "$CTID" -- bash -c "echo $tz >/etc/timezone && ln -sf /usr/share/zoneinfo/$tz /etc/localtime" + if [[ -z "${tz:-}" ]]; then + tz=$(timedatectl show --property=Timezone --value 2>/dev/null || echo "Etc/UTC") + fi - # Install curl - pct exec "$CTID" -- bash -c "apt-get update >/dev/null && apt-get install -y sudo curl mc gnupg2 >/dev/null" + if pct exec "$CTID" -- test -e "/usr/share/zoneinfo/$tz"; then + pct exec "$CTID" -- bash -c "tz='$tz'; echo \"\$tz\" >/etc/timezone && ln -sf \"/usr/share/zoneinfo/\$tz\" /etc/localtime" + else + msg_warn "Skipping timezone setup – zone '$tz' not found in container" + fi + + pct exec "$CTID" -- bash -c "apt-get update >/dev/null && apt-get install -y sudo curl mc gnupg2 >/dev/null" || { + msg_error "apt-get base packages installation failed" + exit 1 + } fi msg_ok "Customized LXC Container" - lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/remz1337/ProxmoxVED/pr-keycloak/install/$var_install.sh)" $? + if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/"$var_install".sh)"; then + exit $? + fi } # This function sets the description of the container. diff --git a/misc/core.func b/misc/core.func index b4ad2fa5..a8774fc2 100644 --- a/misc/core.func +++ b/misc/core.func @@ -9,21 +9,21 @@ # } # fi -trap 'on_error $? $LINENO' ERR -trap 'on_exit' EXIT -trap 'on_interrupt' INT -trap 'on_terminate' TERM +# trap 'on_error $? $LINENO' ERR +# trap 'on_exit' EXIT +# trap 'on_interrupt' INT +# trap 'on_terminate' TERM -if ! declare -f wait_for >/dev/null; then - wait_for() { - true - } -fi +# if ! declare -f wait_for >/dev/null; then +# wait_for() { +# true +# } +# fi -declare -A MSG_INFO_SHOWN=() -SPINNER_PID="" -SPINNER_ACTIVE=0 -SPINNER_MSG="" +# declare -A MSG_INFO_SHOWN=() +# SPINNER_PID="" +# SPINNER_ACTIVE=0 +# SPINNER_MSG="" # ------------------------------------------------------------------------------ # Loads core utility groups once (colors, formatting, icons, defaults). @@ -47,9 +47,9 @@ load_functions() { # Error & Signal Handling – robust, universal, subshell-safe # ============================================================================ -_stop_spinner_on_error() { - [[ -n "${SPINNER_PID:-}" ]] && kill "$SPINNER_PID" 2>/dev/null && wait "$SPINNER_PID" 2>/dev/null || true -} +# _stop_spinner_on_error() { +# [[ -n "${SPINNER_PID:-}" ]] && kill "$SPINNER_PID" 2>/dev/null && wait "$SPINNER_PID" 2>/dev/null || true +# } _tool_error_hint() { local cmd="$1" @@ -89,44 +89,39 @@ _tool_error_hint() { esac } -on_error() { - local code="$?" - local line="${BASH_LINENO[0]:-unknown}" - local cmd="${BASH_COMMAND:-unknown}" +# on_error() { +# local code="$?" +# local line="${BASH_LINENO[0]:-unknown}" +# local cmd="${BASH_COMMAND:-unknown}" - # Signalcode unterdrΓΌcken, falls INT/TERM kommt - [[ "$code" == "130" || "$code" == "143" ]] && return +# # Signalcode unterdrΓΌcken, falls INT/TERM kommt +# [[ "$code" == "130" || "$code" == "143" ]] && return - _stop_spinner_on_error - msg_error "Script failed at line $line with exit code $code: $cmd" - exit "$code" -} +# _stop_spinner_on_error +# msg_error "Script failed at line $line with exit code $code: $cmd" +# exit "$code" +# } -on_exit() { - _stop_spinner_on_error - [[ "${VERBOSE:-no}" == "yes" ]] && msg_info "Script exited cleanly" -} +# on_exit() { +# _stop_spinner_on_error +# [[ "${VERBOSE:-no}" == "yes" ]] && msg_info "Script exited cleanly" +# } -on_interrupt() { - _stop_spinner_on_error - msg_error "Interrupted by user (CTRL+C)" - exit 130 -} +# on_interrupt() { +# _stop_spinner_on_error +# msg_error "Interrupted by user (CTRL+C)" +# exit 130 +# } -on_terminate() { - _stop_spinner_on_error - msg_error "Terminated by signal (SIGTERM)" - exit 143 -} +# on_terminate() { +# _stop_spinner_on_error +# msg_error "Terminated by signal (SIGTERM)" +# exit 143 +# } catch_errors() { - trap 'on_error' ERR - trap 'on_exit' EXIT - trap 'on_interrupt' INT - trap 'on_terminate' TERM - set -Eeuo pipefail - shopt -s inherit_errexit + trap 'error_handler $LINENO "$BASH_COMMAND"' ERR } # ------------------------------------------------------------------------------ @@ -143,6 +138,13 @@ color() { CL=$(echo "\033[m") } +# Special for spinner and colorized output via printf +color_spinner() { + CS_YW=$'\033[33m' + CS_YWB=$'\033[93m' + CS_CL=$'\033[m' +} + # ------------------------------------------------------------------------------ # Defines formatting helpers like tab, bold, and line reset sequences. # ------------------------------------------------------------------------------ @@ -248,77 +250,39 @@ header_info() { fi } -# ------------------------------------------------------------------------------ -# Performs a curl request with retry logic and inline feedback. -# ------------------------------------------------------------------------------ - -run_curl() { - if [ "$VERBOSE" = "no" ]; then - $STD curl "$@" - else - curl "$@" +ensure_tput() { + if ! command -v tput >/dev/null 2>&1; then + if grep -qi 'alpine' /etc/os-release; then + apk add --no-cache ncurses >/dev/null 2>&1 + elif command -v apt-get >/dev/null 2>&1; then + apt-get update -qq >/dev/null + apt-get install -y -qq ncurses-bin >/dev/null 2>&1 + fi fi } -curl_handler() { - set +e - trap 'set -e' RETURN - local args=() - local url="" - local max_retries=3 - local delay=2 - local attempt=1 - local exit_code - local has_output_file=false - local result="" +is_alpine() { + local os_id="${var_os:-${PCT_OSTYPE:-}}" - # Parse arguments - for arg in "$@"; do - if [[ "$arg" != -* && -z "$url" ]]; then - url="$arg" - fi - [[ "$arg" == "-o" || "$arg" == --output ]] && has_output_file=true - args+=("$arg") - done - - if [[ -z "$url" ]]; then - msg_error "No valid URL or option entered for curl_handler" - return 1 + if [[ -z "$os_id" && -f /etc/os-release ]]; then + os_id="$( + . /etc/os-release 2>/dev/null + echo "${ID:-}" + )" fi - $STD msg_info "Fetching: $url" + [[ "$os_id" == "alpine" ]] +} - while [[ $attempt -le $max_retries ]]; do - if $has_output_file; then - $STD run_curl "${args[@]}" - exit_code=$? - else - result=$(run_curl "${args[@]}") - exit_code=$? - fi - - if [[ $exit_code -eq 0 ]]; then - $STD msg_ok "Fetched: $url" - $has_output_file || printf '%s' "$result" - return 0 - fi - - if ((attempt >= max_retries)); then - # Read error log if it exists - if [ -s /tmp/curl_error.log ]; then - local curl_stderr - curl_stderr=$(&2 - sleep "$delay" - ((attempt++)) - done - set -e +is_verbose_mode() { + local verbose="${VERBOSE:-${var_verbose:-no}}" + local tty_status + if [[ -t 2 ]]; then + tty_status="interactive" + else + tty_status="not-a-tty" + fi + [[ "$verbose" != "no" || ! -t 2 ]] } # ------------------------------------------------------------------------------ @@ -363,149 +327,158 @@ fatal() { kill -INT $$ } -# Ensure POSIX compatibility across Alpine and Debian/Ubuntu -# === Spinner Start === -# Trap cleanup on various signals -trap 'cleanup_spinner' EXIT INT TERM HUP - -spinner_frames=('β ‹' 'β ™' 'β Ή' 'β Έ' 'β Ό' 'β ΄' 'β ¦' 'β §' 'β ‡' '⠏') - -# === Spinner Start === -start_spinner() { - local msg="$1" - local spin_i=0 - local interval=0.1 - - stop_spinner - SPINNER_MSG="$msg" - SPINNER_ACTIVE=1 - - { - while [[ "$SPINNER_ACTIVE" -eq 1 ]]; do - if [[ -t 2 ]]; then - printf "\r\e[2K%s %b" "${TAB}${spinner_frames[spin_i]}${TAB}" "${YW}${SPINNER_MSG}${CL}" >&2 - else - printf "%s...\n" "$SPINNER_MSG" >&2 - break - fi - spin_i=$(((spin_i + 1) % ${#spinner_frames[@]})) - sleep "$interval" - done - } & - - local pid=$! - if ps -p "$pid" >/dev/null 2>&1; then - SPINNER_PID="$pid" - else - SPINNER_ACTIVE=0 - SPINNER_PID="" - fi +spinner() { + local chars=(β ‹ β ™ β Ή β Έ β Ό β ΄ β ¦ β § β ‡ ⠏) + local i=0 + while true; do + local index=$((i++ % ${#chars[@]})) + printf "\r\033[2K%s %b" "${CS_YWB}${chars[$index]}${CS_CL}" "${CS_YWB}${SPINNER_MSG:-}${CS_CL}" + sleep 0.1 + done +} + +clear_line() { + tput cr 2>/dev/null || echo -en "\r" + tput el 2>/dev/null || echo -en "\033[K" } -# === Spinner Stop === stop_spinner() { - if [[ "$SPINNER_ACTIVE" -eq 1 && -n "$SPINNER_PID" ]]; then - SPINNER_ACTIVE=0 + local pid="${SPINNER_PID:-}" + [[ -z "$pid" && -f /tmp/.spinner.pid ]] && pid=$(/dev/null; then - kill "$SPINNER_PID" 2>/dev/null || true - for _ in $(seq 1 10); do - sleep 0.05 - kill -0 "$SPINNER_PID" 2>/dev/null || break - done + if [[ -n "$pid" && "$pid" =~ ^[0-9]+$ ]]; then + if kill "$pid" 2>/dev/null; then + sleep 0.05 + kill -9 "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true fi - - if [[ "$SPINNER_PID" =~ ^[0-9]+$ ]]; then - ps -p "$SPINNER_PID" -o pid= >/dev/null 2>&1 && wait "$SPINNER_PID" 2>/dev/null || true - fi - - printf "\r\e[2K" >&2 - SPINNER_PID="" + rm -f /tmp/.spinner.pid fi -} -cleanup_spinner() { - stop_spinner + unset SPINNER_PID SPINNER_MSG + stty sane 2>/dev/null || true } msg_info() { local msg="$1" - [[ -z "$msg" || -n "${MSG_INFO_SHOWN["$msg"]+x}" ]] && return + [[ -z "$msg" ]] && return + + if ! declare -p MSG_INFO_SHOWN &>/dev/null || ! declare -A MSG_INFO_SHOWN &>/dev/null; then + declare -gA MSG_INFO_SHOWN=() + fi + [[ -n "${MSG_INFO_SHOWN["$msg"]+x}" ]] && return MSG_INFO_SHOWN["$msg"]=1 stop_spinner + SPINNER_MSG="$msg" - if [[ "${VERBOSE:-no}" != "no" || "${var_os:-}" == "alpine" || ! -t 2 ]]; then - printf "\r\e[2K%s %b\n" "$HOURGLASS" "${YW}${msg}${CL}" >&2 - else - start_spinner "$msg" + if is_verbose_mode || is_alpine; then + local HOURGLASS="${TAB}⏳${TAB}" + printf "\r\e[2K%s %b" "$HOURGLASS" "${YW}${msg}${CL}" >&2 + return fi + + color_spinner + spinner & + SPINNER_PID=$! + echo "$SPINNER_PID" >/tmp/.spinner.pid + disown "$SPINNER_PID" 2>/dev/null || true } msg_ok() { local msg="$1" [[ -z "$msg" ]] && return stop_spinner - printf "\r\e[2K%s %b\n" "$CM" "${GN}${msg}${CL}" >&2 - if declare -p MSG_INFO_SHOWN &>/dev/null && [[ "$(declare -p MSG_INFO_SHOWN 2>/dev/null)" =~ "declare -A" ]]; then - unset MSG_INFO_SHOWN["$msg"] - fi + clear_line + printf "%s %b\n" "$CM" "${GN}${msg}${CL}" >&2 + unset MSG_INFO_SHOWN["$msg"] } msg_error() { - local msg="$1" - [[ -z "$msg" ]] && return stop_spinner - printf "\r\e[2K%s %b\n" "$CROSS" "${RD}${msg}${CL}" >&2 + local msg="$1" + echo -e "${BFR:-} ${CROSS:-βœ–οΈ} ${RD}${msg}${CL}" } msg_warn() { - local msg="$1" - [[ -z "$msg" ]] && return stop_spinner - printf "\r\e[2K%s %b\n" "$INFO" "${YWB}${msg}${CL}" >&2 - if declare -p MSG_INFO_SHOWN &>/dev/null && [[ "$(declare -p MSG_INFO_SHOWN 2>/dev/null)" =~ "declare -A" ]]; then - unset MSG_INFO_SHOWN["$msg"] - fi + local msg="$1" + echo -e "${BFR:-} ${INFO:-ℹ️} ${YWB}${msg}${CL}" } msg_custom() { local symbol="${1:-"[*]"}" - local color="${2:-"\e[36m"}" # Default: Cyan + local color="${2:-"\e[36m"}" local msg="${3:-}" - [[ -z "$msg" ]] && return - stop_spinner 2>/dev/null || true - printf "\r\e[2K%s %b\n" "$symbol" "${color}${msg}${CL:-\e[0m}" >&2 + stop_spinner + echo -e "${BFR:-} ${symbol} ${color}${msg}${CL:-\e[0m}" } -msg_progress() { - local current="$1" - local total="$2" - local label="$3" - local width=40 - local filled percent bar empty - local fill_char="#" - local empty_char="-" +# msg_ok() { +# local msg="$1" +# [[ -z "$msg" ]] && return +# stop_spinner +# printf "\r\e[2K%s %b\n" "$CM" "${GN}${msg}${CL}" >&2 +# if declare -p MSG_INFO_SHOWN &>/dev/null && [[ "$(declare -p MSG_INFO_SHOWN 2>/dev/null)" =~ "declare -A" ]]; then +# unset MSG_INFO_SHOWN["$msg"] +# fi +# } - if ! [[ "$current" =~ ^[0-9]+$ ]] || ! [[ "$total" =~ ^[0-9]+$ ]] || [[ "$total" -eq 0 ]]; then - printf "\r\e[2K%s %b\n" "$CROSS" "${RD}Invalid progress input${CL}" >&2 - return - fi +# msg_error() { +# local msg="$1" +# [[ -z "$msg" ]] && return +# stop_spinner +# printf "\r\e[2K%s %b\n" "$CROSS" "${RD}${msg}${CL}" >&2 +# } - percent=$(((current * 100) / total)) - filled=$(((current * width) / total)) - empty=$((width - filled)) +# msg_warn() { +# local msg="$1" +# [[ -z "$msg" ]] && return +# stop_spinner +# printf "\r\e[2K%s %b\n" "$INFO" "${YWB}${msg}${CL}" >&2 +# if declare -p MSG_INFO_SHOWN &>/dev/null && [[ "$(declare -p MSG_INFO_SHOWN 2>/dev/null)" =~ "declare -A" ]]; then +# unset MSG_INFO_SHOWN["$msg"] +# fi +# } - bar=$(printf "%${filled}s" | tr ' ' "$fill_char") - bar+=$(printf "%${empty}s" | tr ' ' "$empty_char") +# msg_custom() { +# local symbol="${1:-"[*]"}" +# local color="${2:-"\e[36m"}" # Default: Cyan +# local msg="${3:-}" - printf "\r\e[2K%s [%s] %3d%% %s" "${TAB}" "$bar" "$percent" "$label" >&2 +# [[ -z "$msg" ]] && return +# stop_spinner 2>/dev/null || true +# printf "\r\e[2K%s %b\n" "$symbol" "${color}${msg}${CL:-\e[0m}" >&2 +# } - if [[ "$current" -eq "$total" ]]; then - printf "\n" >&2 - fi -} +# msg_progress() { +# local current="$1" +# local total="$2" +# local label="$3" +# local width=40 +# local filled percent bar empty +# local fill_char="#" +# local empty_char="-" + +# if ! [[ "$current" =~ ^[0-9]+$ ]] || ! [[ "$total" =~ ^[0-9]+$ ]] || [[ "$total" -eq 0 ]]; then +# printf "\r\e[2K%s %b\n" "$CROSS" "${RD}Invalid progress input${CL}" >&2 +# return +# fi + +# percent=$(((current * 100) / total)) +# filled=$(((current * width) / total)) +# empty=$((width - filled)) + +# bar=$(printf "%${filled}s" | tr ' ' "$fill_char") +# bar+=$(printf "%${empty}s" | tr ' ' "$empty_char") + +# printf "\r\e[2K%s [%s] %3d%% %s" "${TAB}" "$bar" "$percent" "$label" >&2 + +# if [[ "$current" -eq "$total" ]]; then +# printf "\n" >&2 +# fi +# } run_container_safe() { local ct="$1" @@ -555,3 +528,5 @@ check_or_create_swap() { return 1 fi } + +trap 'stop_spinner' EXIT INT TERM diff --git a/misc/create_lxc.sh b/misc/create_lxc.sh index 81520d0a..b7d1f46e 100644 --- a/misc/create_lxc.sh +++ b/misc/create_lxc.sh @@ -27,7 +27,7 @@ trap on_terminate TERM function on_exit() { local exit_code="$?" - [[ -n "${lockfile:-}" ]] + [[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile" exit "$exit_code" } @@ -50,22 +50,35 @@ function on_terminate() { exit 143 } +function check_storage_support() { + local CONTENT="$1" + local -a VALID_STORAGES=() + + while IFS= read -r line; do + local STORAGE=$(awk '{print $1}' <<<"$line") + [[ "$STORAGE" == "storage" || -z "$STORAGE" ]] && continue + VALID_STORAGES+=("$STORAGE") + done < <(pvesm status -content "$CONTENT" 2>/dev/null | awk 'NR>1') + + [[ ${#VALID_STORAGES[@]} -gt 0 ]] +} + # This checks for the presence of valid Container Storage and Template Storage locations msg_info "Validating Storage" -VALIDCT=$(pvesm status -content rootdir | awk 'NR>1') -if [ -z "$VALIDCT" ]; then - msg_error "Unable to detect a valid Container Storage location." +if ! check_storage_support "rootdir"; then + msg_error "No valid storage found for 'rootdir' (Container)." exit 1 fi -VALIDTMP=$(pvesm status -content vztmpl | awk 'NR>1') -if [ -z "$VALIDTMP" ]; then - msg_error "Unable to detect a valid Template Storage location." +if ! check_storage_support "vztmpl"; then + msg_error "No valid storage found for 'vztmpl' (Template)." exit 1 fi +msg_ok "Validated Storage (rootdir / vztmpl)." # This function is used to select the storage class and determine the corresponding storage content type and label. function select_storage() { local CLASS=$1 CONTENT CONTENT_LABEL + case $CLASS in container) CONTENT='rootdir' @@ -92,46 +105,64 @@ function select_storage() { CONTENT_LABEL='Snippets' ;; *) - msg_error "Invalid storage class '$CLASS'." - exit 201 + msg_error "Invalid storage class '$CLASS'" + return 1 ;; esac - command -v whiptail >/dev/null || { - msg_error "whiptail missing." - exit 220 - } - command -v numfmt >/dev/null || { - msg_error "numfmt missing." - exit 221 - } - + # >>> NEW: support STORAGE preset <<< + if [ "$CONTENT" = "rootdir" ] && [ -n "${STORAGE:-}" ]; then + if pvesm status -content "$CONTENT" | awk 'NR>1 {print $1}' | grep -qx "$STORAGE"; then + STORAGE_RESULT="$STORAGE" + msg_info "Using preset storage: $STORAGE_RESULT for $CONTENT_LABEL" + return 0 + else + msg_error "Preset storage '$STORAGE' is not valid for content type '$CONTENT'." + return 2 + fi + fi local -a MENU - while read -r line; do - local TAG=$(echo $line | awk '{print $1}') - local TYPE=$(echo $line | awk '{printf "%-10s", $2}') - local FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( "%9sB", $6)}') - MENU+=("$TAG" "Type: $TYPE Free: $FREE " "OFF") - done < <(pvesm status -content $CONTENT | awk 'NR>1') + local -A STORAGE_MAP + local COL_WIDTH=0 + + while read -r TAG TYPE _ TOTAL USED FREE _; do + [[ -n "$TAG" && -n "$TYPE" ]] || continue + local DISPLAY="${TAG} (${TYPE})" + local USED_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$USED") + local FREE_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$FREE") + local INFO="Free: ${FREE_FMT}B Used: ${USED_FMT}B" + STORAGE_MAP["$DISPLAY"]="$TAG" + MENU+=("$DISPLAY" "$INFO" "OFF") + ((${#DISPLAY} > COL_WIDTH)) && COL_WIDTH=${#DISPLAY} + done < <(pvesm status -content "$CONTENT" | awk 'NR>1') if [ ${#MENU[@]} -eq 0 ]; then msg_error "No storage found for content type '$CONTENT'." - exit 203 + return 2 fi if [ $((${#MENU[@]} / 3)) -eq 1 ]; then - printf "%s" "${MENU[0]}" - return + STORAGE_RESULT="${STORAGE_MAP[${MENU[0]}]}" + return 0 fi - local STORAGE - STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ - "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \ - 16 70 6 "${MENU[@]}" 3>&1 1>&2 2>&3) || { - msg_error "Storage selection cancelled by user." - exit 202 - } - printf "%s" "$STORAGE" + local WIDTH=$((COL_WIDTH + 42)) + while true; do + local DISPLAY_SELECTED=$(whiptail --backtitle "Proxmox VE Helper Scripts" \ + --title "Storage Pools" \ + --radiolist "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \ + 16 "$WIDTH" 6 "${MENU[@]}" 3>&1 1>&2 2>&3) + + [[ $? -ne 0 ]] && return 3 + + if [[ -z "$DISPLAY_SELECTED" || -z "${STORAGE_MAP[$DISPLAY_SELECTED]+_}" ]]; then + whiptail --msgbox "No valid storage selected. Please try again." 8 58 + continue + fi + + STORAGE_RESULT="${STORAGE_MAP[$DISPLAY_SELECTED]}" + return 0 + done } # Test if required variables are set @@ -158,25 +189,55 @@ if qm status "$CTID" &>/dev/null || pct status "$CTID" &>/dev/null; then exit 206 fi -DEFAULT_FILE="/usr/local/community-scripts/default_storage" -if [[ -f "$DEFAULT_FILE" ]]; then - source "$DEFAULT_FILE" - if [[ -n "$TEMPLATE_STORAGE" && -n "$CONTAINER_STORAGE" ]]; then - msg_info "Using default storage configuration from: $DEFAULT_FILE" - msg_ok "Template Storage: ${BL}$TEMPLATE_STORAGE${CL} ${GN}|${CL} Container Storage: ${BL}$CONTAINER_STORAGE${CL}" - else - msg_warn "Default storage file exists but is incomplete – falling back to manual selection" - TEMPLATE_STORAGE=$(select_storage template) - msg_ok "Using ${BL}$TEMPLATE_STORAGE${CL} ${GN}for Template Storage." - CONTAINER_STORAGE=$(select_storage container) - msg_ok "Using ${BL}$CONTAINER_STORAGE${CL} ${GN}for Container Storage." +# DEFAULT_FILE="/usr/local/community-scripts/default_storage" +# if [[ -f "$DEFAULT_FILE" ]]; then +# source "$DEFAULT_FILE" +# if [[ -n "$TEMPLATE_STORAGE" && -n "$CONTAINER_STORAGE" ]]; then +# msg_info "Using default storage configuration from: $DEFAULT_FILE" +# msg_ok "Template Storage: ${BL}$TEMPLATE_STORAGE${CL} ${GN}|${CL} Container Storage: ${BL}$CONTAINER_STORAGE${CL}" +# else +# msg_warn "Default storage file exists but is incomplete – falling back to manual selection" +# TEMPLATE_STORAGE=$(select_storage template) +# msg_ok "Using ${BL}$TEMPLATE_STORAGE${CL} ${GN}for Template Storage." +# CONTAINER_STORAGE=$(select_storage container) +# msg_ok "Using ${BL}$CONTAINER_STORAGE${CL} ${GN}for Container Storage." +# fi +# else +# # TEMPLATE STORAGE SELECTION +# # Template Storage +# while true; do +# TEMPLATE_STORAGE=$(select_storage template) +# if [[ -n "$TEMPLATE_STORAGE" ]]; then +# msg_ok "Using ${BL}$TEMPLATE_STORAGE${CL} ${GN}for Template Storage." +# break +# fi +# msg_warn "No valid template storage selected. Please try again." +# done + +# while true; do +# CONTAINER_STORAGE=$(select_storage container) +# if [[ -n "$CONTAINER_STORAGE" ]]; then +# msg_ok "Using ${BL}$CONTAINER_STORAGE${CL} ${GN}for Container Storage." +# break +# fi +# msg_warn "No valid container storage selected. Please try again." +# done + +# fi + +while true; do + if select_storage template; then + TEMPLATE_STORAGE="$STORAGE_RESULT" + break fi -else - TEMPLATE_STORAGE=$(select_storage template) - msg_ok "Using ${BL}$TEMPLATE_STORAGE${CL} ${GN}for Template Storage." - CONTAINER_STORAGE=$(select_storage container) - msg_ok "Using ${BL}$CONTAINER_STORAGE${CL} ${GN}for Container Storage." -fi +done + +while true; do + if select_storage container; then + CONTAINER_STORAGE="$STORAGE_RESULT" + break + fi +done # Check free space on selected container storage STORAGE_FREE=$(pvesm status | awk -v s="$CONTAINER_STORAGE" '$1 == s { print $6 }') @@ -223,29 +284,35 @@ fi TEMPLATE="${TEMPLATES[-1]}" TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || echo "/var/lib/vz/template/cache/$TEMPLATE")" -# Check if template exists and is valid -if ! pveam list "$TEMPLATE_STORAGE" | grep -q "$TEMPLATE" || ! zstdcat "$TEMPLATE_PATH" | tar -tf - >/dev/null 2>&1; then - msg_warn "Template $TEMPLATE not found or appears to be corrupted. Re-downloading." +TEMPLATE_VALID=1 +if ! pveam list "$TEMPLATE_STORAGE" | grep -q "$TEMPLATE"; then + TEMPLATE_VALID=0 +elif [ ! -s "$TEMPLATE_PATH" ]; then + TEMPLATE_VALID=0 +elif ! tar --use-compress-program=zstdcat -tf "$TEMPLATE_PATH" >/dev/null 2>&1; then + TEMPLATE_VALID=0 +fi +if [ "$TEMPLATE_VALID" -eq 0 ]; then + msg_warn "Template $TEMPLATE not found or appears to be corrupted. Re-downloading." [[ -f "$TEMPLATE_PATH" ]] && rm -f "$TEMPLATE_PATH" for attempt in {1..3}; do msg_info "Attempt $attempt: Downloading LXC template..." - - if timeout 120 pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1; then + if pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1; then msg_ok "Template download successful." break fi - if [ $attempt -eq 3 ]; then - msg_error "Failed after 3 attempts. Please check your Proxmox host’s internet access or manually run:\n pveam download $TEMPLATE_STORAGE $TEMPLATE" + msg_error "Failed after 3 attempts. Please check network access or manually run:\n pveam download $TEMPLATE_STORAGE $TEMPLATE" exit 208 fi - sleep $((attempt * 5)) done fi msg_ok "LXC Template '$TEMPLATE' is ready to use." + +msg_info "Creating LXC Container" # Check and fix subuid/subgid grep -q "root:100000:65536" /etc/subuid || echo "root:100000:65536" >>/etc/subuid grep -q "root:100000:65536" /etc/subgid || echo "root:100000:65536" >>/etc/subgid @@ -265,7 +332,6 @@ flock -w 60 9 || { exit 211 } -msg_info "Creating LXC Container" if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" &>/dev/null; then msg_error "Container creation failed. Checking if template is corrupted or incomplete." diff --git a/misc/install.func b/misc/install.func index efc77be5..e3751c29 100644 --- a/misc/install.func +++ b/misc/install.func @@ -31,7 +31,7 @@ catch_errors() { # This function handles errors error_handler() { - source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) + source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/api.func) printf "\e[?25h" local exit_code="$?" local line_number="$1" @@ -65,7 +65,8 @@ setting_up_container() { rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED systemctl disable -q --now systemd-networkd-wait-online.service msg_ok "Set up Container OS" - msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)" + #msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)" + msg_ok "Network Connected: ${BL}$(hostname -I)" } # This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected @@ -104,24 +105,24 @@ network_check() { fi # DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6) - GITHUB_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org") - GITHUB_STATUS="GitHub DNS:" + GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org") + GIT_STATUS="Git DNS:" DNS_FAILED=false - for HOST in "${GITHUB_HOSTS[@]}"; do + for HOST in "${GIT_HOSTS[@]}"; do RESOLVEDIP=$(getent hosts "$HOST" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1) if [[ -z "$RESOLVEDIP" ]]; then - GITHUB_STATUS+="$HOST:($DNSFAIL)" + GIT_STATUS+="$HOST:($DNSFAIL)" DNS_FAILED=true else - GITHUB_STATUS+=" $HOST:($DNSOK)" + GIT_STATUS+=" $HOST:($DNSOK)" fi done if [[ "$DNS_FAILED" == true ]]; then - fatal "$GITHUB_STATUS" + fatal "$GIT_STATUS" else - msg_ok "$GITHUB_STATUS" + msg_ok "$GIT_STATUS" fi set -e @@ -133,7 +134,7 @@ update_os() { msg_info "Updating Container OS" if [[ "$CACHER" == "yes" ]]; then echo "Acquire::http::Proxy-Auto-Detect \"/usr/local/bin/apt-proxy-detect.sh\";" >/etc/apt/apt.conf.d/00aptproxy - cat </usr/local/bin/apt-proxy-detect.sh + cat <<'EOF' >/usr/local/bin/apt-proxy-detect.sh #!/bin/bash if nc -w1 -z "${CACHER_IP}" 3142; then echo -n "http://${CACHER_IP}:3142" diff --git a/misc/tools.func b/misc/tools.func index 2b4b5769..dace8ca7 100644 --- a/misc/tools.func +++ b/misc/tools.func @@ -18,7 +18,6 @@ function setup_nodejs() { local CURRENT_NODE_VERSION="" local NEED_NODE_INSTALL=false - # Check if Node.js is already installed if command -v node >/dev/null; then CURRENT_NODE_VERSION="$(node -v | grep -oP '^v\K[0-9]+')" if [[ "$CURRENT_NODE_VERSION" != "$NODE_VERSION" ]]; then @@ -38,87 +37,99 @@ function setup_nodejs() { } fi - # Install Node.js if required if [[ "$NEED_NODE_INSTALL" == true ]]; then $STD apt-get purge -y nodejs - rm -f /etc/apt/sources.list.d/nodesource.list /etc/apt/keyrings/nodesource.gpg + rm -f /etc/apt/sources.list.d/nodesource.list /usr/share/keyrings/nodesource.gpg - mkdir -p /etc/apt/keyrings + mkdir -p /usr/share/keyrings + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | + gpg --dearmor -o /usr/share/keyrings/nodesource.gpg || { + msg_error "Failed to import NodeSource GPG key" + exit 1 + } + chmod 644 /usr/share/keyrings/nodesource.gpg - if ! curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | - gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg; then - msg_error "Failed to download or import NodeSource GPG key" + local ARCH + ARCH=$(dpkg --print-architecture) + if ! [[ "$ARCH" =~ ^(amd64|arm64|armhf)$ ]]; then + msg_error "Unsupported architecture: $ARCH" exit 1 fi - echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_VERSION}.x nodistro main" \ - >/etc/apt/sources.list.d/nodesource.list + echo "deb [arch=$ARCH signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_VERSION}.x nodistro main" >/etc/apt/sources.list.d/nodesource.list + cat </etc/apt/preferences.d/nodejs +Package: nodejs +Pin: origin deb.nodesource.com +Pin-Priority: 700 +EOF + + sleep 2 if ! apt-get update >/dev/null 2>&1; then - msg_error "Failed to update APT repositories after adding NodeSource" - exit 1 + msg_warn "APT update failed – retrying in 5s" + sleep 5 + if ! apt-get update >/dev/null 2>&1; then + msg_error "Failed to update APT repositories after adding NodeSource" + exit 1 + fi fi - if ! apt-get install -y nodejs >/dev/null 2>&1; then + if ! apt-get install -y -t nodistro nodejs >/dev/null 2>&1; then msg_error "Failed to install Node.js ${NODE_VERSION} from NodeSource" + apt-cache policy nodejs | tee "$STD" exit 1 fi + $STD npm install -g npm@latest || { + msg_error "Failed to update npm to latest version" + } msg_ok "Setup Node.js ${NODE_VERSION}" fi export NODE_OPTIONS="--max-old-space-size=4096" - # Ensure valid working directory for npm (avoids uv_cwd error) - if [[ ! -d /opt ]]; then - mkdir -p /opt - fi + [[ -d /opt ]] || mkdir -p /opt cd /opt || { msg_error "Failed to set safe working directory before npm install" exit 1 } - # Install global Node modules if [[ -n "$NODE_MODULE" ]]; then IFS=',' read -ra MODULES <<<"$NODE_MODULE" for mod in "${MODULES[@]}"; do local MODULE_NAME MODULE_REQ_VERSION MODULE_INSTALLED_VERSION if [[ "$mod" == @*/*@* ]]; then - # Scoped package with version, e.g. @vue/cli-service@latest MODULE_NAME="${mod%@*}" MODULE_REQ_VERSION="${mod##*@}" elif [[ "$mod" == *"@"* ]]; then - # Unscoped package with version, e.g. yarn@latest MODULE_NAME="${mod%@*}" MODULE_REQ_VERSION="${mod##*@}" else - # No version specified MODULE_NAME="$mod" MODULE_REQ_VERSION="latest" fi - # Check if the module is already installed if npm list -g --depth=0 "$MODULE_NAME" >/dev/null 2>&1; then MODULE_INSTALLED_VERSION="$(npm list -g --depth=0 "$MODULE_NAME" | grep "$MODULE_NAME@" | awk -F@ '{print $2}' | tr -d '[:space:]')" if [[ "$MODULE_REQ_VERSION" != "latest" && "$MODULE_REQ_VERSION" != "$MODULE_INSTALLED_VERSION" ]]; then msg_info "Updating $MODULE_NAME from v$MODULE_INSTALLED_VERSION to v$MODULE_REQ_VERSION" - if ! $STD npm install -g "${MODULE_NAME}@${MODULE_REQ_VERSION}"; then + $STD npm install -g "${MODULE_NAME}@${MODULE_REQ_VERSION}" || { msg_error "Failed to update $MODULE_NAME to version $MODULE_REQ_VERSION" exit 1 - fi + } elif [[ "$MODULE_REQ_VERSION" == "latest" ]]; then msg_info "Updating $MODULE_NAME to latest version" - if ! $STD npm install -g "${MODULE_NAME}@latest"; then + $STD npm install -g "${MODULE_NAME}@latest" || { msg_error "Failed to update $MODULE_NAME to latest version" exit 1 - fi + } fi else msg_info "Installing $MODULE_NAME@$MODULE_REQ_VERSION" - if ! $STD npm install -g "${MODULE_NAME}@${MODULE_REQ_VERSION}"; then + $STD npm install -g "${MODULE_NAME}@${MODULE_REQ_VERSION}" || { msg_error "Failed to install $MODULE_NAME@$MODULE_REQ_VERSION" exit 1 - fi + } fi done msg_ok "Installed Node.js modules: $NODE_MODULE" @@ -235,10 +246,14 @@ setup_mariadb() { DISTRO_CODENAME="$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)" CURRENT_OS="$(awk -F= '/^ID=/{print $2}' /etc/os-release)" + if ! curl -fsI http://mirror.mariadb.org/repo/ >/dev/null; then + msg_error "MariaDB mirror not reachable" + return 1 + fi + msg_info "Setting up MariaDB $MARIADB_VERSION" # grab dynamic latest LTS version if [[ "$MARIADB_VERSION" == "latest" ]]; then - $STD msg_info "Resolving latest GA MariaDB version" MARIADB_VERSION=$(curl -fsSL http://mirror.mariadb.org/repo/ | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+/' | grep -vE 'rc/|rolling/' | @@ -249,7 +264,6 @@ setup_mariadb() { msg_error "Could not determine latest GA MariaDB version" return 1 fi - $STD msg_ok "Latest GA MariaDB version is $MARIADB_VERSION" fi local CURRENT_VERSION="" @@ -274,7 +288,6 @@ setup_mariadb() { $STD msg_info "Setup MariaDB $MARIADB_VERSION" fi - $STD msg_info "Setting up MariaDB Repository" curl -fsSL "https://mariadb.org/mariadb_release_signing_key.asc" | gpg --dearmor -o /etc/apt/trusted.gpg.d/mariadb.gpg @@ -380,7 +393,6 @@ function setup_mysql() { # PHP_POST_MAX_SIZE - (default: 128M) # PHP_MAX_EXECUTION_TIME - (default: 300) # ------------------------------------------------------------------------------ - function setup_php() { local PHP_VERSION="${PHP_VERSION:-8.4}" local PHP_MODULE="${PHP_MODULE:-}" @@ -404,9 +416,10 @@ function setup_php() { COMBINED_MODULES="${DEFAULT_MODULES}" fi - # Deduplicate modules + # Deduplicate COMBINED_MODULES=$(echo "$COMBINED_MODULES" | tr ',' '\n' | awk '!seen[$0]++' | paste -sd, -) + # Get current PHP-CLI version local CURRENT_PHP="" if command -v php >/dev/null 2>&1; then CURRENT_PHP=$(php -v 2>/dev/null | awk '/^PHP/{print $2}' | cut -d. -f1,2) @@ -428,44 +441,44 @@ function setup_php() { $STD apt-get update fi + # Build module list local MODULE_LIST="php${PHP_VERSION}" IFS=',' read -ra MODULES <<<"$COMBINED_MODULES" for mod in "${MODULES[@]}"; do - MODULE_LIST+=" php${PHP_VERSION}-${mod}" + if apt-cache show "php${PHP_VERSION}-${mod}" >/dev/null 2>&1; then + MODULE_LIST+=" php${PHP_VERSION}-${mod}" + else + msg_warn "PHP-Module ${mod} for PHP ${PHP_VERSION} not found – skipping" + fi done - if [[ "$PHP_FPM" == "YES" ]]; then MODULE_LIST+=" php${PHP_VERSION}-fpm" fi - if [[ "$PHP_APACHE" == "YES" ]] && [[ -n "$CURRENT_PHP" ]]; then - if [[ -f /etc/apache2/mods-enabled/php${CURRENT_PHP}.load ]]; then - $STD a2dismod php${CURRENT_PHP} || true + # install apache2 with PHP support if requested + if [[ "$PHP_APACHE" == "YES" ]]; then + if ! dpkg -l | grep -q "libapache2-mod-php${PHP_VERSION}"; then + msg_info "Installing Apache with PHP${PHP_VERSION} support" + $STD apt-get install -y apache2 libapache2-mod-php${PHP_VERSION} + else + msg_info "Apache with PHP${PHP_VERSION} already installed – skipping install" fi fi - if [[ "$PHP_FPM" == "YES" ]] && [[ -n "$CURRENT_PHP" ]]; then - $STD systemctl stop php${CURRENT_PHP}-fpm || true - $STD systemctl disable php${CURRENT_PHP}-fpm || true - fi - + # setup / update PHP modules $STD apt-get install -y $MODULE_LIST msg_ok "Setup PHP $PHP_VERSION" - if [[ "$PHP_APACHE" == "YES" ]]; then - $STD systemctl restart apache2 || true - fi - - if [[ "$PHP_FPM" == "YES" ]]; then - $STD systemctl enable php${PHP_VERSION}-fpm - $STD systemctl restart php${PHP_VERSION}-fpm + # optional stop old PHP-FPM service + if [[ "$PHP_FPM" == "YES" && -n "$CURRENT_PHP" && "$CURRENT_PHP" != "$PHP_VERSION" ]]; then + $STD systemctl stop php"${CURRENT_PHP}"-fpm || true + $STD systemctl disable php"${CURRENT_PHP}"-fpm || true fi # Patch all relevant php.ini files local PHP_INI_PATHS=("/etc/php/${PHP_VERSION}/cli/php.ini") [[ "$PHP_FPM" == "YES" ]] && PHP_INI_PATHS+=("/etc/php/${PHP_VERSION}/fpm/php.ini") [[ "$PHP_APACHE" == "YES" ]] && PHP_INI_PATHS+=("/etc/php/${PHP_VERSION}/apache2/php.ini") - for ini in "${PHP_INI_PATHS[@]}"; do if [[ -f "$ini" ]]; then $STD msg_info "Patching $ini" @@ -476,6 +489,28 @@ function setup_php() { $STD msg_ok "Patched $ini" fi done + + # patch Apache configuration if needed + if [[ "$PHP_APACHE" == "YES" ]]; then + for mod in $(ls /etc/apache2/mods-enabled/ 2>/dev/null | grep -E '^php[0-9]\.[0-9]\.conf$' | sed 's/\.conf//'); do + if [[ "$mod" != "php${PHP_VERSION}" ]]; then + $STD a2dismod "$mod" || true + fi + done + $STD a2enmod mpm_prefork + $STD a2enmod "php${PHP_VERSION}" + $STD systemctl restart apache2 || true + fi + + # enable and restart PHP-FPM if requested + if [[ "$PHP_FPM" == "YES" ]]; then + if systemctl list-unit-files | grep -q "php${PHP_VERSION}-fpm.service"; then + $STD systemctl enable php${PHP_VERSION}-fpm + $STD systemctl restart php${PHP_VERSION}-fpm + else + msg_warn "FPM requested but service php${PHP_VERSION}-fpm not found" + fi + fi } # ------------------------------------------------------------------------------ @@ -645,9 +680,24 @@ function setup_mongodb() { DISTRO_ID=$(awk -F= '/^ID=/{ gsub(/"/,"",$2); print $2 }' /etc/os-release) DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{ print $2 }' /etc/os-release) + # Check AVX support + if ! grep -qm1 'avx[^ ]*' /proc/cpuinfo; then + local major="${MONGO_VERSION%%.*}" + if ((major > 5)); then + msg_error "MongoDB ${MONGO_VERSION} requires AVX support, which is not available on this system." + return 1 + fi + fi + case "$DISTRO_ID" in - ubuntu) MONGO_BASE_URL="https://repo.mongodb.org/apt/ubuntu" ;; - debian) MONGO_BASE_URL="https://repo.mongodb.org/apt/debian" ;; + ubuntu) + MONGO_BASE_URL="https://repo.mongodb.org/apt/ubuntu" + REPO_COMPONENT="multiverse" + ;; + debian) + MONGO_BASE_URL="https://repo.mongodb.org/apt/debian" + REPO_COMPONENT="main" + ;; *) msg_error "Unsupported distribution: $DISTRO_ID" return 1 @@ -747,11 +797,12 @@ function fetch_and_deploy_gh_release() { local mode="${3:-tarball}" # tarball | binary | prebuild | singlefile local version="${4:-latest}" local target="${5:-/opt/$app}" + local asset_pattern="${6:-}" local app_lc=$(echo "${app,,}" | tr -d ' ') local version_file="$HOME/.${app_lc}" - local api_timeout="--connect-timeout 5 --max-time 10" + local api_timeout="--connect-timeout 10 --max-time 60" local download_timeout="--connect-timeout 15 --max-time 900" local current_version="" @@ -766,6 +817,14 @@ function fetch_and_deploy_gh_release() { local header=() [[ -n "${GITHUB_TOKEN:-}" ]] && header=(-H "Authorization: token $GITHUB_TOKEN") + # dns pre check + local gh_host + gh_host=$(awk -F/ '{print $3}' <<<"$api_url") + if ! getent hosts "$gh_host" &>/dev/null; then + msg_error "DNS resolution failed for $gh_host – check /etc/resolv.conf or networking" + return 1 + fi + local max_retries=3 retry_delay=2 attempt=1 success=false resp http_code while ((attempt <= max_retries)); do @@ -775,7 +834,7 @@ function fetch_and_deploy_gh_release() { done if ! $success; then - msg_error "Failed to fetch release metadata after $max_retries attempts" + msg_error "Failed to fetch release metadata from $api_url after $max_retries attempts" return 1 fi @@ -799,8 +858,9 @@ function fetch_and_deploy_gh_release() { tmpdir=$(mktemp -d) || return 1 local filename="" url="" - msg_info "Setup $app ($version)" + msg_info "Fetching GitHub release: $app ($version)" + ### Tarball Mode ### if [[ "$mode" == "tarball" || "$mode" == "source" ]]; then url=$(echo "$json" | jq -r '.tarball_url // empty') [[ -z "$url" ]] && url="https://github.com/$repo/archive/refs/tags/v$version.tar.gz" @@ -821,22 +881,34 @@ function fetch_and_deploy_gh_release() { cp -r "$unpack_dir"/* "$target/" shopt -u dotglob nullglob + ### Binary Mode ### elif [[ "$mode" == "binary" ]]; then local arch arch=$(dpkg --print-architecture 2>/dev/null || uname -m) - [[ "$arch" == "x86_64" ]] && arch="x86_64" + [[ "$arch" == "x86_64" ]] && arch="amd64" [[ "$arch" == "aarch64" ]] && arch="arm64" local assets url_match="" assets=$(echo "$json" | jq -r '.assets[].browser_download_url') - for u in $assets; do - if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then - url_match="$u" - break - fi - done + # If explicit filename pattern is provided (param $6), match that first + if [[ -n "$asset_pattern" ]]; then + for u in $assets; do + [[ "$u" =~ $asset_pattern || "$u" == *"$asset_pattern" ]] && url_match="$u" && break + done + fi + # If no match via explicit pattern, fall back to architecture heuristic + if [[ -z "$url_match" ]]; then + for u in $assets; do + if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then + url_match="$u" + break + fi + done + fi + + # Fallback: any .deb file if [[ -z "$url_match" ]]; then for u in $assets; do [[ "$u" =~ \.deb$ ]] && url_match="$u" && break @@ -865,8 +937,10 @@ function fetch_and_deploy_gh_release() { } } + ### Prebuild Mode ### elif [[ "$mode" == "prebuild" ]]; then - local pattern="$6" + local pattern="${6%\"}" + pattern="${pattern#\"}" [[ -z "$pattern" ]] && { msg_error "Mode 'prebuild' requires 6th parameter (asset filename pattern)" rm -rf "$tmpdir" @@ -875,7 +949,13 @@ function fetch_and_deploy_gh_release() { local asset_url="" for u in $(echo "$json" | jq -r '.assets[].browser_download_url'); do - [[ "$u" =~ $pattern || "$u" == *"$pattern" ]] && asset_url="$u" && break + filename_candidate="${u##*/}" + case "$filename_candidate" in + $pattern) + asset_url="$u" + break + ;; + esac done [[ -z "$asset_url" ]] && { @@ -891,22 +971,46 @@ function fetch_and_deploy_gh_release() { return 1 } + local unpack_tmp + unpack_tmp=$(mktemp -d) mkdir -p "$target" + if [[ "$filename" == *.zip ]]; then if ! command -v unzip &>/dev/null; then $STD apt-get install -y unzip fi - $STD unzip "$tmpdir/$filename" -d "$target" - elif [[ "$filename" == *.tar.gz ]]; then - tar -xzf "$tmpdir/$filename" -C "$target" + unzip -q "$tmpdir/$filename" -d "$unpack_tmp" + elif [[ "$filename" == *.tar.* || "$filename" == *.tgz ]]; then + tar -xf "$tmpdir/$filename" -C "$unpack_tmp" else msg_error "Unsupported archive format: $filename" - rm -rf "$tmpdir" + rm -rf "$tmpdir" "$unpack_tmp" return 1 fi + local top_dirs + top_dirs=$(find "$unpack_tmp" -mindepth 1 -maxdepth 1 -type d | wc -l) + + if [[ "$top_dirs" -eq 1 ]]; then + # Strip leading folder + local inner_dir + inner_dir=$(find "$unpack_tmp" -mindepth 1 -maxdepth 1 -type d) + shopt -s dotglob nullglob + cp -r "$inner_dir"/* "$target/" + shopt -u dotglob nullglob + else + # Copy all contents + shopt -s dotglob nullglob + cp -r "$unpack_tmp"/* "$target/" + shopt -u dotglob nullglob + fi + + rm -rf "$unpack_tmp" + + ### Singlefile Mode ### elif [[ "$mode" == "singlefile" ]]; then - local pattern="$6" + local pattern="${6%\"}" + pattern="${pattern#\"}" [[ -z "$pattern" ]] && { msg_error "Mode 'singlefile' requires 6th parameter (asset filename pattern)" rm -rf "$tmpdir" @@ -915,7 +1019,13 @@ function fetch_and_deploy_gh_release() { local asset_url="" for u in $(echo "$json" | jq -r '.assets[].browser_download_url'); do - [[ "$u" =~ $pattern || "$u" == *"$pattern" ]] && asset_url="$u" && break + filename_candidate="${u##*/}" + case "$filename_candidate" in + $pattern) + asset_url="$u" + break + ;; + esac done [[ -z "$asset_url" ]] && { @@ -926,13 +1036,20 @@ function fetch_and_deploy_gh_release() { filename="${asset_url##*/}" mkdir -p "$target" - curl $download_timeout -fsSL -o "$target/$app" "$asset_url" || { + + local use_filename="${USE_ORIGINAL_FILENAME:-false}" + local target_file="$app" + [[ "$use_filename" == "true" ]] && target_file="$filename" + + curl $download_timeout -fsSL -o "$target/$target_file" "$asset_url" || { msg_error "Download failed: $asset_url" rm -rf "$tmpdir" return 1 } - chmod +x "$target/$app" + if [[ "$target_file" != *.jar && -f "$target/$target_file" ]]; then + chmod +x "$target/$target_file" + fi else msg_error "Unknown mode: $mode" @@ -941,7 +1058,7 @@ function fetch_and_deploy_gh_release() { fi echo "$version" >"$version_file" - msg_ok "Setup $app ($version)" + msg_ok "Deployed: $app ($version)" rm -rf "$tmpdir" } @@ -1611,3 +1728,236 @@ function setup_imagemagick() { ensure_usr_local_bin_persist msg_ok "Setup ImageMagick $VERSION" } + +# ------------------------------------------------------------------------------ +# Installs FFmpeg from source or prebuilt binary (Debian/Ubuntu only). +# +# Description: +# - Downloads and builds FFmpeg from GitHub (https://github.com/FFmpeg/FFmpeg) +# - Supports specific version override via FFMPEG_VERSION (e.g. n7.1.1) +# - Supports build profile via FFMPEG_TYPE: +# - minimal : x264, vpx, mp3 only +# - medium : adds subtitles, fonts, opus, vorbis +# - full : adds dav1d, svt-av1, zlib, numa +# - binary : downloads static build (johnvansickle.com) +# - Defaults to latest stable version and full feature set +# +# Notes: +# - Requires: curl, jq, build-essential, and matching codec libraries +# - Result is installed to /usr/local/bin/ffmpeg +# ------------------------------------------------------------------------------ + +function setup_ffmpeg() { + local TMP_DIR + TMP_DIR=$(mktemp -d) + local GITHUB_REPO="FFmpeg/FFmpeg" + local VERSION="${FFMPEG_VERSION:-latest}" + local TYPE="${FFMPEG_TYPE:-full}" + local BIN_PATH="/usr/local/bin/ffmpeg" + + # Binary fallback mode + if [[ "$TYPE" == "binary" ]]; then + msg_info "Installing FFmpeg (static binary)" + curl -fsSL https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o "$TMP_DIR/ffmpeg.tar.xz" + tar -xf "$TMP_DIR/ffmpeg.tar.xz" -C "$TMP_DIR" + local EXTRACTED_DIR + EXTRACTED_DIR=$(find "$TMP_DIR" -maxdepth 1 -type d -name "ffmpeg-*") + cp "$EXTRACTED_DIR/ffmpeg" "$BIN_PATH" + cp "$EXTRACTED_DIR/ffprobe" /usr/local/bin/ffprobe + chmod +x "$BIN_PATH" /usr/local/bin/ffprobe + rm -rf "$TMP_DIR" + msg_ok "Installed FFmpeg binary ($($BIN_PATH -version | head -n1))" + return + fi + + if ! command -v jq &>/dev/null; then + $STD apt-get update + $STD apt-get install -y jq + fi + + # Auto-detect latest stable version if none specified + if [[ "$VERSION" == "latest" || -z "$VERSION" ]]; then + msg_info "Resolving latest FFmpeg tag" + VERSION=$(curl -fsSL "https://api.github.com/repos/${GITHUB_REPO}/tags" | + jq -r '.[].name' | + grep -E '^n[0-9]+\.[0-9]+\.[0-9]+$' | + sort -V | tail -n1) + fi + + if [[ -z "$VERSION" ]]; then + msg_error "Could not determine FFmpeg version" + rm -rf "$TMP_DIR" + return 1 + fi + + msg_info "Installing FFmpeg ${VERSION} ($TYPE)" + + # Dependency selection + local DEPS=(build-essential yasm nasm pkg-config) + case "$TYPE" in + minimal) + DEPS+=(libx264-dev libvpx-dev libmp3lame-dev) + ;; + medium) + DEPS+=(libx264-dev libvpx-dev libmp3lame-dev libfreetype6-dev libass-dev libopus-dev libvorbis-dev) + ;; + full) + DEPS+=( + libx264-dev libx265-dev libvpx-dev libmp3lame-dev + libfreetype6-dev libass-dev libopus-dev libvorbis-dev + libdav1d-dev libsvtav1-dev zlib1g-dev libnuma-dev + ) + ;; + *) + msg_error "Invalid FFMPEG_TYPE: $TYPE" + rm -rf "$TMP_DIR" + return 1 + ;; + esac + + $STD apt-get update + $STD apt-get install -y "${DEPS[@]}" + + curl -fsSL "https://github.com/${GITHUB_REPO}/archive/refs/tags/${VERSION}.tar.gz" -o "$TMP_DIR/ffmpeg.tar.gz" + tar -xzf "$TMP_DIR/ffmpeg.tar.gz" -C "$TMP_DIR" + cd "$TMP_DIR/FFmpeg-"* || { + msg_error "Source extraction failed" + rm -rf "$TMP_DIR" + return 1 + } + + local args=( + --enable-gpl + --enable-shared + --enable-nonfree + --disable-static + --enable-libx264 + --enable-libvpx + --enable-libmp3lame + ) + + if [[ "$TYPE" != "minimal" ]]; then + args+=(--enable-libfreetype --enable-libass --enable-libopus --enable-libvorbis) + fi + + if [[ "$TYPE" == "full" ]]; then + args+=(--enable-libx265 --enable-libdav1d --enable-zlib) + fi + + if [[ ${#args[@]} -eq 0 ]]; then + msg_error "FFmpeg configure args array is empty – aborting." + rm -rf "$TMP_DIR" + return 1 + fi + + ./configure "${args[@]}" >"$TMP_DIR/configure.log" 2>&1 || { + msg_error "FFmpeg ./configure failed (see $TMP_DIR/configure.log)" + cat "$TMP_DIR/configure.log" | tail -n 20 + rm -rf "$TMP_DIR" + return 1 + } + + $STD make -j"$(nproc)" + $STD make install + echo "/usr/local/lib" >/etc/ld.so.conf.d/ffmpeg.conf + ldconfig + + ldconfig -p | grep libavdevice >/dev/null || { + msg_error "libavdevice not registered with dynamic linker" + return 1 + } + + if ! command -v ffmpeg &>/dev/null; then + msg_error "FFmpeg installation failed" + rm -rf "$TMP_DIR" + return 1 + fi + + local FINAL_VERSION + FINAL_VERSION=$(ffmpeg -version | head -n1 | awk '{print $3}') + rm -rf "$TMP_DIR" + ensure_usr_local_bin_persist + msg_ok "Setup FFmpeg $FINAL_VERSION" +} + +# ------------------------------------------------------------------------------ +# Installs ClickHouse server and client, sets up DB/user with credentials. +# +# Description: +# - Adds official ClickHouse APT repo with GPG key +# - Installs clickhouse-server and clickhouse-client +# - Creates database and user (credentials optionally overrideable via env) +# +# Variables: +# CLICKHOUSE_DB - Database name (default: analytics) +# CLICKHOUSE_USER - Username (default: analytics_user) +# CLICKHOUSE_PASS - Password (default: auto-generated) +# ------------------------------------------------------------------------------ + +function setup_clickhouse() { + local CLICKHOUSE_DB="${CLICKHOUSE_DB:-analytics}" + local CLICKHOUSE_USER="${CLICKHOUSE_USER:-analytics_user}" + local CLICKHOUSE_PASS="${CLICKHOUSE_PASS:-$(openssl rand -base64 18 | cut -c1-13)}" + local GPG_URL="https://packages.clickhouse.com/rpm/lts/repodata/repomd.xml.key" + local GPG_KEY_PATH="/usr/share/keyrings/clickhouse-keyring.gpg" + local ARCH + ARCH=$(dpkg --print-architecture) + + if ! command -v clickhouse >/dev/null; then + msg_info "Setup ClickHouse" + + if ! curl -fsSL --connect-timeout 5 https://packages.clickhouse.com >/dev/null 2>&1; then + msg_error "Connection to packages.clickhouse.com:443 failed – possibly blocked" + echo "πŸ’‘ Check AdGuard/Pi-hole or firewall rules" + return 1 + fi + + if ! curl -fsSL --retry 3 --connect-timeout 10 "$GPG_URL" | + gpg --dearmor -o "$GPG_KEY_PATH"; then + msg_error "Failed to fetch ClickHouse GPG key" + return 1 + fi + + echo "deb [signed-by=$GPG_KEY_PATH arch=$ARCH] https://packages.clickhouse.com/deb stable main" \ + >/etc/apt/sources.list.d/clickhouse.list + + env -u CLICKHOUSE_USER $STD apt-get update + env -u CLICKHOUSE_USER DEBIAN_FRONTEND=noninteractive $STD apt-get install -y clickhouse-server clickhouse-client + + $STD systemctl enable --now clickhouse-server + + msg_info "Waiting for ClickHouse to be ready" + for i in {1..10}; do + if clickhouse client --query "SELECT 1" &>/dev/null; then break; fi + sleep 1 + done + + # User anlegen + clickhouse client --query "CREATE DATABASE IF NOT EXISTS $CLICKHOUSE_DB" + clickhouse client --query "CREATE USER IF NOT EXISTS $CLICKHOUSE_USER IDENTIFIED WITH plaintext_password BY '$CLICKHOUSE_PASS'" + clickhouse client --query "GRANT ALL ON $CLICKHOUSE_DB.* TO $CLICKHOUSE_USER" + + # Default-User ggf. deaktivieren + cat </etc/clickhouse-server/users.d/disable-default.xml + + + + + +EOF + systemctl restart clickhouse-server + + msg_ok "Setup ClickHouse (DB: $CLICKHOUSE_DB, User: $CLICKHOUSE_USER)" + + { + echo "ClickHouse DB: $CLICKHOUSE_DB" + echo "ClickHouse User: $CLICKHOUSE_USER" + echo "ClickHouse Pass: $CLICKHOUSE_PASS" + } >>~/clickhouse.creds + else + msg_info "Updating ClickHouse packages" + env -u CLICKHOUSE_USER $STD apt-get update + env -u CLICKHOUSE_USER $STD apt-get install -y --only-upgrade clickhouse-server clickhouse-client + msg_ok "ClickHouse updated" + fi +} diff --git a/tools/addon/filebrowser.sh b/tools/addon/filebrowser.sh index 29015d31..b648c155 100644 --- a/tools/addon/filebrowser.sh +++ b/tools/addon/filebrowser.sh @@ -88,7 +88,7 @@ if [ -f "$INSTALL_PATH" ]; then read -r -p "Would you like to update ${APP}? (y/N): " update_prompt if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then msg_info "Updating ${APP}" - curl -fsSL https://github.com/filebrowser/filebrowser/releases/latest/download/linux-amd64-filebrowser.tar.gz | tar -xzv -C /usr/local/bin &>/dev/null + curl -fsSL "https://github.com/filebrowser/filebrowser/releases/latest/download/linux-amd64-filebrowser.tar.gz" | tar -xzv -C /usr/local/bin &>/dev/null chmod +x "$INSTALL_PATH" msg_ok "Updated ${APP}" exit 0 @@ -106,7 +106,7 @@ read -r -p "Would you like to install ${APP}? (y/n): " install_prompt if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then msg_info "Installing ${APP} on ${OS}" $PKG_MANAGER wget tar curl &>/dev/null - curl -fsSL https://github.com/filebrowser/filebrowser/releases/latest/download/linux-amd64-filebrowser.tar.gz | tar -xzv -C /usr/local/bin &>/dev/null + curl -fsSL "https://github.com/filebrowser/filebrowser/releases/latest/download/linux-amd64-filebrowser.tar.gz" | tar -xzv -C /usr/local/bin &>/dev/null chmod +x "$INSTALL_PATH" msg_ok "Installed ${APP}" @@ -119,22 +119,17 @@ if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then chmod 644 "$DB_PATH" msg_ok "Directory created successfully" + cd /usr/local/community-scripts + filebrowser config init &>/dev/null + filebrowser config set -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null + filebrowser users add admin helper-scripts.com --perm.admin --database "$DB_PATH" &>/dev/null + read -r -p "Would you like to use No Authentication? (y/N): " auth_prompt if [[ "${auth_prompt,,}" =~ ^(y|yes)$ ]]; then msg_info "Configuring No Authentication" - cd /usr/local/community-scripts - filebrowser config init -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null - filebrowser config set -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null - filebrowser config init --auth.method=noauth &>/dev/null filebrowser config set --auth.method=noauth &>/dev/null - filebrowser users add ID 1 --perm.admin &>/dev/null msg_ok "No Authentication configured" else - msg_info "Setting up default authentication" - cd /usr/local/community-scripts - filebrowser config init -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null - filebrowser config set -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null - filebrowser users add admin helper-scripts.com --perm.admin --database "$DB_PATH" &>/dev/null msg_ok "Default authentication configured (admin:helper-scripts.com)" fi @@ -149,8 +144,8 @@ After=network-online.target User=root WorkingDirectory=/usr/local/community-scripts ExecStartPre=/bin/touch /usr/local/community-scripts/filebrowser.db -ExecStartPre=/usr/local/bin/filebrowser config set -a "0.0.0.0" -p 9000 -d /usr/local/community-scripts/filebrowser.db -ExecStart=/usr/local/bin/filebrowser -r / -d /usr/local/community-scripts/filebrowser.db -p 9000 +ExecStartPre=/usr/local/bin/filebrowser config set -a "0.0.0.0" -p ${PORT} -d /usr/local/community-scripts/filebrowser.db +ExecStart=/usr/local/bin/filebrowser -r / -d /usr/local/community-scripts/filebrowser.db -p ${PORT} Restart=always [Install] diff --git a/tools/pve/add-iptag.sh b/tools/pve/add-iptag.sh new file mode 100644 index 00000000..742a07a0 --- /dev/null +++ b/tools/pve/add-iptag.sh @@ -0,0 +1,1408 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: MickLesk (Canbiz) && Desert_Gamer +# License: MIT + +function header_info { + clear + cat <<"EOF" + ___ ____ _____ +|_ _| _ \ _ |_ _|_ _ __ _ + | || |_) (_) | |/ _` |/ _` | + | || __/ _ | | (_| | (_| | +|___|_| (_) |_|\__,_|\__, | + |___/ +EOF +} + +clear +header_info +APP="IP-Tag" +hostname=$(hostname) + +# Color variables +YW=$(echo "\033[33m") +GN=$(echo "\033[1;92m") +RD=$(echo "\033[01;31m") +CL=$(echo "\033[m") +BFR="\\r\\033[K" +HOLD=" " +CM=" βœ”οΈ ${CL}" +CROSS=" βœ–οΈ ${CL}" + +# Error handler for displaying error messages +error_handler() { + if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then + kill $SPINNER_PID >/dev/null + fi + printf "\e[?25h" + local exit_code="$?" + local line_number="$1" + local command="$2" + local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" + echo -e "\n$error_message\n" +} + +# Spinner for progress indication +spinner() { + local frames=('β ‹' 'β ™' 'β Ή' 'β Έ' 'β Ό' 'β ΄' 'β ¦' 'β §' 'β ‡' '⠏') + local spin_i=0 + local interval=0.1 + printf "\e[?25l" + + local color="${YWB}" + + while true; do + printf "\r ${color}%s${CL}" "${frames[spin_i]}" + spin_i=$(((spin_i + 1) % ${#frames[@]})) + sleep "$interval" + done +} + +# Info message +msg_info() { + local msg="$1" + echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}" + spinner & + SPINNER_PID=$! +} + +# Success message +msg_ok() { + if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then + kill $SPINNER_PID >/dev/null + fi + printf "\e[?25h" + local msg="$1" + echo -e "${BFR}${CM}${GN}${msg}${CL}" +} + +# Error message +msg_error() { + if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then + kill $SPINNER_PID >/dev/null + fi + printf "\e[?25h" + local msg="$1" + echo -e "${BFR}${CROSS}${RD}${msg}${CL}" +} + +# Check if service exists +check_service_exists() { + if systemctl is-active --quiet iptag.service; then + return 0 + else + return 1 + fi +} + +# Migrate configuration from old path to new +migrate_config() { + local old_config="/opt/lxc-iptag" + local new_config="/opt/iptag/iptag.conf" + + if [[ -f "$old_config" ]]; then + msg_info "Migrating configuration from old path" + if cp "$old_config" "$new_config" &>/dev/null; then + rm -rf "$old_config" &>/dev/null + msg_ok "Configuration migrated and old config removed" + else + msg_error "Failed to migrate configuration" + fi + fi +} + +# Update existing installation +update_installation() { + msg_info "Updating IP-Tag Scripts" + systemctl stop iptag.service &>/dev/null + msg_ok "Stopped IP-Tag service" + + # Create directory if it doesn't exist + if [[ ! -d "/opt/iptag" ]]; then + mkdir -p /opt/iptag + fi + + # Create new config file (check if exists and ask user) + if [[ -f "/opt/iptag/iptag.conf" ]]; then + echo -e "\n${YW}Configuration file already exists.${CL}" + while true; do + read -p "Do you want to replace it with defaults? (y/n): " yn + case $yn in + [Yy]*) + msg_info "Replacing configuration file" + generate_config >/opt/iptag/iptag.conf + msg_ok "Configuration file replaced with defaults" + break + ;; + [Nn]*) + echo -e "${GN}βœ”οΈ Keeping existing configuration file${CL}" + break + ;; + *) + echo -e "${RD}Please answer yes or no.${CL}" + ;; + esac + done + else + msg_info "Creating new configuration file" + generate_config >/opt/iptag/iptag.conf + msg_ok "Created new configuration file at /opt/iptag/iptag.conf" + fi + + # Update main script + msg_info "Updating main script" + generate_main_script >/opt/iptag/iptag + chmod +x /opt/iptag/iptag + msg_ok "Updated main script" + + # Update service file + msg_info "Updating service file" + generate_service >/lib/systemd/system/iptag.service + msg_ok "Updated service file" + + msg_info "Creating manual run command" + cat <<'EOF' >/usr/local/bin/iptag-run +#!/usr/bin/env bash +CONFIG_FILE="/opt/iptag/iptag.conf" +SCRIPT_FILE="/opt/iptag/iptag" +if [[ ! -f "$SCRIPT_FILE" ]]; then + echo "❌ Main script not found: $SCRIPT_FILE" + exit 1 +fi +export FORCE_SINGLE_RUN=true +exec "$SCRIPT_FILE" +EOF + chmod +x /usr/local/bin/iptag-run + 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." + + msg_info "Restarting service" + systemctl daemon-reload &>/dev/null + systemctl enable -q --now iptag.service &>/dev/null + msg_ok "Updated IP-Tag Scripts" +} + +# Generate configuration file content +generate_config() { + cat <&2 + fi +} + +# Color constants +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[0;33m' +readonly BLUE='\033[0;34m' +readonly PURPLE='\033[0;35m' +readonly CYAN='\033[0;36m' +readonly WHITE='\033[1;37m' +readonly GRAY='\033[0;37m' +readonly NC='\033[0m' # No Color + +# Logging functions with colors +log_success() { + echo -e "${GREEN}βœ“${NC} $*" +} + +log_info() { + echo -e "${BLUE}β„Ή${NC} $*" +} + +log_warning() { + echo -e "${YELLOW}⚠${NC} $*" +} + +log_error() { + echo -e "${RED}βœ—${NC} $*" +} + +log_change() { + echo -e "${CYAN}~${NC} $*" +} + +log_unchanged() { + echo -e "${GRAY}=${NC} $*" +} + +# Check if IP is in CIDR +ip_in_cidr() { + local ip="$1" cidr="$2" + debug_log "ip_in_cidr: checking '$ip' against '$cidr'" + + # Manual CIDR check - Π±ΠΎΠ»Π΅Π΅ Π½Π°Π΄Ρ‘ΠΆΠ½Ρ‹ΠΉ ΠΌΠ΅Ρ‚ΠΎΠ΄ + debug_log "ip_in_cidr: using manual check (bypassing ipcalc)" + local network prefix + IFS='/' read -r network prefix <<< "$cidr" + + # Convert IP and network to integers for comparison + local ip_int net_int mask + IFS='.' read -r a b c d <<< "$ip" + ip_int=$(( (a << 24) + (b << 16) + (c << 8) + d )) + + IFS='.' read -r a b c d <<< "$network" + net_int=$(( (a << 24) + (b << 16) + (c << 8) + d )) + + # Create subnet mask + mask=$(( 0xFFFFFFFF << (32 - prefix) )) + + # Apply mask and compare + local ip_masked=$((ip_int & mask)) + local net_masked=$((net_int & mask)) + + debug_log "ip_in_cidr: IP=$ip ($ip_int), Network=$network ($net_int), Prefix=$prefix" + debug_log "ip_in_cidr: Mask=$mask (hex: $(printf '0x%08x' $mask))" + 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))))" + 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))))" + + if (( ip_masked == net_masked )); then + debug_log "ip_in_cidr: manual check PASSED - IP is in CIDR" + return 0 + else + debug_log "ip_in_cidr: manual check FAILED - IP is NOT in CIDR" + return 1 + fi +} + +# Format IP address according to the configuration +format_ip_tag() { + local ip="$1" + [[ -z "$ip" ]] && return + local format="${TAG_FORMAT:-$DEFAULT_TAG_FORMAT}" + case "$format" in + "last_octet") echo "${ip##*.}" ;; + "last_two_octets") echo "${ip#*.*.}" ;; + *) echo "$ip" ;; + esac +} + +format_ipv6_tag() { + local ip="$1" + [[ -z "$ip" ]] && return + local format="${IPV6_TAG_FORMAT:-short}" + + case "$format" in + "last_block") + # take last hex block + echo "${ip##*:}" + ;; + "full") + # return full as-is + echo "$ip" + ;; + "short"|"compressed") + # compress repeated zeros (::) automatically + # Linux ip command already returns compressed by default + echo "$ip" + ;; + *) + # fallback + echo "$ip" + ;; + esac +} + + +# Check if IP is in any CIDRs +ip_in_cidrs() { + local ip="$1" cidrs="$2" + [[ -z "$cidrs" ]] && return 1 + local IFS=' ' + debug_log "Checking IP '$ip' against CIDRs: '$cidrs'" + for cidr in $cidrs; do + debug_log "Testing IP '$ip' against CIDR '$cidr'" + if ip_in_cidr "$ip" "$cidr"; then + debug_log "IP '$ip' matches CIDR '$cidr' - PASSED" + return 0 + else + debug_log "IP '$ip' does not match CIDR '$cidr'" + fi + done + debug_log "IP '$ip' failed all CIDR checks" + return 1 +} + +# Check if IP is valid +is_valid_ipv4() { + local ip="$1" + [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1 + + local IFS='.' parts + read -ra parts <<< "$ip" + for part in "${parts[@]}"; do + (( part >= 0 && part <= 255 )) || return 1 + done + return 0 +} + +# Get VM IPs using multiple methods with performance optimizations +get_vm_ips() { + local vmid=$1 ips="" + local vm_config="/etc/pve/qemu-server/${vmid}.conf" + [[ ! -f "$vm_config" ]] && return + + debug_log "vm $vmid: starting optimized IP detection" + + # Check if VM is running first (avoid expensive operations for stopped VMs) + local vm_status="" + if command -v qm >/dev/null 2>&1; then + vm_status=$(qm status "$vmid" 2>/dev/null | awk '{print $2}') + fi + + if [[ "$vm_status" != "running" ]]; then + debug_log "vm $vmid: not running (status: $vm_status), skipping expensive detection" + return + fi + + # Cache for this execution + local cache_file="/tmp/iptag_vm_${vmid}_cache" + local cache_ttl=60 # 60 seconds cache + + # Check cache first + if [[ -f "$cache_file" ]] && [[ $(($(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0))) -lt $cache_ttl ]]; then + local cached_ips=$(cat "$cache_file" 2>/dev/null) + if [[ -n "$cached_ips" ]]; then + debug_log "vm $vmid: using cached IPs: $cached_ips" + echo "$cached_ips" + return + fi + fi + + # Method 1: Quick ARP table lookup (fastest) + 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) + debug_log "vm $vmid: found MACs: $mac_addresses" + + # Quick ARP check without forced refresh (most common case) + for mac in $mac_addresses; do + local mac_lower=$(echo "$mac" | tr '[:upper:]' '[:lower:]') + local ip=$(ip neighbor show | grep "$mac_lower" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) + if [[ -n "$ip" && "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "vm $vmid: found IP $ip via quick ARP for MAC $mac_lower" + ips+="$ip " + fi + done + + # Early exit if we found IPs via ARP + if [[ -n "$ips" ]]; then + local unique_ips=$(echo "$ips" | tr ' ' '\n' | sort -u | tr '\n' ' ') + unique_ips="${unique_ips% }" + debug_log "vm $vmid: early exit with IPs: '$unique_ips'" + echo "$unique_ips" > "$cache_file" + echo "$unique_ips" + return + fi + + # Method 2: QM guest agent (fast if available) + if command -v qm >/dev/null 2>&1; then + local qm_ips=$(timeout 3 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 -2) + for qm_ip in $qm_ips; do + if [[ "$qm_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "vm $vmid: found IP $qm_ip via qm guest cmd" + ips+="$qm_ip " + fi + done + fi + + # Early exit if we found IPs via QM + if [[ -n "$ips" ]]; then + local unique_ips=$(echo "$ips" | tr ' ' '\n' | sort -u | tr '\n' ' ') + unique_ips="${unique_ips% }" + debug_log "vm $vmid: early exit with QM IPs: '$unique_ips'" + echo "$unique_ips" > "$cache_file" + echo "$unique_ips" + return + fi + + # Method 3: DHCP leases check (medium cost) + for mac in $mac_addresses; do + local mac_lower=$(echo "$mac" | tr '[:upper:]' '[:lower:]') + + for dhcp_file in "/var/lib/dhcp/dhcpd.leases" "/var/lib/dhcpcd5/dhcpcd.leases" "/tmp/dhcp.leases"; do + if [[ -f "$dhcp_file" ]]; then + local dhcp_ip=$(timeout 2 grep -A 10 "ethernet $mac_lower" "$dhcp_file" 2>/dev/null | grep "binding state active" -A 5 | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" | head -1) + if [[ -n "$dhcp_ip" && "$dhcp_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "vm $vmid: found IP $dhcp_ip via DHCP leases for MAC $mac_lower" + ips+="$dhcp_ip " + break 2 + fi + fi + done + done + + # Early exit if we found IPs via DHCP + if [[ -n "$ips" ]]; then + local unique_ips=$(echo "$ips" | tr ' ' '\n' | sort -u | tr '\n' ' ') + unique_ips="${unique_ips% }" + debug_log "vm $vmid: early exit with DHCP IPs: '$unique_ips'" + echo "$unique_ips" > "$cache_file" + echo "$unique_ips" + return + fi + + # Method 4: Limited network discovery (expensive - only if really needed) + debug_log "vm $vmid: falling back to limited network discovery" + + for mac in $mac_addresses; do + local mac_lower=$(echo "$mac" | tr '[:upper:]' '[:lower:]') + + # Get bridge interfaces + local bridges=$(grep -E "^net[0-9]+:" "$vm_config" | grep -oE "bridge=\w+" | cut -d= -f2 | head -1) + for bridge in $bridges; do + if [[ -n "$bridge" && -d "/sys/class/net/$bridge" ]]; then + # Get bridge IP range + local bridge_ip=$(ip addr show "$bridge" 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]+' | head -1) + if [[ -n "$bridge_ip" ]]; then + local network=$(echo "$bridge_ip" | cut -d'/' -f1) + debug_log "vm $vmid: limited scan on bridge $bridge network $bridge_ip" + + # Force ARP refresh with broadcast ping (limited) + IFS='.' read -r a b c d <<< "$network" + local broadcast="$a.$b.$c.255" + timeout 1 ping -c 1 -b "$broadcast" >/dev/null 2>&1 || true + + # Check ARP again after refresh + sleep 0.5 + local ip=$(ip neighbor show | grep "$mac_lower" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) + if [[ -n "$ip" && "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "vm $vmid: found IP $ip via ARP after broadcast for MAC $mac_lower" + ips+="$ip " + break 2 + fi + + # Only do very limited ping scan (reduced range) + IFS='.' read -r a b c d <<< "$network" + local base_net="$a.$b.$c" + + # Try only most common ranges (much smaller than before) + for last_octet in {100..105} {200..205}; do + local test_ip="$base_net.$last_octet" + + # Very quick ping test (reduced timeout) + if timeout 0.2 ping -c 1 -W 1 "$test_ip" >/dev/null 2>&1; then + # Check if this IP corresponds to our MAC + sleep 0.1 + local found_mac=$(ip neighbor show "$test_ip" 2>/dev/null | grep -oE "([0-9a-f]{2}:){5}[0-9a-f]{2}") + if [[ "$found_mac" == "$mac_lower" ]]; then + debug_log "vm $vmid: found IP $test_ip via limited ping scan for MAC $mac_lower" + ips+="$test_ip " + break 2 + fi + fi + done + + # Skip extended scanning entirely (too expensive) + debug_log "vm $vmid: skipping extended scan to preserve CPU" + fi + fi + done + done + + # Method 5: Static configuration check (fast) + if [[ -z "$ips" ]]; then + debug_log "vm $vmid: checking for static IP configuration" + + # Check cloud-init configuration if exists + local cloudinit_file="/var/lib/vz/snippets/${vmid}-cloud-init.yml" + if [[ -f "$cloudinit_file" ]]; then + local static_ip=$(grep -E "addresses?:" "$cloudinit_file" 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) + if [[ -n "$static_ip" && "$static_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "vm $vmid: found static IP $static_ip in cloud-init config" + ips+="$static_ip " + fi + fi + + # Check VM config for any IP hints + local config_ip=$(grep -E "(ip=|gw=)" "$vm_config" 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) + if [[ -n "$config_ip" && "$config_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "vm $vmid: found IP hint $config_ip in VM config" + ips+="$config_ip " + fi + fi + + # Remove duplicates and cache result + local unique_ips=$(echo "$ips" | tr ' ' '\n' | sort -u | tr '\n' ' ') + unique_ips="${unique_ips% }" + + # Cache the result (even if empty) + echo "$unique_ips" > "$cache_file" + + debug_log "vm $vmid: final optimized IPs: '$unique_ips'" + echo "$unique_ips" +} + +# Update tags for container or VM +update_tags() { + local type="$1" vmid="$2" + local current_ips_full + + if [[ "$type" == "lxc" ]]; then + current_ips_full=$(get_lxc_ips "${vmid}") + while IFS= read -r line; do + [[ "$line" == tags:* ]] && current_tags_raw="${line#tags: }" && break + done < <(pct config "$vmid" 2>/dev/null) + else + current_ips_full=$(get_vm_ips "${vmid}") + local vm_config="/etc/pve/qemu-server/${vmid}.conf" + if [[ -f "$vm_config" ]]; then + local current_tags_raw=$(grep "^tags:" "$vm_config" 2>/dev/null | cut -d: -f2 | sed 's/^[[:space:]]*//') + fi + fi + + local current_tags=() next_tags=() current_ip_tags=() + if [[ -n "$current_tags_raw" ]]; then + mapfile -t current_tags < <(echo "$current_tags_raw" | sed 's/;/\n/g') + fi + + # Separate IP/numeric and user tags + for tag in "${current_tags[@]}"; do + if is_valid_ipv4 "${tag}" || [[ "$tag" =~ ^[0-9]+(\.[0-9]+)*$ ]]; then + current_ip_tags+=("${tag}") + else + next_tags+=("${tag}") + fi + done + + # Generate new IP tags from current IPs + local formatted_ips=() + debug_log "$type $vmid current_ips_full: '$current_ips_full'" + debug_log "$type $vmid CIDR_LIST: ${CIDR_LIST[*]}" + for ip in $current_ips_full; do + [[ -z "$ip" ]] && continue + debug_log "$type $vmid processing IP: '$ip'" + + if is_valid_ipv4 "$ip"; then + debug_log "$type $vmid IP '$ip' is valid IPv4" + # Only check IPv4 against CIDR list + if ip_in_cidrs "$ip" "${CIDR_LIST[*]}"; then + debug_log "$type $vmid IPv4 '$ip' passed CIDR check" + local formatted_ip4 + formatted_ip4=$(format_ip_tag "$ip") + debug_log "$type $vmid formatted IPv4 '$ip' -> '$formatted_ip4'" + [[ -n "$formatted_ip4" ]] && formatted_ips+=("$formatted_ip4") + else + debug_log "$type $vmid IPv4 '$ip' failed CIDR check, skipping" + fi + + elif [[ "${ENABLE_IPV6_TAGS,,}" == "true" ]]; then + # IPv6 handling only if enabled + debug_log "$type $vmid IP '$ip' not IPv4, treating as IPv6" + # basic IPv6 validation + if [[ "$ip" =~ ^[0-9a-fA-F:]+$ ]]; then + debug_log "$type $vmid IPv6 '$ip' accepted" + local formatted_ip6 + formatted_ip6=$(format_ipv6_tag "$ip") + debug_log "$type $vmid formatted IPv6 '$ip' -> '$formatted_ip6'" + [[ -n "$formatted_ip6" ]] && formatted_ips+=("$formatted_ip6") + else + debug_log "$type $vmid value '$ip' not recognized as valid IPv6, skipping" + fi + + else + debug_log "$type $vmid IP '$ip' is invalid or IPv6 not enabled" + fi + done + debug_log "$type $vmid final formatted_ips: ${formatted_ips[*]}" + + + # If LXC and no IPs detected, do not touch tags at all + if [[ "$type" == "lxc" && ${#formatted_ips[@]} -eq 0 ]]; then + log_unchanged "LXC ${GRAY}${vmid}${NC}: No IP detected, tags unchanged" + return + fi + + # Add new IP tags + for new_ip in "${formatted_ips[@]}"; do + next_tags+=("$new_ip") + done + + # Update tags if there are changes + local old_tags_str=$(IFS=';'; echo "${current_tags[*]}") + local new_tags_str=$(IFS=';'; echo "${next_tags[*]}") + + debug_log "$type $vmid old_tags: '$old_tags_str'" + debug_log "$type $vmid new_tags: '$new_tags_str'" + debug_log "$type $vmid tags_equal: $([[ "$old_tags_str" == "$new_tags_str" ]] && echo true || echo false)" + + if [[ "$old_tags_str" != "$new_tags_str" ]]; then + # Determine what changed + local old_ip_tags_count=${#current_ip_tags[@]} + local new_ip_tags_count=${#formatted_ips[@]} + + # Build detailed change message + local change_details="" + + if [[ $old_ip_tags_count -eq 0 ]]; then + change_details="added ${new_ip_tags_count} IP tag(s): [${GREEN}${formatted_ips[*]}${NC}]" + else + # Compare old and new IP tags + local added_tags=() removed_tags=() common_tags=() + + # Find removed tags + for old_tag in "${current_ip_tags[@]}"; do + local found=false + for new_tag in "${formatted_ips[@]}"; do + if [[ "$old_tag" == "$new_tag" ]]; then + found=true + break + fi + done + if [[ "$found" == false ]]; then + removed_tags+=("$old_tag") + else + common_tags+=("$old_tag") + fi + done + + # Find added tags + for new_tag in "${formatted_ips[@]}"; do + local found=false + for old_tag in "${current_ip_tags[@]}"; do + if [[ "$new_tag" == "$old_tag" ]]; then + found=true + break + fi + done + if [[ "$found" == false ]]; then + added_tags+=("$new_tag") + fi + done + + # Build change message + local change_parts=() + if [[ ${#added_tags[@]} -gt 0 ]]; then + change_parts+=("added [${GREEN}${added_tags[*]}${NC}]") + fi + if [[ ${#removed_tags[@]} -gt 0 ]]; then + change_parts+=("removed [${YELLOW}${removed_tags[*]}${NC}]") + fi + if [[ ${#common_tags[@]} -gt 0 ]]; then + change_parts+=("kept [${GRAY}${common_tags[*]}${NC}]") + fi + + change_details=$(IFS=', '; echo "${change_parts[*]}") + fi + + log_change "${type^^} ${CYAN}${vmid}${NC}: ${change_details}" + + if [[ "$type" == "lxc" ]]; then + pct set "${vmid}" -tags "$(IFS=';'; echo "${next_tags[*]}")" &>/dev/null + else + local vm_config="/etc/pve/qemu-server/${vmid}.conf" + if [[ -f "$vm_config" ]]; then + sed -i '/^tags:/d' "$vm_config" + if [[ ${#next_tags[@]} -gt 0 ]]; then + echo "tags: $(IFS=';'; echo "${next_tags[*]}")" >> "$vm_config" + fi + fi + fi + else + # Tags unchanged + local ip_count=${#formatted_ips[@]} + local status_msg="" + + if [[ $ip_count -eq 0 ]]; then + status_msg="No IPs detected" + elif [[ $ip_count -eq 1 ]]; then + status_msg="IP tag [${GRAY}${formatted_ips[0]}${NC}] unchanged" + else + status_msg="${ip_count} IP tags [${GRAY}${formatted_ips[*]}${NC}] unchanged" + fi + + log_unchanged "${type^^} ${GRAY}${vmid}${NC}: ${status_msg}" + fi +} + +# Update all instances of specified type +update_all_tags() { + local type="$1" vmids count=0 + + if [[ "$type" == "lxc" ]]; then + vmids=($(pct list 2>/dev/null | grep -v VMID | awk '{print $1}')) + else + local all_vm_configs=($(ls /etc/pve/qemu-server/*.conf 2>/dev/null | sed 's/.*\/\([0-9]*\)\.conf/\1/' | sort -n)) + vmids=("${all_vm_configs[@]}") + fi + + count=${#vmids[@]} + [[ $count -eq 0 ]] && return + + # Display processing header with color + if [[ "$type" == "lxc" ]]; then + log_info "Processing ${WHITE}${count}${NC} LXC container(s) in parallel" + + # Clean up old cache files before processing LXC + cleanup_vm_cache + + # Process LXC containers in parallel for better performance + process_lxc_parallel "${vmids[@]}" + else + log_info "Processing ${WHITE}${count}${NC} virtual machine(s) in parallel" + + # Clean up old cache files before processing VMs + cleanup_vm_cache + + # Process VMs in parallel for better performance + process_vms_parallel "${vmids[@]}" + fi + + # Add completion message + if [[ "$type" == "lxc" ]]; then + log_success "Completed processing LXC containers" + else + log_success "Completed processing virtual machines" + fi +} + +# Check if status changed +check_status_changed() { + local type="$1" current + case "$type" in + "lxc") current=$(pct list 2>/dev/null | grep -v VMID) ;; + "vm") current=$(ls -la /etc/pve/qemu-server/*.conf 2>/dev/null) ;; + "fw") current=$(ip link show type bridge 2>/dev/null) ;; + esac + local last_var="last_${type}_status" + [[ "${!last_var}" == "$current" ]] && return 1 + eval "$last_var='$current'" + return 0 +} + +# Main check function +check() { + local current_time changes_detected=false + current_time=$(date +%s) + + local update_lxc=false + local update_vm=false + + # Periodic cache cleanup (every 10 minutes) + local time_since_last_cleanup=$((current_time - ${last_cleanup_time:-0})) + if [[ $time_since_last_cleanup -ge 600 ]]; then + cleanup_vm_cache + last_cleanup_time=$current_time + debug_log "Performed periodic cache cleanup" + fi + + # Check LXC status + local time_since_last_lxc_check=$((current_time - last_lxc_status_check_time)) + if [[ "${LXC_STATUS_CHECK_INTERVAL:-60}" -gt 0 ]] && \ + [[ "$time_since_last_lxc_check" -ge "${LXC_STATUS_CHECK_INTERVAL:-60}" ]]; then + last_lxc_status_check_time=$current_time + if check_status_changed "lxc"; then + update_lxc=true + log_warning "LXC status changes detected" + fi + fi + + # Check VM status + local time_since_last_vm_check=$((current_time - last_vm_status_check_time)) + if [[ "${VM_STATUS_CHECK_INTERVAL:-60}" -gt 0 ]] && \ + [[ "$time_since_last_vm_check" -ge "${VM_STATUS_CHECK_INTERVAL:-60}" ]]; then + last_vm_status_check_time=$current_time + if check_status_changed "vm"; then + update_vm=true + log_warning "VM status changes detected" + fi + fi + + # Check network interface changes + local time_since_last_fw_check=$((current_time - last_fw_net_interface_check_time)) + if [[ "${FW_NET_INTERFACE_CHECK_INTERVAL:-60}" -gt 0 ]] && \ + [[ "$time_since_last_fw_check" -ge "${FW_NET_INTERFACE_CHECK_INTERVAL:-60}" ]]; then + last_fw_net_interface_check_time=$current_time + if check_status_changed "fw"; then + update_lxc=true + update_vm=true + log_warning "Network interface changes detected" + fi + fi + + # Force update if interval exceeded + for type in "lxc" "vm"; do + local last_update_var="last_update_${type}_time" + local time_since_last_update=$((current_time - ${!last_update_var})) + if [[ $time_since_last_update -ge ${FORCE_UPDATE_INTERVAL:-1800} ]]; then + if [[ "$type" == "lxc" ]]; then + update_lxc=true + log_info "Scheduled LXC update (every $((FORCE_UPDATE_INTERVAL / 60)) minutes)" + else + update_vm=true + log_info "Scheduled VM update (every $((FORCE_UPDATE_INTERVAL / 60)) minutes)" + fi + eval "${last_update_var}=${current_time}" + fi + done + + # Final execution + $update_lxc && update_all_tags "lxc" + $update_vm && update_all_tags "vm" +} + +# Initialize time variables +declare -g last_lxc_status="" last_vm_status="" last_fw_status="" +declare -g last_lxc_status_check_time=0 last_vm_status_check_time=0 last_fw_net_interface_check_time=0 +declare -g last_update_lxc_time=0 last_update_vm_time=0 last_cleanup_time=0 + +# Main loop +main() { + # Display startup message + echo -e "\n${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + log_success "IP-Tag service started successfully" + echo -e "${BLUE}β„Ή${NC} Loop interval: ${WHITE}${LOOP_INTERVAL:-$DEFAULT_CHECK_INTERVAL}${NC} seconds" + echo -e "${BLUE}β„Ή${NC} Debug mode: ${WHITE}${DEBUG:-false}${NC}" + echo -e "${BLUE}β„Ή${NC} Tag format: ${WHITE}${TAG_FORMAT:-$DEFAULT_TAG_FORMAT}${NC}" + echo -e "${BLUE}β„Ή${NC} Allowed CIDRs: ${WHITE}${CIDR_LIST[*]}${NC}" + echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + + if [[ "$FORCE_SINGLE_RUN" == "true" ]]; then + check + exit 0 + fi + + while true; do + check + sleep "${LOOP_INTERVAL:-300}" + done +} + + +# Cache cleanup function +cleanup_vm_cache() { + local cache_dir="/tmp" + local vm_cache_ttl=${VM_IP_CACHE_TTL:-120} + local lxc_cache_ttl=${LXC_IP_CACHE_TTL:-120} + local status_cache_ttl=${LXC_STATUS_CACHE_TTL:-30} + local current_time=$(date +%s) + + debug_log "Starting extreme cache cleanup" + + # Clean VM cache files + for cache_file in "$cache_dir"/iptag_vm_*_cache; do + if [[ -f "$cache_file" ]]; then + local file_time=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0) + if [[ $((current_time - file_time)) -gt $vm_cache_ttl ]]; then + rm -f "$cache_file" 2>/dev/null + debug_log "Cleaned up expired VM cache file: $cache_file" + fi + fi + done + + # Clean LXC IP cache files + for cache_file in "$cache_dir"/iptag_lxc_*_cache; do + if [[ -f "$cache_file" ]]; then + local file_time=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0) + if [[ $((current_time - file_time)) -gt $lxc_cache_ttl ]]; then + rm -f "$cache_file" 2>/dev/null + # Also clean meta files + rm -f "${cache_file}.meta" 2>/dev/null + debug_log "Cleaned up expired LXC cache file: $cache_file" + fi + fi + done + + # Clean LXC status cache files (shorter TTL) + for cache_file in "$cache_dir"/iptag_lxc_status_*_cache; do + if [[ -f "$cache_file" ]]; then + local file_time=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0) + if [[ $((current_time - file_time)) -gt $status_cache_ttl ]]; then + rm -f "$cache_file" 2>/dev/null + debug_log "Cleaned up expired LXC status cache: $cache_file" + fi + fi + done + + # Clean LXC PID cache files (60 second TTL) + for cache_file in "$cache_dir"/iptag_lxc_pid_*_cache; do + if [[ -f "$cache_file" ]]; then + local file_time=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0) + if [[ $((current_time - file_time)) -gt 60 ]]; then + rm -f "$cache_file" 2>/dev/null + debug_log "Cleaned up expired LXC PID cache: $cache_file" + fi + fi + done + + # Clean any orphaned meta files + for meta_file in "$cache_dir"/iptag_*.meta; do + if [[ -f "$meta_file" ]]; then + local base_file="${meta_file%.meta}" + if [[ ! -f "$base_file" ]]; then + rm -f "$meta_file" 2>/dev/null + debug_log "Cleaned up orphaned meta file: $meta_file" + fi + fi + done + + debug_log "Completed extreme cache cleanup" +} + +# Parallel VM processing function +process_vms_parallel() { + local vm_list=("$@") + local max_parallel=${MAX_PARALLEL_VM_CHECKS:-5} + local job_count=0 + local pids=() + local pid_start_times=() + + for vmid in "${vm_list[@]}"; do + if [[ $job_count -ge $max_parallel ]]; then + local pid_to_wait="${pids[0]}" + local start_time="${pid_start_times[0]}" + local waited=0 + while kill -0 "$pid_to_wait" 2>/dev/null && [[ $waited -lt 10 ]]; do + sleep 1 + ((waited++)) + done + if kill -0 "$pid_to_wait" 2>/dev/null; then + kill -9 "$pid_to_wait" 2>/dev/null + log_warning "VM parallel: killed stuck process $pid_to_wait after 10s timeout" + else + wait "$pid_to_wait" + fi + pids=("${pids[@]:1}") + pid_start_times=("${pid_start_times[@]:1}") + ((job_count--)) + fi + # Start background job + (update_tags "vm" "$vmid") & + pids+=($!) + pid_start_times+=("$(date +%s)") + ((job_count++)) + done + for i in "${!pids[@]}"; do + local pid="${pids[$i]}" + local waited=0 + while kill -0 "$pid" 2>/dev/null && [[ $waited -lt 10 ]]; do + sleep 1 + ((waited++)) + done + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null + log_warning "VM parallel: killed stuck process $pid after 10s timeout" + else + wait "$pid" + fi + done +} + +# Parallel LXC processing function +process_lxc_parallel() { + local lxc_list=("$@") + local max_parallel=${MAX_PARALLEL_LXC_CHECKS:-2} + local batch_size=${LXC_BATCH_SIZE:-20} + local job_count=0 + local pids=() + local pid_start_times=() + + debug_log "Starting parallel LXC processing: ${#lxc_list[@]} containers, max_parallel=$max_parallel" + + if [[ ${#lxc_list[@]} -gt 5 ]]; then + debug_log "Pre-loading LXC statuses for ${#lxc_list[@]} containers" + local all_statuses=$(pct list 2>/dev/null) + for vmid in "${lxc_list[@]}"; do + local status=$(echo "$all_statuses" | grep "^$vmid" | awk '{print $2}') + if [[ -n "$status" ]]; then + local status_cache_file="/tmp/iptag_lxc_status_${vmid}_cache" + echo "$status" > "$status_cache_file" 2>/dev/null & + fi + done + wait + debug_log "Completed batch status pre-loading" + fi + for vmid in "${lxc_list[@]}"; do + if [[ $job_count -ge $max_parallel ]]; then + local pid_to_wait="${pids[0]}" + local start_time="${pid_start_times[0]}" + local waited=0 + while kill -0 "$pid_to_wait" 2>/dev/null && [[ $waited -lt 10 ]]; do + sleep 1 + ((waited++)) + done + if kill -0 "$pid_to_wait" 2>/dev/null; then + kill -9 "$pid_to_wait" 2>/dev/null + log_warning "LXC parallel: killed stuck process $pid_to_wait after 10s timeout" + else + wait "$pid_to_wait" + fi + pids=("${pids[@]:1}") + pid_start_times=("${pid_start_times[@]:1}") + ((job_count--)) + fi + # Start background job with higher priority + (update_tags "lxc" "$vmid") & + pids+=($!) + pid_start_times+=("$(date +%s)") + ((job_count++)) + done + for i in "${!pids[@]}"; do + local pid="${pids[$i]}" + local waited=0 + while kill -0 "$pid" 2>/dev/null && [[ $waited -lt 10 ]]; do + sleep 1 + ((waited++)) + done + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null + log_warning "LXC parallel: killed stuck process $pid after 10s timeout" + else + wait "$pid" + fi + done + debug_log "Completed parallel LXC processing" +} + +# Optimized LXC IP detection with caching and alternative methods +# ------------------------------------------- +# Combined optimized LXC IP detection +# Keeps advanced debug logs & methods +# Adds IPv6 detection controlled by ENABLE_IPV6_TAGS +# ------------------------------------------- +get_lxc_ips() { + local vmid=$1 + local ips="" + local method_used="" + + # status cache for container state + local status_cache_file="/tmp/iptag_lxc_status_${vmid}_cache" + local status_cache_ttl=${LXC_STATUS_CACHE_TTL:-30} + + debug_log "lxc $vmid: starting combined IP detection" + + # ----- STATUS CHECK ----- + local lxc_status="" + if [[ -f "$status_cache_file" ]] && [[ $(($(date +%s) - $(stat -c %Y "$status_cache_file" 2>/dev/null || echo 0))) -lt $status_cache_ttl ]]; then + lxc_status=$(cat "$status_cache_file" 2>/dev/null) + debug_log "lxc $vmid: using cached status: $lxc_status" + else + lxc_status=$(pct status "${vmid}" 2>/dev/null | awk '{print $2}') + echo "$lxc_status" > "$status_cache_file" 2>/dev/null + debug_log "lxc $vmid: fetched fresh status: $lxc_status" + fi + + if [[ "$lxc_status" != "running" ]]; then + debug_log "lxc $vmid: not running (status: $lxc_status)" + return + fi + + # ----- TRY CONFIG FOR STATIC IP ----- + local pve_lxc_config="/etc/pve/lxc/${vmid}.conf" + if [[ -f "$pve_lxc_config" ]]; then + local static_ip=$(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 | head -1) + debug_log "lxc $vmid: [CONFIG] static_ip='$static_ip'" + if [[ -n "$static_ip" && "$static_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + ips="$static_ip" + method_used="proxmox_config" + fi + fi + + # ----- NAMESPACE FAST PARSE ----- + if [[ -z "$ips" ]]; then + local ns_file="/var/lib/lxc/${vmid}/rootfs/proc/net/fib_trie" + debug_log "lxc $vmid: trying namespace fib_trie" + if [[ -f "$ns_file" ]]; then + local ns_ip=$(timeout 1 grep -m1 -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' "$ns_file" 2>/dev/null | grep -v '127.0.0.1' | head -1) + if [[ -n "$ns_ip" ]] && is_valid_ipv4 "$ns_ip"; then + ips="$ns_ip" + method_used="namespace_fib" + debug_log "lxc $vmid: found IP via namespace: $ips" + fi + fi + fi + + # ----- ARP TABLE ----- + if [[ -z "$ips" ]]; then + debug_log "lxc $vmid: trying ARP lookup" + local mac_addr=$(grep -Eo 'hwaddr=([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}' "$pve_lxc_config" | head -1 | cut -d'=' -f2 | tr 'A-F' 'a-f') + if [[ -n "$mac_addr" ]]; then + local bridge_ip=$(ip neighbor show | grep "$mac_addr" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) + if [[ -n "$bridge_ip" && "$bridge_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + ips="$bridge_ip" + method_used="arp_table" + debug_log "lxc $vmid: found IP via ARP: $ips" + fi + fi + fi + + # ----- PROCESS NAMESPACE (fast) ----- + if [[ -z "$ips" && "${LXC_SKIP_SLOW_METHODS:-true}" != "true" ]]; then + debug_log "lxc $vmid: trying process namespace" + local pid_cache_file="/tmp/iptag_lxc_pid_${vmid}_cache" + local container_pid="" + if [[ -f "$pid_cache_file" ]] && [[ $(($(date +%s) - $(stat -c %Y "$pid_cache_file" 2>/dev/null || echo 0))) -lt 60 ]]; then + container_pid=$(cat "$pid_cache_file" 2>/dev/null) + else + container_pid=$(pct list 2>/dev/null | grep "^$vmid" | awk '{print $3}') + [[ -n "$container_pid" && "$container_pid" != "-" ]] && echo "$container_pid" > "$pid_cache_file" + fi + if [[ -n "$container_pid" && "$container_pid" != "-" ]]; then + local ns_ip=$(timeout 1 nsenter -t "$container_pid" -n ip -4 addr show 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | grep -v '127.0.0.1' | head -1) + if [[ -n "$ns_ip" ]] && is_valid_ipv4 "$ns_ip"; then + ips="$ns_ip" + method_used="process_ns" + debug_log "lxc $vmid: found IP via process namespace: $ips" + fi + fi + fi + + # ----- FORCED METHODS (attach/exec) ----- + if [[ -z "$ips" && "${LXC_ALLOW_FORCED_COMMANDS:-false}" == "true" ]]; then + debug_log "lxc $vmid: trying forced pct exec" + local pct_ip=$(timeout 7s pct exec "$vmid" -- ip -4 addr show 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | grep -v '127.0.0.1' | head -1) + if [[ -n "$pct_ip" ]] && is_valid_ipv4 "$pct_ip"; then + ips="$pct_ip" + method_used="pct_exec_forced" + debug_log "lxc $vmid: found IP via pct exec: $ips" + fi + fi + + # ----- OPTIONAL IPv6 detection ----- + if [[ "${ENABLE_IPV6_TAGS,,}" == "true" ]]; then + debug_log "lxc $vmid: IPv6 detection enabled" + + # 1. Try to get IPv6 from inside the container + local pct_ipv6=$(timeout 3 pct exec "$vmid" -- ip -6 addr show scope global 2>/dev/null \ + | grep -oE '([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}' \ + | grep -v '^fe80' | head -1) + if [[ -n "$pct_ipv6" ]]; then + debug_log "lxc $vmid: found IPv6 via pct exec: $pct_ipv6" + [[ -n "$ips" ]] && ips="$ips $pct_ipv6" || ips="$pct_ipv6" + method_used="${method_used:+$method_used,}ipv6_exec" + else + debug_log "lxc $vmid: no IPv6 from pct exec, fallback to neighbor table" + + # 2. Fallback: neighbor table + local mac_addr=$(grep -Eo 'hwaddr=([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}' "$pve_lxc_config" \ + | head -1 | cut -d'=' -f2 | tr 'A-F' 'a-f') + if [[ -n "$mac_addr" ]]; then + local ipv6_nb=$(ip -6 neighbor show | grep -i "$mac_addr" \ + | grep -oE '([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}' \ + | grep -v '^fe80' | head -1) + if [[ -n "$ipv6_nb" ]]; then + debug_log "lxc $vmid: found IPv6 via neighbor: $ipv6_nb" + [[ -n "$ips" ]] && ips="$ips $ipv6_nb" || ips="$ipv6_nb" + method_used="${method_used:+$method_used,}ipv6_neighbor" + else + debug_log "lxc $vmid: no IPv6 found in neighbor table" + fi + fi + fi + fi + + # ----- FINAL RESULT ----- + debug_log "lxc $vmid: [RESULT] ips='$ips' method='$method_used'" + echo "$ips" +} + + +main +EOF +} + +# Main installation process +if check_service_exists; then + while true; do + read -p "IP-Tag service is already installed. Do you want to update it? (y/n): " yn + case $yn in + [Yy]*) + update_installation + exit 0 + ;; + [Nn]*) + msg_error "Installation cancelled." + exit 0 + ;; + *) + msg_error "Please answer yes or no." + ;; + esac + done +fi + +while true; do + read -p "This will install ${APP} on ${hostname}. Proceed? (y/n): " yn + case $yn in + [Yy]*) + break + ;; + [Nn]*) + msg_error "Installation cancelled." + exit + ;; + *) + msg_error "Please answer yes or no." + ;; + esac +done + +if ! pveversion | grep -Eq "pve-manager/8\.[0-4](\.[0-9]+)*"; then + msg_error "This version of Proxmox Virtual Environment is not supported" + msg_error "⚠️ Requires Proxmox Virtual Environment Version 8.0 or later." + msg_error "Exiting..." + sleep 2 + exit +fi + +FILE_PATH="/usr/local/bin/iptag" +if [[ -f "$FILE_PATH" ]]; then + msg_info "The file already exists: '$FILE_PATH'. Skipping installation." + exit 0 +fi + +msg_info "Installing Dependencies" +apt-get update &>/dev/null +apt-get install -y ipcalc net-tools &>/dev/null +msg_ok "Installed Dependencies" + +msg_info "Setting up IP-Tag Scripts" +mkdir -p /opt/iptag +msg_ok "Setup IP-Tag Scripts" + +# Migrate config if needed +migrate_config + +msg_info "Setup Default Config" +if [[ ! -f /opt/iptag/iptag.conf ]]; then + generate_config >/opt/iptag/iptag.conf + msg_ok "Setup default config" +else + msg_ok "Default config already exists" +fi + +msg_info "Setup Main Function" +if [[ ! -f /opt/iptag/iptag ]]; then + generate_main_script >/opt/iptag/iptag + chmod +x /opt/iptag/iptag + msg_ok "Setup Main Function" +else + msg_ok "Main Function already exists" +fi + +msg_info "Creating Service" +if [[ ! -f /lib/systemd/system/iptag.service ]]; then + generate_service >/lib/systemd/system/iptag.service + msg_ok "Created Service" +else + msg_ok "Service already exists." +fi + +msg_ok "Setup IP-Tag Scripts" + +msg_info "Starting Service" +systemctl daemon-reload &>/dev/null +systemctl enable -q --now iptag.service &>/dev/null +msg_ok "Started Service" + +msg_info "Restarting Service with optimizations" +systemctl restart iptag.service &>/dev/null +msg_ok "Service restarted with CPU optimizations" + +msg_info "Creating manual run command" +cat <<'EOF' >/usr/local/bin/iptag-run +#!/usr/bin/env bash +CONFIG_FILE="/opt/iptag/iptag.conf" +SCRIPT_FILE="/opt/iptag/iptag" +if [[ ! -f "$SCRIPT_FILE" ]]; then + echo "❌ Main script not found: $SCRIPT_FILE" + exit 1 +fi +export FORCE_SINGLE_RUN=true +exec "$SCRIPT_FILE" +EOF +chmod +x /usr/local/bin/iptag-run +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." + +SPINNER_PID="" +echo -e "\n${APP} installation completed successfully! ${CL}\n" + +# Proper script termination +exit 0 diff --git a/tools/pve/lxc-delete.sh b/tools/pve/lxc-delete.sh index 8cadfda0..85487cd2 100644 --- a/tools/pve/lxc-delete.sh +++ b/tools/pve/lxc-delete.sh @@ -40,53 +40,72 @@ CM="${TAB}βœ”οΈ${TAB}${CL}" header_info echo "Loading..." -whiptail --backtitle "Proxmox VE Helper Scripts" --title "Proxmox VE LXC Deletion" --yesno "This will delete LXC containers. Proceed?" 10 58 +if ! whiptail --backtitle "Proxmox VE Helper Scripts" --title "Proxmox VE LXC Deletion" --yesno "This will delete LXC containers. Proceed?" 10 58; then + echo -e "${RD}Aborted by user.${CL}" + exit 0 +fi -NODE=$(hostname) -containers=$(pct list | tail -n +2 | awk '{print $0 " " $4}') +mapfile -t containers < <(pct list | tail -n +2) -if [ -z "$containers" ]; then +if [ ${#containers[@]} -eq 0 ]; then whiptail --title "LXC Container Delete" --msgbox "No LXC containers available!" 10 60 exit 1 fi -menu_items=("ALL" "Delete ALL containers" "OFF") # Add as first option -FORMAT="%-10s %-15s %-10s" +menu_items=("ALL" "Delete ALL containers" "OFF") +FORMAT="%-10s %-10s %-10s %-10s" -while read -r container; do - container_id=$(echo $container | awk '{print $1}') - container_name=$(echo $container | awk '{print $2}') - container_status=$(echo $container | awk '{print $3}') - formatted_line=$(printf "$FORMAT" "$container_name" "$container_status") +for line in "${containers[@]}"; do + container_id=$(echo "$line" | awk '{print $1}') + container_name=$(echo "$line" | awk '{print $2}') + container_status=$(echo "$line" | awk '{print $3}') + container_os=$(echo "$line" | awk '{print $4}') + protected=$(pct config "$container_id" | awk '/^protection:/ {print $2}') + is_protected="No" + [[ "$protected" == "1" ]] && is_protected="Yes" + formatted_line=$(printf "$FORMAT" "$container_name" "$container_status" "$container_os" "$is_protected") menu_items+=("$container_id" "$formatted_line" "OFF") -done <<<"$containers" +done CHOICES=$(whiptail --title "LXC Container Delete" \ - --checklist "Select LXC containers to delete:" 25 60 13 \ + --checklist "Select LXC containers to delete:\n\nNAME STATUS OS PROTECTED" 25 70 15 \ "${menu_items[@]}" 3>&2 2>&1 1>&3) if [ -z "$CHOICES" ]; then - whiptail --title "LXC Container Delete" \ - --msgbox "No containers selected!" 10 60 + whiptail --title "LXC Container Delete" --msgbox "No containers selected!" 10 60 exit 1 fi read -p "Delete containers manually or automatically? (Default: manual) m/a: " DELETE_MODE DELETE_MODE=${DELETE_MODE:-m} - selected_ids=$(echo "$CHOICES" | tr -d '"' | tr -s ' ' '\n') -# If "ALL" is selected, override with all container IDs +# ALL ausgewΓ€hlt if echo "$selected_ids" | grep -q "^ALL$"; then - selected_ids=$(echo "$containers" | awk '{print $1}') + selected_ids=$(printf '%s\n' "${containers[@]}" | awk '{print $1}') fi for container_id in $selected_ids; do - status=$(pct status $container_id) + status=$(pct status "$container_id") + protected=$(pct config "$container_id" | awk '/^protection:/ {print $2}') + is_protected="No" + [[ "$protected" == "1" ]] && is_protected="Yes" - if [ "$status" == "status: running" ]; then + if [[ "$is_protected" == "Yes" && "$DELETE_MODE" == "a" ]]; then + echo -e "${BL}[Info]${RD} Skipping protected container $container_id (auto mode).${CL}" + continue + fi + + if [[ "$status" == "status: running" ]]; then + if [[ "$is_protected" == "Yes" && "$DELETE_MODE" == "m" ]]; then + read -p "⚠️ Container $container_id is PROTECTED. Delete anyway? (y/N): " CONFIRM_PROTECTED + [[ ! "$CONFIRM_PROTECTED" =~ ^[Yy]$ ]] && { + echo -e "${BL}[Info]${RD} Skipping protected container $container_id...${CL}" + continue + } + fi echo -e "${BL}[Info]${GN} Stopping container $container_id...${CL}" - pct stop $container_id & + pct stop "$container_id" & sleep 5 echo -e "${BL}[Info]${GN} Container $container_id stopped.${CL}" fi @@ -96,15 +115,30 @@ for container_id in $selected_ids; do pct destroy "$container_id" -f & pid=$! spinner $pid - [ $? -eq 0 ] && echo "Container $container_id deleted." || whiptail --title "Error" --msgbox "Failed to delete container $container_id." 10 60 + if [ $? -eq 0 ]; then + echo "Container $container_id deleted." + else + whiptail --title "Error" --msgbox "Failed to delete container $container_id." 10 60 + fi else read -p "Delete container $container_id? (y/N): " CONFIRM if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then + if [[ "$is_protected" == "Yes" ]]; then + read -p "⚠️ Container $container_id is PROTECTED. Delete anyway? (y/N): " CONFIRM_PROTECTED + [[ ! "$CONFIRM_PROTECTED" =~ ^[Yy]$ ]] && { + echo -e "${BL}[Info]${RD} Skipping protected container $container_id...${CL}" + continue + } + fi echo -e "${BL}[Info]${GN} Deleting container $container_id...${CL}" pct destroy "$container_id" -f & pid=$! spinner $pid - [ $? -eq 0 ] && echo "Container $container_id deleted." || whiptail --title "Error" --msgbox "Failed to delete container $container_id." 10 60 + if [ $? -eq 0 ]; then + echo "Container $container_id deleted." + else + whiptail --title "Error" --msgbox "Failed to delete container $container_id." 10 60 + fi else echo -e "${BL}[Info]${RD} Skipping container $container_id...${CL}" fi diff --git a/tools/pve/post-pve-install.sh b/tools/pve/post-pve-install.sh index 5afc3bd0..b6c704f0 100644 --- a/tools/pve/post-pve-install.sh +++ b/tools/pve/post-pve-install.sh @@ -44,6 +44,12 @@ msg_error() { echo -e "${BFR} ${CROSS} ${RD}${msg}${CL}" } +msg_custom() { + local msg="$1" + echo -e "${BFR} ${YW}⚠ ${msg}${CL}" +} + + start_routines() { header_info @@ -141,7 +147,7 @@ EOF yes) 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 msg_info "Disabling subscription nag" - echo "DPkg::Post-Invoke { \"dpkg -V proxmox-widget-toolkit | grep -q '/proxmoxlib\.js$'; if [ \$? -eq 1 ]; then { echo 'Removing subscription nag from UI...'; 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 + echo "DPkg::Post-Invoke { \"dpkg -V proxmox-widget-toolkit 2>/dev/null && [ -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js ] && echo 'Removing subscription nag from UI...' && sed -i '/data\.status/{s/\!/=/;s/active/NoMoreNagging/}' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js\"; };" >/etc/apt/apt.conf.d/no-nag-script apt --reinstall install proxmox-widget-toolkit &>/dev/null msg_ok "Disabled subscription nag (Delete browser cache)" ;; @@ -246,12 +252,38 @@ while true; do esac done -if ! pveversion | grep -Eq "pve-manager/8\.[0-4](\.[0-9]+)*"; then +# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported. +pve_check() { + local PVE_VER + PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" + + # 8 Version Check + if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if (( MINOR < 1 || MINOR > 4 )); then + msg_error "This version of Proxmox Virtual Environment is not supported" + echo -e "Requires Proxmox Virtual Environment Version 8.1 – 8.4" + echo -e "Exiting..." + sleep 2 + exit 1 + fi + return 0 + fi + + # 9 Beta Version Check + if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then + msg_custom "Detected Proxmox Virtual Environment $PVE_VER – Beta state, use with caution!" + return 0 + fi + + # All others (unsupported versions) msg_error "This version of Proxmox Virtual Environment is not supported" - echo -e "Requires Proxmox Virtual Environment Version 8.0 or later." + echo -e "Requires Proxmox Virtual Environment Version 8.1 – 8.4 or 9.x (Beta)" echo -e "Exiting..." sleep 2 - exit -fi + exit 1 +} + +pve_check start_routines diff --git a/tools/pve/update-apps.sh b/tools/pve/update-apps.sh index e7712e4e..8461196c 100644 --- a/tools/pve/update-apps.sh +++ b/tools/pve/update-apps.sh @@ -1,9 +1,11 @@ #!/usr/bin/env bash # Copyright (c) 2021-2025 community-scripts ORG -# Author: BvdBerg01 +# Author: BvdBerg01 | Co-Author: remz1337 # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/refs/heads/main/misc/core.func) + function header_info { clear cat <<"EOF" @@ -16,8 +18,6 @@ function header_info { EOF } -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) - header_info echo "Loading..." whiptail --backtitle "Proxmox VE Helper Scripts" --title "LXC Container Update" --yesno "This will update LXC container. Proceed?" 10 58 || exit @@ -45,8 +45,8 @@ while read -r container; do done <<< "$containers" CHOICE=$(whiptail --title "LXC Container Update" \ - --radiolist "Select LXC container to update:" 25 60 13 \ - "${menu_items[@]}" 3>&2 2>&1 1>&3) + --checklist "Select LXC containers to update:" 25 60 13 \ + "${menu_items[@]}" 3>&2 2>&1 1>&3 | tr -d '"') if [ -z "$CHOICE" ]; then whiptail --title "LXC Container Update" \ @@ -55,12 +55,21 @@ if [ -z "$CHOICE" ]; then fi header_info -if(whiptail --backtitle "Proxmox VE Helper Scripts" --title "LXC Container Update" --yesno "Do you want to create a backup from your container?" 10 58); then +BACKUP_CHOICE="no" +if(whiptail --backtitle "Proxmox VE Helper Scripts" --title "LXC Container Update" --yesno "Do you want to backup your containers before update?" 10 58); then + BACKUP_CHOICE="yes" +fi +UNATTENDED_UPDATE="no" +if(whiptail --backtitle "Proxmox VE Helper Scripts" --title "LXC Container Update" --yesno "Run updates unattended?" 10 58); then + UNATTENDED_UPDATE="yes" +fi + +if [ "$BACKUP_CHOICE" == "yes" ]; then STORAGES=$(awk '/^(\S+):/ {storage=$2} /content.*backup/ {print storage}' /etc/pve/storage.cfg) if [ -z "$STORAGES" ]; then - whiptail --msgbox "Geen opslag met 'backup' gevonden!" 8 40 + whiptail --msgbox "No storage with 'backup' found!" 8 40 exit 1 fi @@ -75,37 +84,148 @@ if(whiptail --backtitle "Proxmox VE Helper Scripts" --title "LXC Container Updat msg_error "No storage selected!" exit 1 fi +fi - msg_info "Creating backup" - vzdump $CHOICE --compress zstd --storage $STORAGE_CHOICE -notes-template "community-scripts backup updater" > /dev/null 2>&1 +function backup_container(){ + msg_info "Creating backup for container $1" + vzdump $1 --compress zstd --storage $STORAGE_CHOICE -notes-template "community-scripts backup updater" > /dev/null 2>&1 status=$? if [ $status -eq 0 ]; then - msg_ok "Backup created" - pct exec $CHOICE -- update --from-pve - exit_code=$? + msg_ok "Backup created" else - msg_error "Backup failed" + msg_error "Backup failed for container $1" + exit 1 fi +} -else - pct exec $CHOICE -- update --from-pve - exit_code=$? +UPDATE_CMD="update;" +if [ "$UNATTENDED_UPDATE" == "yes" ];then + UPDATE_CMD="export PHS_SILENT=1;update;" fi -if [ $exit_code -eq 0 ]; then - msg_ok "Update completed" -else - msg_info "Restoring LXC from backup" - pct stop $CHOICE - LXC_STORAGE=$(pct config $CHOICE | awk -F '[:,]' '/rootfs/ {print $2}') - pct restore $CHOICE /var/lib/vz/dump/vzdump-lxc-$CHOICE-*.tar.zst --storage $LXC_STORAGE --force > /dev/null 2>&1 - pct start $CHOICE - restorestatus=$? - if [ $restorestatus -eq 0 ]; then - msg_ok "Restored LXC from backup" - else - msg_error "Restored LXC from backup failed" +containers_needing_reboot=() +for container in $CHOICE; do + msg_info "Updating container $container" + + if [ "BACKUP_CHOICE" == "yes" ];then + backup_container $container fi + #1) Detect service using the service name in the update command + pushd $(mktemp -d) >/dev/null + pct pull "$container" /usr/bin/update update 2>/dev/null + service=$(cat update | sed 's|.*/ct/||g' | sed 's|\.sh).*||g') + popd >/dev/null + + #1.1) If update script not detected, return + if [ -z "${service}" ]; then + echo -e "${YW}[WARN]${CL} Update script not found. Skipping to next container" + continue + else + echo -e "${BL}[INFO]${CL} Detected service: ${GN}${service}${CL}" + fi + + #2) Extract service build/update resource requirements from config/installation file + script=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/ct/${service}.sh) + config=$(pct config "$container") + 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') + 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') + run_cpu=$(echo "$script" | { grep -m 1 "pct set \$CTID -cores" || test $? = 1; } | sed 's|.*cores ||g') + run_ram=$(echo "$script" | { grep -m 1 "pct set \$CTID -memory" || test $? = 1; } | sed 's|.*memory ||g') + current_cpu=$(echo "$config" | grep -m 1 "cores:" | sed 's|cores: ||g') + current_ram=$(echo "$config" | grep -m 1 "memory:" | sed 's|memory: ||g') + + #Test if all values are valid (>0) + if [ -z "${run_cpu}" ] || [ "$run_cpu" -le 0 ]; then + #echo "No valid value found for run_cpu. Assuming same as current configuration." + run_cpu=$current_cpu + fi + + if [ -z "${run_ram}" ] || [ "$run_ram" -le 0 ]; then + #echo "No valid value found for run_ram. Assuming same as current configuration." + run_ram=$current_ram + fi + + if [ -z "${build_cpu}" ] || [ "$build_cpu" -le 0 ]; then + #echo "No valid value found for build_cpu. Assuming same as current configuration." + build_cpu=$current_cpu + fi + + if [ -z "${build_ram}" ] || [ "$build_ram" -le 0 ]; then + #echo "No valid value found for build_ram. Assuming same as current configuration." + build_ram=$current_ram + fi + + UPDATE_BUILD_RESOURCES=0 + if [ "$build_cpu" -gt "$run_cpu" ] || [ "$build_ram" -gt "$run_ram" ]; then + UPDATE_BUILD_RESOURCES=1 + fi + + #3) if build resources are different than run resources, then: + if [ "$UPDATE_BUILD_RESOURCES" -eq "1" ]; then + pct set "$container" --cores "$build_cpu" --memory "$build_ram" + fi + + os=$(pct config "$container" | awk '/^ostype/ {print $2}') + + #4) Update service, using the update command + case "$os" in + alpine) pct exec "$container" -- ash -c "$UPDATE_CMD" ;; + archlinux) pct exec "$container" -- bash -c "$UPDATE_CMD" ;; + fedora | rocky | centos | alma) pct exec "$container" -- bash -c "$UPDATE_CMD" ;; + ubuntu | debian | devuan) pct exec "$container" -- bash -c "$UPDATE_CMD" ;; + opensuse) pct exec "$container" -- bash -c "$UPDATE_CMD" ;; + esac + exit_code=$? + + #5) if build resources are different than run resources, then: + if [ "$UPDATE_BUILD_RESOURCES" -eq "1" ]; then + pct set "$container" --cores "$run_cpu" --memory "$run_ram" + fi + + if pct exec "$container" -- [ -e "/var/run/reboot-required" ]; then + # Get the container's hostname and add it to the list + container_hostname=$(pct exec "$container" hostname) + containers_needing_reboot+=("$container ($container_hostname)") + fi + + if [ $exit_code -eq 0 ]; then + msg_ok "Updated container $container" + elif [ "BACKUP_CHOICE" == "yes" ];then + msg_info "Restoring LXC from backup" + pct stop $container + LXC_STORAGE=$(pct config $container | awk -F '[:,]' '/rootfs/ {print $2}') + pct restore $container /var/lib/vz/dump/vzdump-lxc-${container}-*.tar.zst --storage $LXC_STORAGE --force > /dev/null 2>&1 + pct start $container + restorestatus=$? + if [ $restorestatus -eq 0 ]; then + msg_ok "Restored LXC from backup" + else + msg_error "Restored LXC from backup failed" + exit 1 + fi + else + msg_error "Update failed for container $container. Exiting" + exit 1 + fi +done + +wait +header_info +echo -e "${GN}The process is complete, and the containers have been successfully updated.${CL}\n" +if [ "${#containers_needing_reboot[@]}" -gt 0 ]; then + echo -e "${RD}The following containers require a reboot:${CL}" + for container_name in "${containers_needing_reboot[@]}"; do + echo "$container_name" + done + echo -ne "${INFO} Do you wish to reboot these containers? " + read -r prompt + if [[ ${prompt,,} =~ ^(yes)$ ]]; then + echo -e "${CROSS}${HOLD} ${YWB}Rebooting containers.${CL}" + for container_name in "${containers_needing_reboot[@]}"; do + container=$(echo $container_name | cut -d " " -f 1) + pct reboot ${container} + done + fi fi