1 Commits

Author SHA1 Message Date
Michel Roegl-Brunner
530c6a463b Change all links to gite 2025-05-14 10:17:14 +02:00
716 changed files with 36722 additions and 79724 deletions

View File

@@ -10,7 +10,7 @@ indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 2
trim_trailing_whitespace = true
; trim_trailing_whitespace = true ; disabled until files are cleaned up
[*.md]
trim_trailing_whitespace = false

33
.gitattributes vendored
View File

@@ -1,33 +0,0 @@
# ---------------------------------------
# Treat Shell files as first-class code
# ---------------------------------------
*.sh linguist-detectable=true
*.bash linguist-language=Shell
*.func linguist-language=Shell
*.install linguist-language=Shell
# ---------------------------------------
# Treat Golang files as Go (for /api/)
api/**/*.go linguist-language=Go
# ---------------------------------------
# Exclude documentation from stats
*.md linguist-documentation
docs/** linguist-documentation
README.md linguist-documentation
CONTRIBUTING.md linguist-documentation
SECURITY.md linguist-documentation
# ---------------------------------------
# Exclude generated/config files
*.json linguist-generated
json/*.json linguist-generated=false
*.lock linguist-generated
*.yml linguist-generated
*.yaml linguist-generated
.github/** linguist-generated
.vscode/** linguist-generated
# ---------------------------------------
# Standard text handling
* text=auto eol=lf

View File

@@ -5,10 +5,10 @@
1) [adguard.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/ct/adguard.sh): This script collects system parameters. (Also holds the function to update the application.)
2) [build.func](https://github.com/community-scripts/ProxmoxVE/blob/main/misc/build.func): Adds user settings and integrates collected information.
3) [create_lxc.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/misc/create_lxc.sh): Constructs the LXC container.
3) [create_lxc.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/ct/create_lxc.sh): Constructs the LXC container.
4) [adguard-install.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/install/adguard-install.sh): Executes functions from [install.func](https://github.com/community-scripts/ProxmoxVE/blob/main/misc/install.func), and installs the application.
5) [adguard.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/ct/adguard.sh) (again): To display the completion message.
The installation process uses reusable scripts: [build.func](https://github.com/community-scripts/ProxmoxVE/blob/main/misc/build.func), [create_lxc.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/misc/create_lxc.sh), and [install.func](https://github.com/community-scripts/ProxmoxVE/blob/main/misc/install.func), which are not specific to any particular application.
The installation process uses reusable scripts: [build.func](https://github.com/community-scripts/ProxmoxVE/blob/main/misc/build.func), [create_lxc.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/ct/create_lxc.sh), and [install.func](https://github.com/community-scripts/ProxmoxVE/blob/main/misc/install.func), which are not specific to any particular application.
To gain a better understanding, focus on reviewing [adguard-install.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/install/adguard-install.sh). This script contains the commands and configurations for installing and configuring AdGuard Home within the LXC container.

View File

@@ -65,7 +65,7 @@ source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVED/
Example:
```bash
# Copyright (c) 2021-2026 community-scripts ORG
# Copyright (c) 2021-2025 community-scripts ORG
# Author: [YourUserName]
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: [SOURCE_URL]
@@ -266,7 +266,7 @@ start
build_container
description
msg_ok "Completed successfully!\n"
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}"

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Copyright (c) 2021-2025 community-scripts ORG
# Author: [YourUserName]
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: [SOURCE_URL]
@@ -9,7 +9,7 @@ source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVED/
APP="[APP_NAME]"
# Name of the app (e.g. Google, Adventurelog, Apache-Guacamole"
var_tags="[TAGS]"
# Tags for Proxmox VE, maximum 2 pcs., no spaces allowed, separated by a semicolon ; (e.g. database | adblock;dhcp)
# Tags for Proxmox VE, maximum 2 pcs., no spaces allowed, separated by a semicolon ; (e.g. database | adblock;dhcp)
var_cpu="[CPU]"
# Number of cores (1-X) (e.g. 4) - default are 2
var_ram="[RAM]"
@@ -80,7 +80,7 @@ start
build_container
description
msg_ok "Completed successfully!\n"
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}:[PORT]${CL}"

View File

@@ -57,7 +57,7 @@
Example:
```bash
# Copyright (c) 2021-2026 community-scripts ORG
# Copyright (c) 2021-2025 community-scripts ORG
# Author: [YourUserName]
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: [SOURCE_URL]

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Copyright (c) 2021-2025 community-scripts ORG
# Author: [YourUserName]
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: [SOURCE_URL]

View File

@@ -1,36 +1,25 @@
---
name: 🛠️ New Script
description: New Script proposal.
labels: [task]
name: "🛠️ New Script"
description: "New Script proposal."
labels: ["task"]
body:
- type: markdown
attributes:
value: |
# 🛠️ **New Script**
Create an Issue when you want to merge a new Script. The name of the Issue must be the same as your APP.sh file. (Example: SnipeIT, snipeit.sh; Alpine-Docker, alpine-docker.sh)
Create a Issue when you want to merge a new Script. The name of the Issue must be the same as your APP.sh file. (Example: SnipeIT, snipeit.sh; Alpine-Docker, alpine-docker.sh)
- type: input
id: task_summary
attributes:
label: Name of the Script
placeholder: e.g., SnipeIT
validations:
required: true
- type: dropdown
id: script_type
attributes:
label: Script Type
description: What type of script is this?
options:
- CT (LXC Container)
- VM (Virtual Machine)
- Addon (tools/addon)
- PVE Tool (tools/pve)
label: "Name of the Script"
placeholder: "e.g., SnipeIT"
validations:
required: true
- type: textarea
id: task_details
attributes:
label: 📋 Script Details
placeholder: Explain what is needed or special about this script
label: "📋 Scritpt Details"
placeholder: "Explain what is needed or special about this script"
validations:
required: true

View File

@@ -1,125 +1,90 @@
{
"new script": [
{
"fileStatus": "added",
"includeGlobs": [
"ct/**",
"install/**",
"misc/**",
"turnkey/**",
"vm/**"
],
"excludeGlobs": []
}
],
"update script": [
{
"fileStatus": "modified",
"includeGlobs": [
"ct/**",
"install/**",
"misc/**",
"turnkey/**",
"vm/**"
],
"excludeGlobs": [
"misc/build.func",
"misc/install.func",
"misc/api.func"
]
}
],
"delete script": [
{
"fileStatus": "removed",
"includeGlobs": [
"ct/**",
"install/**",
"misc/**",
"turnkey/**",
"vm/**"
],
"excludeGlobs": []
}
],
"maintenance": [
{
"fileStatus": null,
"includeGlobs": [
"*.md",
".github/**",
"misc/*.func",
"misc/create_lxc.sh",
"api/**"
],
"excludeGlobs": []
}
],
"core": [
{
"fileStatus": null,
"includeGlobs": [
"misc/*.func",
"misc/create_lxc.sh"
],
"excludeGlobs": []
}
],
"website": [
{
"fileStatus": null,
"includeGlobs": [
"frontend/**"
],
"excludeGlobs": []
}
],
"api": [
{
"fileStatus": null,
"includeGlobs": [
"api/**",
"misc/api.func"
],
"excludeGlobs": []
}
],
"github": [
{
"fileStatus": null,
"includeGlobs": [
".github/**"
],
"excludeGlobs": []
}
],
"json": [
{
"fileStatus": "modified",
"includeGlobs": [
"json/**"
],
"excludeGlobs": []
}
],
"high risk": [
{
"fileStatus": null,
"includeGlobs": [
"misc/build.func",
"misc/install.func",
"misc/create_lxc.sh"
],
"excludeGlobs": []
}
],
"documentation": [
{
"fileStatus": null,
"includeGlobs": [
"*.md"
],
"excludeGlobs": []
}
]
"new script": [
{
"fileStatus": "added",
"includeGlobs": ["ct/**", "install/**", "misc/**", "turnkey/**", "vm/**"],
"excludeGlobs": []
}
],
"update script": [
{
"fileStatus": "modified",
"includeGlobs": ["ct/**", "install/**", "misc/**", "turnkey/**", "vm/**"],
"excludeGlobs": ["misc/build.func", "misc/install.func", "misc/api.func"]
}
],
"delete script": [
{
"fileStatus": "removed",
"includeGlobs": ["ct/**", "install/**", "misc/**", "turnkey/**", "vm/**"],
"excludeGlobs": []
}
],
"maintenance": [
{
"fileStatus": null,
"includeGlobs": [
"*.md",
".github/**",
"misc/*.func",
"ct/create_lxc.sh",
"api/**"
],
"excludeGlobs": []
}
],
"core": [
{
"fileStatus": null,
"includeGlobs": ["misc/*.func", "ct/create_lxc.sh"],
"excludeGlobs": []
}
],
"website": [
{
"fileStatus": null,
"includeGlobs": ["frontend/**"],
"excludeGlobs": []
}
],
"api": [
{
"fileStatus": null,
"includeGlobs": ["api/**", "misc/api.func"],
"excludeGlobs": []
}
],
"github": [
{
"fileStatus": null,
"includeGlobs": [".github/**"],
"excludeGlobs": []
}
],
"json": [
{
"fileStatus": "modified",
"includeGlobs": ["frontend/publuc/json/**"],
"excludeGlobs": []
}
],
"high risk": [
{
"fileStatus": null,
"includeGlobs": [
"misc/build.func",
"misc/install.func",
"ct/create_lxc.sh"
],
"excludeGlobs": []
}
],
"documentation": [
{
"fileStatus": null,
"includeGlobs": ["*.md"],
"excludeGlobs": []
}
]
}

View File

@@ -1,4 +1,7 @@
## **Scripts which are clearly AI generated and not further revised by the Author of this PR (in terms of Coding Standards and Script Layout) may be closed without review.**
🛑 **New scripts must first be submitted to [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED) for testing.**
PRs for new scripts that skip this process will be closed.
---
## ✍️ Description
<!-- Briefly describe your changes. -->
@@ -37,18 +40,3 @@ Link: #
## 📋 Additional Information (optional)
<!-- Add any extra context, screenshots, or references. -->
---
## 📦 Application Requirements (for new scripts)
> Required for **🆕 New script** submissions.
> Pull requests that do not meet these requirements may be closed without review.
- [ ] The application is **at least 6 months old**
- [ ] The application is **actively maintained**
- [ ] The application has **600+ GitHub stars**
- [ ] Official **release tarballs** are published
- [ ] I understand that not all scripts will be accepted due to various reasons and criteria by the community-scripts ORG
## 🌐 Source
<!-- Add any sources and github links. -->

View File

@@ -20,22 +20,10 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v1
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@v2
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: community-scripts
repositories: ProxmoxVED
# Step 1: Checkout repository
- name: Checkout repository
@@ -103,17 +91,14 @@ jobs:
gh pr review $PR_NUMBER --approve
fi
- name: Approve pull request and merge
- name: Re-approve pull request after update
if: env.changed == 'true'
env:
GH_TOKEN: ${{ steps.generate-token-merge.outputs.token }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global user.name "github-actions-automege[bot]"
git config --global user.email "github-actions-automege[bot]@users.noreply.github.com"
PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[0].number')
PR_NUMBER=$(gh pr list --head "pr-update-app-files" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review "$PR_NUMBER" --approve
gh pr merge "$PR_NUMBER" --squash --admin
gh pr review $PR_NUMBER --approve
fi
# Step 8: Output success message when no changes

View File

@@ -0,0 +1,60 @@
name: Update date_created in JSON files
on:
# Dieser Trigger wird für das Öffnen von PRs sowie für das Aktualisieren von offenen PRs verwendet
pull_request:
types: [opened, synchronize]
schedule:
# Dieser Trigger wird 4x am Tag ausgelöst, um sicherzustellen, dass das Datum aktualisiert wird
- cron: "0 0,6,12,18 * * *" # Führt alle 6 Stunden aus
workflow_dispatch: # Manuelle Ausführung des Workflows möglich
jobs:
update-date:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install yq
run: |
sudo apt-get update
sudo apt-get install -y yq
- name: Set the current date
id: set_date
run: echo "TODAY=$(date -u +%Y-%m-%d)" >> $GITHUB_ENV
- name: Check for changes in PR
run: |
# Hole den PR-Branch
PR_BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge"
git fetch origin $PR_BRANCH
# Liste alle JSON-Dateien im PR auf, die geändert wurden
CHANGED_JSON_FILES=$(git diff --name-only origin/main...$PR_BRANCH | grep '.json')
if [ -z "$CHANGED_JSON_FILES" ]; then
echo "No JSON files changed in this PR."
exit 0
fi
# Gehe alle geänderten JSON-Dateien durch und aktualisiere das Datum
for file in $CHANGED_JSON_FILES; do
echo "Updating date_created in $file"
# Setze das aktuelle Datum
yq eval ".date_created = \"${{ env.TODAY }}\"" -i "$file"
git add "$file"
done
- name: Commit and push changes
run: |
# Prüfe, ob es Änderungen gibt und committe sie
git config user.name "json-updater-bot"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit -m "Update date_created to ${{ env.TODAY }}" || echo "No changes to commit"
# Push zurück in den PR-Branch
git push origin $PR_BRANCH

60
.github/workflows/backup/shellcheck.yml vendored Normal file
View File

@@ -0,0 +1,60 @@
name: Shellcheck
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
schedule:
- cron: "5 1 * * *"
jobs:
shellcheck:
name: Shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v45
with:
files: |
**.sh
- name: Download ShellCheck
shell: bash
env:
INPUT_VERSION: "v0.10.0"
run: |
set -euo pipefail
if [[ "${{ runner.os }}" == "macOS" ]]; then
osvariant="darwin"
else
osvariant="linux"
fi
baseurl="https://github.com/koalaman/shellcheck/releases/download"
curl -Lso "${{ github.workspace }}/sc.tar.xz" \
"${baseurl}/${INPUT_VERSION}/shellcheck-${INPUT_VERSION}.${osvariant}.x86_64.tar.xz"
tar -xf "${{ github.workspace }}/sc.tar.xz" -C "${{ github.workspace }}"
mv "${{ github.workspace }}/shellcheck-${INPUT_VERSION}/shellcheck" \
"${{ github.workspace }}/shellcheck"
- name: Verify ShellCheck binary
run: |
ls -l "${{ github.workspace }}/shellcheck"
- name: Display ShellCheck version
run: |
"${{ github.workspace }}/shellcheck" --version
- name: Run ShellCheck
if: steps.changed-files.outputs.any_changed == 'true'
env:
ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
echo "${ALL_CHANGED_FILES}" | xargs "${{ github.workspace }}/shellcheck"

View File

@@ -0,0 +1,88 @@
name: Auto Update JSON-Date
on:
push:
branches:
- main
workflow_dispatch:
jobs:
update-json-dates:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for proper detection
- name: Set up Git
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Find JSON files with incorrect date_created
id: find_wrong_json
run: |
TODAY=$(date -u +"%Y-%m-%d")
> incorrect_json_files.txt
for FILE in json/*.json; do
if [[ -f "$FILE" ]]; then
DATE_IN_JSON=$(jq -r '.date_created' "$FILE" 2>/dev/null || echo "")
if [[ "$DATE_IN_JSON" != "$TODAY" ]]; then
echo "$FILE" >> incorrect_json_files.txt
fi
fi
done
if [[ -s incorrect_json_files.txt ]]; then
echo "CHANGED=true" >> $GITHUB_ENV
else
echo "CHANGED=false" >> $GITHUB_ENV
fi
- name: Run update script
if: env.CHANGED == 'true'
run: |
chmod +x .github/workflows/scripts/update-json.sh
while read -r FILE; do
.github/workflows/scripts/update-json.sh "$FILE"
done < incorrect_json_files.txt
- name: Commit and create PR if changes exist
if: env.CHANGED == 'true'
run: |
git add json/*.json
git commit -m "Auto-update date_created in incorrect JSON files"
git checkout -b pr-fix-json-dates
git push origin pr-fix-json-dates --force
gh pr create --title "[core] Fix incorrect JSON date_created fields" \
--body "This PR is auto-generated to fix incorrect `date_created` fields in JSON files." \
--head pr-fix-json-dates \
--base main \
--label "automated pr"
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
- name: Approve pull request
if: env.CHANGED == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=$(gh pr list --head "pr-fix-json-dates" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
fi

View File

@@ -0,0 +1,133 @@
name: Validate script formatting
on:
push:
branches:
- main
pull_request_target:
paths:
- "**/*.sh"
- "**/*.func"
jobs:
shfmt:
name: Check changed files
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Get pull request information
if: github.event_name == 'pull_request_target'
uses: actions/github-script@v7
id: pr
with:
script: |
const { data: pullRequest } = await github.rest.pulls.get({
...context.repo,
pull_number: context.payload.pull_request.number,
});
return pullRequest;
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Ensure the full history is fetched for accurate diffing
ref: ${{ github.event_name == 'pull_request_target' && fromJSON(steps.pr.outputs.result).merge_commit_sha || '' }}
- name: Get changed files
id: changed-files
run: |
if ${{ github.event_name == 'pull_request_target' }}; then
echo "files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ steps.pr.outputs.result && fromJSON(steps.pr.outputs.result).merge_commit_sha }} | grep -E '\.(sh|func)$' | xargs)" >> $GITHUB_OUTPUT
else
echo "files=$(git diff --name-only ${{ github.event.before }} ${{ github.event.after }} | grep -E '\.(sh|func)$' | xargs)" >> $GITHUB_OUTPUT
fi
- name: Set up Go
if: steps.changed-files.outputs.files != ''
uses: actions/setup-go@v5
- name: Install shfmt
if: steps.changed-files.outputs.files != ''
run: |
go install mvdan.cc/sh/v3/cmd/shfmt@latest
echo "$GOPATH/bin" >> $GITHUB_PATH
- name: Run shfmt
if: steps.changed-files.outputs.files != ''
id: shfmt
run: |
set +e
shfmt_output=$(shfmt -d ${{ steps.changed-files.outputs.files }})
if [[ $? -eq 0 ]]; then
exit 0
else
echo "diff=\"$(echo -n "$shfmt_output" | base64 -w 0)\"" >> $GITHUB_OUTPUT
printf "%s" "$shfmt_output"
exit 1
fi
- name: Post comment with results
if: always() && steps.changed-files.outputs.files != '' && github.event_name == 'pull_request_target'
uses: actions/github-script@v7
with:
script: |
const result = "${{ job.status }}" === "success" ? "success" : "failure";
const diff = Buffer.from(
${{ steps.shfmt.outputs.diff }},
"base64",
).toString();
const issueNumber = context.payload.pull_request
? context.payload.pull_request.number
: null;
const commentIdentifier = "validate-formatting";
let newCommentBody = `<!-- ${commentIdentifier}-start -->\n### Script formatting\n\n`;
if (result === "failure") {
newCommentBody +=
`:x: We found issues in the formatting of the following changed files:\n\n\`\`\`diff\n${diff}\n\`\`\`\n`;
} else {
newCommentBody += `:rocket: All changed shell scripts are formatted correctly!\n`;
}
newCommentBody += `\n\n<!-- ${commentIdentifier}-end -->`;
if (issueNumber) {
const { data: comments } = await github.rest.issues.listComments({
...context.repo,
issue_number: issueNumber,
});
const existingComment = comments.find(
(comment) => comment.user.login === "github-actions[bot]",
);
if (existingComment) {
if (existingComment.body.includes(commentIdentifier)) {
const re = new RegExp(
String.raw`<!-- ${commentIdentifier}-start -->[\s\S]*?<!-- ${commentIdentifier}-end -->`,
"",
);
newCommentBody = existingComment.body.replace(re, newCommentBody);
} else {
newCommentBody = existingComment.body + "\n\n---\n\n" + newCommentBody;
}
await github.rest.issues.updateComment({
...context.repo,
comment_id: existingComment.id,
body: newCommentBody,
});
} else {
await github.rest.issues.createComment({
...context.repo,
issue_number: issueNumber,
body: newCommentBody,
});
}
}

View File

@@ -0,0 +1,234 @@
name: Validate scripts
on:
push:
branches:
- main
pull_request_target:
paths:
- "ct/*.sh"
- "install/*.sh"
jobs:
check-scripts:
name: Check changed files
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Debug event payload
run: |
echo "Event name: ${{ github.event_name }}"
echo "Payload: $(cat $GITHUB_EVENT_PATH)"
- name: Get pull request information
if: github.event_name == 'pull_request_target'
uses: actions/github-script@v7
id: pr
with:
script: |
const { data: pullRequest } = await github.rest.pulls.get({
...context.repo,
pull_number: context.payload.pull_request.number,
});
return pullRequest;
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'pull_request_target' && fromJSON(steps.pr.outputs.result).merge_commit_sha || '' }}
- name: Get changed files
id: changed-files
run: |
if [ "${{ github.event_name }}" == "pull_request_target" ]; then
echo "files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ steps.pr.outputs.result && fromJSON(steps.pr.outputs.result).merge_commit_sha }} | grep -E '\.(sh|func)$' | xargs)" >> $GITHUB_OUTPUT
else
echo "files=$(git diff --name-only ${{ github.event.before }} ${{ github.event.after }} | grep -E '\.(sh|func)$' | xargs)" >> $GITHUB_OUTPUT
fi
- name: Check build.func line
if: always() && steps.changed-files.outputs.files != ''
id: build-func
run: |
NON_COMPLIANT_FILES=""
for FILE in ${{ steps.changed-files.outputs.files }}; do
if [[ "$FILE" == ct/* ]] && [[ $(sed -n '2p' "$FILE") != "source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)" ]]; then
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE"
fi
done
if [ -n "$NON_COMPLIANT_FILES" ]; then
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT
echo "Build.func line missing or incorrect in files:"
for FILE in $NON_COMPLIANT_FILES; do
echo "$FILE"
done
exit 1
fi
- name: Check executable permissions
if: always() && steps.changed-files.outputs.files != ''
id: check-executable
run: |
NON_COMPLIANT_FILES=""
for FILE in ${{ steps.changed-files.outputs.files }}; do
if [[ ! -x "$FILE" ]]; then
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE"
fi
done
if [ -n "$NON_COMPLIANT_FILES" ]; then
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT
echo "Files not executable:"
for FILE in $NON_COMPLIANT_FILES; do
echo "$FILE"
done
exit 1
fi
- name: Check copyright
if: always() && steps.changed-files.outputs.files != ''
id: check-copyright
run: |
NON_COMPLIANT_FILES=""
for FILE in ${{ steps.changed-files.outputs.files }}; do
if ! sed -n '3p' "$FILE" | grep -qE "^# Copyright \(c\) [0-9]{4}(-[0-9]{4})? (tteck \| community-scripts ORG|community-scripts ORG|tteck)$"; then
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE"
fi
done
if [ -n "$NON_COMPLIANT_FILES" ]; then
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT
echo "Copyright header missing or not on line 3 in files:"
for FILE in $NON_COMPLIANT_FILES; do
echo "$FILE"
done
exit 1
fi
- name: Check author
if: always() && steps.changed-files.outputs.files != ''
id: check-author
run: |
NON_COMPLIANT_FILES=""
for FILE in ${{ steps.changed-files.outputs.files }}; do
if ! sed -n '4p' "$FILE" | grep -qE "^# Author: .+"; then
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE"
fi
done
if [ -n "$NON_COMPLIANT_FILES" ]; then
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT
echo "Author header missing or invalid on line 4 in files:"
for FILE in $NON_COMPLIANT_FILES; do
echo "$FILE"
done
exit 1
fi
- name: Check license
if: always() && steps.changed-files.outputs.files != ''
id: check-license
run: |
NON_COMPLIANT_FILES=""
for FILE in ${{ steps.changed-files.outputs.files }}; do
if [[ "$(sed -n '5p' "$FILE")" != "# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE" ]]; then
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE"
fi
done
if [ -n "$NON_COMPLIANT_FILES" ]; then
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT
echo "License header missing or not on line 5 in files:"
for FILE in $NON_COMPLIANT_FILES; do
echo "$FILE"
done
exit 1
fi
- name: Check source
if: always() && steps.changed-files.outputs.files != ''
id: check-source
run: |
NON_COMPLIANT_FILES=""
for FILE in ${{ steps.changed-files.outputs.files }}; do
if ! sed -n '6p' "$FILE" | grep -qE "^# Source: .+"; then
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE"
fi
done
if [ -n "$NON_COMPLIANT_FILES" ]; then
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT
echo "Source header missing or not on line 6 in files:"
for FILE in $NON_COMPLIANT_FILES; do
echo "$FILE"
done
exit 1
fi
- name: Post results and comment
if: always() && steps.changed-files.outputs.files != '' && github.event_name == 'pull_request_target'
uses: actions/github-script@v7
with:
script: |
const result = '${{ job.status }}' === 'success' ? 'success' : 'failure';
const nonCompliantFiles = {
'Invalid build.func source': "${{ steps.build-func.outputs.files || '' }}",
'Not executable': "${{ steps.check-executable.outputs.files || '' }}",
'Copyright header line missing or invalid': "${{ steps.check-copyright.outputs.files || '' }}",
'Author header line missing or invalid': "${{ steps.check-author.outputs.files || '' }}",
'License header line missing or invalid': "${{ steps.check-license.outputs.files || '' }}",
'Source header line missing or invalid': "${{ steps.check-source.outputs.files || '' }}"
};
const issueNumber = context.payload.pull_request ? context.payload.pull_request.number : null;
const commentIdentifier = 'validate-scripts';
let newCommentBody = `<!-- ${commentIdentifier}-start -->\n### Script validation\n\n`;
if (result === 'failure') {
newCommentBody += ':x: We found issues in the following changed files:\n\n';
for (const [check, files] of Object.entries(nonCompliantFiles)) {
if (files) {
newCommentBody += `**${check}:**\n`;
files.trim().split(' ').forEach(file => {
newCommentBody += `- ${file}: ${check}\n`;
});
newCommentBody += `\n`;
}
}
} else {
newCommentBody += `:rocket: All changed shell scripts passed validation!\n`;
}
newCommentBody += `\n\n<!-- ${commentIdentifier}-end -->`;
if (issueNumber) {
const { data: comments } = await github.rest.issues.listComments({
...context.repo,
issue_number: issueNumber
});
const existingComment = comments.find(comment =>
comment.body.includes(`<!-- ${commentIdentifier}-start -->`) &&
comment.user.login === 'github-actions[bot]'
);
if (existingComment) {
const re = new RegExp(String.raw`<!-- ${commentIdentifier}-start -->[\\s\\S]*?<!-- ${commentIdentifier}-end -->`, "m");
newCommentBody = existingComment.body.replace(re, newCommentBody);
await github.rest.issues.updateComment({
...context.repo,
comment_id: existingComment.id,
body: newCommentBody
});
} else {
await github.rest.issues.createComment({
...context.repo,
issue_number: issueNumber,
body: newCommentBody
});
}
}

View File

@@ -1,106 +0,0 @@
name: Bump build.func Revision
on:
push:
branches:
- main
paths:
- "misc/**"
workflow_dispatch:
jobs:
bump-revision:
if: github.repository == 'community-scripts/ProxmoxVED'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Generate token for PR
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Generate token for auto-merge
id: generate-token-merge
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID_APPROVE_AND_MERGE }}
private-key: ${{ secrets.APP_KEY_APPROVE_AND_MERGE }}
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Get changed files
id: changes
run: |
git diff --name-only HEAD^ HEAD > changed_files.txt
echo "Changed files:"
cat changed_files.txt
- name: Skip if only build.func changed
id: skipcheck
run: |
if grep -q "^misc/build.func$" changed_files.txt && [ $(wc -l < changed_files.txt) -eq 1 ]; then
echo "skip=true" >> $GITHUB_ENV
else
echo "skip=false" >> $GITHUB_ENV
fi
- name: Disable file mode changes
run: git config core.fileMode false
- name: Bump build.func revision
if: env.skip == 'false'
run: |
REV_FILE=".build-revision"
if [ ! -f "$REV_FILE" ]; then echo 0 > "$REV_FILE"; fi
REV_NUM=$(($(cat $REV_FILE) + 1))
echo $REV_NUM > $REV_FILE
SHORT_SHA=$(git rev-parse --short HEAD)
REV_STR="Revision: r${REV_NUM} (git-${SHORT_SHA})"
echo "Updating build.func with $REV_STR"
sed -i "s/^# Revision:.*/# $REV_STR/" misc/build.func
echo "REV_STR=$REV_STR" >> $GITHUB_ENV
git config --global user.name "GitHub Actions"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add misc/build.func .build-revision
git commit -m "chore: bump build.func to $REV_STR"
- name: Create PR
if: env.skip == 'false'
run: |
BRANCH_NAME="pr-build-revision-$(date +'%Y%m%d%H%M%S')"
git checkout -b $BRANCH_NAME
git push origin $BRANCH_NAME
gh pr create --title "[core] bump build.func to $REV_STR" \
--body "This PR bumps build.func revision because files in misc/ changed." \
--head $BRANCH_NAME \
--base main \
--label "automated pr"
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
- name: Approve PR and merge
if: env.skip == 'false'
env:
GH_TOKEN: ${{ steps.generate-token-merge.outputs.token }}
run: |
PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
gh pr merge $PR_NUMBER --squash --admin
fi
- name: Skip log
if: env.skip == 'true'
run: echo "Only build.func changed nothing to do."

View File

@@ -1,505 +0,0 @@
name: Update Versions from GitHub
on:
workflow_dispatch:
schedule:
# Runs at 06:00 and 18:00 UTC
- cron: "0 6,18 * * *"
permissions:
contents: write
pull-requests: write
env:
SOURCES_FILE: json/version-sources.json
VERSIONS_FILE: json/versions.json
jobs:
update-versions:
if: github.repository == 'community-scripts/ProxmoxVE'
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: main
- name: Extract version sources from install scripts
run: |
set -euo pipefail
echo "========================================="
echo " Extracting version sources from scripts"
echo "========================================="
# Initialize sources array
sources_json="[]"
# Function to add a source entry
add_source() {
local slug="$1"
local type="$2"
local source="$3"
local script="$4"
# Check if slug already exists (avoid duplicates)
if echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
return
fi
sources_json=$(echo "$sources_json" | jq \
--arg slug "$slug" \
--arg type "$type" \
--arg source "$source" \
--arg script "$script" \
'. += [{"slug": $slug, "type": $type, "source": $source, "script": $script, "version": null, "date": null}]')
}
echo ""
echo "=== Method 1: fetch_and_deploy_gh_release calls ==="
count=0
for script in install/*-install.sh; do
[[ ! -f "$script" ]] && continue
slug=$(basename "$script" | sed 's/-install\.sh$//')
# Extract repo from fetch_and_deploy_gh_release "app" "owner/repo"
while IFS= read -r line; do
if [[ "$line" =~ fetch_and_deploy_gh_release[[:space:]]+\"[^\"]*\"[[:space:]]+\"([^\"]+)\" ]]; then
repo="${BASH_REMATCH[1]}"
add_source "$slug" "github" "$repo" "$script"
((count++))
break # Only first match per script
fi
done < <(grep 'fetch_and_deploy_gh_release' "$script" 2>/dev/null || true)
done
echo "Found $count scripts with fetch_and_deploy_gh_release"
echo ""
echo "=== Method 2: GitHub URLs in scripts (fallback) ==="
count=0
for script in install/*-install.sh; do
[[ ! -f "$script" ]] && continue
slug=$(basename "$script" | sed 's/-install\.sh$//')
# Skip if already found
if echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
continue
fi
# Look for github.com/owner/repo patterns
repo=$(grep -oE 'github\.com/([a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+)' "$script" 2>/dev/null \
| sed 's|github\.com/||' \
| sed 's/\.git$//' \
| grep -v 'community-scripts/ProxmoxVE' \
| grep -v '^repos/' \
| head -1 || true)
if [[ -n "$repo" && "$repo" =~ ^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+$ ]]; then
add_source "$slug" "github" "$repo" "$script"
((count++))
fi
done
echo "Found $count additional scripts with GitHub URLs"
echo ""
echo "=== Method 3: npm packages ==="
# Detect npm install --global <package>
for script in install/*-install.sh; do
[[ ! -f "$script" ]] && continue
slug=$(basename "$script" | sed 's/-install\.sh$//')
# Skip if already found
if echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
continue
fi
# Look for npm install --global <package>
pkg=$(grep -oE 'npm install[^|;]*--global[^|;]*' "$script" 2>/dev/null \
| grep -oE '\s[a-z][a-z0-9_-]+(@[^\s]+)?$' \
| tr -d ' ' \
| sed 's/@.*//' \
| tail -1 || true)
if [[ -n "$pkg" ]]; then
add_source "$slug" "npm" "$pkg" "$script"
fi
done
echo ""
echo "=== Method 4: Docker images ==="
# Known Docker-based apps (from docker pull or docker run)
declare -A docker_mappings=(
["homeassistant"]="homeassistant/home-assistant"
["portainer"]="portainer/portainer-ce"
["dockge"]="louislam/dockge"
["immich"]="ghcr.io/immich-app/immich-server"
["audiobookshelf"]="ghcr.io/advplyr/audiobookshelf"
["podman-homeassistant"]="homeassistant/home-assistant"
)
for slug in "${!docker_mappings[@]}"; do
image="${docker_mappings[$slug]}"
if ! echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
add_source "$slug" "docker" "$image" "install/${slug}-install.sh"
fi
done
echo ""
echo "=== Method 5: Manual GitHub mappings (apt-based apps) ==="
# Apps that install via apt but have GitHub releases for version tracking
declare -A manual_github_mappings=(
["actualbudget"]="actualbudget/actual"
["apache-cassandra"]="apache/cassandra"
["apache-couchdb"]="apache/couchdb"
["apache-guacamole"]="apache/guacamole-server"
["apache-tomcat"]="apache/tomcat"
["archivebox"]="ArchiveBox/ArchiveBox"
["aria2"]="aria2/aria2"
["asterisk"]="asterisk/asterisk"
["casaos"]="IceWhaleTech/CasaOS"
["checkmk"]="Checkmk/checkmk"
["cloudflared"]="cloudflare/cloudflared"
["coolify"]="coollabsio/coolify"
["crafty-controller"]="crafty-controller/crafty-4"
["cross-seed"]="cross-seed/cross-seed"
["deconz"]="dresden-elektronik/deconz-rest-plugin"
["deluge"]="deluge-torrent/deluge"
["dokploy"]="Dokploy/dokploy"
["emqx"]="emqx/emqx"
["esphome"]="esphome/esphome"
["flowiseai"]="FlowiseAI/Flowise"
["forgejo"]="forgejo/forgejo"
["garage"]="deuxfleurs-org/garage"
["ghost"]="TryGhost/Ghost"
["grafana"]="grafana/grafana"
["graylog"]="Graylog2/graylog2-server"
["homebridge"]="homebridge/homebridge"
["hyperhdr"]="awawa-dev/HyperHDR"
["hyperion"]="hyperion-project/hyperion.ng"
["influxdb"]="influxdata/influxdb"
["iobroker"]="ioBroker/ioBroker"
["jenkins"]="jenkinsci/jenkins"
["komodo"]="moghingold/komodo"
["lazylibrarian"]="lazylibrarian/LazyLibrarian"
["limesurvey"]="LimeSurvey/LimeSurvey"
["mariadb"]="MariaDB/server"
["mattermost"]="mattermost/mattermost"
["meshcentral"]="Ylianst/MeshCentral"
["metabase"]="metabase/metabase"
["mongodb"]="mongodb/mongo"
["mysql"]="mysql/mysql-server"
["neo4j"]="neo4j/neo4j"
["node-red"]="node-red/node-red"
["ntfy"]="binwiederhier/ntfy"
["nzbget"]="nzbgetcom/nzbget"
["octoprint"]="OctoPrint/OctoPrint"
["onedev"]="theonedev/onedev"
["onlyoffice"]="ONLYOFFICE/DocumentServer"
["openhab"]="openhab/openhab-distro"
["openobserve"]="openobserve/openobserve"
["openwebui"]="open-webui/open-webui"
["passbolt"]="passbolt/passbolt_api"
["pihole"]="pi-hole/pi-hole"
["postgresql"]="postgres/postgres"
["rabbitmq"]="rabbitmq/rabbitmq-server"
["readarr"]="Readarr/Readarr"
["redis"]="redis/redis"
["runtipi"]="runtipi/runtipi"
["sftpgo"]="drakkan/sftpgo"
["shinobi"]="ShinobiCCTV/Shinobi"
["sonarqube"]="SonarSource/sonarqube"
["sonarr"]="Sonarr/Sonarr"
["syncthing"]="syncthing/syncthing"
["tdarr"]="HaveAGitGat/Tdarr"
["technitiumdns"]="TechnitiumSoftware/DnsServer"
["transmission"]="transmission/transmission"
["typesense"]="typesense/typesense"
["unmanic"]="Unmanic/unmanic"
["valkey"]="valkey-io/valkey"
["verdaccio"]="verdaccio/verdaccio"
["vikunja"]="go-vikunja/vikunja"
["wazuh"]="wazuh/wazuh"
["wordpress"]="WordPress/WordPress"
["zabbix"]="zabbix/zabbix"
["zammad"]="zammad/zammad"
["zerotier-one"]="zerotier/ZeroTierOne"
# Apps without known GitHub repos (use "-" as placeholder)
["agentdvr"]="-"
["apt-cacher-ng"]="-"
["channels"]="-"
["daemonsync"]="-"
["dotnetaspwebapi"]="-"
["fhem"]="-"
["fileflows"]="-"
["fumadocs"]="-"
["infisical"]="-"
["itsm-ng"]="-"
["jupyternotebook"]="-"
["kasm"]="-"
["lyrionmusicserver"]="-"
["minarca"]="-"
["mqtt"]="-"
["nextcloudpi"]="-"
["nextpvr"]="-"
["notifiarr"]="-"
["nxwitness"]="-"
["omada"]="-"
["omv"]="-"
["plex"]="-"
["podman"]="-"
["readeck"]="-"
["resiliosync"]="-"
["smokeping"]="-"
["splunk-enterprise"]="-"
["sqlserver2022"]="-"
["swizzin"]="-"
["teamspeak-server"]="-"
["twingate-connector"]="-"
["unifi"]="-"
["urbackupserver"]="-"
["yunohost"]="-"
)
for slug in "${!manual_github_mappings[@]}"; do
repo="${manual_github_mappings[$slug]}"
if ! echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
# Skip placeholder entries in extraction, they get added in Method 8
[[ "$repo" == "-" ]] && continue
add_source "$slug" "github" "$repo" "install/${slug}-install.sh"
fi
done
echo ""
echo "=== Method 6: Proxmox LXC templates ==="
# Base OS versions from Proxmox template index
declare -A pveam_mappings=(
["debian"]="pveam:debian"
["ubuntu"]="pveam:ubuntu"
["alpine"]="pveam:alpine"
)
for slug in "${!pveam_mappings[@]}"; do
template="${pveam_mappings[$slug]}"
if ! echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
add_source "$slug" "pveam" "$template" "ct/${slug}.sh"
fi
done
echo ""
echo "=== Method 7: Special sources ==="
# Home Assistant OS VM
if ! echo "$sources_json" | jq -e ".[] | select(.slug == \"haos-vm\")" > /dev/null 2>&1; then
add_source "haos-vm" "github" "home-assistant/operating-system" "vm/haos-vm.sh"
fi
echo ""
echo "=== Method 8: Unknown/Manual apps ==="
# Apps without known version sources - add with type "manual" for manual updates
unknown_apps=(
"agentdvr" "apt-cacher-ng" "channels" "daemonsync" "dotnetaspwebapi"
"fhem" "fileflows" "fumadocs" "infisical" "itsm-ng" "jupyternotebook"
"kasm" "lyrionmusicserver" "minarca" "mqtt" "nextcloudpi" "nextpvr"
"notifiarr" "nxwitness" "omada" "omv" "plex" "podman" "readeck"
"resiliosync" "smokeping" "splunk-enterprise" "sqlserver2022" "swizzin"
"teamspeak-server" "twingate-connector" "unifi" "urbackupserver" "yunohost"
)
for slug in "${unknown_apps[@]}"; do
if ! echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
add_source "$slug" "manual" "-" "install/${slug}-install.sh"
fi
done
# Save sources file
echo "$sources_json" | jq --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{generated: $date, sources: (. | sort_by(.slug))}' > "$SOURCES_FILE"
total=$(echo "$sources_json" | jq 'length')
echo ""
echo "========================================="
echo " Total sources extracted: $total"
echo "========================================="
- name: Fetch versions for all sources
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
echo "========================================="
echo " Fetching versions from sources"
echo "========================================="
success=0
failed=0
manual=0
total=$(jq '.sources | length' "$SOURCES_FILE")
# Process each source
for i in $(seq 0 $((total - 1))); do
entry=$(jq -r ".sources[$i]" "$SOURCES_FILE")
slug=$(echo "$entry" | jq -r '.slug')
type=$(echo "$entry" | jq -r '.type')
source=$(echo "$entry" | jq -r '.source')
echo -n "[$((i+1))/$total] $slug ($type: $source) ... "
version=""
date=""
case "$type" in
github)
# Try releases first
response=$(gh api "repos/${source}/releases/latest" 2>/dev/null || echo '{"message": "Not Found"}')
if echo "$response" | jq -e '.tag_name' > /dev/null 2>&1; then
version=$(echo "$response" | jq -r '.tag_name')
date=$(echo "$response" | jq -r '.published_at // empty')
else
# Fallback to tags
version=$(gh api "repos/${source}/tags" --jq '.[0].name // empty' 2>/dev/null || echo "")
fi
;;
npm)
response=$(curl -fsSL "https://registry.npmjs.org/${source}/latest" 2>/dev/null || echo '{}')
version=$(echo "$response" | jq -r '.version // empty')
;;
docker)
if [[ "$source" == ghcr.io/* ]]; then
# GitHub Container Registry
ghcr_path="${source#ghcr.io/}"
owner="${ghcr_path%%/*}"
pkg="${ghcr_path##*/}"
version=$(gh api "users/${owner}/packages/container/${pkg}/versions" --jq '.[0].metadata.container.tags[] | select(. != "latest")' 2>/dev/null | head -1 || echo "")
else
# Docker Hub
version=$(curl -fsSL "https://hub.docker.com/v2/repositories/${source}/tags?page_size=10&ordering=last_updated" 2>/dev/null \
| jq -r '.results[] | select(.name != "latest") | .name' | head -1 || echo "")
fi
;;
pveam)
# Proxmox LXC template versions from download.proxmox.com
os_name="${source#pveam:}"
# Fetch the template index and get latest version
version=$(curl -fsSL "http://download.proxmox.com/images/system/" 2>/dev/null \
| grep -oE "${os_name}-[0-9]+\.[0-9]+-default_[0-9]+_amd64" \
| sed "s/${os_name}-//" | sed 's/-default.*//' \
| sort -V | tail -1 || echo "")
;;
manual)
# Manual entries - no automatic version fetching
# These need to be updated manually or have their source type changed
version="-"
((manual++))
echo -n "(manual) "
;;
esac
if [[ -n "$version" && "$version" != "null" ]]; then
# Update the source entry with version
jq --arg idx "$i" --arg version "$version" --arg date "${date:-}" \
'.sources[$idx | tonumber].version = $version | .sources[$idx | tonumber].date = $date' \
"$SOURCES_FILE" > "${SOURCES_FILE}.tmp" && mv "${SOURCES_FILE}.tmp" "$SOURCES_FILE"
echo "✓ $version"
((success++))
else
echo "⚠ no version found"
((failed++))
fi
done
echo ""
echo "========================================="
echo " SUMMARY"
echo "========================================="
echo "Success: $success (automated)"
echo "Manual: $manual (placeholder)"
echo "Failed: $failed"
echo "Total: $total"
echo "========================================="
- name: Generate versions.json for compatibility
run: |
# Convert version-sources.json to versions.json format for backward compatibility
jq '[.sources[] | select(.version != null) | {name: .source, version: .version, date: .date}]' \
"$SOURCES_FILE" > "$VERSIONS_FILE"
echo "Generated versions.json with $(jq length "$VERSIONS_FILE") entries"
- name: Check for changes
id: check-changes
run: |
if git diff --quiet "$SOURCES_FILE" "$VERSIONS_FILE" 2>/dev/null; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No changes detected"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "Changes detected:"
git diff --stat "$SOURCES_FILE" "$VERSIONS_FILE" 2>/dev/null || true
fi
- name: Create Pull Request
if: steps.check-changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH_NAME="automated/update-versions-$(date +%Y%m%d)"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "GitHub Actions[bot]"
# Check if branch exists and delete it
git push origin --delete "$BRANCH_NAME" 2>/dev/null || true
git checkout -b "$BRANCH_NAME"
git add "$SOURCES_FILE" "$VERSIONS_FILE"
git commit -m "chore: update version-sources.json and versions.json
Sources: $(jq '.sources | length' "$SOURCES_FILE")
With versions: $(jq '[.sources[] | select(.version != null)] | length' "$SOURCES_FILE")
Generated: $(jq -r '.generated' "$SOURCES_FILE")"
git push origin "$BRANCH_NAME" --force
# Check if PR already exists
existing_pr=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number // empty')
if [[ -n "$existing_pr" ]]; then
echo "PR #$existing_pr already exists, updating..."
else
gh pr create \
--title "[Automated] Update version-sources.json" \
--body "This PR updates version information from multiple sources.
## Sources
- **GitHub Releases**: Direct from \`fetch_and_deploy_gh_release\` calls
- **GitHub URLs**: Extracted from install scripts
- **npm Registry**: For Node.js based apps
- **Docker Hub/GHCR**: For container-based apps
## Stats
- Total sources: $(jq '.sources | length' "$SOURCES_FILE")
- With versions: $(jq '[.sources[] | select(.version != null)] | length' "$SOURCES_FILE")
---
*Automatically generated from install scripts*" \
--base main \
--head "$BRANCH_NAME" \
--label "automated pr"
fi
- name: Auto-approve PR
if: steps.check-changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH_NAME="automated/update-versions-$(date +%Y%m%d)"
pr_number=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number')
if [[ -n "$pr_number" ]]; then
gh pr review "$pr_number" --approve
fi

View File

@@ -1,159 +0,0 @@
name: Update Issue on PR Merge
on:
pull_request:
types: [closed]
branches:
- main
permissions:
issues: write
pull-requests: read
jobs:
update_issues:
if: github.event.pull_request.merged == true && github.repository == 'community-scripts/ProxmoxVED'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Extract Script Names from Changed Files
id: extract_scripts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHANGED_FILES=$(gh pr diff ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --name-only)
SCRIPT_NAMES=()
while IFS= read -r FILE; do
if [[ $FILE =~ ^ct/(.+)\.sh$ ]]; then
SCRIPT_NAME="${BASH_REMATCH[1]}"
if [[ ! " ${SCRIPT_NAMES[@]} " =~ " ${SCRIPT_NAME} " ]]; then
SCRIPT_NAMES+=("$SCRIPT_NAME")
fi
elif [[ $FILE =~ ^install/(.+)-install\.sh$ ]]; then
SCRIPT_NAME="${BASH_REMATCH[1]}"
if [[ ! " ${SCRIPT_NAMES[@]} " =~ " ${SCRIPT_NAME} " ]]; then
SCRIPT_NAMES+=("$SCRIPT_NAME")
fi
fi
done <<< "$CHANGED_FILES"
if [ ${#SCRIPT_NAMES[@]} -eq 0 ]; then
echo "No script files found in PR"
echo "script_names=[]" >> $GITHUB_OUTPUT
exit 0
fi
JSON_NAMES=$(printf '%s\n' "${SCRIPT_NAMES[@]}" | jq -R . | jq -s -c .)
echo "script_names=$JSON_NAMES" >> $GITHUB_OUTPUT
echo "Found script names: ${SCRIPT_NAMES[*]}"
- name: Process Each Script
if: steps.extract_scripts.outputs.script_names != '[]'
uses: actions/github-script@v7
env:
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
SCRIPT_NAMES: ${{ steps.extract_scripts.outputs.script_names }}
with:
script: |
const scriptNames = JSON.parse(process.env.SCRIPT_NAMES);
const prAuthor = process.env.PR_AUTHOR;
const message = `@${prAuthor} This PR got merged now and is in the testing phase, it will be migrated to ProxmoxVE when testing is completed`;
const labelName = 'Ready For Testing';
for (const scriptName of scriptNames) {
console.log(`Processing script: ${scriptName}`);
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100
});
const scriptNameLower = scriptName.toLowerCase();
let existingIssue = issues.find(issue =>
!issue.pull_request && issue.title.toLowerCase() === scriptNameLower
);
if (!existingIssue) {
const { data: searchResults } = await github.rest.search.issuesAndPullRequests({
q: `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open ${scriptName}`,
per_page: 100
});
existingIssue = searchResults.items.find(issue =>
issue.title.toLowerCase() === scriptNameLower
);
}
if (existingIssue) {
console.log(`Found existing issue #${existingIssue.number}: ${existingIssue.title}`);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existingIssue.number,
body: message
});
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existingIssue.number
});
const hasLabel = labels.some(label => label.name === labelName);
if (!hasLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existingIssue.number,
labels: [labelName]
});
console.log(`Added "${labelName}" label to issue #${existingIssue.number}`);
} else {
console.log(`Issue #${existingIssue.number} already has "${labelName}" label`);
}
} else {
console.log(`No existing issue found for "${scriptName}", creating new issue`);
const scriptType = 'CT (LXC Container)';
const formattedName = scriptName
.split(/[-_]/)
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
const issueBody = '# 🛠️ **New Script**\n' +
'Created by Github Action Bot\n\n' +
'### Name of the Script\n\n' +
formattedName + '\n\n' +
'### Script Type\n\n' +
scriptType + '\n\n' +
'### 📋 Script Details\n\n' +
'This script has been merged and is ready for testing. ';
const { data: newIssue } = await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: scriptName,
body: issueBody,
labels: [labelName]
});
console.log(`Created new issue #${newIssue.number}: ${newIssue.title}`);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: newIssue.number,
body: message
});
}
}

272
.github/workflows/changelog-pr.yaml vendored Normal file
View File

@@ -0,0 +1,272 @@
name: Create Changelog Pull Request
on:
push:
branches: ["main"]
workflow_dispatch:
jobs:
update-changelog-pull-request:
if: github.repository == 'community-scripts/ProxmoxVED'
runs-on: ubuntu-latest
env:
CONFIG_PATH: .github/changelog-pr-config.json
BRANCH_NAME: github-action-update-changelog
AUTOMATED_PR_LABEL: "automated pr"
permissions:
contents: write
pull-requests: write
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get latest dates in changelog
run: |
DATES=$(grep -E '^## [0-9]{4}-[0-9]{2}-[0-9]{2}' CHANGELOG.md | head -n 2 | awk '{print $2}')
LATEST_DATE=$(echo "$DATES" | sed -n '1p')
SECOND_LATEST_DATE=$(echo "$DATES" | sed -n '2p')
TODAY=$(date -u +%Y-%m-%d)
echo "TODAY=$TODAY" >> $GITHUB_ENV
if [[ "$LATEST_DATE" == "$TODAY" ]]; then
echo "LATEST_DATE=$SECOND_LATEST_DATE" >> $GITHUB_ENV
else
echo "LATEST_DATE=$LATEST_DATE" >> $GITHUB_ENV
fi
- name: Get categorized pull requests
id: get-categorized-prs
uses: actions/github-script@v7
with:
script: |
async function main() {
const fs = require('fs').promises;
const path = require('path');
const configPath = path.resolve(process.env.CONFIG_PATH);
const fileContent = await fs.readFile(configPath, 'utf-8');
const changelogConfig = JSON.parse(fileContent);
const categorizedPRs = changelogConfig.map(obj => ({
...obj,
notes: [],
subCategories: obj.subCategories ?? (
obj.labels.includes("update script") ? [
{ title: "🐞 Bug Fixes", labels: ["bugfix"], notes: [] },
{ title: "✨ New Features", labels: ["feature"], notes: [] },
{ title: "💥 Breaking Changes", labels: ["breaking change"], notes: [] },
{ title: "🔧 Refactor", labels: ["refactor"], notes: [] },
] :
obj.labels.includes("maintenance") ? [
{ title: "🐞 Bug Fixes", labels: ["bugfix"], notes: [] },
{ title: "✨ New Features", labels: ["feature"], notes: [] },
{ title: "💥 Breaking Changes", labels: ["breaking change"], notes: [] },
{ title: "📡 API", labels: ["api"], notes: [] },
{ title: "Github", labels: ["github"], notes: [] },
{ title: "📝 Documentation", labels: ["documentation"], notes: [] },
{ title: "🔧 Refactor", labels: ["refactor"], notes: [] }
] :
obj.labels.includes("website") ? [
{ title: "🐞 Bug Fixes", labels: ["bugfix"], notes: [] },
{ title: "✨ New Features", labels: ["feature"], notes: [] },
{ title: "💥 Breaking Changes", labels: ["breaking change"], notes: [] },
{ title: "Script Information", labels: ["json"], notes: [] }
] : []
)
}));
const latestDateInChangelog = new Date(process.env.LATEST_DATE);
latestDateInChangelog.setUTCHours(23, 59, 59, 999);
const { data: pulls } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: "ProxmoxVE",
base: "main",
state: "closed",
sort: "updated",
direction: "desc",
per_page: 100,
});
const filteredPRs = pulls.filter(pr =>
pr.merged_at &&
new Date(pr.merged_at) > latestDateInChangelog &&
!pr.labels.some(label =>
["invalid", "wontdo", process.env.AUTOMATED_PR_LABEL].includes(label.name.toLowerCase())
)
);
for (const pr of filteredPRs) {
const prLabels = pr.labels.map(label => label.name.toLowerCase());
if (pr.user.login.includes("push-app-to-main[bot]")) {
const scriptName = pr.title;
try {
const { data: relatedIssues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: "ProxmoxVED",
state: "all",
labels: ["Started Migration To ProxmoxVE"]
});
const matchingIssue = relatedIssues.find(issue =>
issue.title.toLowerCase().includes(scriptName.toLowerCase())
);
if (matchingIssue) {
const issueAuthor = matchingIssue.user.login;
const issueAuthorUrl = `https://github.com/${issueAuthor}`;
prNote = `- ${pr.title} [@${issueAuthor}](${issueAuthorUrl}) ([#${pr.number}](${pr.html_url}))`;
}
else {
prNote = `- ${pr.title} ([#${pr.number}](${pr.html_url}))`;
}
} catch (error) {
console.error(`Error fetching related issues: ${error}`);
prNote = `- ${pr.title} ([#${pr.number}](${pr.html_url}))`;
}
}else{
prNote = `- ${pr.title} [@${pr.user.login}](https://github.com/${pr.user.login}) ([#${pr.number}](${pr.html_url}))`;
}
if (prLabels.includes("new script")) {
const newScriptCategory = categorizedPRs.find(category =>
category.title === "New Scripts" || category.labels.includes("new script"));
if (newScriptCategory) {
newScriptCategory.notes.push(prNote);
}
} else {
let categorized = false;
const priorityCategories = categorizedPRs.slice();
for (const category of priorityCategories) {
if (categorized) break;
if (category.labels.some(label => prLabels.includes(label))) {
if (category.subCategories && category.subCategories.length > 0) {
const subCategory = category.subCategories.find(sub =>
sub.labels.some(label => prLabels.includes(label))
);
if (subCategory) {
subCategory.notes.push(prNote);
} else {
category.notes.push(prNote);
}
} else {
category.notes.push(prNote);
}
categorized = true;
}
}
}
}
return categorizedPRs;
}
return await main();
- name: Update CHANGELOG.md
uses: actions/github-script@v7
with:
script: |
const fs = require('fs').promises;
const path = require('path');
const today = process.env.TODAY;
const latestDateInChangelog = process.env.LATEST_DATE;
const changelogPath = path.resolve('CHANGELOG.md');
const categorizedPRs = ${{ steps.get-categorized-prs.outputs.result }};
let newReleaseNotes = `## ${today}\n\n`;
for (const { title, notes, subCategories } of categorizedPRs) {
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`;
}
}
}
}
const changelogContent = await fs.readFile(changelogPath, 'utf-8');
const changelogIncludesTodaysReleaseNotes = changelogContent.includes(`\n## ${today}`);
const regex = changelogIncludesTodaysReleaseNotes
? new RegExp(`## ${today}.*(?=## ${latestDateInChangelog})`, "gs")
: new RegExp(`(?=## ${latestDateInChangelog})`, "gs");
const newChangelogContent = changelogContent.replace(regex, newReleaseNotes);
await fs.writeFile(changelogPath, newChangelogContent);
- name: Check for changes
id: verify-diff
run: |
git diff --quiet . || echo "changed=true" >> $GITHUB_ENV
- name: Commit and push changes
if: env.changed == 'true'
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add CHANGELOG.md
git commit -m "Update CHANGELOG.md"
git checkout -b $BRANCH_NAME || git checkout $BRANCH_NAME
git push origin $BRANCH_NAME --force
- name: Create pull request if not exists
if: env.changed == 'true'
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |
PR_EXISTS=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number')
if [ -z "$PR_EXISTS" ]; then
gh pr create --title "[Github Action] Update CHANGELOG.md" \
--body "This PR is auto-generated by a Github Action to update the CHANGELOG.md file." \
--head $BRANCH_NAME \
--base main \
--label "$AUTOMATED_PR_LABEL"
fi
- name: Approve pull request
if: env.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
fi
- name: Re-approve pull request after update
if: env.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
fi

View File

@@ -1,82 +0,0 @@
name: Cleanup Branches (Merged or Closed)
on:
schedule:
- cron: "0 3 * * *" # daily at 03:00 UTC
workflow_dispatch:
permissions:
contents: write
pull-requests: read
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Delete merged or closed branches older than 7 days
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
CUTOFF_DATE=$(date -u -d "7 days ago" +%s)
echo "Cutoff timestamp: $CUTOFF_DATE"
# Get all existing branches (except protected ones)
echo "Fetching existing branches..."
EXISTING_BRANCHES=$(gh api repos/${{ github.repository }}/git/matching-refs/heads \
--jq '.[].ref | ltrimstr("refs/heads/")' | grep -vE '^(main|master|develop)$' || true)
if [ -z "$EXISTING_BRANCHES" ]; then
echo "No branches to check."
exit 0
fi
echo "Found $(echo "$EXISTING_BRANCHES" | wc -l) branches to check."
# Get branches from merged/closed PRs older than 7 days
echo "Fetching closed/merged PRs older than 7 days..."
OLD_CLOSED_PR_BRANCHES=$(gh pr list \
--state all \
--base main \
--json state,mergedAt,closedAt,headRefName \
--limit 500 |
jq -r --arg cutoff "$CUTOFF_DATE" '.[] |
select(.state == "MERGED" or .state == "CLOSED") |
select(((.mergedAt // .closedAt) | fromdateiso8601) < ($cutoff | tonumber)) |
.headRefName' | sort -u)
# Get branches with open PRs (never delete these)
echo "Fetching branches with open PRs..."
OPEN_PR_BRANCHES=$(gh pr list \
--state open \
--base main \
--json headRefName \
--limit 500 |
jq -r '.[].headRefName' | sort -u)
# Delete only branches that:
# 1. Exist in repo
# 2. Have a merged/closed PR older than 7 days
# 3. Do NOT have an open PR
for branch in $EXISTING_BRANCHES; do
# Skip if branch has an open PR
if echo "$OPEN_PR_BRANCHES" | grep -qx "$branch"; then
echo "Skipping (open PR): $branch"
continue
fi
# Only delete if branch has a closed/merged PR
if echo "$OLD_CLOSED_PR_BRANCHES" | grep -qx "$branch"; then
echo "Deleting branch: $branch (PR was merged/closed >7 days ago)"
gh api -X DELETE repos/${{ github.repository }}/git/refs/heads/$branch \
|| echo "Failed to delete branch $branch"
else
echo "Skipping (no closed PR found): $branch"
fi
done
echo "Cleanup complete."

164
.github/workflows/close-discussion.yaml vendored Normal file
View File

@@ -0,0 +1,164 @@
name: Close Discussion on PR Merge
on:
push:
branches:
- main
permissions:
contents: read
discussions: write
jobs:
close-discussion:
if: github.repository == 'community-scripts/ProxmoxVED'
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set Up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Dependencies
run: npm install zx @octokit/graphql
- name: Close Discussion
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_SHA: ${{ github.sha }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
npx zx << 'EOF'
import { graphql } from "@octokit/graphql";
(async function () {
try {
const token = process.env.GITHUB_TOKEN;
const commitSha = process.env.GITHUB_SHA;
const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/");
if (!token || !commitSha || !owner || !repo) {
console.log("Missing required environment variables.");
process.exit(1);
}
const graphqlWithAuth = graphql.defaults({
headers: { authorization: `Bearer ${token}` },
});
// Find PR from commit SHA
const searchQuery = `
query($owner: String!, $repo: String!, $sha: GitObjectID!) {
repository(owner: $owner, name: $repo) {
object(oid: $sha) {
... on Commit {
associatedPullRequests(first: 1) {
nodes {
number
body
}
}
}
}
}
}
`;
const prResult = await graphqlWithAuth(searchQuery, {
owner,
repo,
sha: commitSha,
});
const pr = prResult.repository.object.associatedPullRequests.nodes[0];
if (!pr) {
console.log("No PR found for this commit.");
return;
}
const prNumber = pr.number;
const prBody = pr.body;
const match = prBody.match(/#(\d+)/);
if (!match) {
console.log("No discussion ID found in PR body.");
return;
}
const discussionNumber = match[1];
console.log(`Extracted Discussion Number: ${discussionNumber}`);
// Fetch GraphQL discussion ID
const discussionQuery = `
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
discussion(number: $number) {
id
}
}
}
`;
//
try {
const discussionResponse = await graphqlWithAuth(discussionQuery, {
owner,
repo,
number: parseInt(discussionNumber, 10),
});
const discussionQLId = discussionResponse.repository.discussion.id;
if (!discussionQLId) {
console.log("Failed to fetch discussion GraphQL ID.");
return;
}
} catch (error) {
console.error("Discussion not found or error occurred while fetching discussion:", error);
return;
}
// Post comment
const commentMutation = `
mutation($discussionId: ID!, $body: String!) {
addDiscussionComment(input: { discussionId: $discussionId, body: $body }) {
comment { id body }
}
}
`;
const commentResponse = await graphqlWithAuth(commentMutation, {
discussionId: discussionQLId,
body: `Merged with PR #${prNumber}`,
});
const commentId = commentResponse.addDiscussionComment.comment.id;
if (!commentId) {
console.log("Failed to post the comment.");
return;
}
console.log(`Comment Posted Successfully! Comment ID: ${commentId}`);
// Mark comment as answer
const markAnswerMutation = `
mutation($id: ID!) {
markDiscussionCommentAsAnswer(input: { id: $id }) {
discussion { id title }
}
}
`;
await graphqlWithAuth(markAnswerMutation, { id: commentId });
console.log("Comment marked as answer successfully!");
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
})();
EOF

View File

@@ -1,4 +1,5 @@
name: Auto-Close Wrong Template Issues
name: Auto-Close tteck Issues
on:
issues:
types: [opened]
@@ -8,7 +9,7 @@ jobs:
if: github.repository == 'community-scripts/ProxmoxVED'
runs-on: ubuntu-latest
steps:
- name: Auto-close if wrong Template issue detected
- name: Auto-close if tteck script detected
uses: actions/github-script@v7
with:
script: |
@@ -17,13 +18,18 @@ jobs:
const issueNumber = issue.number;
// Check for tteck script mention
if (content.includes("Template debian-13-standard_13.1-2_amd64.tar.zst [local]") || content.includes("Container creation failed. Checking if template is corrupted or incomplete.") || content.includes("Template is valid, but container creation still failed.")){
const message = `Hello, it looks like you are referencing a container creation issue!.
if (content.includes("tteck") || content.includes("tteck/Proxmox")) {
const message = `Hello, it looks like you are referencing the **old tteck repo**.
We get many simmilar issues with this topic, so please check this disscusion #8126.
If this did not solve your problem, please reopen this issue.
This repository is no longer used for active scripts.
**Please update your bookmarks** and use: [https://helper-scripts.com](https://helper-scripts.com)
This issue is being closed automatically.`;
Also make sure your Bash command starts with:
\`\`\`bash
bash <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/...)
\`\`\`
This issue is being closed automatically.`;
await github.rest.issues.createComment({
...context.repo,

View File

@@ -7,80 +7,29 @@ on:
permissions:
issues: write
actions: write
jobs:
post_to_discord:
runs-on: ubuntu-latest
if: contains(github.event.issue.labels.*.name, 'Ready For Testing') && github.repository == 'community-scripts/ProxmoxVED'
steps:
- name: Extract Issue Title and Script Type
id: extract_info
env:
ISSUE_BODY: ${{ github.event.issue.body }}
run: |
# Extract title (lowercase, spaces to dashes)
TITLE=$(echo '${{ github.event.issue.title }}' | tr '[:upper:]' '[:lower:]' | sed 's/ /-/g')
echo "TITLE=$TITLE" >> $GITHUB_ENV
- name: Extract Issue Title (Lowercase & Underscores)
id: extract_title
run: echo "TITLE=$(echo '${{ github.event.issue.title }}' | tr '[:upper:]' '[:lower:]' | sed 's/ /-/g')" >> $GITHUB_ENV
# Extract script type from issue body (using env var to handle special chars)
if echo "$ISSUE_BODY" | grep -qi "CT (LXC Container)"; then
SCRIPT_TYPE="ct"
elif echo "$ISSUE_BODY" | grep -qi "VM (Virtual Machine)"; then
SCRIPT_TYPE="vm"
elif echo "$ISSUE_BODY" | grep -qi "Addon (tools/addon)"; then
SCRIPT_TYPE="addon"
elif echo "$ISSUE_BODY" | grep -qi "PVE Tool (tools/pve)"; then
SCRIPT_TYPE="pve"
else
# Fallback: detect by filename pattern or default to ct
if [[ "$TITLE" == *"-vm"* ]]; then
SCRIPT_TYPE="vm"
else
SCRIPT_TYPE="ct"
fi
fi
echo "SCRIPT_TYPE=$SCRIPT_TYPE" >> $GITHUB_ENV
echo "Detected script type: $SCRIPT_TYPE for title: $TITLE"
- name: Check if Files Exist in community-scripts/ProxmoxVED
- name: Check if Files Exist in community-scripts/ProxmoxVE
id: check_files
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
REPO="community-scripts/ProxmoxVED"
API_URL="https://api.github.com/repos/$REPO/contents"
TITLE="${{ env.TITLE }}"
SCRIPT_TYPE="${{ env.SCRIPT_TYPE }}"
# Define files based on script type
case "$SCRIPT_TYPE" in
ct)
FILES=(
"ct/${TITLE}.sh"
"install/${TITLE}-install.sh"
"json/${TITLE}.json"
)
;;
vm)
FILES=(
"vm/${TITLE}.sh"
"json/${TITLE}.json"
)
;;
addon)
FILES=(
"tools/addon/${TITLE}.sh"
"json/${TITLE}.json"
)
;;
pve)
FILES=(
"tools/pve/${TITLE}.sh"
)
;;
esac
FILES=(
"ct/${{ env.TITLE }}.sh"
"install/${{ env.TITLE }}-install.sh"
"frontend/public/json/${{ env.TITLE }}.json"
)
EXISTING_FILES=()
@@ -93,89 +42,45 @@ jobs:
echo "$FILE does NOT exist in $REPO"
fi
done
echo "EXISTING_FILES=${EXISTING_FILES[*]}" >> $GITHUB_ENV
echo "EXISTING_FILES=${EXISTING_FILES[@]}" >> $GITHUB_ENV
- name: Create message to send
id: create_message
run: |
TITLE="${{ env.TITLE }}"
SCRIPT_TYPE="${{ env.SCRIPT_TYPE }}"
VAR="The ${{ env.TITLE }} script is ready for testing:\n"
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${{ env.TITLE }}.sh)\"\`\`\`\n"
if [[ " ${EXISTING_FILES[@]} " =~ " frontend/public/json/${TITLE}.json " ]]; then
JSON=$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/frontend/public/json/${{ env.TITLE }}.json)
username=$(echo "$JSON" | jq -r '.default_credentials.username')
password=$(echo "$JSON" | jq -r '.default_credentials.password')
mapfile -t notes_array < <(echo "$JSON" | jq -r '.notes[].text')
VAR="The ${TITLE} script is ready for testing:\n"
if [[ -n "$username" && "$username" != "null" || -n "$password" && "$password" != "null" ]]; then
VAR+="Default credentials:\n"
# Generate correct command based on script type
case "$SCRIPT_TYPE" in
ct)
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${TITLE}.sh)\"\`\`\`\n"
;;
vm)
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/vm/${TITLE}.sh)\"\`\`\`\n"
;;
addon)
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/tools/addon/${TITLE}.sh)\"\`\`\`\n"
;;
pve)
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/tools/pve/${TITLE}.sh)\"\`\`\`\n"
;;
esac
if [[ -n "$username" && "$username" != "null" ]]; then
VAR+="Username: $username\n"
fi
# Try to get JSON info (may not exist for all types)
JSON_FILE=""
case "$SCRIPT_TYPE" in
ct|vm|addon)
JSON_FILE="json/${TITLE}.json"
;;
esac
if [[ -n "$password" && "$password" != "null" ]]; then
VAR+="Password: $password\n"
fi
VAR+="\n"
fi
if [[ -n "$JSON_FILE" ]]; then
JSON=$(curl -fsSL "https://github.com/community-scripts/ProxmoxVED/raw/main/${JSON_FILE}" 2>/dev/null || echo "{}")
if [[ "$JSON" != "{}" && "$JSON" != "" ]]; then
username=$(echo "$JSON" | jq -r '.default_credentials.username // empty')
password=$(echo "$JSON" | jq -r '.default_credentials.password // empty')
if [[ -n "$username" || -n "$password" ]]; then
VAR+="Default credentials:\n"
[[ -n "$username" ]] && VAR+="Username: $username\n"
[[ -n "$password" ]] && VAR+="Password: $password\n"
VAR+="\n"
fi
# Get notes
mapfile -t notes_array < <(echo "$JSON" | jq -r '.notes[]?.text // empty' 2>/dev/null)
if [ ${#notes_array[@]} -gt 0 ]; then
for note in "${notes_array[@]}"; do
[[ -n "$note" ]] && VAR+="$note\n"
done
VAR+="\n"
fi
if [ ${#notes_array[@]} -gt 0 ]; then
for note in "${notes_array[@]}"; do
VAR+="$note\n"
done
VAR+="\n"
fi
fi
VAR+="Note: This is not in the official repo yet—it's just a dev version! After merging into ProxmoxVE, it will need to be recreated.\n\n"
VAR+="Discussion & issue tracking:\n"
VAR+="${{ github.event.issue.html_url }}"
echo "message=$VAR" >> $GITHUB_ENV
- name: Check if Discord thread exists
id: check_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" | \
jq -r --arg TITLE "$ISSUE_TITLE" --arg PARENT_ID "${{ secrets.DISCORD_CHANNEL_ID }}" \
'.threads[] | select(.parent_id == $PARENT_ID and .name == ("Wanted Tester for " + $TITLE)) | .id')
if [ -n "$THREAD_ID" ]; then
echo "thread_exists=true" >> "$GITHUB_OUTPUT"
else
echo "thread_exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Create a forumpost in Discord
if: steps.check_thread.outputs.thread_exists != 'true'
id: post_to_discord
env:
DISCORD_CHANNEL_ID: ${{ secrets.DISCORD_CHANNEL_ID }}
@@ -191,16 +96,8 @@ jobs:
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD")
HTTP_BODY=$(echo "$RESPONSE" | head -n -1)
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
THREAD_ID=$(echo "$HTTP_BODY" | jq -r '.id')
STATUS_CODE=$(echo "$RESPONSE" | tail -n 1)
if [[ "$HTTP_CODE" == "201" && -n "$THREAD_ID" ]]; then
LOCK_RESPONSE=$(curl -s -X PATCH "https://discord.com/api/v10/channels/$THREAD_ID" \
-H "Authorization: Bot $DISCORD_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"locked": true}')
if [ "$STATUS_CODE" -eq 201 ]; then
echo "Discord post created successfully!"
else
echo "Response: $RESPONSE"
@@ -209,7 +106,6 @@ jobs:
fi
- name: Comment on Issue
if: steps.check_thread.outputs.thread_exists != 'true'
id: comment_on_issue
env:
MESSAGE: ${{ env.message }}
@@ -218,10 +114,3 @@ jobs:
echo -e "$MESSAGE" > comment.txt
sed -i '/Discussion & issue tracking:/,$d' comment.txt
gh issue comment ${{ github.event.issue.number }} --repo ${{ github.repository }} --body-file comment.txt
- name: Push script JSON to PocketBase
if: env.SCRIPT_TYPE == 'ct' || env.SCRIPT_TYPE == 'vm' || env.SCRIPT_TYPE == 'addon'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run push_json_to_pocketbase.yml --repo ${{ github.repository }} -f script_slug=${{ env.TITLE }}

View File

@@ -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" | \

View File

@@ -14,43 +14,9 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Generate a token for PR approval and merge
id: generate-token-merge
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 Info and Script Type
id: extract_info
run: |
TITLE=$(echo '${{ github.event.issue.title }}' | tr '[:upper:]' '[:lower:]' | tr -d ' ')
BODY='${{ github.event.issue.body }}'
echo "TITLE=$TITLE" >> $GITHUB_ENV
# Detect script type from issue body
if echo "$BODY" | grep -qi "CT (LXC Container)"; then
SCRIPT_TYPE="ct"
elif echo "$BODY" | grep -qi "VM (Virtual Machine)"; then
SCRIPT_TYPE="vm"
elif echo "$BODY" | grep -qi "Addon (tools/addon)"; then
SCRIPT_TYPE="addon"
elif echo "$BODY" | grep -qi "PVE Tool (tools/pve)"; then
SCRIPT_TYPE="pve"
else
# Fallback detection
if [[ "$TITLE" == *"-vm"* ]]; then
SCRIPT_TYPE="vm"
else
SCRIPT_TYPE="ct"
fi
fi
echo "SCRIPT_TYPE=$SCRIPT_TYPE" >> $GITHUB_ENV
echo "Detected: $TITLE (type: $SCRIPT_TYPE)"
- name: Extract Issue Title (Lowercase & Underscores)
id: extract_title
run: echo "TITLE=$(echo '${{ github.event.issue.title }}' | tr '[:upper:]' '[:lower:]' | sed 's/ /_/g')" >> $GITHUB_ENV
- name: Check if Files Exist in community-scripts/ProxmoxVE
id: check_files
@@ -59,36 +25,12 @@ jobs:
run: |
REPO="community-scripts/ProxmoxVE"
API_URL="https://api.github.com/repos/$REPO/contents"
TITLE="${{ env.TITLE }}"
SCRIPT_TYPE="${{ env.SCRIPT_TYPE }}"
# Define files based on script type
case "$SCRIPT_TYPE" in
ct)
FILES=(
"ct/${TITLE}.sh"
"install/${TITLE}-install.sh"
"frontend/public/json/${TITLE}.json"
)
;;
vm)
FILES=(
"vm/${TITLE}.sh"
"frontend/public/json/${TITLE}.json"
)
;;
addon)
FILES=(
"tools/addon/${TITLE}.sh"
"frontend/public/json/${TITLE}.json"
)
;;
pve)
FILES=(
"tools/pve/${TITLE}.sh"
)
;;
esac
FILES=(
"ct/${TITLE}.sh"
"install/${TITLE}-install.sh"
"frontend/public/json/${TITLE}.json"
)
EXISTS=false
for FILE in "${FILES[@]}"; do
@@ -110,79 +52,33 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TITLE="${{ env.TITLE }}"
SCRIPT_TYPE="${{ env.SCRIPT_TYPE }}"
branch="delete_files_${TITLE}"
branch=$(echo "delete-files_${{ github.event.issue.number }}_${TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/ /_/g')
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b $branch
# Delete files based on script type
case "$SCRIPT_TYPE" in
ct)
rm -f "ct/${TITLE}.sh"
rm -f "ct/headers/${TITLE}"
rm -f "install/${TITLE}-install.sh"
rm -f "json/${TITLE}.json"
# Also try alpine variant
if [[ "$TITLE" == alpine-* ]]; then
stripped="${TITLE#alpine-}"
rm -f "json/${stripped}.json"
fi
;;
vm)
rm -f "vm/${TITLE}.sh"
rm -f "json/${TITLE}.json"
;;
addon)
rm -f "tools/addon/${TITLE}.sh"
rm -f "json/${TITLE}.json"
;;
pve)
rm -f "tools/pve/${TITLE}.sh"
;;
esac
rm -f ct/${TITLE}.sh
rm -f install/${TITLE}-install.sh
rm -f frontend/public/json/${TITLE}.json
git add .
if git diff --staged --quiet; then
echo "No files to delete. Exiting..."
exit 0
fi
git commit -m "Deleted files for issue: ${{ github.event.issue.title }}"
git push origin $branch
gh pr create --title "Delete Files for ${{ github.event.issue.title }} after Merge to Main" --body "Delete files after merge in main repo." --base main --head $branch
git commit -m "Delete ${TITLE} (${SCRIPT_TYPE}) after migration to ProxmoxVE"
git push origin $branch --force
gh pr create \
--title "Delete ${TITLE} after Merge to Main" \
--body "Automated cleanup: Delete ${TITLE} (${SCRIPT_TYPE}) files after successful migration to ProxmoxVE main repo." \
--base main \
--head $branch
pr_number=$(gh pr list --head $branch --json number --jq '.[].number')
echo "pr_number=$pr_number" >> $GITHUB_ENV
echo "branch=$branch" >> $GITHUB_ENV
- name: Approve pull request and merge
if: env.pr_number != ''
env:
GH_TOKEN: ${{ steps.generate-token-merge.outputs.token }}
run: |
git config --global user.name "github-actions-automerge[bot]"
git config --global user.email "github-actions-automerge[bot]@users.noreply.github.com"
PR_NUMBER="${{ env.pr_number }}"
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
gh pr merge $PR_NUMBER --squash --admin
fi
pr_number=$(gh pr list | grep -m 1 $branch | awk '{print $1}')
#gh pr merge $pr_number --squash
echo pr_number=$pr_number >> $GITHUB_ENV
- name: Comment on Issue
if: env.pr_number != ''
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issue_number = context.payload.issue.number;
const message = `Files deleted with PR #${process.env.pr_number}`;
github.rest.issues.createComment({
owner: context.repo.owner,

77
.github/workflows/frontend-cicd.yml vendored Normal file
View File

@@ -0,0 +1,77 @@
# Based on https://github.com/actions/starter-workflows/blob/main/pages/nextjs.yml
name: Frontend CI/CD
on:
push:
branches: ["main"]
paths:
- frontend/**
pull_request:
branches: ["main"]
types: [opened, synchronize, reopened, edited]
paths:
- frontend/**
workflow_dispatch:
permissions:
contents: read
concurrency:
group: pages-${{ github.ref }}
cancel-in-progress: false
jobs:
build:
if: github.repository == 'community-scripts/ProxmoxVED'
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend # Set default working directory for all run steps
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci --prefer-offline --legacy-peer-deps
- name: Run tests
run: npm run test
- name: Configure Next.js for pages
uses: actions/configure-pages@v5
with:
static_site_generator: next
- name: Build with Next.js
run: npm run build
- name: Upload artifact
if: github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v3
with:
path: frontend/out
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main' && github.repository == 'community-scripts/ProxmoxVED'
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

View File

@@ -17,19 +17,17 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v2
with:
repository: community-scripts/ProxmoxVED
ref: main
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: community-scripts
repositories: ProxmoxVED
- name: Crawl from Github API
env:
@@ -47,7 +45,7 @@ jobs:
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "GitHub Actions[bot]"
git checkout -b update_versions || git checkout update_versions
git add json/versions.json
git add frontend/public/json/versions.json
if git diff --cached --quiet; then
echo "No changes detected."
echo "changed=false" >> "$GITHUB_ENV"
@@ -70,3 +68,13 @@ jobs:
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
fi
- name: Re-approve pull request after update
if: env.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=$(gh pr list --head "update_versions" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
fi

View File

@@ -24,21 +24,10 @@ jobs:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v1
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@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:
@@ -46,7 +35,7 @@ jobs:
run: |
page=1
projects_file="project_json"
output_file="json/versions.json"
output_file="frontend/public/json/versions.json"
echo "[]" > $output_file
@@ -95,7 +84,7 @@ jobs:
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "GitHub Actions[bot]"
git checkout -b update_versions || git checkout update_versions
git add json/versions.json
git add frontend/public/json/versions.json
if git diff --cached --quiet; then
echo "No changes detected."
echo "changed=false" >> "$GITHUB_ENV"
@@ -121,12 +110,10 @@ jobs:
- name: Approve pull request and merge
if: env.changed == 'true'
env:
GH_TOKEN: ${{ steps.generate-token-merge.outputs.token }}
GH_TOKEN: ${{ secrets.PAT_MICHEL }}
run: |
git config --global user.name "github-actions-automege[bot]"
git config --global user.email "github-actions-automege[bot]@users.noreply.github.com"
PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[0].number')
PR_NUMBER=$(gh pr list --head "update_versions" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review "$PR_NUMBER" --approve
gh pr merge "$PR_NUMBER" --squash --admin
gh pr review $PR_NUMBER --approve
gh pr merge $PR_NUMBER --squash --delete-branch --admin
fi

226
.github/workflows/live/changelog-pr.yml vendored Normal file
View File

@@ -0,0 +1,226 @@
name: Create Changelog Pull Request
on:
push:
branches: ["main"]
workflow_dispatch:
jobs:
update-changelog-pull-request:
runs-on: ubuntu-latest
env:
CONFIG_PATH: .github/changelog-pr-config.json
BRANCH_NAME: github-action-update-changelog
AUTOMATED_PR_LABEL: "automated pr"
permissions:
contents: write
pull-requests: write
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get latest dates in changelog
run: |
DATES=$(grep -E '^## [0-9]{4}-[0-9]{2}-[0-9]{2}' CHANGELOG.md | head -n 2 | awk '{print $2}')
LATEST_DATE=$(echo "$DATES" | sed -n '1p')
SECOND_LATEST_DATE=$(echo "$DATES" | sed -n '2p')
TODAY=$(date -u +%Y-%m-%d)
echo "TODAY=$TODAY" >> $GITHUB_ENV
if [[ "$LATEST_DATE" == "$TODAY" ]]; then
echo "LATEST_DATE=$SECOND_LATEST_DATE" >> $GITHUB_ENV
else
echo "LATEST_DATE=$LATEST_DATE" >> $GITHUB_ENV
fi
- name: Get categorized pull requests
id: get-categorized-prs
uses: actions/github-script@v7
with:
script: |
const fs = require('fs').promises;
const path = require('path');
const configPath = path.resolve(process.env.CONFIG_PATH);
const fileContent = await fs.readFile(configPath, 'utf-8');
const changelogConfig = JSON.parse(fileContent);
const categorizedPRs = changelogConfig.map(obj => ({
...obj,
notes: [],
subCategories: obj.subCategories ?? (
obj.labels.includes("update script") ? [
{ title: "🐞 Bug Fixes", labels: ["bugfix"], notes: [] },
{ title: "✨ New Features", labels: ["feature"], notes: [] },
{ title: "💥 Breaking Changes", labels: ["breaking change"], notes: [] }
] :
obj.labels.includes("maintenance") ? [
{ title: "🐞 Bug Fixes", labels: ["bugfix"], notes: [] },
{ title: "✨ New Features", labels: ["feature"], notes: [] },
{ title: "💥 Breaking Changes", labels: ["breaking change"], notes: [] },
{ title: "📡 API", labels: ["api"], notes: [] },
{ title: "Github", labels: ["github"], notes: [] }
] :
obj.labels.includes("website") ? [
{ title: "🐞 Bug Fixes", labels: ["bugfix"], notes: [] },
{ title: "✨ New Features", labels: ["feature"], notes: [] },
{ title: "💥 Breaking Changes", labels: ["breaking change"], notes: [] },
{ title: "Script Information", labels: ["json"], notes: [] }
] : []
)
}));
const latestDateInChangelog = new Date(process.env.LATEST_DATE);
latestDateInChangelog.setUTCHours(23, 59, 59, 999);
const { data: pulls } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
base: "main",
state: "closed",
sort: "updated",
direction: "desc",
per_page: 100,
});
pulls.filter(pr =>
pr.merged_at &&
new Date(pr.merged_at) > latestDateInChangelog &&
!pr.labels.some(label =>
["invalid", "wontdo", process.env.AUTOMATED_PR_LABEL].includes(label.name.toLowerCase())
)
).forEach(pr => {
const prLabels = pr.labels.map(label => label.name.toLowerCase());
const prNote = `- ${pr.title} [@${pr.user.login}](https://github.com/${pr.user.login}) ([#${pr.number}](${pr.html_url}))`;
const updateScriptsCategory = categorizedPRs.find(category =>
category.labels.some(label => prLabels.includes(label))
);
if (updateScriptsCategory) {
const subCategory = updateScriptsCategory.subCategories.find(sub =>
sub.labels.some(label => prLabels.includes(label))
);
if (subCategory) {
subCategory.notes.push(prNote);
} else {
updateScriptsCategory.notes.push(prNote);
}
}
});
console.log(JSON.stringify(categorizedPRs, null, 2));
return categorizedPRs;
- name: Update CHANGELOG.md
uses: actions/github-script@v7
with:
script: |
const fs = require('fs').promises;
const path = require('path');
const today = process.env.TODAY;
const latestDateInChangelog = process.env.LATEST_DATE;
const changelogPath = path.resolve('CHANGELOG.md');
const categorizedPRs = ${{ steps.get-categorized-prs.outputs.result }};
console.log(JSON.stringify(categorizedPRs, null, 2));
let newReleaseNotes = `## ${today}\n\n`;
for (const { title, notes, subCategories } of categorizedPRs) {
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`;
}
}
}
}
const changelogContent = await fs.readFile(changelogPath, 'utf-8');
const changelogIncludesTodaysReleaseNotes = changelogContent.includes(`\n## ${today}`);
const regex = changelogIncludesTodaysReleaseNotes
? new RegExp(`## ${today}.*(?=## ${latestDateInChangelog})`, "gs")
: new RegExp(`(?=## ${latestDateInChangelog})`, "gs");
const newChangelogContent = changelogContent.replace(regex, newReleaseNotes);
await fs.writeFile(changelogPath, newChangelogContent);
- name: Check for changes
id: verify-diff
run: |
git diff --quiet . || echo "changed=true" >> $GITHUB_ENV
- name: Commit and push changes
if: env.changed == 'true'
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add CHANGELOG.md
git commit -m "Update CHANGELOG.md"
git checkout -b $BRANCH_NAME || git checkout $BRANCH_NAME
git push origin $BRANCH_NAME --force
- name: Create pull request if not exists
if: env.changed == 'true'
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |
PR_EXISTS=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number')
if [ -z "$PR_EXISTS" ]; then
gh pr create --title "[Github Action] Update CHANGELOG.md" \
--body "This PR is auto-generated by a Github Action to update the CHANGELOG.md file." \
--head $BRANCH_NAME \
--base main \
--label "$AUTOMATED_PR_LABEL"
fi
- name: Approve pull request
if: env.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
fi
- name: Re-approve pull request after update
if: env.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
fi

View File

@@ -0,0 +1,122 @@
name: Close Discussion on PR Merge
on:
pull_request:
types: [closed]
jobs:
close-discussion:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set Up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Dependencies
run: npm install zx @octokit/graphql
- name: Close Discussion
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_BODY: ${{ github.event.pull_request.body }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
run: |
npx zx << 'EOF'
import { graphql } from "@octokit/graphql";
(async function() {
try {
const token = process.env.GITHUB_TOKEN;
const prBody = process.env.PR_BODY;
const prNumber = process.env.PR_NUMBER;
const owner = process.env.REPO_OWNER;
const repo = process.env.REPO_NAME;
if (!token || !prBody || !prNumber || !owner || !repo) {
console.log("Missing required environment variables.");
process.exit(1);
}
const match = prBody.match(/#(\d+)/);
if (!match) {
console.log("No discussion ID found in PR body.");
return;
}
const discussionNumber = match[1];
console.log(`Extracted Discussion Number: ${discussionNumber}`);
console.log(`PR Number: ${prNumber}`);
console.log(`Repository: ${owner}/${repo}`);
const graphqlWithAuth = graphql.defaults({
headers: { authorization: `Bearer ${token}` },
});
const discussionQuery = `
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
discussion(number: $number) {
id
}
}
}
`;
const discussionResponse = await graphqlWithAuth(discussionQuery, {
owner,
repo,
number: parseInt(discussionNumber, 10),
});
const discussionQLId = discussionResponse.repository.discussion.id;
if (!discussionQLId) {
console.log("Failed to fetch discussion GraphQL ID.");
return;
}
console.log(`GraphQL Discussion ID: ${discussionQLId}`);
const commentMutation = `
mutation($discussionId: ID!, $body: String!) {
addDiscussionComment(input: { discussionId: $discussionId, body: $body }) {
comment { id body }
}
}
`;
const commentResponse = await graphqlWithAuth(commentMutation, {
discussionId: discussionQLId,
body: `Merged with PR #${prNumber}`,
});
const commentId = commentResponse.addDiscussionComment.comment.id;
if (!commentId) {
console.log("Failed to post the comment.");
return;
}
console.log(`Comment Posted Successfully! Comment ID: ${commentId}`);
const markAnswerMutation = `
mutation($id: ID!) {
markDiscussionCommentAsAnswer(input: { id: $id }) {
discussion { id title }
}
}
`;
await graphqlWithAuth(markAnswerMutation, { id: commentId });
console.log("Comment marked as answer successfully!");
} catch (error) {
console.error("Error:", error);
return;
}
})();
EOF

View File

@@ -0,0 +1,37 @@
name: Build and Publish Docker Image
on:
push:
branches:
- main
paths:
- '.github/runner/docker/**'
schedule:
- cron: '0 0 * * *'
jobs:
build:
runs-on: ubuntu-latest #To ensure it always builds we use the github runner with all the right tooling
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Log in to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
run: |
repo_name=${{ github.repository }} # Get repository name
repo_name_lower=$(echo $repo_name | tr '[:upper:]' '[:lower:]') # Convert to lowercase
docker build -t ghcr.io/$repo_name_lower/gh-runner-self:latest -f .github/runner/docker/gh-runner-self.dockerfile .
- name: Push Docker image to GHCR
run: |
repo_name=${{ github.repository }} # Get repository name
repo_name_lower=$(echo $repo_name | tr '[:upper:]' '[:lower:]') # Convert to lowercase
docker push ghcr.io/$repo_name_lower/gh-runner-self:latest

View File

@@ -0,0 +1,28 @@
name: Delete JSON date PR Branch
on:
pull_request:
types: [closed]
branches:
- main
jobs:
delete_branch:
runs-on: ubuntu-latest
steps:
- name: Checkout the code
uses: actions/checkout@v3
- name: Delete PR Update Branch
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'pr-update-json-')
run: |
PR_BRANCH="${{ github.event.pull_request.head.ref }}"
echo "Deleting branch $PR_BRANCH..."
# Avoid deleting the default branch (e.g., main)
if [[ "$PR_BRANCH" != "main" ]]; then
git push origin --delete "$PR_BRANCH"
else
echo "Skipping deletion of the main branch"
fi

View File

@@ -0,0 +1,57 @@
name: Create Daily Release
on:
schedule:
- cron: '1 0 * * *' # Runs daily at 00:01 UTC
workflow_dispatch:
jobs:
create-daily-release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Extract first 5000 characters from CHANGELOG.md
run: head -c 5000 CHANGELOG.md > changelog_cropped.md
- name: Debugging - Show extracted changelog
run: |
echo "=== CHANGELOG EXCERPT ==="
cat changelog_cropped.md
echo "========================="
- name: Parse CHANGELOG.md and create release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
YESTERDAY=$(date -u --date="yesterday" +%Y-%m-%d)
echo "Checking for changes on: $YESTERDAY"
# Ensure yesterday's date exists in the changelog
if ! grep -q "## $YESTERDAY" changelog_cropped.md; then
echo "No entry found for $YESTERDAY, skipping release."
exit 0
fi
# Extract section for yesterday's date
awk -v date="## $YESTERDAY" '
$0 ~ date {found=1; next}
found && /^## [0-9]{4}-[0-9]{2}-[0-9]{2}/ {exit}
found
' changelog_cropped.md > changelog_tmp.md
echo "=== Extracted Changelog ==="
cat changelog_tmp.md
echo "==========================="
# Skip if no content was found
if [ ! -s changelog_tmp.md ]; then
echo "No changes found for $YESTERDAY, skipping release."
exit 0
fi
# Create GitHub release
gh release create "$YESTERDAY" -t "$YESTERDAY" -F changelog_tmp.md

177
.github/workflows/live/script-test.yml vendored Normal file
View File

@@ -0,0 +1,177 @@
name: Run Scripts on PVE Node for testing
permissions:
pull-requests: write
on:
pull_request_target:
branches:
- main
paths:
- 'install/**.sh'
- 'ct/**.sh'
jobs:
run-install-script:
runs-on: pvenode
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: 0
- name: Add Git safe directory
run: |
git config --global --add safe.directory /__w/ProxmoxVED/ProxmoxVE
- name: Set up GH_TOKEN
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "GH_TOKEN=${GH_TOKEN}" >> $GITHUB_ENV
- name: Get Changed Files
run: |
CHANGED_FILES=$(gh pr diff ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --name-only)
CHANGED_FILES=$(echo "$CHANGED_FILES" | tr '\n' ' ')
echo "Changed files: $CHANGED_FILES"
echo "SCRIPT=$CHANGED_FILES" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get scripts
id: check-install-script
run: |
ALL_FILES=()
ADDED_FILES=()
for FILE in ${{ env.SCRIPT }}; do
if [[ $FILE =~ ^install/.*-install\.sh$ ]] || [[ $FILE =~ ^ct/.*\.sh$ ]]; then
STRIPPED_NAME=$(basename "$FILE" | sed 's/-install//' | sed 's/\.sh$//')
if [[ ! " ${ADDED_FILES[@]} " =~ " $STRIPPED_NAME " ]]; then
ALL_FILES+=("$FILE")
ADDED_FILES+=("$STRIPPED_NAME") # Mark this base file as added (without the path)
fi
fi
done
ALL_FILES=$(echo "${ALL_FILES[@]}" | xargs)
echo "$ALL_FILES"
echo "ALL_FILES=$ALL_FILES" >> $GITHUB_ENV
- name: Run scripts
id: run-install
continue-on-error: true
run: |
set +e
#run for each files in /ct
for FILE in ${{ env.ALL_FILES }}; do
STRIPPED_NAME=$(basename "$FILE" | sed 's/-install//' | sed 's/\.sh$//')
echo "Running Test for: $STRIPPED_NAME"
if grep -E -q 'read\s+-r\s+-p\s+".*"\s+\w+' "$FILE"; then
echo "The script contains an interactive prompt. Skipping execution."
continue
fi
if [[ $FILE =~ ^install/.*-install\.sh$ ]]; then
CT_SCRIPT="ct/$STRIPPED_NAME.sh"
if [[ ! -f $CT_SCRIPT ]]; then
echo "No CT script found for $STRIPPED_NAME"
ERROR_MSG="No CT script found for $FILE"
echo "$ERROR_MSG" > result_$STRIPPED_NAME.log
continue
fi
if grep -E -q 'read\s+-r\s+-p\s+".*"\s+\w+' "install/$STRIPPED_NAME-install.sh"; then
echo "The script contains an interactive prompt. Skipping execution."
continue
fi
echo "Found CT script for $STRIPPED_NAME"
chmod +x "$CT_SCRIPT"
RUNNING_FILE=$CT_SCRIPT
elif [[ $FILE =~ ^ct/.*\.sh$ ]]; then
INSTALL_SCRIPT="install/$STRIPPED_NAME-install.sh"
if [[ ! -f $INSTALL_SCRIPT ]]; then
echo "No install script found for $STRIPPED_NAME"
ERROR_MSG="No install script found for $FILE"
echo "$ERROR_MSG" > result_$STRIPPED_NAME.log
continue
fi
echo "Found install script for $STRIPPED_NAME"
chmod +x "$INSTALL_SCRIPT"
RUNNING_FILE=$FILE
if grep -E -q 'read\s+-r\s+-p\s+".*"\s+\w+' "ct/$STRIPPED_NAME.sh"; then
echo "The script contains an interactive prompt. Skipping execution."
continue
fi
fi
git remote add community-scripts https://github.com/community-scripts/ProxmoxVE.git
git fetch community-scripts
rm -f .github/workflows/scripts/app-test/pr-build.func || true
rm -f .github/workflows/scripts/app-test/pr-install.func || true
rm -f .github/workflows/scripts/app-test/pr-alpine-install.func || true
rm -f .github/workflows/scripts/app-test/pr-create-lxc.sh || true
git checkout community-scripts/main -- .github/workflows/scripts/app-test/pr-build.func
git checkout community-scripts/main -- .github/workflows/scripts/app-test/pr-install.func
git checkout community-scripts/main -- .github/workflows/scripts/app-test/pr-alpine-install.func
git checkout community-scripts/main -- .github/workflows/scripts/app-test/pr-create-lxc.sh
chmod +x $RUNNING_FILE
chmod +x .github/workflows/scripts/app-test/pr-create-lxc.sh
chmod +x .github/workflows/scripts/app-test/pr-install.func
chmod +x .github/workflows/scripts/app-test/pr-alpine-install.func
chmod +x .github/workflows/scripts/app-test/pr-build.func
sed -i 's|source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)|source .github/workflows/scripts/app-test/pr-build.func|g' "$RUNNING_FILE"
echo "Executing $RUNNING_FILE"
ERROR_MSG=$(./$RUNNING_FILE 2>&1 > /dev/null)
echo "Finished running $FILE"
if [ -n "$ERROR_MSG" ]; then
echo "ERROR in $STRIPPED_NAME: $ERROR_MSG"
echo "$ERROR_MSG" > result_$STRIPPED_NAME.log
fi
done
set -e # Restore exit-on-error
- name: Cleanup PVE Node
run: |
containers=$(pct list | tail -n +2 | awk '{print $0 " " $4}' | awk '{print $1}')
for container_id in $containers; do
status=$(pct status $container_id | awk '{print $2}')
if [[ $status == "running" ]]; then
pct stop $container_id
pct destroy $container_id
fi
done
- name: Post error comments
run: |
ERROR="false"
SEARCH_LINE=".github/workflows/scripts/app-test/pr-build.func: line 255:"
# Get all existing comments on the PR
EXISTING_COMMENTS=$(gh pr view ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --json comments --jq '.comments[].body')
for FILE in ${{ env.ALL_FILES }}; do
STRIPPED_NAME=$(basename "$FILE" | sed 's/-install//' | sed 's/\.sh$//')
if [[ ! -f result_$STRIPPED_NAME.log ]]; then
continue
fi
ERROR_MSG=$(cat result_$STRIPPED_NAME.log)
if [ -n "$ERROR_MSG" ]; then
CLEANED_ERROR_MSG=$(echo "$ERROR_MSG" | sed "s|$SEARCH_LINE.*||")
COMMENT_BODY=":warning: The script _**$FILE**_ failed with the following message: <br> <div><strong>${CLEANED_ERROR_MSG}</strong></div>"
# Check if the comment already exists
if echo "$EXISTING_COMMENTS" | grep -qF "$COMMENT_BODY"; then
echo "Skipping duplicate comment for $FILE"
else
echo "Posting error message for $FILE"
gh pr comment ${{ github.event.pull_request.number }} \
--repo ${{ github.repository }} \
--body "$COMMENT_BODY"
ERROR="true"
fi
fi
done
echo "ERROR=$ERROR" >> $GITHUB_ENV

243
.github/workflows/live/script_format.yml vendored Normal file
View File

@@ -0,0 +1,243 @@
name: Script Format Check
permissions:
pull-requests: write
on:
pull_request_target:
branches:
- main
paths:
- 'install/*.sh'
- 'ct/*.sh'
jobs:
run-install-script:
runs-on: pvenode
steps:
- name: Checkout PR branch (supports forks)
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: 0
- name: Add Git safe directory
run: |
git config --global --add safe.directory /__w/ProxmoxVED/ProxmoxVE
- name: Set up GH_TOKEN
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "GH_TOKEN=${GH_TOKEN}" >> $GITHUB_ENV
- name: Get Changed Files
run: |
CHANGED_FILES=$(gh pr diff ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --name-only)
CHANGED_FILES=$(echo "$CHANGED_FILES" | tr '\n' ' ')
echo "Changed files: $CHANGED_FILES"
echo "SCRIPT=$CHANGED_FILES" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check scripts
id: run-install
continue-on-error: true
run: |
for FILE in ${{ env.SCRIPT }}; do
STRIPPED_NAME=$(basename "$FILE" | sed 's/-install//' | sed 's/\.sh$//')
echo "Running Test for: $STRIPPED_NAME"
FILE_STRIPPED="${FILE##*/}"
LOG_FILE="result_$FILE_STRIPPED.log"
if [[ $FILE =~ ^ct/.*\.sh$ ]]; then
FIRST_LINE=$(sed -n '1p' "$FILE")
[[ "$FIRST_LINE" != "#!/usr/bin/env bash" ]] && echo "Line 1 was $FIRST_LINE | Should be: #!/usr/bin/env bash" >> "$LOG_FILE"
SECOND_LINE=$(sed -n '2p' "$FILE")
[[ "$SECOND_LINE" != "source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)" ]] &&
echo "Line 2 was $SECOND_LINE | Should be: source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)" >> "$LOG_FILE"
THIRD_LINE=$(sed -n '3p' "$FILE")
if ! [[ "$THIRD_LINE" =~ ^#\ Copyright\ \(c\)\ [0-9]{4}-[0-9]{4}\ community-scripts\ ORG$ || "$THIRD_LINE" =~ ^Copyright\ \(c\)\ [0-9]{4}-[0-9]{4}\ tteck$ ]]; then
echo "Line 3 was $THIRD_LINE | Should be: # Copyright (c) 2021-2025 community-scripts ORG" >> "$LOG_FILE"
fi
EXPECTED_AUTHOR="# Author:"
EXPECTED_LICENSE="# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE"
EXPECTED_SOURCE="# Source:"
EXPECTED_EMPTY=""
for i in {4..7}; do
LINE=$(sed -n "${i}p" "$FILE")
case $i in
4)
[[ $LINE == $EXPECTED_AUTHOR* ]] || printf "Line %d was: '%s' | Should start with: '%s'\n" "$i" "$LINE" "$EXPECTED_AUTHOR" >> $LOG_FILE
;;
5)
[[ "$LINE" == "$EXPECTED_LICENSE" ]] || printf "Line %d was: '%s' | Should be: '%s'\n" "$i" "$LINE" "$EXPECTED_LICENSE" >> $LOG_FILE
;;
6)
[[ $LINE == $EXPECTED_SOURCE* ]] || printf "Line %d was: '%s' | Should start with: '%s'\n" "$i" "$LINE" "$EXPECTED_SOURCE" >> $LOG_FILE
;;
7)
[[ -z $LINE ]] || printf "Line %d was: '%s' | Should be empty\n" "$i" "$LINE" >> $LOG_FILE
;;
esac
done
EXPECTED_PREFIXES=(
"APP="
"var_tags="
"var_cpu=" # Must be a number
"var_ram=" # Must be a number
"var_disk=" # Must be a number
"var_os=" # Must be debian, alpine, or ubuntu
"var_version="
"var_unprivileged=" # Must be 0 or 1
)
for i in {8..15}; do
LINE=$(sed -n "${i}p" "$FILE")
INDEX=$((i - 8))
case $INDEX in
2|3|4) # var_cpu, var_ram, var_disk (must be numbers)
if [[ "$LINE" =~ ^${EXPECTED_PREFIXES[$INDEX]}([0-9]+)$ ]]; then
continue # Valid
else
echo "Line $i was '$LINE' | Should be: '${EXPECTED_PREFIXES[$INDEX]}<NUMBER>'" >> "$LOG_FILE"
fi
;;
5) # var_os (must be debian, alpine, or ubuntu)
if [[ "$LINE" =~ ^var_os=(debian|alpine|ubuntu)$ ]]; then
continue # Valid
else
echo "Line $i was '$LINE' | Should be: 'var_os=[debian|alpine|ubuntu]'" >> "$LOG_FILE"
fi
;;
7) # var_unprivileged (must be 0 or 1)
if [[ "$LINE" =~ ^var_unprivileged=[01]$ ]]; then
continue # Valid
else
echo "Line $i was '$LINE' | Should be: 'var_unprivileged=[0|1]'" >> "$LOG_FILE"
fi
;;
*) # Other lines (must start with expected prefix)
if [[ "$LINE" == ${EXPECTED_PREFIXES[$INDEX]}* ]]; then
continue # Valid
else
echo "Line $i was '$LINE' | Should start with '${EXPECTED_PREFIXES[$INDEX]}'" >> "$LOG_FILE"
fi
;;
esac
done
for i in {16..20}; do
LINE=$(sed -n "${i}p" "$FILE")
EXPECTED=(
"header_info \"$APP\""
"variables"
"color"
"catch_errors"
"function update_script() {"
)
[[ "$LINE" != "${EXPECTED[$((i-16))]}" ]] && echo "Line $i was $LINE | Should be: ${EXPECTED[$((i-16))]}" >> "$LOG_FILE"
done
cat "$LOG_FILE"
elif [[ $FILE =~ ^install/.*-install\.sh$ ]]; then
FIRST_LINE=$(sed -n '1p' "$FILE")
[[ "$FIRST_LINE" != "#!/usr/bin/env bash" ]] && echo "Line 1 was $FIRST_LINE | Should be: #!/usr/bin/env bash" >> "$LOG_FILE"
SECOND_LINE=$(sed -n '2p' "$FILE")
[[ -n "$SECOND_LINE" ]] && echo "Line 2 should be empty" >> "$LOG_FILE"
THIRD_LINE=$(sed -n '3p' "$FILE")
if ! [[ "$THIRD_LINE" =~ ^#\ Copyright\ \(c\)\ [0-9]{4}-[0-9]{4}\ community-scripts\ ORG$ || "$THIRD_LINE" =~ ^Copyright\ \(c\)\ [0-9]{4}-[0-9]{4}\ tteck$ ]]; then
echo "Line 3 was $THIRD_LINE | Should be: # Copyright (c) 2021-2025 community-scripts ORG" >> "$LOG_FILE"
fi
EXPECTED_AUTHOR="# Author:"
EXPECTED_LICENSE="# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE"
EXPECTED_SOURCE="# Source:"
EXPECTED_EMPTY=""
for i in {4..7}; do
LINE=$(sed -n "${i}p" "$FILE")
case $i in
4)
[[ $LINE == $EXPECTED_AUTHOR* ]] || printf "Line %d was: '%s' | Should start with: '%s'\n" "$i" "$LINE" "$EXPECTED_AUTHOR" >> $LOG_FILE
;;
5)
[[ "$LINE" == "$EXPECTED_LICENSE" ]] || printf "Line %d was: '%s' | Should be: '%s'\n" "$i" "$LINE" "$EXPECTED_LICENSE" >> $LOG_FILE
;;
6)
[[ $LINE == $EXPECTED_SOURCE* ]] || printf "Line %d was: '%s' | Should start with: '%s'\n" "$i" "$LINE" "$EXPECTED_SOURCE" >> $LOG_FILE
;;
7)
[[ -z $LINE ]] || printf "Line %d was: '%s' | Should be empty\n" "$i" "$LINE" >> $LOG_FILE
;;
esac
done
[[ "$(sed -n '8p' "$FILE")" != 'source /dev/stdin <<< "$FUNCTIONS_FILE_PATH"' ]] && echo 'Line 8 should be: source /dev/stdin <<< "$FUNCTIONS_FILE_PATH"' >> "$LOG_FILE"
for i in {9..14}; do
LINE=$(sed -n "${i}p" "$FILE")
EXPECTED=(
"color"
"verb_ip6"
"catch_errors"
"setting_up_container"
"network_check"
"update_os"
)
[[ "$LINE" != "${EXPECTED[$((i-9))]}" ]] && echo "Line $i was $LINE | Should be: ${EXPECTED[$((i-9))]}" >> "$LOG_FILE"
done
[[ -n "$(sed -n '15p' "$FILE")" ]] && echo "Line 15 should be empty" >> "$LOG_FILE"
[[ "$(sed -n '16p' "$FILE")" != 'msg_info "Installing Dependencies"' ]] && echo 'Line 16 should be: msg_info "Installing Dependencies"' >> "$LOG_FILE"
LAST_3_LINES=$(tail -n 3 "$FILE")
[[ "$LAST_3_LINES" != *"$STD apt-get -y autoremove"* ]] && echo 'Third to last line should be: $STD apt-get -y autoremove' >> "$LOG_FILE"
[[ "$LAST_3_LINES" != *"$STD apt-get -y autoclean"* ]] && echo 'Second to last line should be: $STD apt-get -y clean' >> "$LOG_FILE"
[[ "$LAST_3_LINES" != *'msg_ok "Cleaned"'* ]] && echo 'Last line should be: msg_ok "Cleaned"' >> "$LOG_FILE"
cat "$LOG_FILE"
fi
done
- name: Post error comments
run: |
ERROR="false"
for FILE in ${{ env.SCRIPT }}; do
FILE_STRIPPED="${FILE##*/}"
LOG_FILE="result_$FILE_STRIPPED.log"
echo $LOG_FILE
if [[ ! -f $LOG_FILE ]]; then
continue
fi
ERROR_MSG=$(cat $LOG_FILE)
if [ -n "$ERROR_MSG" ]; then
echo "Posting error message for $FILE"
echo ${ERROR_MSG}
gh pr comment ${{ github.event.pull_request.number }} \
--repo ${{ github.repository }} \
--body ":warning: The script _**$FILE**_ has the following formatting errors: <br> <div><strong>${ERROR_MSG}</strong></div>"
ERROR="true"
fi
done
echo "ERROR=$ERROR" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Fail if error
if: ${{ env.ERROR == 'true' }}
run: exit 1

View File

@@ -0,0 +1,131 @@
name: Update JSON Date
on:
push:
branches:
- main
paths:
- 'json/**.json'
workflow_dispatch:
jobs:
update-app-files:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- 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 }}
run: |
echo "GH_TOKEN=${GH_TOKEN}" >> $GITHUB_ENV
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 2 # Ensure we have the last two commits
- name: Get Previous Commit
id: prev_commit
run: |
PREV_COMMIT=$(git rev-parse HEAD^)
echo "Previous commit: $PREV_COMMIT"
echo "prev_commit=$PREV_COMMIT" >> $GITHUB_ENV
- name: Get Newly Added JSON Files
id: new_json_files
run: |
git diff --name-only --diff-filter=A ${{ env.prev_commit }} HEAD | grep '^json/.*\.json$' > new_files.txt || true
echo "New files detected:"
cat new_files.txt || echo "No new files."
- name: Disable file mode changes
run: git config core.fileMode false
- name: Set up Git
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Change JSON Date
id: change-json-date
run: |
current_date=$(date +"%Y-%m-%d")
while IFS= read -r file; do
# Skip empty lines
[[ -z "$file" ]] && continue
if [[ -f "$file" ]]; then
echo "Processing $file..."
current_json_date=$(jq -r '.date_created // empty' "$file")
if [[ -z "$current_json_date" || "$current_json_date" != "$current_date" ]]; then
echo "Updating $file with date $current_date"
jq --arg date "$current_date" '.date_created = $date' "$file" > temp.json && mv temp.json "$file"
else
echo "Date in $file is already up to date."
fi
else
echo "Warning: File $file not found!"
fi
done < new_files.txt
rm new_files.txt
- name: Check if there are any changes
run: |
echo "Checking for changes..."
git add -A # Untracked Dateien aufnehmen
git status
if git diff --cached --quiet; then
echo "No changes detected."
echo "changed=false" >> "$GITHUB_ENV"
else
echo "Changes detected:"
git diff --stat --cached
echo "changed=true" >> "$GITHUB_ENV"
fi
# Step 7: Commit and create PR if changes exist
- 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 }}
gh pr create --title "[core] update date in json" \
--body "This PR is auto-generated by a GitHub Action to update the date in json." \
--head ${{ env.BRANCH_NAME }} \
--base main \
--label "automated pr"
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
- name: Approve pull request
if: env.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=$(gh pr list --head "${{ env.BRANCH_NAME }}" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
fi
- name: No changes detected
if: env.changed == 'false'
run: echo "No changes to commit. Workflow completed successfully."

View File

@@ -0,0 +1,161 @@
name: Validate filenames
on:
pull_request_target:
paths:
- "ct/*.sh"
- "install/*.sh"
- "json/*.json"
jobs:
check-files:
name: Check changed files
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Get pull request information
if: github.event_name == 'pull_request_target'
uses: actions/github-script@v7
id: pr
with:
script: |
const { data: pullRequest } = await github.rest.pulls.get({
...context.repo,
pull_number: context.payload.pull_request.number,
});
return pullRequest;
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Ensure the full history is fetched for accurate diffing
ref: ${{ github.event_name == 'pull_request_target' && fromJSON(steps.pr.outputs.result).merge_commit_sha || '' }}
- name: Get changed files
id: changed-files
run: |
if ${{ github.event_name == 'pull_request_target' }}; then
echo "files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ steps.pr.outputs.result && fromJSON(steps.pr.outputs.result).merge_commit_sha }} | xargs)" >> $GITHUB_OUTPUT
else
echo "files=$(git diff --name-only ${{ github.event.before }} ${{ github.event.after }} | xargs)" >> $GITHUB_OUTPUT
fi
- name: "Validate filenames in ct and install directory"
if: always() && steps.changed-files.outputs.files != ''
id: check-scripts
run: |
CHANGED_FILES=$(printf "%s\n" ${{ steps.changed-files.outputs.files }} | { grep -E '^(ct|install)/.*\.sh$' || true; })
NON_COMPLIANT_FILES=""
for FILE in $CHANGED_FILES; do
# Datei "ct/create_lxc.sh" explizit überspringen
if [[ "$FILE" == "ct/create_lxc.sh" ]]; then
continue
fi
BASENAME=$(echo "$(basename "${FILE%.*}")")
if [[ ! "$BASENAME" =~ ^[a-z0-9-]+$ ]]; then
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE"
fi
done
if [ -n "$NON_COMPLIANT_FILES" ]; then
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT
echo "Non-compliant filenames found, change to lowercase:"
for FILE in $NON_COMPLIANT_FILES; do
echo "$FILE"
done
exit 1
fi
- name: "Validate filenames in json directory."
if: always() && steps.changed-files.outputs.files != ''
id: check-json
run: |
CHANGED_FILES=$(printf "%s\n" ${{ steps.changed-files.outputs.files }} | { grep -E '^json/.*\.json$' || true; })
NON_COMPLIANT_FILES=""
for FILE in $CHANGED_FILES; do
BASENAME=$(echo "$(basename "${FILE%.*}")")
if [[ ! "$BASENAME" =~ ^[a-z0-9-]+$ ]]; then
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE"
fi
done
if [ -n "$NON_COMPLIANT_FILES" ]; then
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT
echo "Non-compliant filenames found, change to lowercase:"
for FILE in $NON_COMPLIANT_FILES; do
echo "$FILE"
done
exit 1
fi
- name: Post results and comment
if: always() && steps.check-scripts.outputs.files != '' && steps.check-json.outputs.files != '' && github.event_name == 'pull_request_target'
uses: actions/github-script@v7
with:
script: |
const result = "${{ job.status }}" === "success" ? "success" : "failure";
const nonCompliantFiles = {
script: "${{ steps.check-scripts.outputs.files }}",
JSON: "${{ steps.check-json.outputs.files }}",
};
const issueNumber = context.payload.pull_request
? context.payload.pull_request.number
: null;
const commentIdentifier = "validate-filenames";
let newCommentBody = `<!-- ${commentIdentifier}-start -->\n### Filename validation\n\n`;
if (result === "failure") {
newCommentBody += ":x: We found issues in the following changed files:\n\n";
for (const [check, files] of Object.entries(nonCompliantFiles)) {
if (files) {
newCommentBody += `**${check.charAt(0).toUpperCase() + check.slice(1)} filename invalid:**\n${files
.trim()
.split(" ")
.map((file) => `- ${file}`)
.join("\n")}\n\n`;
}
}
newCommentBody +=
"Please change the filenames to lowercase and use only alphanumeric characters and dashes.\n";
} else {
newCommentBody += `:rocket: All files passed filename validation!\n`;
}
newCommentBody += `\n\n<!-- ${commentIdentifier}-end -->`;
if (issueNumber) {
const { data: comments } = await github.rest.issues.listComments({
...context.repo,
issue_number: issueNumber,
});
const existingComment = comments.find(
(comment) => comment.user.login === "github-actions[bot]",
);
if (existingComment) {
if (existingComment.body.includes(commentIdentifier)) {
const re = new RegExp(String.raw`<!-- ${commentIdentifier}-start -->[\s\S]*?<!-- ${commentIdentifier}-end -->`, "");
newCommentBody = existingComment.body.replace(re, newCommentBody);
} else {
newCommentBody = existingComment.body + '\n\n---\n\n' + newCommentBody;
}
await github.rest.issues.updateComment({
...context.repo,
comment_id: existingComment.id,
body: newCommentBody,
});
} else {
await github.rest.issues.createComment({
...context.repo,
issue_number: issueNumber,
body: newCommentBody,
});
}
}

View File

@@ -18,7 +18,7 @@ jobs:
steps:
- name: Generate a token
id: app-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.PUSH_MAIN_APP_ID }}
private-key: ${{ secrets.PUSH_MAIN_APP_SECRET }}
@@ -34,98 +34,63 @@ jobs:
repository: community-scripts/ProxmoxVED
token: ${{ secrets.GH_MERGE_PAT }}
- name: List Issues and Extract Script Type
- name: List Issues in Repository
id: list_issues
env:
GH_TOKEN: ${{ github.token }}
run: |
echo "Filtering Issues with Label Migration To ProxmoxVE"
raw_output=$(gh issue list --json title,labels,number,body)
filtered_issue=$(echo "$raw_output" | jq -r '[.[] | select(.labels[]?.name == "Migration To ProxmoxVE")][0]')
raw_output=$(gh issue list --json title,labels,number)
filtered_issues=$(echo "$raw_output" | jq -r '.[] | select(.labels[]?.name == "Migration To ProxmoxVE") | .title' | head -n 1)
issue_nr=$(echo "$raw_output" | jq -r '.[] | select(.labels[]?.name == "Migration To ProxmoxVE") | .number' | head -n 1)
if [ "$filtered_issue" == "null" ] || [ -z "$filtered_issue" ]; then
if [ -z "$filtered_issues" ]; then
echo "No issues found with label 'Migration To ProxmoxVE'."
exit 1
fi
script_name=$(echo "$filtered_issue" | jq -r '.title' | tr '[:upper:]' '[:lower:]' | tr -d ' ')
issue_nr=$(echo "$filtered_issue" | jq -r '.number')
issue_body=$(echo "$filtered_issue" | jq -r '.body')
echo "Script Name: $script_name"
echo "Issue Number: $issue_nr"
# Detect script type from issue body
if echo "$issue_body" | grep -qi "CT (LXC Container)"; then
script_type="ct"
elif echo "$issue_body" | grep -qi "VM (Virtual Machine)"; then
script_type="vm"
elif echo "$issue_body" | grep -qi "Addon (tools/addon)"; then
script_type="addon"
elif echo "$issue_body" | grep -qi "PVE Tool (tools/pve)"; then
script_type="pve"
else
# Fallback detection by filename pattern
if [[ "$script_name" == *"-vm"* ]]; then
script_type="vm"
else
script_type="ct"
fi
script_name=$(echo "$filtered_issues" | head -n 1) # Nur das erste Issue nehmen
script_name_lowercase=$(echo "$script_name" | tr '[:upper:]' '[:lower:]' | tr -d ' ')
echo "Script Name: $script_name_lowercase"
echo "script_name=$script_name_lowercase" >> $GITHUB_OUTPUT
echo "issue_nr=$issue_nr" >> $GITHUB_OUTPUT
fi
echo "Script Type: $script_type"
echo "script_name=$script_name" >> $GITHUB_OUTPUT
echo "issue_nr=$issue_nr" >> $GITHUB_OUTPUT
echo "script_type=$script_type" >> $GITHUB_OUTPUT
- name: Check if script files exist
id: check_files
run: |
script_name="${{ steps.list_issues.outputs.script_name }}"
script_type="${{ steps.list_issues.outputs.script_type }}"
files_found="true"
missing_files=""
ct_file="ct/${script_name}.sh"
install_file="install/${script_name}-install.sh"
json_file="frontend/public/json/${script_name}.json"
# Check files based on script type
case "$script_type" in
ct)
if [[ ! -f "ct/${script_name}.sh" ]]; then
echo "ct file not found: ct/${script_name}.sh"
files_found="false"
missing_files+="ct/${script_name}.sh "
fi
if [[ ! -f "install/${script_name}-install.sh" ]]; then
echo "install file not found: install/${script_name}-install.sh"
files_found="false"
missing_files+="install/${script_name}-install.sh "
fi
;;
vm)
if [[ ! -f "vm/${script_name}.sh" ]]; then
echo "vm file not found: vm/${script_name}.sh"
files_found="false"
missing_files+="vm/${script_name}.sh "
fi
;;
addon)
if [[ ! -f "tools/addon/${script_name}.sh" ]]; then
echo "addon file not found: tools/addon/${script_name}.sh"
files_found="false"
missing_files+="tools/addon/${script_name}.sh "
fi
;;
pve)
if [[ ! -f "tools/pve/${script_name}.sh" ]]; then
echo "pve tool file not found: tools/pve/${script_name}.sh"
files_found="false"
missing_files+="tools/pve/${script_name}.sh "
fi
;;
esac
echo "files_found=$files_found" >> $GITHUB_OUTPUT
echo "missing=$missing_files" >> $GITHUB_OUTPUT
if [[ ! -f "$ct_file" ]]; then
echo "ct file not found."
echo "files_found=false" >> $GITHUB_OUTPUT
echo "missing=$ct_file" >> $GITHUB_OUTPUT
fi
if [[ ! -f "$install_file" ]]; then
echo "install file not found."
echo "files_found=false" >> $GITHUB_OUTPUT
echo "missing=$install_file" >> $GITHUB_OUTPUT
fi
if [[ ! -f "$json_file" ]]; then
if [[ "$json_file" = *alpine* ]]; then
stripped_name="${json_file/frontend\/public\/json\/alpine-/frontend/public/json/}"
echo $stripped_name
if [[ -f "$stripped_name" ]]; then
echo "files_found=true" >> $GITHUB_OUTPUT
else
echo "json file striped not found."
echo "files_found=false" >> $GITHUB_OUTPUT
echo "missing=$json_file" >> $GITHUB_OUTPUT
fi
else
echo "json file not found."
echo "files_found=false" >> $GITHUB_OUTPUT
echo "missing=$json_file" >> $GITHUB_OUTPUT
fi
fi
- name: Comment if not all Files found
if: steps.check_files.outputs.files_found == 'false'
@@ -133,8 +98,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
script_name="${{ steps.list_issues.outputs.script_name }}"
script_type="${{ steps.list_issues.outputs.script_type }}"
gh issue comment ${{ steps.list_issues.outputs.issue_nr }} --body "Not all required files were found for **$script_name** (type: $script_type). Please check the files and try again. Missing: ${{ steps.check_files.outputs.missing }}"
gh issue comment ${{ steps.list_issues.outputs.issue_nr }} --body "Not all required files were found for $script_name. Please check the files and try again. Missing: ${{ steps.check_files.outputs.missing }}."
exit 1
- name: Get GitHub App User ID
@@ -151,87 +115,58 @@ jobs:
- name: Prepare branch name
run: |
script_name="${{ steps.list_issues.outputs.script_name }}"
timestamp=$(date +%s)
branch_name="add-script-${script_name//[^a-zA-Z0-9_-]/}-${timestamp}"
branch_name="add-script-${script_name//[^a-zA-Z0-9_-]/}"
echo "Using branch: $branch_name"
echo "branch_name=$branch_name" >> $GITHUB_ENV
- name: Clone ProxmoxVE and Copy Files
- name: Clone ProxmoxVE (Target Repo)
run: |
script_name="${{ steps.list_issues.outputs.script_name }}"
script_type="${{ steps.list_issues.outputs.script_type }}"
git clone https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/community-scripts/ProxmoxVE.git ProxmoxVE
cd ProxmoxVE
# Check if branch already exists remotely and delete it
if git ls-remote --exit-code --heads origin "$branch_name" >/dev/null 2>&1; then
echo "Branch $branch_name already exists remotely, deleting it..."
git push origin --delete "$branch_name" || true
if [[ -f "ct/${script_name}.sh" ]]; then
echo "ct file already exists in ProxmoxVE"
exit 1
fi
if [[ -f "install/${script_name}-install.sh" ]]; then
echo "install file already exists in ProxmoxVE"
exit 1
fi
if [[ -f "frontend/public/json/${script_name}.json" ]]; then
echo "json file already exists in ProxmoxVE"
exit 1
fi
# Check if files already exist in target repo
case "$script_type" in
ct)
[[ -f "ct/${script_name}.sh" ]] && echo "ct file already exists in ProxmoxVE" && exit 1
[[ -f "install/${script_name}-install.sh" ]] && echo "install file already exists in ProxmoxVE" && exit 1
;;
vm)
[[ -f "vm/${script_name}.sh" ]] && echo "vm file already exists in ProxmoxVE" && exit 1
;;
addon)
[[ -f "tools/addon/${script_name}.sh" ]] && echo "addon file already exists in ProxmoxVE" && exit 1
;;
pve)
[[ -f "tools/pve/${script_name}.sh" ]] && echo "pve tool file already exists in ProxmoxVE" && exit 1
;;
esac
git checkout -b "$branch_name"
# Copy files based on script type
case "$script_type" in
ct)
cp ../ct/${script_name}.sh ct/
cp ../ct/headers/${script_name} ct/headers/ 2>/dev/null || true
cp ../install/${script_name}-install.sh install/
cp ../ct/$script_name.sh ct/.
cp ../ct/headers/$script_name ct/headers/. || true
cp ../install/$script_name-install.sh install/.
# Update URLs in ct script
sed -i "s|https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func|https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func|" ct/${script_name}.sh
sed -i "s|https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func|https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func|" ct/${script_name}.sh
sed -i "s|community-scripts/ProxmoxVED|community-scripts/ProxmoxVE|g" ct/${script_name}.sh
sed -i "s|community-scripts/ProxmoxVED|community-scripts/ProxmoxVE|g" install/${script_name}-install.sh
;;
vm)
cp ../vm/${script_name}.sh vm/
json_file="${script_name}.json"
# Update URLs in vm script
sed -i "s|https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func|https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func|" vm/${script_name}.sh
sed -i "s|community-scripts/ProxmoxVED|community-scripts/ProxmoxVE|g" vm/${script_name}.sh
;;
addon)
mkdir -p tools/addon
cp ../tools/addon/${script_name}.sh tools/addon/
if [[ ! -f "../frontend/public/json/$json_file" ]]; then
if [[ "$json_file" = *alpine* ]]; then
stripped_name="${json_file#alpine-}"
if [[ -f "../frontend/public/json/$stripped_name" ]]; then
cp ../frontend/public/json/$stripped_name frontend/public/json/. || true
fi
fi
else
cp ../frontend/public/json/$json_file frontend/public/json/. || true
fi
# Update URLs in addon script
sed -i "s|community-scripts/ProxmoxVED|community-scripts/ProxmoxVE|g" tools/addon/${script_name}.sh
;;
pve)
mkdir -p tools/pve
cp ../tools/pve/${script_name}.sh tools/pve/
# Update URLs in pve script
sed -i "s|community-scripts/ProxmoxVED|community-scripts/ProxmoxVE|g" tools/pve/${script_name}.sh
;;
esac
git add . > /dev/null 2>&1
sed -i 's|source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)|source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)|' ct/$script_name.sh
sed -i 's|# License: MIT \| https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE|# License: MIT \| https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE|' ct/$script_name.sh
sed -i 's|# License: MIT \| https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE|# License: MIT \| https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE|' install/$script_name-install.sh
git add .
if git diff --cached --exit-code; then
echo "No changes detected, skipping commit."
exit 0
fi
git commit -m "Add ${script_name} (${script_type})"
git commit -m "${commit_message:-'Add new script'}"
- name: Push to ProxmoxVE
run: |
@@ -244,13 +179,12 @@ jobs:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
script_name="${{ steps.list_issues.outputs.script_name }}"
script_type="${{ steps.list_issues.outputs.script_type }}"
gh pr create \
--repo community-scripts/ProxmoxVE \
--head "$branch_name" \
--base main \
--title "${script_name}" \
--body "Automated migration of **${script_name}** (type: ${script_type}) from ProxmoxVED to ProxmoxVE."
--title "$script_name" \
--body "Automated migration of $script_name from ProxmoxVED to ProxmoxVE."
PR_NUMBER=$(gh pr list --repo community-scripts/ProxmoxVE --head "$branch_name" --json number --jq '.[].number')
echo "PR_NUMBER=$PR_NUMBER"
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT

27
.github/workflows/push-to-gitea.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Sync to Gitea
on:
push:
branches:
- main
jobs:
sync:
if: github.repository == 'community-scripts/ProxmoxVED'
runs-on: ubuntu-latest
steps:
- name: Checkout source repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Push to Gitea
run: |
git config --global user.name "Push From Github"
git config --global user.email "actions@github.com"
git remote add gitea https://$GITEA_USER:$GITEA_TOKEN@git.community-scripts.org/community-scripts/ProxmoxVED.git
git push gitea --mirror
env:
GITEA_USER: ${{ secrets.GITEA_USERNAME }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}

View File

@@ -1,279 +0,0 @@
name: Push JSON changes to PocketBase
on:
workflow_dispatch:
inputs:
script_slug:
description: 'Script slug (e.g. my-app)'
required: true
type: string
jobs:
push-json:
runs-on: self-hosted
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get JSON file for script
id: changed
run: |
script_slug="${{ github.event.inputs.script_slug }}"
file="json/${script_slug}.json"
if [[ ! -f "$file" ]]; then
echo "No JSON file at $file."
echo "count=0" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! jq -e '.slug' "$file" >/dev/null 2>&1; then
echo "File $file has no .slug."
echo "count=0" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "$file" > changed_app_jsons.txt
echo "count=1" >> "$GITHUB_OUTPUT"
- name: Push to PocketBase
if: steps.changed.outputs.count != '0'
env:
POCKETBASE_URL: ${{ secrets.POCKETBASE_URL }}
POCKETBASE_COLLECTION: ${{ secrets.POCKETBASE_COLLECTION }}
POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }}
POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }}
run: |
node << 'ENDSCRIPT'
(async function() {
const fs = require('fs');
const https = require('https');
const http = require('http');
const url = require('url');
function request(fullUrl, opts) {
return new Promise(function(resolve, reject) {
const u = url.parse(fullUrl);
const isHttps = u.protocol === 'https:';
const body = opts.body;
const options = {
hostname: u.hostname,
port: u.port || (isHttps ? 443 : 80),
path: u.path,
method: opts.method || 'GET',
headers: opts.headers || {}
};
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
const lib = isHttps ? https : http;
const req = lib.request(options, function(res) {
let data = '';
res.on('data', function(chunk) { data += chunk; });
res.on('end', function() {
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data });
});
});
req.on('error', reject);
if (body) req.write(body);
req.end();
});
}
const raw = process.env.POCKETBASE_URL.replace(/\/$/, '');
const apiBase = /\/api$/i.test(raw) ? raw : raw + '/api';
const coll = process.env.POCKETBASE_COLLECTION;
const files = fs.readFileSync('changed_app_jsons.txt', 'utf8').trim().split(/\s+/).filter(Boolean);
const authUrl = apiBase + '/collections/users/auth-with-password';
console.log('Auth URL: ' + authUrl);
const authBody = JSON.stringify({
identity: process.env.POCKETBASE_ADMIN_EMAIL,
password: process.env.POCKETBASE_ADMIN_PASSWORD
});
const authRes = await request(authUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: authBody
});
if (!authRes.ok) {
throw new Error('Auth failed. Tried: ' + authUrl + ' - Verify POST to that URL with body {"identity":"...","password":"..."} works. Response: ' + authRes.body);
}
const token = JSON.parse(authRes.body).token;
const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records';
let categoryIdToName = {};
try {
const metadata = JSON.parse(fs.readFileSync('json/metadata.json', 'utf8'));
(metadata.categories || []).forEach(function(cat) { categoryIdToName[cat.id] = cat.name; });
} catch (e) { console.warn('Could not load metadata.json:', e.message); }
let typeValueToId = {};
let categoryNameToPbId = {};
try {
const typesRes = await request(apiBase + '/collections/z_ref_script_types/records?perPage=500', { headers: { 'Authorization': token } });
if (typesRes.ok) {
const typesData = JSON.parse(typesRes.body);
(typesData.items || []).forEach(function(item) {
if (item.type != null) typeValueToId[item.type] = item.id;
if (item.name != null) typeValueToId[item.name] = item.id;
if (item.value != null) typeValueToId[item.value] = item.id;
});
}
} catch (e) { console.warn('Could not fetch z_ref_script_types:', e.message); }
try {
const catRes = await request(apiBase + '/collections/script_categories/records?perPage=500', { headers: { 'Authorization': token } });
if (catRes.ok) {
const catData = JSON.parse(catRes.body);
(catData.items || []).forEach(function(item) { if (item.name) categoryNameToPbId[item.name] = item.id; });
}
} catch (e) { console.warn('Could not fetch script_categories:', e.message); }
var noteTypeToId = {};
var installMethodTypeToId = {};
var osToId = {};
var osVersionToId = {};
try {
const res = await request(apiBase + '/collections/z_ref_note_types/records?perPage=500', { headers: { 'Authorization': token } });
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) {
if (item.type != null) { noteTypeToId[item.type] = item.id; noteTypeToId[item.type.toLowerCase()] = item.id; }
});
} catch (e) { console.warn('z_ref_note_types:', e.message); }
try {
const res = await request(apiBase + '/collections/z_ref_install_method_types/records?perPage=500', { headers: { 'Authorization': token } });
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) {
if (item.type != null) { installMethodTypeToId[item.type] = item.id; installMethodTypeToId[item.type.toLowerCase()] = item.id; }
});
} catch (e) { console.warn('z_ref_install_method_types:', e.message); }
try {
const res = await request(apiBase + '/collections/z_ref_os/records?perPage=500', { headers: { 'Authorization': token } });
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) {
if (item.os != null) { osToId[item.os] = item.id; osToId[item.os.toLowerCase()] = item.id; }
});
} catch (e) { console.warn('z_ref_os:', e.message); }
try {
const res = await request(apiBase + '/collections/z_ref_os_version/records?perPage=500&expand=os', { headers: { 'Authorization': token } });
if (res.ok) {
(JSON.parse(res.body).items || []).forEach(function(item) {
var osName = item.expand && item.expand.os && item.expand.os.os != null ? item.expand.os.os : null;
if (osName != null && item.version != null) {
var key = osName + '|' + item.version;
osVersionToId[key] = item.id;
osVersionToId[osName.toLowerCase() + '|' + item.version] = item.id;
}
});
}
} catch (e) { console.warn('z_ref_os_version:', e.message); }
var notesCollUrl = apiBase + '/collections/script_notes/records';
var installMethodsCollUrl = apiBase + '/collections/script_install_methods/records';
for (const file of files) {
if (!fs.existsSync(file)) continue;
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
if (!data.slug) { console.log('Skipping', file, '(no slug)'); continue; }
var payload = {
name: data.name,
slug: data.slug,
script_created: data.date_created || data.script_created,
script_updated: data.date_created || data.script_updated,
updateable: data.updateable,
privileged: data.privileged,
port: data.interface_port != null ? data.interface_port : data.port,
documentation: data.documentation,
website: data.website,
logo: data.logo,
description: data.description,
config_path: data.config_path,
default_user: (data.default_credentials && data.default_credentials.username) || data.default_user,
default_passwd: (data.default_credentials && data.default_credentials.password) || data.default_passwd,
is_dev: true
};
var resolvedType = typeValueToId[data.type];
if (resolvedType == null && data.type === 'ct') resolvedType = typeValueToId['lxc'];
if (resolvedType) payload.type = resolvedType;
var resolvedCats = (data.categories || []).map(function(n) { return categoryNameToPbId[categoryIdToName[n]]; }).filter(Boolean);
if (resolvedCats.length) payload.categories = resolvedCats;
if (data.version !== undefined) payload.version = data.version;
if (data.changelog !== undefined) payload.changelog = data.changelog;
if (data.screenshots !== undefined) payload.screenshots = data.screenshots;
const filter = "(slug='" + data.slug + "')";
const listRes = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', {
headers: { 'Authorization': token }
});
const list = JSON.parse(listRes.body);
const existingId = list.items && list.items[0] && list.items[0].id;
async function resolveNotesAndInstallMethods(scriptId) {
var noteIds = [];
for (var i = 0; i < (data.notes || []).length; i++) {
var note = data.notes[i];
var typeId = noteTypeToId[note.type] || (note.type && noteTypeToId[note.type.toLowerCase()]);
if (typeId == null) {
console.warn('Note type not in z_ref_note_types:', note.type);
continue;
}
var postRes = await request(notesCollUrl, {
method: 'POST',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ text: note.text || '', type: typeId, script: scriptId })
});
if (postRes.ok) noteIds.push(JSON.parse(postRes.body).id);
else console.error('script_notes POST failed:', postRes.statusCode, postRes.body);
}
var installMethodIds = [];
for (var j = 0; j < (data.install_methods || []).length; j++) {
var im = data.install_methods[j];
var typeId = installMethodTypeToId[im.type] || (im.type && installMethodTypeToId[im.type.toLowerCase()]);
var res = im.resources || {};
var osId = osToId[res.os] || (res.os && osToId[res.os.toLowerCase()]);
var osVersionKey = (res.os != null && res.version != null) ? res.os + '|' + res.version : null;
var osVersionId = osVersionKey ? (osVersionToId[osVersionKey] || osVersionToId[res.os.toLowerCase() + '|' + res.version]) : null;
var imBody = {
script: scriptId,
resources_cpu: res.cpu != null ? res.cpu : 0,
resources_ram: res.ram != null ? res.ram : 0,
resources_hdd: res.hdd != null ? res.hdd : 0
};
if (typeId) imBody.type = typeId;
if (osId) imBody.os = osId;
if (osVersionId) imBody.os_version = osVersionId;
var imPostRes = await request(installMethodsCollUrl, {
method: 'POST',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify(imBody)
});
if (imPostRes.ok) installMethodIds.push(JSON.parse(imPostRes.body).id);
else console.error('script_install_methods POST failed:', imPostRes.statusCode, imPostRes.body);
}
return { noteIds: noteIds, installMethodIds: installMethodIds };
}
if (existingId) {
var resolved = await resolveNotesAndInstallMethods(existingId);
payload.notes = resolved.noteIds;
payload.install_methods = resolved.installMethodIds;
console.log('Updating', file, '(slug=' + data.slug + ')');
console.log('Created', resolved.noteIds.length, 'notes,', resolved.installMethodIds.length, 'install_methods for slug=' + data.slug);
const r = await request(recordsUrl + '/' + existingId, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!r.ok) throw new Error('PATCH failed: ' + r.body);
} else {
console.log('Creating', file, '(slug=' + data.slug + ')');
const r = await request(recordsUrl, {
method: 'POST',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!r.ok) throw new Error('POST failed: ' + r.body);
var scriptId = JSON.parse(r.body).id;
var resolved = await resolveNotesAndInstallMethods(scriptId);
console.log('Created', resolved.noteIds.length, 'notes,', resolved.installMethodIds.length, 'install_methods for slug=' + data.slug);
var patchPayload = {};
for (var k in payload) patchPayload[k] = payload[k];
patchPayload.notes = resolved.noteIds;
patchPayload.install_methods = resolved.installMethodIds;
var patchRes = await request(recordsUrl + '/' + scriptId, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify(patchPayload)
});
if (!patchRes.ok) throw new Error('PATCH relations failed: ' + patchRes.body);
var patched = JSON.parse(patchRes.body);
console.log('Script linked: notes=' + (patched.notes && patched.notes.length) + ', install_methods=' + (patched.install_methods && patched.install_methods.length));
}
}
console.log('Done.');
})().catch(e => { console.error(e); process.exit(1); });
ENDSCRIPT
shell: bash

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Copyright (c) 2021-2025 community-scripts ORG
# Author: Michel Roegl-Brunner (michelroegl-brunner)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
@@ -83,4 +83,4 @@ motd_ssh() {
customize() {
return
}
}

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Copyright (c) 2021-2025 community-scripts ORG
# Author: Michel Roegl-Brunner (michelroegl-brunner)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Copyright (c) 2021-2025 community-scripts ORG
# Author: Michel Roegl-Brunner (michelroegl-brunner)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Copyright (c) 2021-2025 community-scripts ORG
# Author: Michel Roegl-Brunner (michelroegl-brunner)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
@@ -15,7 +15,7 @@ catch_errors() {
error_handler() {
local line_number="$1"
local command="$2"
local error_message="Failure in line $line_number while executing command '$command'"
local error_message="Failure in line $line_number while executing command '$command'"
echo -e "\n$error_message\n" >&2
exit 1
}
@@ -90,4 +90,4 @@ motd_ssh() {
customize() {
return
}
}

View File

@@ -1,90 +0,0 @@
#!/usr/bin/env pwsh
# Function for generating Figlet headers
function Generate-Headers {
param (
[string]$BaseDir,
[string]$TargetSubdir,
[string]$SearchPattern
)
$HeadersDir = Join-Path $BaseDir $TargetSubdir
# Create headers directory if it doesn't exist
if (-not (Test-Path $HeadersDir)) {
New-Item -ItemType Directory -Path $HeadersDir -Force | Out-Null
}
# Remove existing header files
Get-ChildItem -Path $HeadersDir -File | Remove-Item -Force
# Determine search scope (recursive or not)
if ($SearchPattern -eq "**") {
$FileList = Get-ChildItem -Path $BaseDir -Filter "*.sh" -Recurse -File
}
else {
$FileList = Get-ChildItem -Path $BaseDir -Filter "*.sh" -File
}
foreach ($Script in $FileList) {
# Extract APP name from script
$Content = Get-Content $Script.FullName -Raw
if ($Content -match 'APP="([^"]+)"') {
$AppName = $Matches[1]
$OutputFile = Join-Path $HeadersDir $Script.BaseName
# Generate figlet output
try {
$FigletOutput = & figlet -w 500 -f slant $AppName 2>&1
if ($LASTEXITCODE -eq 0 -and $FigletOutput) {
$FigletOutput | Out-File -FilePath $OutputFile -Encoding utf8
Write-Host "Generated: $OutputFile" -ForegroundColor Green
}
else {
Write-Host "Figlet failed for $AppName in $($Script.Name)" -ForegroundColor Yellow
}
}
catch {
Write-Host "Error running figlet for $AppName : $_" -ForegroundColor Red
}
}
else {
Write-Host "No APP name found in $($Script.Name), skipping." -ForegroundColor Gray
}
}
}
# Check if figlet is available
try {
$null = Get-Command figlet -ErrorAction Stop
}
catch {
Write-Host "Error: figlet is not installed or not in PATH" -ForegroundColor Red
Write-Host "`nInstallation options:" -ForegroundColor Yellow
Write-Host "1. Using Scoop (recommended):" -ForegroundColor Cyan
Write-Host " scoop install figlet" -ForegroundColor White
Write-Host "`n2. Manual download:" -ForegroundColor Cyan
Write-Host " Download from: https://github.com/cmatsuoka/figlet/releases" -ForegroundColor White
Write-Host " Extract and add to PATH" -ForegroundColor White
Write-Host "`n3. Using WSL:" -ForegroundColor Cyan
Write-Host " Run the bash version: bash generate-app-headers.sh" -ForegroundColor White
exit 1
}
# Change to script directory
$ScriptDir = Split-Path -Parent $PSCommandPath
$RepoRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $ScriptDir))
Set-Location $RepoRoot
Write-Host "Processing ct/ directory..." -ForegroundColor Cyan
Generate-Headers -BaseDir ".\ct" -TargetSubdir "headers" -SearchPattern "*"
Write-Host "`nProcessing tools/ directory..." -ForegroundColor Cyan
Generate-Headers -BaseDir ".\tools" -TargetSubdir "headers" -SearchPattern "**"
Write-Host "`nProcessing vm/ directory..." -ForegroundColor Cyan
Generate-Headers -BaseDir ".\vm" -TargetSubdir "headers" -SearchPattern "*"
Write-Host "`nCompleted processing all sections." -ForegroundColor Green

View File

@@ -1,50 +1,34 @@
#!/usr/bin/env bash
# Function for generating Figlet headers
generate_headers() {
local base_dir=$1
local target_subdir=$2
local search_pattern=$3
# Base directory for headers
headers_dir="./ct/headers"
local headers_dir="${base_dir}/headers"
mkdir -p "$headers_dir"
rm -f "$headers_dir"/*
# Ensure the headers directory exists and clear it
mkdir -p "$headers_dir"
rm -f "$headers_dir"/*
# Recursive or non-recursive search
if [[ "$search_pattern" == "**" ]]; then
shopt -s globstar nullglob
file_list=("${base_dir}"/**/*.sh)
shopt -u globstar
else
file_list=("${base_dir}"/*.sh)
fi
# Find all .sh files in ./ct directory, sorted alphabetically
find ./ct -type f -name "*.sh" | sort | while read -r script; do
# Extract the APP name from the APP line
app_name=$(grep -oP '^APP="\K[^"]+' "$script" 2>/dev/null)
for script in "${file_list[@]}"; do
[[ -f "$script" ]] || continue
if [[ -n "$app_name" ]]; then
# Define the output file name in the headers directory
output_file="${headers_dir}/$(basename "${script%.*}")"
app_name=$(grep -oP '^APP="\K[^"]+' "$script" 2>/dev/null)
if [[ -n "$app_name" ]]; then
output_file="${headers_dir}/$(basename "${script%.*}")"
figlet_output=$(figlet -w 500 -f slant "$app_name")
if [[ -n "$figlet_output" ]]; then
echo "$figlet_output" >"$output_file"
echo "Generated: $output_file"
else
echo "Figlet failed for $app_name in $script"
fi
# Generate figlet output
figlet_output=$(figlet -w 500 -f slant "$app_name")
# Check if figlet output is not empty
if [[ -n "$figlet_output" ]]; then
echo "$figlet_output" > "$output_file"
echo "Generated: $output_file"
else
echo "No APP name found in $script, skipping."
echo "Figlet failed for $app_name in $script"
fi
done
}
else
echo "No APP name found in $script, skipping."
fi
done
# ct
generate_headers "./ct" "headers" "*"
# tools (addon, pve, ...)
generate_headers "./tools" "headers" "**"
# vm
generate_headers "./vm" "headers" "*"
echo "Completed processing all sections."
echo "Completed processing .sh files."

View File

@@ -1,13 +1,13 @@
#!/bin/bash
INPUT_FILE=".github/workflows/scripts/repos.txt"
OUTPUT_FILE="json/versions.json"
OUTPUT_FILE="frontend/public/json/versions.json"
TMP_FILE="releases_tmp.json"
if [ -f "$OUTPUT_FILE" ]; then
cp "$OUTPUT_FILE" "$TMP_FILE"
else
echo "[]" >"$TMP_FILE"
echo "[]" > "$TMP_FILE"
fi
while IFS= read -r repo; do
@@ -26,15 +26,14 @@ while IFS= read -r repo; do
if [[ "$existing_version" != "$tag" ]]; then
echo "New release for $repo: $tag"
jq --arg name "$repo" 'del(.[] | select(.name == $name))' "$TMP_FILE" >"$TMP_FILE.tmp" && mv "$TMP_FILE.tmp" "$TMP_FILE"
jq --arg name "$repo" 'del(.[] | select(.name == $name))' "$TMP_FILE" > "$TMP_FILE.tmp" && mv "$TMP_FILE.tmp" "$TMP_FILE"
jq --arg name "$repo" --arg version "$tag" --arg date "$date" \
'. += [{"name": $name, "version": $version, "date": $date}]' "$TMP_FILE" >"$TMP_FILE.tmp" && mv "$TMP_FILE.tmp" "$TMP_FILE"
'. += [{"name": $name, "version": $version, "date": $date}]' "$TMP_FILE" > "$TMP_FILE.tmp" && mv "$TMP_FILE.tmp" "$TMP_FILE"
else
echo "No change for $repo"
fi
done <"$INPUT_FILE"
done < "$INPUT_FILE"
mv "$TMP_FILE" "$OUTPUT_FILE"
echo "Updated $OUTPUT_FILE"
#mv "$TMP_FILE" "$OUTPUT_FILE"

View File

@@ -1,111 +0,0 @@
name: Stale PR Management
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
pull_request_target:
types:
- labeled
jobs:
stale-prs:
runs-on: self-hosted
permissions:
pull-requests: write
issues: write
contents: read
steps:
- name: Handle stale PRs
uses: actions/github-script@v7
with:
script: |
const now = new Date();
const owner = context.repo.owner;
const repo = context.repo.repo;
// --- When stale label is added, comment immediately ---
if (context.eventName === "pull_request_target" && context.payload.action === "labeled") {
const label = context.payload.label?.name;
if (label === "stale") {
const author = context.payload.pull_request.user.login;
await github.rest.issues.createComment({
owner,
repo,
issue_number: context.payload.pull_request.number,
body: `@${author} This PR has been marked as stale. It will be closed if no new commits are added in 7 days.`
});
}
return;
}
// --- Scheduled run: check all stale PRs ---
const { data: prs } = await github.rest.pulls.list({
owner,
repo,
state: "open",
per_page: 100
});
for (const pr of prs) {
const hasStale = pr.labels.some(l => l.name === "stale");
if (!hasStale) continue;
// Get timeline events to find when stale label was added
const { data: events } = await github.rest.issues.listEvents({
owner,
repo,
issue_number: pr.number,
per_page: 100
});
// Find the most recent time the stale label was added
const staleLabelEvents = events
.filter(e => e.event === "labeled" && e.label?.name === "stale")
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
if (staleLabelEvents.length === 0) continue;
const staleLabelDate = new Date(staleLabelEvents[0].created_at);
const daysSinceStale = (now - staleLabelDate) / (1000 * 60 * 60 * 24);
// Check for new commits since stale label was added
const { data: commits } = await github.rest.pulls.listCommits({
owner,
repo,
pull_number: pr.number
});
const lastCommitDate = new Date(commits[commits.length - 1].commit.author.date);
const author = pr.user.login;
// If there are new commits after the stale label, remove it
if (lastCommitDate > staleLabelDate) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: pr.number,
name: "stale"
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: pr.number,
body: `@${author} Recent activity detected. Removing stale label.`
});
}
// If 7 days have passed since stale label, close the PR
else if (daysSinceStale > 7) {
await github.rest.pulls.update({
owner,
repo,
pull_number: pr.number,
state: "closed"
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: pr.number,
body: `@${author} Closing stale PR due to inactivity (no commits for 7 days after stale label).`
});
}
}

View File

@@ -1,41 +0,0 @@
name: Pages Redirect
on:
workflow_dispatch:
permissions:
pages: write
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create redirect page
run: |
mkdir site
cat <<EOF > site/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=https://community-scripts.org/">
<link rel="canonical" href="https://community-scripts.org/">
<title>Redirecting...</title>
</head>
<body>
Redirecting...
</body>
</html>
EOF
- uses: actions/upload-pages-artifact@v3
with:
path: site
- name: Deploy
uses: actions/deploy-pages@v4

View File

@@ -1,89 +0,0 @@
name: PR Script Requirements Check
on:
pull_request:
types: [opened, edited, synchronize]
permissions:
pull-requests: write
contents: read
jobs:
validate-script-requirements:
runs-on: ubuntu-latest
steps:
- name: Validate new script requirements
uses: actions/github-script@v7
with:
script: |
const body = context.payload.pull_request.body || "";
const lines = body.split("\n");
function checkboxChecked(line) {
return /\[\s*x\s*\]/i.test(line);
}
function findLine(text) {
return lines.find(l => l.includes(text));
}
// detect if "New script" is checked
const newScriptLine = findLine("🆕 **New script**");
if (!newScriptLine || !checkboxChecked(newScriptLine)) {
console.log("Not a new script PR — skipping requirement check.");
return;
}
const requirements = [
"The application is **at least 6 months old**",
"The application is **actively maintained**",
"The application has **600+ GitHub stars**",
"Official **release tarballs** are published",
"I understand that not all scripts will be accepted"
];
const missing = [];
for (const req of requirements) {
const line = findLine(req);
if (!line || !checkboxChecked(line)) {
missing.push(req);
}
}
if (missing.length > 0) {
let list = "";
for (const m of missing) {
list += "- " + m + "\n";
}
const message =
"❌ **Pull Request Closed Application Requirements Not Met**\n\n" +
"This pull request is marked as **🆕 New script**, but the required application criteria were not confirmed.\n\n" +
"The following requirement confirmations are missing:\n\n" +
list +
"\nNew application submissions must meet the project requirements before being considered.\n" +
"Please wait until the application satisfies the criteria before submitting a new PR.\n\n" +
"---\n\n" +
"⚠ **Maintainer note**\n\n" +
"The team periodically reviews closed submissions. If a project is still considered valuable to the ecosystem, maintainers may reopen the PR even if it does not fully meet the thresholds.\n\n" +
"**Please do not ping or repeatedly contact maintainers to reopen PRs.**";
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: message
});
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
state: "closed"
});
core.setFailed("Application requirements checklist incomplete.");
}

View File

@@ -1,167 +0,0 @@
name: Update script timestamp on .sh changes
on:
push:
branches:
- main
paths:
- "ct/**/*.sh"
- "install/**/*.sh"
- "tools/**/*.sh"
- "turnkey/**/*.sh"
- "vm/**/*.sh"
jobs:
update-script-timestamp:
runs-on: self-hosted
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get changed .sh files and derive slugs
id: slugs
run: |
changed=$(git diff --name-only "${{ github.event.before }}" "${{ github.event.after }}" -- ct/ install/ tools/ turnkey/ vm/ | grep '\.sh$' || true)
if [[ -z "$changed" ]]; then
echo "No .sh files changed in ct/, install/, tools/, turnkey/, or vm/."
echo "count=0" >> "$GITHUB_OUTPUT"
exit 0
fi
declare -A seen
slugs=""
for f in $changed; do
[[ -f "$f" ]] || continue
base="${f##*/}"
base="${base%.sh}"
if [[ "$f" == install/* && "$base" == *-install ]]; then
slug="${base%-install}"
else
slug="$base"
fi
if [[ -z "${seen[$slug]:-}" ]]; then
seen[$slug]=1
slugs="$slugs $slug"
fi
done
slugs=$(echo $slugs | xargs -n1 | sort -u)
if [[ -z "$slugs" ]]; then
echo "No slugs to update."
echo "count=0" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "$slugs" > changed_slugs.txt
echo "count=$(echo "$slugs" | wc -w)" >> "$GITHUB_OUTPUT"
- name: Parse PR number from merge commit
id: pr
run: |
re='#([0-9]+)'
if [[ "$COMMIT_MSG" =~ $re ]]; then
echo "number=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT"
else
echo "number=" >> "$GITHUB_OUTPUT"
fi
env:
COMMIT_MSG: ${{ github.event.head_commit.message }}
- name: Update script timestamps in PocketBase
if: steps.slugs.outputs.count != '0'
env:
POCKETBASE_URL: ${{ secrets.POCKETBASE_URL }}
POCKETBASE_COLLECTION: ${{ secrets.POCKETBASE_COLLECTION }}
POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }}
POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }}
COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}
PR_URL: ${{ steps.pr.outputs.number != '' && format('{0}/{1}/pull/{2}', github.server_url, github.repository, steps.pr.outputs.number) || '' }}
run: |
node << 'ENDSCRIPT'
(async function() {
const fs = require('fs');
const https = require('https');
const http = require('http');
const url = require('url');
function request(fullUrl, opts) {
return new Promise(function(resolve, reject) {
const u = url.parse(fullUrl);
const isHttps = u.protocol === 'https:';
const body = opts.body;
const options = {
hostname: u.hostname,
port: u.port || (isHttps ? 443 : 80),
path: u.path,
method: opts.method || 'GET',
headers: opts.headers || {}
};
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
const lib = isHttps ? https : http;
const req = lib.request(options, function(res) {
let data = '';
res.on('data', function(chunk) { data += chunk; });
res.on('end', function() {
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data });
});
});
req.on('error', reject);
if (body) req.write(body);
req.end();
});
}
const raw = process.env.POCKETBASE_URL.replace(/\/$/, '');
const apiBase = /\/api$/i.test(raw) ? raw : raw + '/api';
const coll = process.env.POCKETBASE_COLLECTION;
const slugsText = fs.readFileSync('changed_slugs.txt', 'utf8').trim();
const slugs = slugsText ? slugsText.split(/\s+/).filter(Boolean) : [];
if (slugs.length === 0) {
console.log('No slugs to update.');
return;
}
const authUrl = apiBase + '/collections/users/auth-with-password';
const authBody = JSON.stringify({
identity: process.env.POCKETBASE_ADMIN_EMAIL,
password: process.env.POCKETBASE_ADMIN_PASSWORD
});
const authRes = await request(authUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: authBody
});
if (!authRes.ok) {
throw new Error('Auth failed: ' + authRes.body);
}
const token = JSON.parse(authRes.body).token;
const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records';
for (const slug of slugs) {
const filter = "(slug='" + slug.replace(/'/g, "''") + "')";
const listRes = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', {
headers: { 'Authorization': token }
});
const list = JSON.parse(listRes.body);
const record = list.items && list.items[0];
if (!record) {
console.log('Slug not in DB, skipping: ' + slug);
continue;
}
const patchRes = await request(recordsUrl + '/' + record.id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({
name: record.name || record.slug,
last_update_commit: process.env.PR_URL || process.env.COMMIT_URL || ''
})
});
if (!patchRes.ok) {
console.warn('PATCH failed for slug ' + slug + ': ' + patchRes.body);
continue;
}
console.log('Updated timestamp for slug: ' + slug);
}
console.log('Done.');
})().catch(e => { console.error(e); process.exit(1); });
ENDSCRIPT
shell: bash

14
.gitignore vendored
View File

@@ -1,15 +1 @@
.vscode/settings.json
CHANGELOG_MISC.md
misc/COMPLETION_REPORT.md
misc/DOCUMENTATION_INDEX.md
misc/DOCUMENTATION_SUMMARY.md
misc/live/alpine-install.func
misc/live/build.func
misc/live/install.func
vm/debian-13-vm.sh
vm/README-vm-manager.md
vm/vm-manager.sh
vm/debian-13-vm.sh
vm/vm-manager.sh
vm/vm-manager.sh
vm/debian-13-vm.sh

1
.vscode/.shellcheckrc vendored Normal file
View File

@@ -0,0 +1 @@
disable=SC2034,SC1091,SC2155,SC2086,SC2317,SC2181

View File

@@ -1,7 +1,8 @@
{
"recommendations": [
"bmalehorn.shell-syntax",
"timonwong.shellcheck",
"mkhl.shfmt"
"foxundermoon.shell-format"
],
"unwantedRecommendations": []
}

41
.vscode/settings.json vendored
View File

@@ -1,12 +1,33 @@
{
"files.associations": {
"*.func": "shellscript"
},
"[shellscript]": {
"editor.defaultFormatter": "mkhl.shfmt"
},
"editor.codeActionsOnSave": {
"source.fixAll": "never"
},
"shellcheck.useWorkspaceRootAsCwd": true,
"files.associations": {
"*.func": "shellscript"
},
"files.eol": "\n",
"files.encoding": "utf8",
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"files.autoSave": "afterDelay",
"files.autoGuessEncoding": false,
"editor.detectIndentation": false,
"editor.tabSize": 4,
"editor.insertSpaces": true,
"editor.wordWrap": "off",
"editor.renderWhitespace": "boundary",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.minimap.enabled": false,
"terminal.integrated.scrollback": 10000,
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format",
"editor.tabSize": 4,
"editor.insertSpaces": true,
},
"shellcheck.customArgs": [
"--rcfile",
".vscode/.shellcheckrc"
],
"git.autofetch": true,
"git.confirmSync": false,
"git.enableSmartCommit": true,
"extensions.ignoreRecommendations": false
}

View File

@@ -7,76 +7,6 @@
<h3 align="center">All notable changes to this project will be documented in this file.</h3>
## 2025-05-14
### 🆕 New Scripts
- 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))
- streamlink-webui ([#4262](https://github.com/community-scripts/ProxmoxVE/pull/4262))
- Fumadocs ([#4263](https://github.com/community-scripts/ProxmoxVE/pull/4263))
- asterisk ([#4468](https://github.com/community-scripts/ProxmoxVE/pull/4468))
- gatus ([#4443](https://github.com/community-scripts/ProxmoxVE/pull/4443))
- alpine-gatus ([#4442](https://github.com/community-scripts/ProxmoxVE/pull/4442))
- Alpine-Traefik [@MickLesk](https://github.com/MickLesk) ([#4412](https://github.com/community-scripts/ProxmoxVE/pull/4412))
### 🚀 Updated Scripts
- 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))
- #### 🐞 Bug Fixes
- Bugfix: Mikrotik & Pimox HAOS VM (NEXTID) [@MickLesk](https://github.com/MickLesk) ([#4313](https://github.com/community-scripts/ProxmoxVE/pull/4313))
- Bookstack: fix copy of themes/uploads/storage [@MickLesk](https://github.com/MickLesk) ([#4457](https://github.com/community-scripts/ProxmoxVE/pull/4457))
- homarr: fetch versions dynamically from source repo [@CrazyWolf13](https://github.com/CrazyWolf13) ([#4409](https://github.com/community-scripts/ProxmoxVE/pull/4409))
- Authentik: change install to UV & increase resources to 10GB RAM [@MickLesk](https://github.com/MickLesk) ([#4364](https://github.com/community-scripts/ProxmoxVE/pull/4364))
- Jellyseerr: better handling of node and pnpm [@MickLesk](https://github.com/MickLesk) ([#4365](https://github.com/community-scripts/ProxmoxVE/pull/4365))
- Alpine-Rclone: Fix location of passwords file [@tremor021](https://github.com/tremor021) ([#4465](https://github.com/community-scripts/ProxmoxVE/pull/4465))
- Zammad: Enable ElasticSearch service [@tremor021](https://github.com/tremor021) ([#4391](https://github.com/community-scripts/ProxmoxVE/pull/4391))
- openhab: use zulu17-jdk [@moodyblue](https://github.com/moodyblue) ([#4438](https://github.com/community-scripts/ProxmoxVE/pull/4438))
- (fix) Documenso: fix build failures [@vhsdream](https://github.com/vhsdream) ([#4382](https://github.com/community-scripts/ProxmoxVE/pull/4382))
- #### ✨ New Features
- Feature: LXC-Delete (pve helper): add "all items" [@MickLesk](https://github.com/MickLesk) ([#4296](https://github.com/community-scripts/ProxmoxVE/pull/4296))
- Feature: get correct next VMID [@MickLesk](https://github.com/MickLesk) ([#4292](https://github.com/community-scripts/ProxmoxVE/pull/4292))
- HomeAssistant-Core: update script for 2025.5+ [@MickLesk](https://github.com/MickLesk) ([#4363](https://github.com/community-scripts/ProxmoxVE/pull/4363))
- Feature: autologin for Alpine [@MickLesk](https://github.com/MickLesk) ([#4344](https://github.com/community-scripts/ProxmoxVE/pull/4344))
- monitor-all: improvements - tag based filtering [@grizmin](https://github.com/grizmin) ([#4437](https://github.com/community-scripts/ProxmoxVE/pull/4437))
- Make apt-cacher-ng a client of its own server [@pgcudahy](https://github.com/pgcudahy) ([#4092](https://github.com/community-scripts/ProxmoxVE/pull/4092))
- #### 🔧 Refactor
- openhab. correct some typos [@moodyblue](https://github.com/moodyblue) ([#4448](https://github.com/community-scripts/ProxmoxVE/pull/4448))
### 🧰 Maintenance
- #### 💾 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))
- #### 📂 Github
- 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))
- #### 📝 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))
## 2025-03-24
### 🆕 New Scripts
@@ -85,7 +15,7 @@
- wazuh [@omiinaya](https://github.com/omiinaya) ([#3381](https://github.com/community-scripts/ProxmoxVE/pull/3381))
- 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
- Fluid Calendar [@vhsdream](https://github.com/vhsdream) ([#2869](https://github.com/community-scripts/ProxmoxVE/pull/2869))
### 🚀 Updated Scripts
@@ -105,7 +35,7 @@
- GoMFT: Fix build dependencies [@tremor021](https://github.com/tremor021) ([#3313](https://github.com/community-scripts/ProxmoxVE/pull/3313))
- GoMFT: Don't rely on binaries from github [@tremor021](https://github.com/tremor021) ([#3303](https://github.com/community-scripts/ProxmoxVE/pull/3303))
- Wikijs: Remove Dev Message & Performance-Boost [@bvdberg01](https://github.com/bvdberg01) ([#3232](https://github.com/community-scripts/ProxmoxVE/pull/3232))
- Update omada download url [@bvdberg01](https://github.com/bvdberg01) ([#3245](https://github.cooxVE/pull/3245))
- Update omada download url [@bvdberg01](https://github.com/bvdberg01) ([#3245](https://github.com/community-scripts/ProxmoxVE/pull/3245))
- TriliumNotes: Fix release handling [@tremor021](https://github.com/tremor021) ([#3160](https://github.com/community-scripts/ProxmoxVE/pull/3160))
- #### ✨ New Features
@@ -126,6 +56,7 @@
- #### ✨ New Features
- [core] add gitignore to prevent big pulls [@MickLesk](https://github.com/MickLesk) ([#3278](https://github.com/community-scripts/ProxmoxVE/pull/3278))
- [core] install core deps (debian / ubuntu) [@MickLesk](https://github.com/MickLesk) ([#3366](https://github.com/community-scripts/ProxmoxVE/pull/3366))
- #### 💾 Core
@@ -141,7 +72,7 @@
### 🌐 Website
- Update siteConfig.tsx to use new analytics code [@BramSuurdje](https://github.com/BramSuurdje) ([#3389](https://github.com/community-scripts/ProxmoxVE/pull/3389))
- Bump next from 15.1.3 to 15.2.16](https://github.com/community-scripE/pull/3316))
- Bump next from 15.1.3 to 15.2.3 in /frontend [@dependabot[bot]](https://github.com/dependabot[bot]) ([#3316](https://github.com/community-scripts/ProxmoxVE/pull/3316))
- #### 🐞 Bug Fixes

5
api/.env.example Normal file
View File

@@ -0,0 +1,5 @@
MONGO_USER=
MONGO_PASSWORD=
MONGO_IP=
MONGO_PORT=
MONGO_DATABASE=

23
api/go.mod Normal file
View File

@@ -0,0 +1,23 @@
module proxmox-api
go 1.23.2
require (
github.com/gorilla/mux v1.8.1
github.com/joho/godotenv v1.5.1
github.com/rs/cors v1.11.1
go.mongodb.org/mongo-driver v1.17.2
)
require (
github.com/golang/snappy v0.0.4 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/text v0.22.0 // indirect
)

56
api/go.sum Normal file
View File

@@ -0,0 +1,56 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=
go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

450
api/main.go Normal file
View File

@@ -0,0 +1,450 @@
// Copyright (c) 2021-2025 community-scripts ORG
// Author: Michel Roegl-Brunner (michelroegl-brunner)
// License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strconv"
"time"
"github.com/gorilla/mux"
"github.com/joho/godotenv"
"github.com/rs/cors"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var client *mongo.Client
var collection *mongo.Collection
func loadEnv() {
if err := godotenv.Load(); err != nil {
log.Fatal("Error loading .env file")
}
}
// DataModel represents a single document in MongoDB
type DataModel struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
CT_TYPE uint `json:"ct_type" bson:"ct_type"`
DISK_SIZE float32 `json:"disk_size" bson:"disk_size"`
CORE_COUNT uint `json:"core_count" bson:"core_count"`
RAM_SIZE uint `json:"ram_size" bson:"ram_size"`
OS_TYPE string `json:"os_type" bson:"os_type"`
OS_VERSION string `json:"os_version" bson:"os_version"`
DISABLEIP6 string `json:"disableip6" bson:"disableip6"`
NSAPP string `json:"nsapp" bson:"nsapp"`
METHOD string `json:"method" bson:"method"`
CreatedAt time.Time `json:"created_at" bson:"created_at"`
PVEVERSION string `json:"pve_version" bson:"pve_version"`
STATUS string `json:"status" bson:"status"`
RANDOM_ID string `json:"random_id" bson:"random_id"`
TYPE string `json:"type" bson:"type"`
ERROR string `json:"error" bson:"error"`
}
type StatusModel struct {
RANDOM_ID string `json:"random_id" bson:"random_id"`
ERROR string `json:"error" bson:"error"`
STATUS string `json:"status" bson:"status"`
}
type CountResponse struct {
TotalEntries int64 `json:"total_entries"`
StatusCount map[string]int64 `json:"status_count"`
NSAPPCount map[string]int64 `json:"nsapp_count"`
}
// ConnectDatabase initializes the MongoDB connection
func ConnectDatabase() {
loadEnv()
mongoURI := fmt.Sprintf("mongodb://%s:%s@%s:%s",
os.Getenv("MONGO_USER"),
os.Getenv("MONGO_PASSWORD"),
os.Getenv("MONGO_IP"),
os.Getenv("MONGO_PORT"))
database := os.Getenv("MONGO_DATABASE")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var err error
client, err = mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
if err != nil {
log.Fatal("Failed to connect to MongoDB!", err)
}
collection = client.Database(database).Collection("data_models")
fmt.Println("Connected to MongoDB on 10.10.10.18")
}
// UploadJSON handles API requests and stores data as a document in MongoDB
func UploadJSON(w http.ResponseWriter, r *http.Request) {
var input DataModel
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
input.CreatedAt = time.Now()
_, err := collection.InsertOne(context.Background(), input)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Println("Received data:", input)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]string{"message": "Data saved successfully"})
}
// UpdateStatus updates the status of a record based on RANDOM_ID
func UpdateStatus(w http.ResponseWriter, r *http.Request) {
var input StatusModel
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
filter := bson.M{"random_id": input.RANDOM_ID}
update := bson.M{"$set": bson.M{"status": input.STATUS, "error": input.ERROR}}
_, err := collection.UpdateOne(context.Background(), filter, update)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Println("Updated data:", input)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"message": "Record updated successfully"})
}
// GetDataJSON fetches all data from MongoDB
func GetDataJSON(w http.ResponseWriter, r *http.Request) {
var records []DataModel
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := collection.Find(ctx, bson.M{})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
var record DataModel
if err := cursor.Decode(&record); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
records = append(records, record)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(records)
}
func GetPaginatedData(w http.ResponseWriter, r *http.Request) {
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
if page < 1 {
page = 1
}
if limit < 1 {
limit = 10
}
skip := (page - 1) * limit
var records []DataModel
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
options := options.Find().SetSkip(int64(skip)).SetLimit(int64(limit))
cursor, err := collection.Find(ctx, bson.M{}, options)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
var record DataModel
if err := cursor.Decode(&record); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
records = append(records, record)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(records)
}
func GetSummary(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
totalCount, err := collection.CountDocuments(ctx, bson.M{})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
statusCount := make(map[string]int64)
nsappCount := make(map[string]int64)
pipeline := []bson.M{
{"$group": bson.M{"_id": "$status", "count": bson.M{"$sum": 1}}},
}
cursor, err := collection.Aggregate(ctx, pipeline)
if err == nil {
for cursor.Next(ctx) {
var result struct {
ID string `bson:"_id"`
Count int64 `bson:"count"`
}
if err := cursor.Decode(&result); err == nil {
statusCount[result.ID] = result.Count
}
}
}
pipeline = []bson.M{
{"$group": bson.M{"_id": "$nsapp", "count": bson.M{"$sum": 1}}},
}
cursor, err = collection.Aggregate(ctx, pipeline)
if err == nil {
for cursor.Next(ctx) {
var result struct {
ID string `bson:"_id"`
Count int64 `bson:"count"`
}
if err := cursor.Decode(&result); err == nil {
nsappCount[result.ID] = result.Count
}
}
}
response := CountResponse{
TotalEntries: totalCount,
StatusCount: statusCount,
NSAPPCount: nsappCount,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func GetByNsapp(w http.ResponseWriter, r *http.Request) {
nsapp := r.URL.Query().Get("nsapp")
var records []DataModel
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := collection.Find(ctx, bson.M{"nsapp": nsapp})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
var record DataModel
if err := cursor.Decode(&record); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
records = append(records, record)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(records)
}
func GetByDateRange(w http.ResponseWriter, r *http.Request) {
startDate := r.URL.Query().Get("start_date")
endDate := r.URL.Query().Get("end_date")
if startDate == "" || endDate == "" {
http.Error(w, "Both start_date and end_date are required", http.StatusBadRequest)
return
}
start, err := time.Parse("2006-01-02T15:04:05.999999+00:00", startDate+"T00:00:00+00:00")
if err != nil {
http.Error(w, "Invalid start_date format", http.StatusBadRequest)
return
}
end, err := time.Parse("2006-01-02T15:04:05.999999+00:00", endDate+"T23:59:59+00:00")
if err != nil {
http.Error(w, "Invalid end_date format", http.StatusBadRequest)
return
}
var records []DataModel
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := collection.Find(ctx, bson.M{
"created_at": bson.M{
"$gte": start,
"$lte": end,
},
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
var record DataModel
if err := cursor.Decode(&record); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
records = append(records, record)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(records)
}
func GetByStatus(w http.ResponseWriter, r *http.Request) {
status := r.URL.Query().Get("status")
var records []DataModel
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := collection.Find(ctx, bson.M{"status": status})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
var record DataModel
if err := cursor.Decode(&record); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
records = append(records, record)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(records)
}
func GetByOS(w http.ResponseWriter, r *http.Request) {
osType := r.URL.Query().Get("os_type")
osVersion := r.URL.Query().Get("os_version")
var records []DataModel
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := collection.Find(ctx, bson.M{"os_type": osType, "os_version": osVersion})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
var record DataModel
if err := cursor.Decode(&record); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
records = append(records, record)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(records)
}
func GetErrors(w http.ResponseWriter, r *http.Request) {
errorCount := make(map[string]int)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := collection.Find(ctx, bson.M{"error": bson.M{"$ne": ""}})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
var record DataModel
if err := cursor.Decode(&record); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if record.ERROR != "" {
errorCount[record.ERROR]++
}
}
type ErrorCountResponse struct {
Error string `json:"error"`
Count int `json:"count"`
}
var errorCounts []ErrorCountResponse
for err, count := range errorCount {
errorCounts = append(errorCounts, ErrorCountResponse{
Error: err,
Count: count,
})
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(struct {
ErrorCounts []ErrorCountResponse `json:"error_counts"`
}{
ErrorCounts: errorCounts,
})
}
func main() {
ConnectDatabase()
router := mux.NewRouter()
router.HandleFunc("/upload", UploadJSON).Methods("POST")
router.HandleFunc("/upload/updatestatus", UpdateStatus).Methods("POST")
router.HandleFunc("/data/json", GetDataJSON).Methods("GET")
router.HandleFunc("/data/paginated", GetPaginatedData).Methods("GET")
router.HandleFunc("/data/summary", GetSummary).Methods("GET")
router.HandleFunc("/data/nsapp", GetByNsapp).Methods("GET")
router.HandleFunc("/data/date", GetByDateRange).Methods("GET")
router.HandleFunc("/data/status", GetByStatus).Methods("GET")
router.HandleFunc("/data/os", GetByOS).Methods("GET")
router.HandleFunc("/data/errors", GetErrors).Methods("GET")
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
AllowCredentials: true,
})
handler := c.Handler(router)
fmt.Println("Server running on port 8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}

View File

@@ -1,136 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://github.com/toeverything/AFFiNE
APP="AFFiNE"
var_tags="${var_tags:-knowledge;notes;workspace}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-8192}"
var_disk="${var_disk:-20}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/affine ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "affine" "toeverything/AFFiNE"; then
msg_info "Stopping Services"
systemctl stop affine-web affine-worker
msg_ok "Stopped Services"
msg_info "Backing up Data"
cp -r /root/.affine/storage /root/.affine_storage_backup 2>/dev/null || true
cp -r /root/.affine/config /root/.affine_config_backup 2>/dev/null || true
msg_ok "Backed up Data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "affine_app" "toeverything/AFFiNE" "tarball" "latest" "/opt/affine"
msg_info "Rebuilding Application"
cd /opt/affine
source /root/.profile
export PATH="/root/.cargo/bin:/root/.rbenv/shims:$PATH"
set -a && source /opt/affine/.env && set +a
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
export VITE_CORE_COMMIT_SHA=$(get_latest_github_release "toeverything/AFFiNE")
# Initialize git repo (required for build process)
git init -q
git config user.email "build@local"
git config user.name "Build"
git add -A
git commit -q -m "update"
# Force Turbo to run sequentially
mkdir -p /opt/affine/.turbo
cat <<TURBO >/opt/affine/.turbo/config.json
{
"concurrency": 1
}
TURBO
$STD corepack enable
$STD corepack prepare yarn@4.12.0 --activate
$STD yarn config set enableTelemetry 0
export NODE_OPTIONS="--max-old-space-size=2048"
$STD yarn install
$STD npm install -g typescript
$STD yarn affine @affine/native build
$STD yarn affine @affine/server-native build
# Create architecture-specific symlinks
ln -sf /opt/affine/packages/backend/native/server-native.node \
/opt/affine/packages/backend/native/server-native.x64.node
ln -sf /opt/affine/packages/backend/native/server-native.node \
/opt/affine/packages/backend/native/server-native.arm64.node
ln -sf /opt/affine/packages/backend/native/server-native.node \
/opt/affine/packages/backend/native/server-native.armv7.node
$STD yarn affine init
$STD yarn affine build -p @affine/reader
$STD yarn affine build -p @affine/server
export NODE_OPTIONS="--max-old-space-size=4096"
$STD yarn affine build -p @affine/web
# Copy web assets
mkdir -p /opt/affine/packages/backend/server/static
cp -r /opt/affine/packages/frontend/apps/web/dist/* /opt/affine/packages/backend/server/static/
# Mobile manifest placeholder
mkdir -p /opt/affine/packages/backend/server/static/mobile
echo '{"publicPath":"/","js":[],"css":[],"gitHash":"","description":""}' \
>/opt/affine/packages/backend/server/static/mobile/assets-manifest.json
# Admin selfhost.html
mkdir -p /opt/affine/packages/backend/server/static/admin
cp /opt/affine/packages/backend/server/static/selfhost.html \
/opt/affine/packages/backend/server/static/admin/selfhost.html
# Run migrations
cd /opt/affine/packages/backend/server
set -a && source /opt/affine/.env && set +a
$STD node ./scripts/self-host-predeploy.js
msg_info "Restoring Data"
cp -r /root/.affine_storage_backup/. /root/.affine/storage/ 2>/dev/null || true
cp -r /root/.affine_config_backup/. /root/.affine/config/ 2>/dev/null || true
rm -rf /root/.affine_storage_backup /root/.affine_config_backup
msg_ok "Restored Data"
msg_info "Starting Services"
systemctl start affine-web affine-worker
msg_ok "Started Services"
msg_ok "Updated Successfully!"
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}:3010/sign-in${CL}"

View File

@@ -1,42 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://almalinux.org/
APP="AlmaLinux"
var_tags="${var_tags:-os}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-4}"
var_os="${var_os:-almalinux}"
var_version="${var_version:-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 /var ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating $APP LXC"
$STD dnf -y upgrade
msg_ok "Updated $APP LXC"
exit
}
start
build_container
description
msg_ok "Completed successfully!"
msg_custom "🚀" "${GN}" "${APP} setup has been successfully initialized!"

88
ct/alpine-bitmagnet.sh Normal file
View File

@@ -0,0 +1,88 @@
#!/usr/bin/env bash
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func)
# 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/bitmagnet-io/bitmagnet
APP="Alpine-bitmagnet"
var_tags="${var_tags:-alpine;torrent}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-256}"
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() {
header_info
if [[ ! -d /opt/bitmagnet ]]; then
msg_error "No ${APP} Installation Found!"
exit 1
fi
RELEASE=$(curl -s https://api.github.com/repos/bitmagnet-io/bitmagnet/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }')
if [ "${RELEASE}" != "$(cat /opt/bitmagnet_version.txt)" ] || [ ! -f /opt/bitmagnet_version.txt ]; then
msg_info "Backing up database"
rm -f /tmp/backup.sql
$STD sudo -u postgres pg_dump \
--column-inserts \
--data-only \
--on-conflict-do-nothing \
--rows-per-insert=1000 \
--table=metadata_sources \
--table=content \
--table=content_attributes \
--table=content_collections \
--table=content_collections_content \
--table=torrent_sources \
--table=torrents \
--table=torrent_files \
--table=torrent_hints \
--table=torrent_contents \
--table=torrent_tags \
--table=torrents_torrent_sources \
--table=key_values \
bitmagnet \
>/tmp/backup.sql
mv /tmp/backup.sql /opt/
msg_ok "Database backed up"
msg_info "Updating ${APP} from $(cat /opt/bitmagnet_version.txt) to ${RELEASE}"
$STD apk -U upgrade
$STD service bitmagnet stop
[ -f /opt/bitmagnet/.env ] && cp /opt/bitmagnet/.env /opt/
[ -f /opt/bitmagnet/config.yml ] && cp /opt/bitmagnet/config.yml /opt/
rm -rf /opt/bitmagnet/*
temp_file=$(mktemp)
curl -fsSL "https://github.com/bitmagnet-io/bitmagnet/archive/refs/tags/v${RELEASE}.tar.gz" -o "$temp_file"
tar zxf "$temp_file" --strip-components=1 -C /opt/bitmagnet
cd /opt/bitmagnet
$STD go build
chmod +x bitmagnet
[ -f "/opt/.env" ] && cp "/opt/.env" /opt/bitmagnet/
[ -f "/opt/config.yml" ] && cp "/opt/config.yml" /opt/bitmagnet/
rm -f "$temp_file"
echo "${RELEASE}" >/opt/bitmagnet_version.txt
$STD service bitmagnet 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}http://${IP}:3333${CL}"

View File

@@ -1,107 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Sander Koenders (sanderkoenders)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://www.borgbackup.org/
APP="Alpine-BorgBackup-Server"
var_tags="${var_tags:-alpine;backup}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-20}"
var_os="${var_os:-alpine}"
var_version="${var_version:-3.23}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
if [[ ! -f /usr/bin/borg ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
CHOICE=$(msg_menu "BorgBackup Server Update Options" \
"1" "Update BorgBackup Server" \
"2" "Reset SSH Access" \
"3" "Enable password authentication for backup user (not recommended, use SSH key instead)" \
"4" "Disable password authentication for backup user (recommended for security, use SSH key)")
case $CHOICE in
1)
msg_info "Updating $APP LXC"
$STD apk -U upgrade
msg_ok "Updated $APP LXC successfully!"
;;
2)
if [[ "${PHS_SILENT:-0}" == "1" ]]; then
msg_warn "Reset SSH Public key requires interactive mode, skipping."
exit
fi
msg_info "Setting up SSH Public Key for backup user"
msg_info "Please paste your SSH public key (e.g., ssh-rsa AAAAB3... user@host): \n"
read -p "Key: " SSH_PUBLIC_KEY
echo
if [[ -z "$SSH_PUBLIC_KEY" ]]; then
msg_error "No SSH public key provided!"
exit 1
fi
if [[ ! "$SSH_PUBLIC_KEY" =~ ^(ssh-rsa|ssh-dss|ssh-ed25519|ecdsa-sha2-) ]]; then
msg_error "Invalid SSH public key format!"
exit 1
fi
msg_info "Setting up SSH access"
mkdir -p /home/backup/.ssh
echo "$SSH_PUBLIC_KEY" >/home/backup/.ssh/authorized_keys
chown -R backup:backup /home/backup/.ssh
chmod 700 /home/backup/.ssh
chmod 600 /home/backup/.ssh/authorized_keys
msg_ok "SSH access configured for backup user"
;;
3)
if [[ "${PHS_SILENT:-0}" == "1" ]]; then
msg_warn "Enabling password authentication requires interactive mode, skipping."
exit
fi
msg_info "Enabling password authentication for backup user"
msg_warn "Password authentication is less secure than using SSH keys. Consider using SSH keys instead."
passwd backup
sed -i 's/^#*\s*PasswordAuthentication\s\+\(yes\|no\)/PasswordAuthentication yes/' /etc/ssh/sshd_config
rc-service sshd restart
msg_ok "Password authentication enabled for backup user"
;;
4)
msg_info "Disabling password authentication for backup user"
sed -i 's/^#*\s*PasswordAuthentication\s\+\(yes\|no\)/PasswordAuthentication no/' /etc/ssh/sshd_config
rc-service sshd restart
msg_ok "Password authentication disabled for backup user"
;;
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}Connection information:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}ssh backup@${IP}${CL}"
echo -e "${TAB}${VERIFYPW}${YW}To set SSH key, run this script with the 'update' option and select option 2${CL}"

View File

@@ -1,50 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: cobalt (cobaltgit)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://ntfy.sh/
APP="Alpine-ntfy"
var_tags="${var_tags:-notification}"
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
check_container_storage
check_container_resources
if [[ ! -d /etc/ntfy ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating ntfy LXC"
$STD apk -U upgrade
setcap 'cap_net_bind_service=+ep' /usr/bin/ntfy
msg_ok "Updated ntfy LXC"
msg_info "Restarting ntfy"
rc-service ntfy restart
msg_ok "Restarted ntfy"
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}${CL}"

46
ct/alpine-syncthing.sh Normal file
View File

@@ -0,0 +1,46 @@
#!/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 update
$STD apk upgrade
msg_ok "Updated Alpine Packages"
msg_info "Updating Syncthing"
$STD $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}"

View File

@@ -1,75 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Slaviša Arežina (tremor021)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://wakapi.dev/ | https://github.com/muety/wakapi
APP="Alpine-Wakapi"
var_tags="${var_tags:-code;time-tracking}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-4}"
var_os="${var_os:-alpine}"
var_version="${var_version:-3.23}"
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/wakapi ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
RELEASE=$(curl -s https://api.github.com/repos/muety/wakapi/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
if [ "${RELEASE}" != "$(cat ~/.wakapi 2>/dev/null)" ] || [ ! -f ~/.wakapi ]; then
msg_info "Stopping Wakapi Service"
$STD rc-service wakapi stop
msg_ok "Stopped Wakapi Service"
msg_info "Updating Wakapi LXC"
$STD apk -U upgrade
msg_ok "Updated Wakapi LXC"
msg_info "Creating backup"
mkdir -p /opt/wakapi-backup
cp /opt/wakapi/config.yml /opt/wakapi/wakapi_db.db /opt/wakapi-backup/
msg_ok "Created backup"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "wakapi" "muety/wakapi" "tarball"
msg_info "Configuring Wakapi"
cd /opt/wakapi
$STD go mod download
$STD go build -o wakapi
cp /opt/wakapi-backup/config.yml /opt/wakapi/
cp /opt/wakapi-backup/wakapi_db.db /opt/wakapi/
rm -rf /opt/wakapi-backup
msg_ok "Configured Wakapi"
msg_info "Starting Service"
$STD rc-service wakapi start
msg_ok "Started Service"
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 URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}"

View File

@@ -1,18 +1,17 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
source <(curl -s https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func)
# Copyright (c) 2021-2025 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://alpinelinux.org/
APP="Alpine"
var_tags="${var_tags:-os;alpine}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-5}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-0.1}"
var_os="${var_os:-alpine}"
var_version="${var_version:-3.22}"
var_version="${var_version:-3.21}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
@@ -39,4 +38,4 @@ start
build_container
description
msg_ok "Completed successfully!\n"
msg_ok "Completed Successfully!\n"

48
ct/ampache.sh Normal file
View File

@@ -0,0 +1,48 @@
#!/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/ProxmoxVE/raw/main/LICENSE
# Source:
APP="Ampache"
var_tags="${var_tags:-music}"
var_disk="${var_disk:-5}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-2048}"
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/ampache ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if [[ ! -f /opt/${APP}_version.txt ]] || [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]]; then
msg_info "Updating ${APP} LXC"
cd /opt/ampache
###### Update Script Here ######
msg_ok "Updated Successfully"
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}/install.php${CL}"

View File

@@ -1,73 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://github.com/automatic-ripping-machine/automatic-ripping-machine
APP="ARM"
var_tags="${var_tags:-media;ripping;automation}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-16}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-0}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/arm ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "arm" "automatic-ripping-machine/automatic-ripping-machine"; then
msg_info "Stopping Service"
systemctl stop armui
msg_ok "Stopped Service"
msg_info "Backing up Data"
cp /opt/arm/arm.yaml /opt/arm_yaml.bak
msg_ok "Backed up Data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "arm" "automatic-ripping-machine/automatic-ripping-machine" "tarball"
msg_info "Rebuilding Python Environment"
cd /opt/arm
$STD uv venv /opt/arm/venv
$STD uv pip install --python /opt/arm/venv/bin/python \
-r <(curl -fsSL https://raw.githubusercontent.com/automatic-ripping-machine/arm-dependencies/main/requirements.txt) \
-r requirements.txt
msg_ok "Rebuilt Python Environment"
msg_info "Restoring Data"
cp /opt/arm_yaml.bak /opt/arm/arm.yaml
chmod +x /opt/arm/scripts/thickclient/*.sh 2>/dev/null || true
rm -f /opt/arm_yaml.bak
msg_ok "Restored Data"
msg_info "Starting Service"
systemctl start armui
msg_ok "Started Service"
msg_ok "Updated successfully!"
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}:8080${CL}"

View File

@@ -1,41 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE
# Source: https://adguard.com/
APP="Adguard"
var_tags="${var_tags:-adblock}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-2}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/AdGuardHome ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_error "Adguard Home can only be updated via the user interface."
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}"

View File

@@ -1,70 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE
# Source: https://www.bazarr.media/
APP="Bazarr"
var_tags="${var_tags:-arr}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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 /var/lib/bazarr/ ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "bazarr" "morpheus65535/bazarr"; then
apt-get install -y libicu76 &>/dev/null
msg_info "Stopping Service"
systemctl stop bazarr
msg_ok "Stopped Service"
PYTHON_VERSION="3.12" setup_uv
fetch_and_deploy_gh_release "bazarr" "morpheus65535/bazarr" "prebuild" "latest" "/opt/bazarr" "bazarr.zip"
msg_info "Setup Bazarr"
mkdir -p /var/lib/bazarr/
chmod 775 /opt/bazarr /var/lib/bazarr/
# Always ensure venv exists
if [[ ! -d /opt/bazarr/venv/ ]]; then
$STD uv venv --clear /opt/bazarr/venv --python 3.12
fi
# Always check and fix service file if needed
if [[ -f /etc/systemd/system/bazarr.service ]] && grep -q "ExecStart=/usr/bin/python3" /etc/systemd/system/bazarr.service; then
sed -i "s|ExecStart=/usr/bin/python3 /opt/bazarr/bazarr.py|ExecStart=/opt/bazarr/venv/bin/python3 /opt/bazarr/bazarr.py|g" /etc/systemd/system/bazarr.service
systemctl daemon-reload
fi
sed -i.bak 's/--only-binary=Pillow//g' /opt/bazarr/requirements.txt
$STD uv pip install -r /opt/bazarr/requirements.txt --python /opt/bazarr/venv/bin/python3
msg_ok "Setup Bazarr"
msg_info "Starting Service"
systemctl start bazarr
msg_ok "Started Service"
msg_ok "Updated successfully!"
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}:6767${CL}"

View File

@@ -1,62 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: vhsdream
# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE
# Source: https://github.com/alam00000/bentopdf
APP="BentoPDF"
var_tags="${var_tags:-pdf-editor}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/bentopdf ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
NODE_VERSION="24" setup_nodejs
if check_for_gh_release "bentopdf" "alam00000/bentopdf"; then
msg_info "Stopping Service"
systemctl stop bentopdf
msg_ok "Stopped Service"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "bentopdf" "alam00000/bentopdf" "tarball" "latest" "/opt/bentopdf"
msg_info "Updating BentoPDF"
cd /opt/bentopdf
$STD npm ci --no-audit --no-fund
export SIMPLE_MODE=true
$STD npm run build -- --mode production
msg_ok "Updated BentoPDF"
msg_info "Starting Service"
systemctl start bentopdf
msg_ok "Started Service"
msg_ok "Updated successfully!"
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}:8080${CL}"

View File

@@ -1,109 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE
# Source: https://www.home-assistant.io/
APP="Home Assistant"
var_tags="${var_tags:-automation;smarthome}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-16}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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 /var/lib/docker/volumes/hass_config/_data ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
UPD=$(msg_menu "Home Assistant Update Options" \
"1" "Update ALL Containers" \
"2" "Remove ALL Unused Images" \
"3" "Install HACS" \
"4" "Install FileBrowser")
if [ "$UPD" == "1" ]; then
msg_info "Updating All Containers"
CONTAINER_LIST="${1:-$(docker ps -q)}"
for container in ${CONTAINER_LIST}; do
CONTAINER_IMAGE="$(docker inspect --format "{{.Config.Image}}" --type container "${container}")"
RUNNING_IMAGE="$(docker inspect --format "{{.Image}}" --type container "${container}")"
docker pull "${CONTAINER_IMAGE}"
LATEST_IMAGE="$(docker inspect --format "{{.Id}}" --type image "${CONTAINER_IMAGE}")"
if [[ "${RUNNING_IMAGE}" != "${LATEST_IMAGE}" ]]; then
pip install -U runlike
echo "Updating ${container} image ${CONTAINER_IMAGE}"
DOCKER_COMMAND="$(runlike --use-volume-id "${container}")"
docker rm --force "${container}"
eval "${DOCKER_COMMAND}"
fi
done
msg_ok "Updated All Containers"
exit
fi
if [ "$UPD" == "2" ]; then
msg_info "Removing ALL Unused Images"
docker image prune -af
msg_ok "Removed ALL Unused Images"
exit
fi
if [ "$UPD" == "3" ]; then
msg_info "Installing Home Assistant Community Store (HACS)"
$STD apt update
cd /var/lib/docker/volumes/hass_config/_data
$STD bash <(curl -fsSL https://get.hacs.xyz)
msg_ok "Installed Home Assistant Community Store (HACS)"
echo -e "\n Reboot Home Assistant and clear browser cache then Add HACS integration.\n"
exit
fi
if [ "$UPD" == "4" ]; then
msg_info "Installing FileBrowser"
RELEASE=$(curl -fsSL https://api.github.com/repos/filebrowser/filebrowser/releases/latest | grep -o '"tag_name": ".*"' | sed 's/"//g' | sed 's/tag_name: //g')
$STD curl -fsSL https://github.com/filebrowser/filebrowser/releases/download/v2.23.0/linux-arm64-filebrowser.tar.gz | tar -xzv -C /usr/local/bin
$STD filebrowser config init -a '0.0.0.0'
$STD filebrowser config set -a '0.0.0.0'
$STD filebrowser users add admin helper-scripts.com --perm.admin
msg_ok "Installed FileBrowser"
msg_info "Creating Service"
service_path="/etc/systemd/system/filebrowser.service"
echo "[Unit]
Description=Filebrowser
After=network-online.target
[Service]
User=root
WorkingDirectory=/root/
ExecStart=/usr/local/bin/filebrowser -r /
[Install]
WantedBy=default.target" >$service_path
$STD systemctl enable --now filebrowser
msg_ok "Created Service"
msg_ok "Completed successfully!\n"
echo -e "FileBrowser should be reachable by going to the following URL.
${BL}http://$LOCAL_IP:8080${CL} admin|helper-scripts.com\n"
exit
fi
}
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}HA: http://${IP}:8123${CL}"
echo -e "${TAB}${GATEWAY}${BGN}Portainer: https://${IP}:9443${CL}"

View File

@@ -1,52 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE
# Source: https://jellyfin.org/
APP="Jellyfin"
var_tags="${var_tags:-media}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-16}"
var_os="${var_os:-ubuntu}"
var_version="${var_version:-24.04}"
var_unprivileged="${var_unprivileged:-0}"
var_gpu="${var_gpu:-yes}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /usr/lib/jellyfin ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating Jellyfin"
ensure_dependencies libjemalloc2
if [[ ! -f /usr/lib/libjemalloc.so ]]; then
ln -sf /usr/lib/aarch64-linux-gnu/libjemalloc.so.2 /usr/lib/libjemalloc.so
fi
$STD apt update
$STD apt -y upgrade
$STD apt -y --with-new-pkgs upgrade jellyfin jellyfin-server
msg_ok "Updated Jellyfin"
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}:8096${CL}"

View File

@@ -1,79 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE
# Source: https://github.com/Chevron7Locked/kima-hub
APP="Kima-Hub"
var_tags="${var_tags:-music;streaming;media}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-8192}"
var_disk="${var_disk:-20}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/kima-hub ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "kima-hub" "Chevron7Locked/kima-hub"; then
msg_info "Stopping Services"
systemctl stop kima-frontend kima-backend kima-analyzer kima-analyzer-clap
msg_ok "Stopped Services"
msg_info "Backing up Data"
cp /opt/kima-hub/backend/.env /opt/kima-hub-backend-env.bak
cp /opt/kima-hub/frontend/.env /opt/kima-hub-frontend-env.bak
msg_ok "Backed up Data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "kima-hub" "Chevron7Locked/kima-hub" "tarball"
msg_info "Restoring Data"
cp /opt/kima-hub-backend-env.bak /opt/kima-hub/backend/.env
cp /opt/kima-hub-frontend-env.bak /opt/kima-hub/frontend/.env
rm -f /opt/kima-hub-backend-env.bak /opt/kima-hub-frontend-env.bak
msg_ok "Restored Data"
msg_info "Rebuilding Backend"
cd /opt/kima-hub/backend
$STD npm install
$STD npm run build
$STD npx prisma generate
$STD npx prisma migrate deploy
msg_ok "Rebuilt Backend"
msg_info "Rebuilding Frontend"
cd /opt/kima-hub/frontend
$STD npm install
$STD npm run build
msg_ok "Rebuilt Frontend"
msg_info "Starting Services"
systemctl start kima-backend kima-frontend kima-analyzer kima-analyzer-clap
msg_ok "Started Services"
msg_ok "Updated successfully!"
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}:3030${CL}"

View File

@@ -1,74 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: kristocopani
# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE
# Source: https://lubelogger.com/
APP="LubeLogger"
var_tags="${var_tags:-vehicle;car}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-2}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/lubelogger.service ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "lubelogger" "hargata/lubelog"; then
msg_info "Stopping Service"
systemctl stop lubelogger
msg_ok "Stopped Service"
msg_info "Backing up data"
mkdir -p /tmp/lubeloggerData/data
cp /opt/lubelogger/appsettings.json /tmp/lubeloggerData/appsettings.json
cp -r /opt/lubelogger/data/ /tmp/lubeloggerData/
# Lubelogger has moved multiples folders to the 'data' folder, and we need to move them before the update to keep the user data
# Github Discussion: https://github.com/hargata/lubelog/discussions/787
[[ -e /opt/lubelogger/config ]] && cp -r /opt/lubelogger/config /tmp/lubeloggerData/data/
[[ -e /opt/lubelogger/wwwroot/translations ]] && cp -r /opt/lubelogger/wwwroot/translations /tmp/lubeloggerData/data/
[[ -e /opt/lubelogger/wwwroot/documents ]] && cp -r /opt/lubelogger/wwwroot/documents /tmp/lubeloggerData/data/
[[ -e /opt/lubelogger/wwwroot/images ]] && cp -r /opt/lubelogger/wwwroot/images /tmp/lubeloggerData/data/
[[ -e /opt/lubelogger/wwwroot/temp ]] && cp -r /opt/lubelogger/wwwroot/temp /tmp/lubeloggerData/data/
[[ -e /opt/lubelogger/log ]] && cp -r /opt/lubelogger/log /tmp/lubeloggerData/
rm -rf /opt/lubelogger
msg_ok "Backed up data"
fetch_and_deploy_gh_release "lubelogger" "hargata/lubelog" "prebuild" "latest" "/opt/lubelogger" "LubeLogger*linux_x64.zip"
msg_info "Configuring LubeLogger"
chmod 700 /opt/lubelogger/CarCareTracker
cp -rf /tmp/lubeloggerData/* /opt/lubelogger/
rm -rf /tmp/lubeloggerData
msg_ok "Configured LubeLogger"
msg_info "Starting Service"
systemctl start lubelogger
msg_ok "Started Service"
msg_ok "Updated successfully!"
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}:5000${CL}"

View File

@@ -1,47 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE
# Source: https://pi-hole.net/
APP="Pihole"
var_tags="${var_tags:-adblock}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-2}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/pihole ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating PiHole"
set +e
$STD apt update
$STD apt upgrade -y
/usr/local/bin/pihole -up
msg_ok "Updated PiHole"
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}/admin${CL}"

View File

@@ -1,63 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE
# Source: https://github.com/rogerfar/rdt-client
APP="RDTClient"
var_tags="${var_tags:-torrent}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/rdtc/ ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "rdt-client" "rogerfar/rdt-client"; then
msg_info "Stopping Service"
systemctl stop rdtc
msg_ok "Stopped Service"
msg_info "Creating backup"
mkdir -p /opt/rdtc-backup
cp -R /opt/rdtc/appsettings.json /opt/rdtc-backup/
msg_ok "Backup created"
fetch_and_deploy_gh_release "rdt-client" "rogerfar/rdt-client" "prebuild" "latest" "/opt/rdtc" "RealDebridClient.zip"
cp -R /opt/rdtc-backup/appsettings.json /opt/rdtc/
if dpkg-query -W dotnet-sdk-8.0 >/dev/null 2>&1; then
$STD apt remove --purge -y dotnet-sdk-8.0
ensure_dependencies aspnetcore-runtime-9.0
fi
rm -rf /opt/rdtc-backup
msg_info "Starting Service"
systemctl start rdtc
msg_ok "Started Service"
msg_ok "Updated successfully!"
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}:6500${CL}"

View File

@@ -1,119 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE
# Source: https://github.com/dani-garcia/vaultwarden
APP="Vaultwarden"
var_tags="${var_tags:-password-manager}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-6144}"
var_disk="${var_disk:-20}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/vaultwarden.service ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
VAULT=$(get_latest_github_release "dani-garcia/vaultwarden")
WVRELEASE=$(get_latest_github_release "dani-garcia/bw_web_builds")
UPD=$(msg_menu "Vaultwarden Update Options" \
"1" "Update VaultWarden + Web-Vault" \
"2" "Set Admin Token")
if [ "$UPD" == "1" ]; then
if check_for_gh_release "vaultwarden" "dani-garcia/vaultwarden"; then
msg_info "Stopping Service"
systemctl stop vaultwarden
msg_ok "Stopped Service"
fetch_and_deploy_gh_release "vaultwarden" "dani-garcia/vaultwarden" "tarball" "latest" "/tmp/vaultwarden-src"
msg_info "Updating VaultWarden to $VAULT (Patience)"
cd /tmp/vaultwarden-src
VW_VERSION="$VAULT"
export VW_VERSION
$STD cargo build --features "sqlite,mysql,postgresql" --release
if [[ -f /usr/bin/vaultwarden ]]; then
cp target/release/vaultwarden /usr/bin/
else
cp target/release/vaultwarden /opt/vaultwarden/bin/
fi
cd ~ && rm -rf /tmp/vaultwarden-src
msg_ok "Updated VaultWarden to ${VAULT}"
msg_info "Starting Service"
systemctl start vaultwarden
msg_ok "Started Service"
else
msg_ok "VaultWarden is already up-to-date"
fi
if check_for_gh_release "vaultwarden_webvault" "dani-garcia/bw_web_builds"; then
msg_info "Stopping Service"
systemctl stop vaultwarden
msg_ok "Stopped Service"
msg_info "Updating Web-Vault to $WVRELEASE"
rm -rf /opt/vaultwarden/web-vault
mkdir -p /opt/vaultwarden/web-vault
fetch_and_deploy_gh_release "vaultwarden_webvault" "dani-garcia/bw_web_builds" "prebuild" "latest" "/opt/vaultwarden/web-vault" "bw_web_*.tar.gz"
chown -R root:root /opt/vaultwarden/web-vault/
msg_ok "Updated Web-Vault to ${WVRELEASE}"
msg_info "Starting Service"
systemctl start vaultwarden
msg_ok "Started Service"
else
msg_ok "Web-Vault is already up-to-date"
fi
msg_ok "Updated successfully!"
exit
fi
if [ "$UPD" == "2" ]; then
if [[ "${PHS_SILENT:-0}" == "1" ]]; then
msg_warn "Set Admin Token requires interactive mode, skipping."
exit
fi
read -r -s -p "Set the ADMIN_TOKEN: " NEWTOKEN
echo ""
if [[ -n "$NEWTOKEN" ]]; then
ensure_dependencies argon2
TOKEN=$(echo -n "${NEWTOKEN}" | argon2 "$(openssl rand -base64 32)" -t 2 -m 16 -p 4 -l 64 -e)
sed -i "s|ADMIN_TOKEN=.*|ADMIN_TOKEN='${TOKEN}'|" /opt/vaultwarden/.env
if [[ -f /opt/vaultwarden/data/config.json ]]; then
sed -i "s|\"admin_token\":.*|\"admin_token\": \"${TOKEN}\"|" /opt/vaultwarden/data/config.json
fi
systemctl restart vaultwarden
msg_ok "Admin token updated"
fi
exit
fi
}
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}:8000${CL}"

39
ct/asterisk.sh Normal file
View File

@@ -0,0 +1,39 @@
#!/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: michelroegl-brunner
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://asterisk.org/
APP="Asterisk"
var_tags="${var_tags:-os}"
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 /var ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "No Update function provided for ${APP} LXC"
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"

View File

@@ -1,17 +1,17 @@
#!/usr/bin/env bash
source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Thieneret
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://github.com/goauthentik/authentik
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func)
# Copyright (c) 2021-2025 community-scripts ORG
# Author: remz1337
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://goauthentik.io/
APP="authentik"
var_tags="${var_tags:-auth}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-10}"
APP="Authentik"
var_tags="${var_tags:-identity-provider}"
var_disk="${var_disk:-12}"
var_cpu="${var_cpu:-6}"
var_ram="${var_ram:-10240}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_version="${var_version:-12}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
@@ -20,102 +20,72 @@ color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
AUTHENTIK_VERSION="version/2026.2.0"
NODE_VERSION="24"
if [[ ! -d /opt/authentik ]]; then
msg_error "No authentik Installation Found!"
exit
fi
if [[ "$AUTHENTIK_VERSION" == "$(cat $HOME/.authentik)" ]]; then
msg_ok "Authentik up-to-date"
exit
fi
if check_for_gh_release "geoipupdate" "maxmind/geoipupdate"; then
fetch_and_deploy_gh_release "geoipupdate" "maxmind/geoipupdate" "binary"
fi
msg_info "Stopping Services"
systemctl stop authentik-server.service
systemctl stop authentik-worker.service
msg_ok "Stopped Services"
if check_for_gh_release "xmlsec" "lsh123/xmlsec"; then
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "xmlsec" "lsh123/xmlsec" "tarball" "latest" "/opt/xmlsec"
msg_info "Update xmlsec"
cd /opt/xmlsec
$STD ./autogen.sh
$STD make -j $(nproc)
$STD make check
$STD make install
ldconfig
msg_ok "xmlsec updated"
fi
setup_nodejs
setup_go
if check_for_gh_release "authentik" "goauthentik/authentik" "${AUTHENTIK_VERSION}"; then
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "authentik" "goauthentik/authentik" "tarball" "${AUTHENTIK_VERSION}" "/opt/authentik"
msg_info "Update web"
cd /opt/authentik/web
NODE_ENV="production"
$STD npm install
$STD npm run build
$STD npm run build:sfe
msg_ok "Web updated"
msg_info "Update go proxy"
cd /opt/authentik
CGO_ENABLED="1"
$STD go mod download
$STD go build -o /opt/authentik/authentik-server ./cmd/server
msg_ok "Go proxy updated"
setup_uv
setup_rust
msg_info "Update python server"
$STD uv python install 3.14.3 -i /usr/local/bin
UV_NO_BINARY_PACKAGE="cryptography lxml python-kadmin-rs xmlsec"
UV_COMPILE_BYTECODE="1"
UV_LINK_MODE="copy"
UV_NATIVE_TLS="1"
RUSTUP_PERMIT_COPY_RENAME="true"
cd /opt/authentik
export UV_PYTHON_INSTALL_DIR="/usr/local/bin"
$STD uv sync --frozen --no-install-project --no-dev
msg_ok "Python server updated"
chown -R authentik:authentik /opt/authentik
fi
msg_info "Restarting services"
systemctl restart authentik-server.service authentik-worker.service
msg_ok "Started Service"
msg_ok "Updated successfully!"
header_info
check_container_storage
check_container_resources
if [[ ! -f /etc/systemd/system/authentik-server.service ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
RELEASE=$(curl -fsSL https://api.github.com/repos/goauthentik/authentik/releases/latest | grep "tarball_url" | awk '{print substr($2, 2, length($2)-3)}')
if [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]] || [[ ! -f /opt/${APP}_version.txt ]]; then
NODE_VERSION="22"
PG_VERSION="16"
setup_uv
install_postgresql
install_node_and_modules
install_go
msg_info "Stopping ${APP}"
systemctl stop authentik-server
systemctl stop authentik-worker
msg_ok "Stopped ${APP}"
msg_info "Building ${APP} website"
mkdir -p /opt/authentik
curl -fsSL "${RELEASE}" -o "authentik.tar.gz"
tar -xzf authentik.tar.gz -C /opt/authentik --strip-components 1 --overwrite
rm -rf authentik.tar.gz
cd /opt/authentik/website
$STD npm install
$STD npm run build-bundled
cd /opt/authentik/web
$STD npm install
$STD npm run build
msg_ok "Built ${APP} website"
msg_info "Building ${APP} server"
cd /opt/authentik
go mod download
go build -o /go/authentik ./cmd/server
go build -o /opt/authentik/authentik-server /opt/authentik/cmd/server/
msg_ok "Built ${APP} server"
msg_info "Building Authentik"
cd /opt/authentik
$STD uv sync --frozen --no-install-project --no-dev
uv run python -m lifecycle.migrate
ln -s /opt/authentik/.venv/bin/gunicorn /usr/local/bin/gunicorn
ln -s /opt/authentik/.venv/bin/celery /usr/local/bin/celery
msg_ok "Authentik built"
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated ${APP} to v${RELEASE}"
msg_info "Starting ${APP}"
systemctl start authentik-server
systemctl start authentik-worker
msg_ok "Started ${APP}"
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} Initial setup URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:9000/if/flow/initial-setup/${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:9000${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:9000/if/flow/initial-setup/${CL}"

45
ct/babybuddy.sh Normal file
View File

@@ -0,0 +1,45 @@
#!/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/ProxmoxVE/raw/main/LICENSE
# Source:
APP="BabyBuddy"
var_tags="${var_tags:-baby}"
var_disk="${var_disk:-5}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
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/maxun ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
RELEASE=$(curl -s https://api.github.com/repos/xxxxx/xxxxx/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 Services"
systemctl stop APP
msg_ok "Services Stopped"
fi
}
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}:8080${CL}"

136
ct/bar-assistant.sh Normal file
View File

@@ -0,0 +1,136 @@
#!/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:-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/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 Service"
systemctl stop nginx
msg_ok "Stopped Service"
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 /opt/bar-assistant-backup/.env /opt/bar-assistant/.env
cp /opt/bar-assistant-backup/storage/bar-assistant /opt/bar-assistant/storage/bar-assistant
cd /opt/bar-assistant
composer install
php artisan migrate --force
php artisan storage:link
php artisan bar:setup-meilisearch
php artisan scout:sync-index-settings
php artisan config:cache
php artisan route:cache
php artisan event:cache
echo "${RELEASE_BARASSISTANT}" >/opt/${APP}_version.txt
msg_ok "Updated $APP to v${RELEASE_BARASSISTANT}"
msg_info "Starting Service"
systemctl start service nginx
msg_ok "Started Service"
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 Service"
systemctl stop nginx
msg_ok "Stopped Service"
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
npm run build
echo "${RELEASE_SALTRIM}" >/opt/vue-salt-rim_version.txt
msg_ok "Updated $APP to v${RELEASE_SALTRIM}"
msg_info "Starting Service"
systemctl start service nginx
msg_ok "Started Service"
msg_info "Cleaning up"
rm -rf /opt/saltrim.zip
rm -rf /opt/vue-salt-rim-backup
msg_ok "Cleaned"
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 Service"
systemctl stop meilisearch
msg_ok "Stopped Service"
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 v${RELEASE_MEILISEARCH}"
msg_info "Starting Service"
systemctl start meilisearch
msg_ok "Started Service"
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}"

75
ct/bluecherry.sh Normal file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env bash
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func)
# 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/TwiN/gatus
APP="bluecherry"
var_tags="${var_tags:-video;dvr}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-15}"
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/gatus ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
RELEASE=$(curl -s https://api.github.com/repos/TwiN/gatus/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }')
if [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]] || [[ ! -f /opt/${APP}_version.txt ]]; then
msg_info "Updating $APP"
msg_info "Stopping $APP"
systemctl stop gatus
msg_ok "Stopped $APP"
msg_info "Updating $APP to v${RELEASE}"
mv /opt/gatus/config/config.yaml /opt
rm -rf /opt/gatus/*
temp_file=$(mktemp)
curl -fsSL "https://github.com/TwiN/gatus/archive/refs/tags/v${RELEASE}.tar.gz" -o "$temp_file"
tar zxf "$temp_file" --strip-components=1 -C /opt/gatus
cd /opt/gatus
$STD go mod tidy
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gatus .
setcap CAP_NET_RAW+ep gatus
mv /opt/config.yaml config
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated $APP to v${RELEASE}"
msg_info "Starting $APP"
systemctl start gatus
msg_ok "Started $APP"
msg_info "Cleaning Up"
rm -f "$temp_file"
msg_ok "Cleanup Completed"
msg_ok "Update Successful"
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}:8080${CL}"

76
ct/bookstack.sh Normal file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/env bash
source <(curl -fsSL 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/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/BookStackApp/BookStack
APP="Bookstack"
var_tags="${var_tags:-organizer}"
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/bookstack ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
RELEASE=$(curl -fsSL https://api.github.com/repos/BookStackApp/BookStack/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }')
if [[ ! -f /opt/${APP}_version.txt ]] || [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]]; then
msg_info "Stopping Apache2"
systemctl stop apache2
msg_ok "Services Stopped"
msg_info "Updating ${APP} to v${RELEASE}"
mv /opt/bookstack /opt/bookstack-backup
fetch_and_deploy_gh_release BookstackApp/BookStack
cp /opt/bookstack-backup/.env /opt/bookstack/.env
cp -r /opt/bookstack-backup/public/uploads/* /opt/bookstack/public/uploads/ || true
cp -r /opt/bookstack-backup/storage/uploads/* /opt/bookstack/storage/uploads/ || true
cp -r /opt/bookstack-backup/themes/* /opt/bookstack/themes/ || true
cd /opt/bookstack
export COMPOSER_ALLOW_SUPERUSER=1
$STD composer install --no-dev
$STD php artisan migrate --force
chown www-data:www-data -R /opt/bookstack /opt/bookstack/bootstrap/cache /opt/bookstack/public/uploads /opt/bookstack/storage
chmod -R 755 /opt/bookstack /opt/bookstack/bootstrap/cache /opt/bookstack/public/uploads /opt/bookstack/storage
chmod -R 775 /opt/bookstack/storage /opt/bookstack/bootstrap/cache /opt/bookstack/public/uploads
chmod -R 640 /opt/bookstack/.env
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated ${APP} to v${RELEASE}"
msg_info "Starting Apache2"
systemctl start apache2
msg_ok "Started Apache2"
msg_info "Cleaning Up"
rm -rf /opt/bookstack-backup
rm -rf "/opt/BookStack-${RELEASE}.zip"
msg_ok "Cleaned"
msg_ok "Updated Successfully"
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}${CL}"

View File

@@ -1,77 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Slaviša Arežina (tremor021)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://github.com/caddymanager/caddymanager
APP="CaddyManager"
var_tags="${var_tags:-}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/caddymanager ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "caddymanager" "caddymanager/caddymanager"; then
msg_info "Stopping Service"
systemctl stop caddymanager-backend
systemctl stop caddymanager-frontend
msg_ok "Stopped Service"
msg_info "Backing up configuration"
cp /opt/caddymanager/caddymanager.env /opt/
cp /opt/caddymanager/caddymanager.sqlite /opt/
cp /opt/caddymanager/frontend/Caddyfile /opt/
msg_ok "Backed up configuration"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "caddymanager" "caddymanager/caddymanager" "tarball"
msg_info "Installing CaddyManager"
cd /opt/caddymanager/backend
$STD npm install
cd /opt/caddymanager/frontend
$STD npm install
$STD npm run build
msg_ok "Installed CaddyManager"
msg_info "Restoring configuration"
mv /opt/caddymanager.env /opt/caddymanager/
mv /opt/caddymanager.sqlite /opt/caddymanager/
mv -f /opt/Caddyfile /opt/caddymanager/frontend/
msg_ok "Restored configuration"
msg_info "Starting Service"
systemctl start caddymanager-backend
systemctl start caddymanager-frontend
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
cleanup_lxc
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}"

View File

@@ -1,42 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://www.centos.org/centos-stream/
APP="CentOS Stream"
var_tags="${var_tags:-os}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-4}"
var_os="${var_os:-centos}"
var_version="${var_version:-9}"
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 /var ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating CentOS LXC"
$STD dnf -y upgrade
msg_ok "Updated CentOS LXC"
exit
}
start
build_container
description
msg_ok "Completed successfully!"
msg_custom "🚀" "${GN}" "${APP} setup has been successfully initialized!"

37
ct/cloudflare-ddns.sh Normal file
View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func)
# Copyright (c) 2021-2025 community-scripts ORG
# Author: edoardop13
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/favonia/cloudflare-ddns
APP="Cloudflare-DDNS"
var_tags="${var_tags:-network}"
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 [[ ! -f /etc/systemd/system/cloudflare-ddns.service ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_error "There is no update function for ${APP}."
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"

248
ct/create_lxc.sh Normal file
View File

@@ -0,0 +1,248 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2025 tteck
# Author: tteck (tteckster)
# Co-Author: MickLesk
# License: MIT
# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# This sets verbose mode if the global variable is set to "yes"
# if [ "$VERBOSE" == "yes" ]; then set -x; fi
if command -v curl >/dev/null 2>&1; then
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func)
load_functions
#echo "(create-lxc.sh) Loaded core.func via curl"
elif command -v wget >/dev/null 2>&1; then
source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func)
load_functions
#echo "(create-lxc.sh) Loaded core.func via wget"
fi
# This sets error handling options and defines the error_handler function to handle errors
set -Eeuo pipefail
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
# This function handles errors
function error_handler() {
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"
exit 200
}
# 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."
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."
exit 1
fi
# This function is used to select the storage class and determine the corresponding storage content type and label.
function select_storage() {
local CLASS=$1
local CONTENT
local CONTENT_LABEL
case $CLASS in
container)
CONTENT='rootdir'
CONTENT_LABEL='Container'
;;
template)
CONTENT='vztmpl'
CONTENT_LABEL='Container template'
;;
*) false || {
msg_error "Invalid storage class."
exit 201
} ;;
esac
# This Queries all storage locations
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)}')
local ITEM="Type: $TYPE Free: $FREE "
local OFFSET=2
if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then
local MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))
fi
MENU+=("$TAG" "$ITEM" "OFF")
done < <(pvesm status -content $CONTENT | awk 'NR>1')
# Select storage location
if [ $((${#MENU[@]} / 3)) -eq 1 ]; then
printf ${MENU[0]}
else
local STORAGE
while [ -z "${STORAGE:+x}" ]; do
STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \
"Which storage pool you would like to use for the ${CONTENT_LABEL,,}?\nTo make a selection, use the Spacebar.\n" \
16 $(($MSG_MAX_LENGTH + 23)) 6 \
"${MENU[@]}" 3>&1 1>&2 2>&3) || {
msg_error "Menu aborted."
exit 202
}
if [ $? -ne 0 ]; then
echo -e "${CROSS}${RD} Menu aborted by user.${CL}"
exit 0
fi
done
printf "%s" "$STORAGE"
fi
}
# Test if required variables are set
[[ "${CTID:-}" ]] || {
msg_error "You need to set 'CTID' variable."
exit 203
}
[[ "${PCT_OSTYPE:-}" ]] || {
msg_error "You need to set 'PCT_OSTYPE' variable."
exit 204
}
# Test if ID is valid
[ "$CTID" -ge "100" ] || {
msg_error "ID cannot be less than 100."
exit 205
}
# Test if ID is in use
if qm status "$CTID" &>/dev/null || pct status "$CTID" &>/dev/null; then
echo -e "ID '$CTID' is already in use."
unset CTID
msg_error "Cannot use ID that is already in use."
exit 206
fi
# Get template storage
TEMPLATE_STORAGE=$(select_storage template) || exit
msg_ok "Using ${BL}$TEMPLATE_STORAGE${CL} ${GN}for Template Storage."
# Get container storage
CONTAINER_STORAGE=$(select_storage container) || exit
msg_ok "Using ${BL}$CONTAINER_STORAGE${CL} ${GN}for Container Storage."
# Update LXC template list
msg_info "Updating LXC Template List"
#check_network
pveam update >/dev/null
msg_ok "Updated LXC Template List"
# Get LXC template string
TEMPLATE_SEARCH=${PCT_OSTYPE}-${PCT_OSVERSION:-}
mapfile -t TEMPLATES < <(pveam available -section system | sed -n "s/.*\($TEMPLATE_SEARCH.*\)/\1/p" | sort -t - -k 2 -V)
[ ${#TEMPLATES[@]} -gt 0 ] || {
msg_error "Unable to find a template when searching for '$TEMPLATE_SEARCH'."
exit 207
}
TEMPLATE="${TEMPLATES[-1]}"
TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE)"
# Without NAS/Mount: TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE"
# Check if template exists, if corrupt remove and redownload
if ! pveam list "$TEMPLATE_STORAGE" | grep -q "$TEMPLATE" || ! zstdcat "$TEMPLATE_PATH" | tar -tf - >/dev/null 2>&1; then
msg_warn "Template $TEMPLATE not found in storage or seems to be corrupted. Redownloading."
[[ -f "$TEMPLATE_PATH" ]] && rm -f "$TEMPLATE_PATH"
# Download with 3 attempts
for attempt in {1..3}; do
msg_info "Attempt $attempt: Downloading LXC template..."
if timeout 120 pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null; then
msg_ok "Template download successful."
break
fi
if [ $attempt -eq 3 ]; then
msg_error "Three failed attempts. Aborting."
exit 208
fi
sleep $((attempt * 5))
done
fi
msg_ok "LXC Template is ready to use."
# 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
# Combine all options
PCT_OPTIONS=(${PCT_OPTIONS[@]:-${DEFAULT_PCT_OPTIONS[@]}})
[[ " ${PCT_OPTIONS[@]} " =~ " -rootfs " ]] || PCT_OPTIONS+=(-rootfs "$CONTAINER_STORAGE:${PCT_DISK_SIZE:-8}")
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."
if ! zstdcat "$TEMPLATE_PATH" | tar -tf - >/dev/null 2>&1; then
msg_error "Template appears to be corrupted. Removing and re-downloading."
rm -f "$TEMPLATE_PATH"
if ! timeout 120 pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null; then
msg_error "Failed to re-download template."
exit 208
fi
msg_ok "Re-downloaded LXC Template"
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" &>/dev/null; then
msg_error "Container creation failed after re-downloading template."
exit 200
fi
else
msg_error "Container creation failed, but template is not corrupted."
exit 209
fi
fi
: "${UDHCPC_FIX:=}"
if [ "$UDHCPC_FIX" == "yes" ]; then
# Ensure container is mounted
if ! mount | grep -q "/var/lib/lxc/${CTID}/rootfs"; then
pct mount "$CTID" >/dev/null 2>&1
MOUNTED_HERE=true
else
MOUNTED_HERE=false
fi
CONFIG_FILE="/var/lib/lxc/${CTID}/rootfs/etc/udhcpc/udhcpc.conf"
for i in {1..10}; do
[ -f "$CONFIG_FILE" ] && break
sleep 0.5
done
if [ -f "$CONFIG_FILE" ]; then
msg_info "Patching udhcpc.conf for Alpine DNS override"
sed -i '/^#*RESOLV_CONF="/d' "$CONFIG_FILE"
awk '
/^# Do not overwrite \/etc\/resolv\.conf/ {
print
print "RESOLV_CONF=\"no\""
next
}
{ print }
' "$CONFIG_FILE" >"${CONFIG_FILE}.tmp" && mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
msg_ok "Patched udhcpc.conf (RESOLV_CONF=\"no\")"
else
msg_error "udhcpc.conf not found in $CONFIG_FILE after waiting"
fi
# Clean up: only unmount if we mounted it here
if [ "${MOUNTED_HERE}" = true ]; then
pct unmount "$CTID" >/dev/null 2>&1
fi
fi
msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created."

View File

@@ -1,22 +1,18 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
source <(curl -s https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func)
# Copyright (c) 2021-2025 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source:
# Source: https://www.debian.org/
APP="Debian"
var_tags="${var_tags:-}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-8192}"
var_disk="${var_disk:-20}"
var_tags="${var_tags:-os}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-2}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_version="${var_version:-12}"
var_unprivileged="${var_unprivileged:-1}"
var_gpu="${var_gpu:-yes}"
#var_fuse="${var_fuse:-no}"
#var_tun="${var_tun:-no}"
header_info "$APP"
variables
@@ -24,24 +20,23 @@ color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /var ]]; then
msg_error "No ${APP} Installation Found!"
header_info
check_container_storage
check_container_resources
if [[ ! -d /var ]]; 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"
exit
fi
msg_info "Updating Debian LXC"
$STD apt update
$STD apt upgrade -y
msg_ok "Updated Debian LXC"
cleanup_lxc
exit
}
start
build_container
description
msg_ok "Completed successfully!"
msg_custom "🚀" "${GN}" "${APP} setup has been successfully initialized!"
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"

View File

@@ -1,35 +0,0 @@
{
"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": []
}

View File

@@ -1,52 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (Canbiz)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/community-scripts/ProxmoxVE
APP="Docspell"
var_tags="${var_tags:-document}"
var_disk="${var_disk:-7}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-2048}"
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/docspell ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating ${APP} LXC"
cd /opt/bookstack
git config --global --add safe.directory /opt/bookstack >/dev/null 2>&1
git pull origin release >/dev/null 2>&1
composer install --no-interaction --no-dev >/dev/null 2>&1
php artisan migrate --force >/dev/null 2>&1
php artisan cache:clear
php artisan config:clear
php artisan view:clear
msg_ok "Updated Successfully"
exit
msg_error "There is currently no update path available."
}
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}:7880${CL}"

View File

@@ -1,35 +0,0 @@
{
"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": []
}

View File

@@ -1,54 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://github.com/getmaxun/maxun
APP="Maxun"
var_tags="${var_tags:-automation;scraper}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-4096}"
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 [[ ! -d /opt/maxun ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
# NOTE: Updates temporarily disabled due to upstream TypeScript build errors in v0.0.27+
# The mcp-worker.ts file has type instantiation issues that prevent compilation
# Pinned to v0.0.26 until upstream fixes the issue
# See: https://github.com/getmaxun/maxun/releases
msg_warn "Updates are temporarily disabled due to upstream build issues"
msg_info "Current pinned version: v0.0.26"
msg_info "Check https://github.com/getmaxun/maxun/releases for fixes"
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}"
echo -e "${INFO}${YW} MinIO Console:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:9001${CL}"
echo -e "${INFO}${YW} Credentials saved in:${CL}"
echo -e "${TAB}/root/maxun.creds"

View File

@@ -1,46 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://petio.tv/
APP="Petio"
var_tags="${var_tags:-media}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-4}"
var_os="${var_os:-ubuntu}"
var_version="${var_version:-24.04}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/Petio ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating Pepito"
systemctl stop petio
curl -fsSL https://petio.tv/releases/latest -o petio-latest.zip
$STD unzip petio-latest.zip -d /opt/Petio
systemctl start petio
msg_ok "Updated Pepito"
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}:7777${CL}"

View File

@@ -1,130 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2023 tteck
# Author: tteck (tteckster)
# License: MIT
# https://github.com/tteck/Proxmox/raw/main/LICENSE
function header_info {
clear
cat <<"EOF"
__ __ __
____ ___ / /_/ /_ ____ ____ / /_ _ ____ ______
/ __ \/ _ \/ __/ __ \/ __ \/ __ \/ __/ | |/_/ / / /_ /
/ / / / __/ /_/ /_/ / /_/ / /_/ / /__ _> </ /_/ / / /_
/_/ /_/\___/\__/_.___/\____/\____/\__(_)_/|_|\__, / /___/
/____/
EOF
}
header_info
echo -e "Loading..."
APP="netboot.xyz"
var_disk="${var_disk:-2}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_os="${var_os:-debian}"
var_version="${var_version:-12}"
variables
color
catch_errors
function default_settings() {
CT_TYPE="1"
PW=""
CT_ID=$NEXTID
HN=$NSAPP
DISK_SIZE="$var_disk"
CORE_COUNT="$var_cpu"
RAM_SIZE="$var_ram"
BRG="vmbr0"
NET="dhcp"
GATE=""
DISABLEIP6="no"
MTU=""
SD=""
NS=""
MAC=""
VLAN=""
SSH="no"
VERB="no"
echo_default
}
function update_script() {
header_info
if [[ ! -d /opt/netboot.xyz ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Stopping ${APP}"
systemctl disable netbootxyz.service &>/dev/null
systemctl stop netbootxyz
sleep 1
msg_ok "Stopped ${APP}"
msg_info "Backing up Data"
cp -R /opt/netboot.xyz/config config-backup
cp -R /opt/netboot.xyz/assets assets-backup
sleep 1
msg_ok "Backed up Data"
RELEASE=$(curl -fsSLX GET "https://api.github.com/repos/netbootxyz/netboot.xyz/releases/latest" | awk '/tag_name/{print $4;exit}' FS='[""]')
msg_info "Updating netboot.xyz to ${RELEASE}"
curl --silent -o ${RELEASE}.tar.gz -L "https://github.com/netbootxyz/netboot.xyz/archive/${RELEASE}.tar.gz" &>/dev/null
tar xvzf ${RELEASE}.tar.gz &>/dev/null
VER=$(curl -fsSL https://api.github.com/repos/netbootxyz/netboot.xyz/releases/latest |
grep "tag_name" |
awk '{print substr($2, 2, length($2)-3) }')
if [ ! -d "/opt/netboot.xyz" ]; then
mv netboot.xyz-${VER} /opt/netboot.xyz
else
cp -R netboot.xyz-${VER}/* /opt/netboot.xyz
fi
service_path="/etc/systemd/system/netbootxyz.service"
echo "[Unit]
Description=netboot.xyz
After=network.target
[Service]
Restart=always
RestartSec=5
Type=simple
User=root
WorkingDirectory=/opt/netboot.xyz
ExecStart="ansible-playbook" -i inventory site.yml
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target" >$service_path
msg_ok "Updated netboot.xyz to ${RELEASE}"
msg_info "Restoring Data"
cp -R config-backup/* /opt/netboot.xyz/config
cp -R assets-backup/* /opt/netboot.xyz/assets
sleep 1
msg_ok "Restored Data"
msg_info "Cleanup"
rm -rf ${RELEASE}.tar.gz
rm -rf netboot.xyz-${VER}
rm -rf config-backup
rm -rf assets-backup
sleep 1
msg_ok "Cleaned"
msg_info "Starting ${APP}"
systemctl enable --now netbootxyz.service &>/dev/null
sleep 2
msg_ok "Started ${APP}"
msg_ok "Updated Successfully"
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${APP} should be reachable by going to the following URL.
${BL}http://${IP}:3000${CL} \n"

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://www.debian.org/
APP="ocis"
var_tags="${var_tags:-cloud}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-4096}"
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 [[ ! -d /var ]]; 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"
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}:9200${CL}"

Some files were not shown because too many files have changed in this diff Show More