Compare commits

..

11 Commits

Author SHA1 Message Date
CanbiZ (MickLesk)
4e8021e6fd fix(frontend): wrap Tooltip in TooltipProvider 2026-01-28 14:35:18 +01:00
CanbiZ (MickLesk)
29cf802a9a fix(api): add github-versions endpoint and fix legacy versions route 2026-01-28 14:07:29 +01:00
CanbiZ (MickLesk)
649ad2cc8b feat(frontend): show tooltip for pinned versions 2026-01-28 14:03:46 +01:00
CanbiZ (MickLesk)
760603da54 fix(frontend): use github-versions.json for version display
- Update AppVersion type to match new format with slug field
- Switch from versions.json to github-versions.json API
- Simplify version matching by direct slug comparison
- Remove 'Loading versions...' text - show nothing if no version found
2026-01-28 14:02:20 +01:00
CanbiZ (MickLesk)
2434e0ab3b feat(workflow): auto-approve and merge github-versions PR 2026-01-28 13:59:01 +01:00
community-scripts-pr-app[bot]
f76080e9db Update date in json (#11280)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-01-28 12:57:44 +00:00
github-actions[bot]
c83076dbc4 chore: update github-versions.json (#11278)
Total versions: 224
Pinned versions: 2
Generated: 2026-01-28T12:56:09Z

Co-authored-by: GitHub Actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-01-28 13:57:22 +01:00
community-scripts-pr-app[bot]
9b3786fc26 Update CHANGELOG.md (#11279)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-01-28 12:56:40 +00:00
CanbiZ (MickLesk)
49c1eef653 fix(install): fix Debian 13 LXC template root ownership bug (#11277)
The Debian 13 LXC template has a bug where / is owned by nobody instead
of root. This causes systemd-tmpfiles to fail with exit code 73 during
package installation (e.g. authelia).

Fix by checking and correcting / ownership at container setup.
2026-01-28 13:56:17 +01:00
community-scripts-pr-app[bot]
f939170d47 Update CHANGELOG.md (#11275)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-01-28 12:50:41 +00:00
CanbiZ (MickLesk)
21d09cfb17 github: add GitHub-based versions.json updater (#10021)
* feat(workflow): add GitHub-based versions.json updater

Replaces newreleases.io with direct GitHub API queries.
Extracts repos from fetch_and_deploy_gh_release calls in install scripts.
Runs 2x daily (06:00 and 18:00 UTC).

* feat(workflow): extend version crawler with multiple sources

- Method 1: fetch_and_deploy_gh_release calls (direct)
- Method 2: GitHub URLs extracted from all scripts
- Method 3: VM image sources (HAOS)
- Method 4: Docker Hub / GHCR versions
- Method 5: npm Registry versions

Also tries tags fallback when no releases exist.

* feat(workflow): rewrite with version-sources.json config

- Generates version-sources.json with structured metadata
- Each entry has: slug, type, source, script, version, date
- Extracts from: fetch_and_deploy_gh_release, GitHub URLs, npm, Docker
- Generates versions.json for backward compatibility
- Fully automatic, no manual mapping needed

* feat(workflow): add manual GitHub mappings and pveam support

- Method 5: Manual GitHub mappings for 36 apt-based apps
  (grafana, redis, postgresql, mariadb, influxdb, etc.)
- Method 6: Proxmox LXC templates (debian, ubuntu, alpine)
  via download.proxmox.com index
- Method 7: Special sources (HAOS VM)

Total coverage: ~310+ apps

* feat(workflow): expand manual GitHub mappings to 75 apps

Added mappings for:
- Apache projects (cassandra, couchdb, guacamole, tomcat)
- Media apps (tdarr, unmanic, shinobi)
- DevOps (coolify, dokploy, runtipi, sonarqube)
- Databases (mongodb, mysql, neo4j, rabbitmq)
- And 30+ more apps

Total manual mappings: 75

* feat: add manual placeholders for 34 unknown-source apps

- Added 34 apps with 'manual:-' type for apps without known sources
- Added manual type handler in version-fetch (returns '-' placeholder)
- Added manual counter to summary output
- Coverage now 100% (all 405 scripts included)

Manual entries can be updated later when sources are discovered.

* Refactor and update GitHub workflow files

Moved several workflow files to a 'bak' backup directory and renamed 'close-ttek-issues.yaml' to 'close-tteck-issues.yaml'. Refactored 'update-versions-github.yml' to focus on extracting and updating GitHub versions, simplified the extraction logic, and updated the workflow schedule to run four times daily. Minor variable and logic improvements were made in 'close-discussion.yml'.

* clean file

* chore: empty versions.json for workflow test
2026-01-28 13:50:18 +01:00
18 changed files with 1897 additions and 1940 deletions

View File

@@ -28,16 +28,12 @@ jobs:
const matched = patterns.some((regex) => regex.test(content));
if (matched) {
const message = `👋 Hello!
It looks like you are referencing a **container creation issue with a Debian 13 template** (e.g. \`debian-13-standard_13.x-x_amd64.tar.zst\`).
We receive many similar reports about this, and its not related to the scripts themselves but to **a Proxmox base template bug**.
Please refer to [discussion #8126](https://github.com/community-scripts/ProxmoxVE/discussions/8126) for details.
If your issue persists after following the guidance there, feel free to reopen this issue.
_This issue was automatically closed by a bot._`;
const message = "👋 Hello!\n\n" +
"It looks like you are referencing a **container creation issue with a Debian 13 template** (e.g. `debian-13-standard_13.x-x_amd64.tar.zst`).\n\n" +
"We receive many similar reports about this, and it's not related to the scripts themselves but to **a Proxmox base template bug**.\n\n" +
"Please refer to [discussion #8126](https://github.com/community-scripts/ProxmoxVE/discussions/8126) for details.\n" +
"If your issue persists after following the guidance there, feel free to reopen this issue.\n\n" +
"_This issue was automatically closed by a bot._";
await github.rest.issues.createComment({
...context.repo,
@@ -56,4 +52,4 @@ _This issue was automatically closed by a bot._`;
issue_number: issueNumber,
state: "closed"
});
}
}

View File

@@ -103,7 +103,7 @@ jobs:
}
`;
//
let discussionQLId;
try {
const discussionResponse = await graphqlWithAuth(discussionQuery, {
owner,
@@ -111,7 +111,7 @@ jobs:
number: parseInt(discussionNumber, 10),
});
const discussionQLId = discussionResponse.repository.discussion.id;
discussionQLId = discussionResponse.repository.discussion.id;
if (!discussionQLId) {
console.log("Failed to fetch discussion GraphQL ID.");
return;

230
.github/workflows/update-versions-github.yml generated vendored Normal file
View File

@@ -0,0 +1,230 @@
name: Update GitHub Versions (New)
on:
workflow_dispatch:
schedule:
# Runs 4x daily: 00:00, 06:00, 12:00, 18:00 UTC
- cron: "0 0,6,12,18 * * *"
permissions:
contents: write
pull-requests: write
env:
VERSIONS_FILE: frontend/public/json/github-versions.json
BRANCH_NAME: automated/update-github-versions
AUTOMATED_PR_LABEL: "automated pr"
jobs:
update-github-versions:
if: github.repository == 'community-scripts/ProxmoxVE'
runs-on: ubuntu-latest
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 a token for PR approval and 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:
ref: main
- name: Extract GitHub versions from install scripts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
echo "========================================="
echo " Extracting GitHub versions from scripts"
echo "========================================="
# Initialize versions array
versions_json="[]"
# Function to add a version entry
add_version() {
local slug="$1"
local repo="$2"
local version="$3"
local pinned="$4"
local date="$5"
versions_json=$(echo "$versions_json" | jq \
--arg slug "$slug" \
--arg repo "$repo" \
--arg version "$version" \
--argjson pinned "$pinned" \
--arg date "$date" \
'. += [{"slug": $slug, "repo": $repo, "version": $version, "pinned": $pinned, "date": $date}]')
}
# Get list of slugs from JSON files
echo ""
echo "=== Scanning JSON files for slugs ==="
for json_file in frontend/public/json/*.json; do
[[ ! -f "$json_file" ]] && continue
# Skip non-app JSON files
basename_file=$(basename "$json_file")
case "$basename_file" in
metadata.json|versions.json|github-versions.json|dependency-check.json|update-apps.json)
continue
;;
esac
# Extract slug from JSON
slug=$(jq -r '.slug // empty' "$json_file" 2>/dev/null)
[[ -z "$slug" ]] && continue
# Find corresponding install script
install_script="install/${slug}-install.sh"
[[ ! -f "$install_script" ]] && continue
# Look for fetch_and_deploy_gh_release calls
# Pattern: fetch_and_deploy_gh_release "app" "owner/repo" ["mode"] ["version"]
while IFS= read -r line; do
# Skip commented lines
[[ "$line" =~ ^[[:space:]]*# ]] && continue
# Extract repo and version from fetch_and_deploy_gh_release
if [[ "$line" =~ fetch_and_deploy_gh_release[[:space:]]+\"[^\"]*\"[[:space:]]+\"([^\"]+)\"([[:space:]]+\"([^\"]+)\")?([[:space:]]+\"([^\"]+)\")? ]]; then
repo="${BASH_REMATCH[1]}"
mode="${BASH_REMATCH[3]:-tarball}"
pinned_version="${BASH_REMATCH[5]:-latest}"
# Check if version is pinned (not "latest" and not empty)
is_pinned=false
target_version=""
if [[ -n "$pinned_version" && "$pinned_version" != "latest" ]]; then
is_pinned=true
target_version="$pinned_version"
fi
# Fetch version from GitHub
if [[ "$is_pinned" == "true" ]]; then
# For pinned versions, verify it exists and get date
response=$(gh api "repos/${repo}/releases/tags/${target_version}" 2>/dev/null || echo '{}')
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')
add_version "$slug" "$repo" "$version" "true" "$date"
echo "[$slug] ✓ $version (pinned)"
else
echo "[$slug] ⚠ pinned version $target_version not found"
fi
else
# Fetch latest release
response=$(gh api "repos/${repo}/releases/latest" 2>/dev/null || echo '{}')
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')
add_version "$slug" "$repo" "$version" "false" "$date"
echo "[$slug] ✓ $version"
else
# Try tags as fallback
version=$(gh api "repos/${repo}/tags" --jq '.[0].name // empty' 2>/dev/null || echo "")
if [[ -n "$version" ]]; then
add_version "$slug" "$repo" "$version" "false" ""
echo "[$slug] ✓ $version (from tags)"
else
echo "[$slug] ⚠ no version found"
fi
fi
fi
break # Only first match per script
fi
done < <(grep 'fetch_and_deploy_gh_release' "$install_script" 2>/dev/null || true)
done
# Save versions file
echo "$versions_json" | jq --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{generated: $date, versions: (. | sort_by(.slug))}' > "$VERSIONS_FILE"
total=$(echo "$versions_json" | jq 'length')
echo ""
echo "========================================="
echo " Total versions extracted: $total"
echo "========================================="
- name: Check for changes
id: check-changes
run: |
# Check if file is new (untracked) or has changes
if [[ ! -f "$VERSIONS_FILE" ]]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "Versions file was not created"
elif ! git ls-files --error-unmatch "$VERSIONS_FILE" &>/dev/null; then
# File exists but is not tracked - it's new
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "New file created: $VERSIONS_FILE"
elif git diff --quiet "$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 "$VERSIONS_FILE" 2>/dev/null || true
fi
- name: Commit and push changes
if: steps.check-changes.outputs.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 "$VERSIONS_FILE"
git commit -m "chore: update github-versions.json"
git checkout -b $BRANCH_NAME || git checkout $BRANCH_NAME
git push origin $BRANCH_NAME --force
- name: Create pull request if not exists
if: steps.check-changes.outputs.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 github-versions.json" \
--body "This PR is auto-generated by a Github Action to update the github-versions.json file." \
--head $BRANCH_NAME \
--base main \
--label "$AUTOMATED_PR_LABEL"
fi
- name: Approve pull request
if: steps.check-changes.outputs.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: Approve pull request and merge
if: steps.check-changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ steps.generate-token-merge.outputs.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 '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
gh pr merge $PR_NUMBER --squash --admin
fi

View File

@@ -407,6 +407,7 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- #### 🐞 Bug Fixes
- core: fix Debian 13 LXC template root ownership bug [@MickLesk](https://github.com/MickLesk) ([#11277](https://github.com/community-scripts/ProxmoxVE/pull/11277))
- tools.func: prevent systemd-tmpfiles failure in unprivileged LXC during deb install [@MickLesk](https://github.com/MickLesk) ([#11271](https://github.com/community-scripts/ProxmoxVE/pull/11271))
- tools.func: fix php "wait_for" hint [@MickLesk](https://github.com/MickLesk) ([#11254](https://github.com/community-scripts/ProxmoxVE/pull/11254))
@@ -415,6 +416,10 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- core: update dynamic values in LXC profile on update_motd_ip [@MickLesk](https://github.com/MickLesk) ([#11268](https://github.com/community-scripts/ProxmoxVE/pull/11268))
- tools.func: add new function - setup_meilisearch [@MickLesk](https://github.com/MickLesk) ([#11258](https://github.com/community-scripts/ProxmoxVE/pull/11258))
### 📂 Github
- github: add GitHub-based versions.json updater [@MickLesk](https://github.com/MickLesk) ([#10021](https://github.com/community-scripts/ProxmoxVE/pull/10021))
### 🌐 Website
- #### 📝 Script Information

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
import { NextResponse } from "next/server";
import { promises as fs } from "node:fs";
import path from "node:path";
import type { GitHubVersionsResponse } from "@/lib/types";
export const dynamic = "force-static";
const jsonDir = "public/json";
const versionsFileName = "github-versions.json";
const encoding = "utf-8";
async function getVersions(): Promise<GitHubVersionsResponse> {
const filePath = path.resolve(jsonDir, versionsFileName);
const fileContent = await fs.readFile(filePath, encoding);
const data: GitHubVersionsResponse = JSON.parse(fileContent);
return data;
}
export async function GET() {
try {
const versions = await getVersions();
return NextResponse.json(versions);
}
catch (error) {
console.error(error);
const err = error as globalThis.Error;
return NextResponse.json({
generated: "",
versions: [],
error: err.message || "An unexpected error occurred",
}, {
status: 500,
});
}
}

View File

@@ -3,18 +3,22 @@ import { NextResponse } from "next/server";
import { promises as fs } from "node:fs";
import path from "node:path";
import type { AppVersion } from "@/lib/types";
export const dynamic = "force-static";
const jsonDir = "public/json";
const versionsFileName = "versions.json";
const encoding = "utf-8";
interface LegacyVersion {
name: string;
version: string;
date: string;
}
async function getVersions() {
const filePath = path.resolve(jsonDir, versionsFileName);
const fileContent = await fs.readFile(filePath, encoding);
const versions: AppVersion[] = JSON.parse(fileContent);
const versions: LegacyVersion[] = JSON.parse(fileContent);
const modifiedVersions = versions.map((version) => {
let newName = version.name;

View File

@@ -1,13 +1,13 @@
"use client";
import { X } from "lucide-react";
import { X, HelpCircle } from "lucide-react";
import { Suspense } from "react";
import Image from "next/image";
import type { AppVersion, Script } from "@/lib/types";
import { cleanSlug } from "@/lib/utils/resource-utils";
import { Separator } from "@/components/ui/separator";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { useVersions } from "@/hooks/use-versions";
import { basePath } from "@/config/site-config";
import { extractDate } from "@/lib/time";
@@ -108,18 +108,31 @@ function VersionInfo({ item }: { item: Script }) {
const { data: versions = [], isLoading } = useVersions();
if (isLoading || versions.length === 0) {
return <p className="text-sm text-muted-foreground">Loading versions...</p>;
return null;
}
const matchedVersion = versions.find((v: AppVersion) => {
const cleanName = v.name.replace(/[^a-z0-9]/gi, "").toLowerCase();
return cleanName === cleanSlug(item.slug) || cleanName.includes(cleanSlug(item.slug));
});
const matchedVersion = versions.find((v: AppVersion) => v.slug === item.slug);
if (!matchedVersion)
return null;
return <span className="font-medium text-sm">{matchedVersion.version}</span>;
return (
<span className="font-medium text-sm flex items-center gap-1">
{matchedVersion.version}
{matchedVersion.pinned && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent className="max-w-xs">
<p>This version is pinned. We test each update for breaking changes before releasing new versions.</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</span>
);
}
export function ScriptItem({ item, setSelectedScript }: ScriptItemProps) {

View File

@@ -2,7 +2,7 @@
import { useQuery } from "@tanstack/react-query";
import type { AppVersion } from "@/lib/types";
import type { AppVersion, GitHubVersionsResponse } from "@/lib/types";
import { fetchVersions } from "@/lib/data";
@@ -10,14 +10,8 @@ export function useVersions() {
return useQuery<AppVersion[]>({
queryKey: ["versions"],
queryFn: async () => {
const fetchedVersions = await fetchVersions();
if (Array.isArray(fetchedVersions)) {
return fetchedVersions;
}
if (fetchedVersions && typeof fetchedVersions === "object") {
return [fetchedVersions];
}
return [];
const response: GitHubVersionsResponse = await fetchVersions();
return response.versions ?? [];
},
});
}

View File

@@ -10,7 +10,7 @@ export async function fetchCategories() {
}
export async function fetchVersions() {
const response = await fetch(`/ProxmoxVE/api/versions`);
const response = await fetch(`/ProxmoxVE/api/github-versions`);
if (!response.ok) {
throw new Error(`Failed to fetch versions: ${response.statusText}`);
}

View File

@@ -63,7 +63,14 @@ export type OperatingSystem = {
};
export type AppVersion = {
name: string;
slug: string;
repo: string;
version: string;
date: Date;
pinned: boolean;
date: string;
};
export type GitHubVersionsResponse = {
generated: string;
versions: AppVersion[];
};

View File

@@ -81,9 +81,8 @@ setting_up_container() {
msg_info "Setting up Container OS"
# Fix Debian 13 LXC template bug where / is owned by nobody
# Only attempt in privileged containers (unprivileged cannot chown /)
if [[ "$(stat -c '%U' /)" != "root" ]]; then
chown root:root / 2>/dev/null || true
chown root:root /
fi
for ((i = RETRY_NUM; i > 0; i--)); do