diff --git a/.github/workflows/get-versions-from-gh.yaml b/.github/workflows/get-versions-from-gh.yaml index 0cf632d11..0a990efdd 100644 --- a/.github/workflows/get-versions-from-gh.yaml +++ b/.github/workflows/get-versions-from-gh.yaml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: repository: community-scripts/ProxmoxVED ref: main @@ -70,13 +70,3 @@ 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 diff --git a/.github/workflows/scripts/get-gh-release.sh b/.github/workflows/scripts/get-gh-release.sh index ddd966df4..55ae9d8a6 100644 --- a/.github/workflows/scripts/get-gh-release.sh +++ b/.github/workflows/scripts/get-gh-release.sh @@ -7,7 +7,7 @@ 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,14 +26,15 @@ 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" +mv "$TMP_FILE" "$OUTPUT_FILE" +echo "Updated $OUTPUT_FILE" diff --git a/.github/workflows/update-versions-github.yml b/.github/workflows/update-versions-github.yml new file mode 100644 index 000000000..5c18ef2e5 --- /dev/null +++ b/.github/workflows/update-versions-github.yml @@ -0,0 +1,512 @@ +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: frontend/public/json/version-sources.json + VERSIONS_FILE: frontend/public/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: Generate GitHub App Token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - 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 + 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 + 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: ${{ steps.generate-token.outputs.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: ${{ steps.generate-token.outputs.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 diff --git a/ct/nextexplorer.sh b/ct/nextexplorer.sh index 6b2b651b4..27fcf564f 100644 --- a/ct/nextexplorer.sh +++ b/ct/nextexplorer.sh @@ -54,6 +54,7 @@ function update_script() { mv backend/{node_modules,src,package.json} "$APP_DIR" mv frontend/dist/ "$APP_DIR"/src/public chown -R explorer:explorer "$APP_DIR" /etc/nextExplorer + sed -i "\|version|s|$(jq -cr '.version' ${APP_DIR}/package.json)|$(cat ~/.nextexplorer)|" "$APP_DIR"/package.json msg_ok "Updated nextExplorer" msg_info "Starting nextExplorer" diff --git a/ct/termix.sh b/ct/termix.sh new file mode 100644 index 000000000..9e6d2a0ee --- /dev/null +++ b/ct/termix.sh @@ -0,0 +1,71 @@ +#!/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/Termix-SSH/Termix + +APP="Termix" +var_tags="${var_tags:-ssh;terminal;management}" +var_cpu="${var_cpu:-4}" +var_ram="${var_ram:-4096}" +var_disk="${var_disk:-10}" +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/termix ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + if check_for_gh_release "termix" "Termix-SSH/Termix"; then + msg_info "Stopping ${APP}" + systemctl stop termix + msg_ok "Stopped ${APP}" + + msg_info "Backing up Data" + cp -r /opt/termix/data /opt/termix_data_backup + msg_ok "Backed up Data" + + fetch_and_deploy_gh_release "termix" "Termix-SSH/Termix" + + msg_info "Restoring Data" + cp -r /opt/termix_data_backup/. /opt/termix/data + rm -rf /opt/termix_data_backup + msg_ok "Restored Data" + + msg_info "Rebuilding ${APP}" + cd /opt/termix + $STD npm install --ignore-scripts --force + $STD npm rebuild better-sqlite3 --force + $STD npm run build + $STD npm run build:backend + msg_ok "Rebuilt ${APP}" + + msg_info "Starting ${APP}" + systemctl start termix + msg_ok "Started ${APP}" + 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}" diff --git a/frontend/public/json/gwn-manager.json b/frontend/public/json/gwn-manager.json deleted file mode 100644 index 39f0f53ae..000000000 --- a/frontend/public/json/gwn-manager.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "GWN Manager", - "slug": "gwn-manager", - "categories": [ - 9 - ], - "date_created": "2025-12-02", - "type": "ct", - "updateable": true, - "privileged": false, - "interface_port": 8443, - "documentation": "https://documentation.grandstream.com/article-categories/gwn-mgmt/", - "website": "https://www.grandstream.com/products/networking-solutions/wi-fi-management/product/gwn-manager", - "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/gwn-manager.webp", - "config_path": "/gwn/conf/gwn.conf", - "description": "GWN Manager is a free on-premise enterprise-grade, management platform for Grandstream GWN series devices. Typically deployed on a customer’s private network, this flexible, scalable solution offers simplified configuration and management.", - "install_methods": [ - { - "type": "default", - "script": "ct/gwn-manager.sh", - "resources": { - "cpu": 2, - "ram": 6144, - "hdd": 8, - "os": "debian", - "version": "13" - } - } - ], - "default_credentials": { - "username": null, - "password": null - }, - "notes": [ - { - "text": "Installation package is pulled from GrandStream website. Installation may take a while.", - "type": "info" - } - ] -} diff --git a/frontend/public/json/rybbit.json b/frontend/public/json/rybbit.json deleted file mode 100644 index 931e09443..000000000 --- a/frontend/public/json/rybbit.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Rybbit", - "slug": "rybbit", - "categories": [ - 9 - ], - "date_created": "2025-01-15", - "type": "ct", - "updateable": true, - "privileged": false, - "interface_port": 3000, - "documentation": "https://github.com/rybbit-io/rybbit", - "website": "https://github.com/rybbit-io/rybbit", - "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/rybbit-light.webp", - "config_path": "/opt/rybbit", - "description": "Rybbit is a privacy-focused, open-source web analytics platform. Track your website visitors without compromising their privacy or using cookies.", - "install_methods": [ - { - "type": "default", - "script": "ct/rybbit.sh", - "resources": { - "cpu": 2, - "ram": 2048, - "hdd": 5, - "os": "debian", - "version": "13" - } - } - ], - "default_credentials": { - "username": null, - "password": null - }, - "notes": [] -} diff --git a/frontend/public/json/termix.json b/frontend/public/json/termix.json new file mode 100644 index 000000000..e76fd9022 --- /dev/null +++ b/frontend/public/json/termix.json @@ -0,0 +1,35 @@ +{ + "name": "Termix", + "slug": "termix", + "categories": [ + 6 + ], + "date_created": "2026-01-16", + "type": "ct", + "updateable": true, + "privileged": false, + "interface_port": 8080, + "documentation": "https://docs.termix.site/", + "website": "https://termix.site/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/termix.webp", + "config_path": "", + "description": "Termix is an open-source, self-hosted server management platform with SSH terminal access, SSH tunneling, remote file management, Docker management, and multi-platform support.", + "install_methods": [ + { + "type": "default", + "script": "ct/termix.sh", + "resources": { + "cpu": 4, + "ram": 4096, + "hdd": 10, + "os": "Debian", + "version": "13" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} diff --git a/frontend/public/json/transmission-openvpn.json b/frontend/public/json/transmission-openvpn.json deleted file mode 100644 index 319973502..000000000 --- a/frontend/public/json/transmission-openvpn.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "Transmission-Openvpn", - "slug": "transmission-openvpn", - "categories": [ - 11 - ], - "date_created": "2025-09-04", - "type": "ct", - "updateable": true, - "privileged": false, - "interface_port": 9091, - "documentation": "https://haugene.github.io/docker-transmission-openvpn/", - "config_path": "/opt/transmission-openvpn/", - "website": "https://github.com/haugene/docker-transmission-openvpn", - "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/transmission.webp", - "description": "This project runs Transmission + OpenVPN natively in an LXC container, using the popular docker-transmission-openvpn image as a base. It ensures all torrent traffic is securely routed through a VPN tunnel, supports a wide range of VPN providers, and offers flexible configuration options", - "install_methods": [ - { - "type": "default", - "script": "ct/transmission-openvpn.sh", - "resources": { - "cpu": 1, - "ram": 512, - "hdd": 8, - "os": "Debian", - "version": "13" - } - } - ], - "default_credentials": { - "username": null, - "password": null - }, - "notes": [ - { - "text": "This application requires a VPN provider to work. Please refer to your VPN provider’s documentation for setting up OpenVPN.", - "type": "warning" - } - ] -} diff --git a/frontend/public/json/unifi-os-server.json b/frontend/public/json/unifi-os-server.json new file mode 100644 index 000000000..cdd09cc70 --- /dev/null +++ b/frontend/public/json/unifi-os-server.json @@ -0,0 +1,35 @@ +{ + "name": "Unifi OS Server", + "slug": "unifi-os-server", + "categories": [ + 2 + ], + "date_created": "2026-01-16", + "type": "vm", + "updateable": true, + "privileged": false, + "interface_port": 11443, + "documentation": "https://help.ui.com/hc/en-us", + "website": "https://www.ui.com/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/ubiquiti-unifi.webp", + "config_path": "", + "description": "Unifi OS Server is the operating system that powers Ubiquiti's UniFi line of network devices. It provides a centralized platform for managing and monitoring UniFi access points, switches, and security gateways, offering features such as network configuration, device provisioning, and performance analytics.", + "install_methods": [ + { + "type": "default", + "script": "ct/unifi-os-server.sh", + "resources": { + "cpu": 2, + "ram": 4096, + "hdd": 20, + "os": "debian", + "version": "13" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} diff --git a/install/nextexplorer-install.sh b/install/nextexplorer-install.sh index 8ed03263a..940cdec67 100644 --- a/install/nextexplorer-install.sh +++ b/install/nextexplorer-install.sh @@ -119,8 +119,9 @@ SHARES_ENABLED=true # SHARES_ALLOW_ANONYMOUS=true EOF chmod 600 /etc/nextExplorer/.env -$STD useradd -U -s /bin/bash -m -d /home/explorer explorer +$STD useradd -U -s /usr/sbin/nologin -m -d /home/explorer explorer chown -R explorer:explorer "$APP_DIR" /etc/nextExplorer +sed -i "\|version|s|$(jq -cr '.version' ${APP_DIR}/package.json)|$(cat ~/.nextexplorer)|" "$APP_DIR"/package.json msg_ok "Configured nextExplorer" msg_info "Creating nextExplorer Service" @@ -131,6 +132,8 @@ After=network.target [Service] Type=simple +User=explorer +Group=explorer WorkingDirectory=/opt/nextExplorer/app EnvironmentFile=/etc/nextExplorer/.env ExecStart=/usr/bin/node ./src/app.js diff --git a/install/termix-install.sh b/install/termix-install.sh new file mode 100644 index 000000000..9d4828167 --- /dev/null +++ b/install/termix-install.sh @@ -0,0 +1,263 @@ +#!/usr/bin/env bash + +# 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/Termix-SSH/Termix + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +msg_info "Installing Dependencies" +$STD apt-get install -y \ + build-essential \ + python3 \ + make \ + g++ \ + nginx \ + openssl \ + gettext-base +msg_ok "Installed Dependencies" + +NODE_VERSION="22" setup_nodejs +fetch_and_deploy_gh_release "termix" "Termix-SSH/Termix" + +msg_info "Building ${APPLICATION} Frontend (Patience)" +cd /opt/termix +export COREPACK_ENABLE_DOWNLOAD_PROMPT=0 + +find public/fonts -name "*.ttf" ! -name "*Regular.ttf" ! -name "*Bold.ttf" ! -name "*Italic.ttf" -delete 2>/dev/null || true + +$STD npm install --ignore-scripts --force +$STD npm cache clean --force +$STD npm run build +msg_ok "Built ${APPLICATION} Frontend" + +msg_info "Building ${APPLICATION} Backend" +$STD npm rebuild better-sqlite3 --force +$STD npm run build:backend +msg_ok "Built ${APPLICATION} Backend" + +msg_info "Setting up Production Dependencies" +cd /opt/termix +$STD npm ci --only=production --ignore-scripts --force +$STD npm rebuild better-sqlite3 bcryptjs --force +$STD npm cache clean --force +msg_ok "Set up Production Dependencies" + +msg_info "Setting up Directories" +mkdir -p /opt/termix/data \ + /opt/termix/uploads \ + /opt/termix/html \ + /opt/termix/nginx \ + /opt/termix/nginx/logs \ + /opt/termix/nginx/cache \ + /opt/termix/nginx/client_body + +cp -r /opt/termix/dist/* /opt/termix/html/ 2>/dev/null || true +cp -r /opt/termix/src/locales /opt/termix/html/locales 2>/dev/null || true +cp -r /opt/termix/public/fonts /opt/termix/html/fonts 2>/dev/null || true +msg_ok "Set up Directories" + +msg_info "Configuring Nginx" +cat <<'NGINXEOF' >/etc/nginx/sites-available/termix.conf +pid /opt/termix/nginx/nginx.pid; +error_log /opt/termix/nginx/logs/error.log warn; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + access_log /opt/termix/nginx/logs/access.log; + + client_body_temp_path /opt/termix/nginx/client_body; + proxy_temp_path /opt/termix/nginx/proxy_temp; + + sendfile on; + keepalive_timeout 65; + client_header_timeout 300s; + + server { + listen 8080; + server_name _; + + add_header X-Content-Type-Options nosniff always; + add_header X-XSS-Protection "1; mode=block" always; + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + root /opt/termix/html; + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + location / { + root /opt/termix/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location ~ ^/users(/.*)?$ { + proxy_pass http://127.0.0.1:30001; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ~ ^/(version|releases|alerts|rbac|credentials|snippets|terminal|encryption)(/.*)?$ { + proxy_pass http://127.0.0.1:30001; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ~ ^/(database|db)(/.*)?$ { + client_max_body_size 5G; + client_body_timeout 300s; + proxy_pass http://127.0.0.1:30001; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_connect_timeout 60s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + proxy_request_buffering off; + proxy_buffering off; + } + + location /ssh/ { + proxy_pass http://127.0.0.1:30001; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + location /ssh/websocket/ { + proxy_pass http://127.0.0.1:30002/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + proxy_buffering off; + proxy_request_buffering off; + } + + location /ssh/tunnel/ { + proxy_pass http://127.0.0.1:30003; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + location /ssh/file_manager/ssh/ { + client_max_body_size 5G; + client_body_timeout 300s; + proxy_pass http://127.0.0.1:30004; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_connect_timeout 60s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + proxy_request_buffering off; + proxy_buffering off; + } + + location ~ ^/ssh/file_manager/(recent|pinned|shortcuts)$ { + proxy_pass http://127.0.0.1:30001; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + location /health { + proxy_pass http://127.0.0.1:30001; + proxy_http_version 1.1; + proxy_set_header Host $host; + } + + location ~ ^/(status|metrics)(/.*)?$ { + proxy_pass http://127.0.0.1:30005; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + location ~ ^/(uptime|activity)(/.*)?$ { + proxy_pass http://127.0.0.1:30006; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + location ^~ /docker/console/ { + proxy_pass http://127.0.0.1:30008/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + proxy_buffering off; + proxy_request_buffering off; + } + + location ~ ^/docker(/.*)?$ { + proxy_pass http://127.0.0.1:30007; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_connect_timeout 60s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + } + } +} +NGINXEOF +rm -f /etc/nginx/sites-enabled/default +rm -f /etc/nginx/nginx.conf +ln -sf /etc/nginx/sites-available/termix.conf /etc/nginx/nginx.conf +msg_ok "Configured Nginx" + +msg_info "Creating Service" +cat </etc/systemd/system/termix.service +[Unit] +Description=Termix Backend +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/termix +Environment=NODE_ENV=production +Environment=DATA_DIR=/opt/termix/data +Environment=PORT=8080 +ExecStart=/usr/bin/node /opt/termix/dist/backend/index.js +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target +EOF +systemctl enable -q --now termix nginx +msg_ok "Created Service" + +motd_ssh +customize +cleanup_lxc diff --git a/vm/truenas-vm.sh b/vm/truenas-vm.sh index bc9ab24b1..8aee3a72e 100644 --- a/vm/truenas-vm.sh +++ b/vm/truenas-vm.sh @@ -3,6 +3,7 @@ # Copyright (c) 2021-2026 community-scripts ORG # Author: juronja # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://www.truenas.com/truenas-community-edition/ source /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/api.func)