Compare commits

..

2 Commits

Author SHA1 Message Date
CanbiZ
b26ad4dcb9 Quote JAVA_VERSION assignment in scripts
Changed JAVA_VERSION=21 to JAVA_VERSION="21" in omada.sh and omada-install.sh to ensure the variable is treated as a string. This improves consistency and prevents potential issues with variable usage.
2025-11-20 08:37:13 +01:00
CanbiZ
e404a1213c Omada - AVX-only support
Discontinue support for non-AVX CPUs in Omada installation scripts, exiting with an error if AVX is not detected. Simplify Java installation by using a setup_java function and remove manual Azul Zulu Java installation steps. Update default Omada version from 13 to 12 in scripts and JSON config. Replace manual cleanup steps with cleanup_lxc.
2025-11-20 08:35:32 +01:00
427 changed files with 3771 additions and 3223 deletions

View File

@@ -10,92 +10,8 @@
> [!CAUTION]
Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit the project's popularity for potentially malicious purposes.
## 2025-11-23
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- [LibreNMS] Correcting mariadb sed string for Debian 13 default in install/librenms-install.sh, website config for Debian 13 #9369 [@htmlspinnr](https://github.com/htmlspinnr) ([#9370](https://github.com/community-scripts/ProxmoxVE/pull/9370))
- fix: Snipe-IT update check failure [@ruanmed](https://github.com/ruanmed) ([#9371](https://github.com/community-scripts/ProxmoxVE/pull/9371))
- #### ✨ New Features
- PVE Kernel Clean: Add info about currently running kernel [@tremor021](https://github.com/tremor021) ([#9388](https://github.com/community-scripts/ProxmoxVE/pull/9388))
- #### 🔧 Refactor
- Update glpi-install.sh to remove install.php [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9378](https://github.com/community-scripts/ProxmoxVE/pull/9378))
### 🌐 Website
- #### 🐞 Bug Fixes
- fix: enhance back navigation in NotFoundPage component and remove unused deps [@BramSuurdje](https://github.com/BramSuurdje) ([#9341](https://github.com/community-scripts/ProxmoxVE/pull/9341))
- #### ✨ New Features
- feat(frontend): add script disable functionality with visual indicators [@AlphaLawless](https://github.com/AlphaLawless) ([#9374](https://github.com/community-scripts/ProxmoxVE/pull/9374))
## 2025-11-22
### 🆕 New Scripts
- Upgopher ([#9360](https://github.com/community-scripts/ProxmoxVE/pull/9360))
### 🚀 Updated Scripts
- Expand support to Proxmox VE 9.1 in VM scripts [@MickLesk](https://github.com/MickLesk) ([#9351](https://github.com/community-scripts/ProxmoxVE/pull/9351))
- #### 🐞 Bug Fixes
- fix: Snipe-IT install and update failure due to new repository url [@ruanmed](https://github.com/ruanmed) ([#9362](https://github.com/community-scripts/ProxmoxVE/pull/9362))
- glpi - allow migration of existing databases [@moodyblue](https://github.com/moodyblue) ([#9353](https://github.com/community-scripts/ProxmoxVE/pull/9353))
- #### ✨ New Features
- Refactor cleanup steps to use cleanup_lxc function (install/ Folder) [@MickLesk](https://github.com/MickLesk) ([#9354](https://github.com/community-scripts/ProxmoxVE/pull/9354))
- Remove redundant cleanup steps from update scripts (ct/ Folder) [@MickLesk](https://github.com/MickLesk) ([#9359](https://github.com/community-scripts/ProxmoxVE/pull/9359))
### 🌐 Website
- #### ✨ New Features
- Refactor /data page [@BramSuurdje](https://github.com/BramSuurdje) ([#9343](https://github.com/community-scripts/ProxmoxVE/pull/9343))
## 2025-11-21
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- plex: prevent [] syntax issue [@MickLesk](https://github.com/MickLesk) ([#9318](https://github.com/community-scripts/ProxmoxVE/pull/9318))
- fix: karakeep strip "v" from release version [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9324](https://github.com/community-scripts/ProxmoxVE/pull/9324))
- NetVisor: fix grep in update [@vhsdream](https://github.com/vhsdream) ([#9334](https://github.com/community-scripts/ProxmoxVE/pull/9334))
- Immich: pin correct version [@vhsdream](https://github.com/vhsdream) ([#9332](https://github.com/community-scripts/ProxmoxVE/pull/9332))
- #### 🔧 Refactor
- Refactor IPv6 disable logic and add 'disable' option [@MickLesk](https://github.com/MickLesk) ([#9326](https://github.com/community-scripts/ProxmoxVE/pull/9326))
## 2025-11-20
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- core: change 'uv cache clear' to 'uv cache clean' [@MickLesk](https://github.com/MickLesk) ([#9299](https://github.com/community-scripts/ProxmoxVE/pull/9299))
- #### ✨ New Features
- Immich v2.3.1: OpenVINO tuning, OCR fixes, Maintenance mode, workflows/plugin framework [@vhsdream](https://github.com/vhsdream) ([#9310](https://github.com/community-scripts/ProxmoxVE/pull/9310))
- kasm: add: update [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9253](https://github.com/community-scripts/ProxmoxVE/pull/9253))
- tools/pve: expand PVE support to 9.09.1 (post-install & netdata) [@MickLesk](https://github.com/MickLesk) ([#9298](https://github.com/community-scripts/ProxmoxVE/pull/9298))
- #### 💥 Breaking Changes
- Omada - AVX-only support [@MickLesk](https://github.com/MickLesk) ([#9295](https://github.com/community-scripts/ProxmoxVE/pull/9295))
## 2025-11-19
### 🚀 Updated Scripts

View File

@@ -1,6 +1,6 @@
module proxmox-api
go 1.24.0
go 1.23.2
require (
github.com/gorilla/mux v1.8.1
@@ -17,7 +17,7 @@ require (
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.45.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/text v0.31.0 // 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
)

View File

@@ -27,16 +27,16 @@ go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793Sqyh
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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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=
@@ -48,8 +48,8 @@ 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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
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=

View File

@@ -57,6 +57,15 @@ function update_script() {
$STD composer install --no-dev --prefer-dist
php artisan 2fauth:install
$STD systemctl restart nginx
msg_info "Cleaning Up"
if dpkg -l | grep -q 'php8.2'; then
$STD apt remove --purge -y php8.2*
fi
$STD apt -y autoremove
$STD apt -y autoclean
$STD apt -y clean
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -62,7 +62,6 @@ function update_script() {
cd /opt/adventurelog/frontend || exit
$STD pnpm i
$STD pnpm build
rm -rf /opt/adventurelog-backup
msg_ok "Updated ${APP}"
msg_info "Starting Services"
@@ -70,6 +69,10 @@ function update_script() {
systemctl start adventurelog-backend
systemctl start adventurelog-frontend
msg_ok "Services Started"
msg_info "Cleaning Up"
rm -rf /opt/adventurelog-backup
msg_ok "Cleaned"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -38,13 +38,15 @@ function update_script() {
curl -fsSL -o tika-server-standard-${RELEASE}.jar "https://dlcdn.apache.org/tika/${RELEASE}/tika-server-standard-${RELEASE}.jar"
mv --force tika-server-standard.jar tika-server-standard-prev-version.jar
mv tika-server-standard-${RELEASE}.jar tika-server-standard.jar
rm -rf /opt/apache-tika/tika-server-standard-prev-version.jar
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated ${APP} to v${RELEASE}"
msg_info "Starting Service"
systemctl start apache-tika
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -rf /opt/apache-tika/tika-server-standard-prev-version.jar
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at v${RELEASE}"

View File

@@ -31,11 +31,15 @@ function update_script() {
fi
if check_for_gh_release "authelia" "authelia/authelia"; then
$STD apt update
$STD apt -y upgrade
$STD apt-get update
$STD apt-get -y upgrade
fetch_and_deploy_gh_release "authelia" "authelia/authelia" "binary"
msg_info "Cleaning Up"
$STD apt-get -y autoremove
$STD apt-get -y autoclean
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -39,13 +39,16 @@ function update_script() {
curl -fsSL "https://github.com/garethgeorge/backrest/releases/download/v${RELEASE}/backrest_Linux_x86_64.tar.gz" -o "$temp_file"
tar xzf $temp_file -C /opt/backrest/bin
chmod +x /opt/backrest/bin/backrest
rm -f "$temp_file"
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated ${APP} to ${RELEASE}"
msg_info "Starting Service"
systemctl start backrest
msg_ok "Started Service"
msg_info "Cleaning up"
rm -f "$temp_file"
msg_ok "Cleaned up"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"

View File

@@ -47,12 +47,15 @@ function update_script() {
chmod -R 755 /opt/baikal/
cd /opt/baikal
$STD composer install
rm -rf /opt/baikal-backup
msg_ok "Configured Baikal"
msg_info "Starting Service"
systemctl start apache2
msg_ok "Started Service"
msg_info "Cleaning up"
rm -rf /opt/baikal-backup
msg_ok "Cleaned"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -54,12 +54,15 @@ function update_script() {
$STD php artisan route:cache
$STD php artisan event:cache
chown -R www-data:www-data /opt/bar-assistant
rm -rf /opt/bar-assistant-backup
msg_ok "Updated Bar-Assistant"
msg_info "Starting nginx"
systemctl start nginx
msg_ok "Started nginx"
msg_info "Cleaning up"
rm -rf /opt/bar-assistant-backup
msg_ok "Cleaned"
fi
if check_for_gh_release "vue-salt-rim" "karlomikus/vue-salt-rim"; then
@@ -78,12 +81,15 @@ function update_script() {
cd /opt/vue-salt-rim
$STD npm install
$STD npm run build
rm -rf /opt/vue-salt-rim-backup
msg_ok "Updated Vue Salt Rim"
msg_info "Starting nginx"
systemctl start nginx
msg_ok "Started nginx"
msg_info "Cleaning up"
rm -rf /opt/vue-salt-rim-backup
msg_ok "Cleaned"
fi
if check_for_gh_release "meilisearch" "meilisearch/meilisearch"; then

View File

@@ -57,12 +57,15 @@ function update_script() {
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
rm -rf /opt/bookstack-backup
msg_ok "Configured BookStack"
msg_info "Starting Apache2"
systemctl start apache2
msg_ok "Started Apache2"
msg_info "Cleaning Up"
rm -rf /opt/bookstack-backup
msg_ok "Cleaned"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -109,7 +109,6 @@ function update_script() {
fi
$STD uv run python manage.py migrate --noinput
$STD uv run python manage.py collectstatic --noinput
rm -f /tmp/dispatcharr_db_*.sql
msg_ok "Migrations Complete"
msg_info "Starting Services"
@@ -118,6 +117,10 @@ function update_script() {
systemctl start dispatcharr-celerybeat
systemctl start dispatcharr-daphne
msg_ok "Started Services"
msg_info "Cleaning up"
rm -f /tmp/dispatcharr_db_*.sql
msg_ok "Cleanup completed"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -76,6 +76,11 @@ function update_script() {
portainer/agent
msg_ok "Updated Portainer Agent"
fi
msg_info "Cleaning up"
$STD apt-get -y autoremove
$STD apt-get -y autoclean
msg_ok "Cleanup complete"
msg_ok "Updated successfully!"
exit
}

View File

@@ -50,13 +50,16 @@ function update_script() {
$STD turbo run build --filter=@documenso/remix
$STD npm run prisma:migrate-deploy
$STD turbo daemon stop
rm -rf /opt/v${RELEASE}.zip
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated ${APP}"
msg_info "Starting Service"
systemctl start documenso
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -rf /opt/v${RELEASE}.zip
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"

View File

@@ -47,13 +47,16 @@ function update_script() {
msg_info "Installing EMQX"
$STD apt-get install -y "$DEB_FILE"
rm -f "$DEB_FILE"
echo "$RELEASE" >~/.emqx
msg_ok "Installed EMQX v${RELEASE}"
msg_info "Starting EMQX"
systemctl start emqx
echo "$RELEASE" >~/.emqx
msg_ok "Started EMQX"
msg_info "Cleaning Up"
rm -f "$DEB_FILE"
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
else
msg_ok "No update required. EMQX is already at v${RELEASE}"

View File

@@ -48,13 +48,16 @@ function update_script() {
temp_file=$(mktemp)
curl -fsSL https://fileflows.com/downloads/zip -o "$temp_file"
$STD unzip -o -d /opt/fileflows "$temp_file"
rm -rf "$temp_file"
rm -rf "$backup_filename"
msg_ok "Updated $APP to latest version"
msg_info "Starting Service"
systemctl start fileflows
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -rf "$temp_file"
rm -rf "$backup_filename"
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at latest version"

View File

@@ -56,6 +56,10 @@ function update_script() {
msg_info "Starting Service"
systemctl start ghostfolio
msg_ok "Started Service"
msg_info "Cleaning Up"
$STD npm cache clean --force
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -1,6 +0,0 @@
__ __ __
/ / / /___ ____ _____ ____ / /_ ___ _____
/ / / / __ \/ __ `/ __ \/ __ \/ __ \/ _ \/ ___/
/ /_/ / /_/ / /_/ / /_/ / /_/ / / / / __/ /
\____/ .___/\__, /\____/ .___/_/ /_/\___/_/
/_/ /____/ /_/

View File

@@ -61,15 +61,6 @@ EOF
msg_ok "Installed libmimalloc3"
fi
if [[ ! -f /etc/apt/sources.list.d/mise.list ]]; then
msg_info "Installing Mise"
curl -fSs https://mise.jdx.dev/gpg-key.pub | tee /etc/apt/keyrings/mise-archive-keyring.pub 1>/dev/null
echo "deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.pub arch=amd64] https://mise.jdx.dev/deb stable main" | tee /etc/apt/sources.list.d/mise.list
$STD apt update
$STD apt install -y mise
msg_ok "Installed Mise"
fi
STAGING_DIR=/opt/staging
BASE_DIR=${STAGING_DIR}/base-images
SOURCE_DIR=${STAGING_DIR}/image-source
@@ -102,7 +93,7 @@ EOF
msg_ok "Image-processing libraries up to date"
fi
RELEASE="2.3.1"
RELEASE="2.2.3"
if check_for_gh_release "immich" "immich-app/immich" "${RELEASE}"; then
msg_info "Stopping Services"
systemctl stop immich-web
@@ -130,7 +121,6 @@ EOF
UPLOAD_DIR="$(sed -n '/^IMMICH_MEDIA_LOCATION/s/[^=]*=//p' /opt/immich/.env)"
SRC_DIR="${INSTALL_DIR}/source"
APP_DIR="${INSTALL_DIR}/app"
PLUGIN_DIR="${APP_DIR}/corePlugin"
ML_DIR="${APP_DIR}/machine-learning"
GEO_DIR="${INSTALL_DIR}/geodata"
@@ -155,7 +145,9 @@ EOF
rm -rf "${APP_DIR:?}"/*
)
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "immich" "immich-app/immich" "tarball" "v${RELEASE}" "$SRC_DIR"
rm -rf "$SRC_DIR"
fetch_and_deploy_gh_release "immich" "immich-app/immich" "tarball" "v${RELEASE}" "$SRC_DIR"
msg_info "Updating ${APP} web and microservices"
cd "$SRC_DIR"/server
@@ -188,18 +180,7 @@ EOF
$STD pnpm --filter @immich/cli --prod --no-optional deploy "$APP_DIR"/cli
cd "$APP_DIR"
mv "$INSTALL_DIR"/start.sh "$APP_DIR"/bin
# plugins
cd "$SRC_DIR"
$STD mise trust --ignore ./mise.toml
$STD mise trust ./plugins/mise.toml
cd plugins
$STD mise install
$STD mise run build
mkdir -p "$PLUGIN_DIR"
cp -r ./dist "$PLUGIN_DIR"/dist
cp ./manifest.json "$PLUGIN_DIR"
msg_ok "Updated ${APP} server, web, cli and plugins"
msg_ok "Updated ${APP} web and microservices"
cd "$SRC_DIR"/machine-learning
mkdir -p "$ML_DIR" && chown -R immich:immich "$ML_DIR"

View File

@@ -20,57 +20,62 @@ color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/jellyseerr ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if [[ ! -d /opt/jellyseerr ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if [ "$(node -v | cut -c2-3)" -ne 22 ]; then
msg_info "Updating Node.js Repository"
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" >/etc/apt/sources.list.d/nodesource.list
msg_ok "Updating Node.js Repository"
if [ "$(node -v | cut -c2-3)" -ne 22 ]; then
msg_info "Updating Node.js Repository"
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" >/etc/apt/sources.list.d/nodesource.list
msg_ok "Updating Node.js Repository"
msg_info "Updating Packages"
$STD apt-get update
$STD apt-get -y upgrade
msg_ok "Updating Packages"
fi
msg_info "Updating Packages"
$STD apt-get update
$STD apt-get -y upgrade
msg_ok "Updating Packages"
cd /opt/jellyseerr
output=$(git pull --no-rebase)
msg_info "Cleaning up"
apt-get -y autoremove
apt-get -y autoclean
msg_ok "Cleaning up"
fi
pnpm_current=$(pnpm --version 2>/dev/null)
pnpm_desired=$(grep -Po '"pnpm":\s*"\K[^"]+' /opt/jellyseerr/package.json)
cd /opt/jellyseerr
output=$(git pull --no-rebase)
if [ -z "$pnpm_current" ]; then
msg_error "pnpm not found. Installing version $pnpm_desired..."
NODE_VERSION="22" NODE_MODULE="pnpm@$pnpm_desired" setup_nodejs
elif ! node -e "const semver = require('semver'); process.exit(semver.satisfies('$pnpm_current', '$pnpm_desired') ? 0 : 1)"; then
msg_error "Updating pnpm from version $pnpm_current to $pnpm_desired..."
NODE_VERSION="22" NODE_MODULE="pnpm@$pnpm_desired" setup_nodejs
else
msg_ok "pnpm is already installed and satisfies version $pnpm_desired."
fi
pnpm_current=$(pnpm --version 2>/dev/null)
pnpm_desired=$(grep -Po '"pnpm":\s*"\K[^"]+' /opt/jellyseerr/package.json)
msg_info "Updating $APP"
if echo "$output" | grep -q "Already up to date."; then
msg_ok "$APP is already up to date."
exit
fi
if [ -z "$pnpm_current" ]; then
msg_error "pnpm not found. Installing version $pnpm_desired..."
NODE_VERSION="22" NODE_MODULE="pnpm@$pnpm_desired" setup_nodejs
elif ! node -e "const semver = require('semver'); process.exit(semver.satisfies('$pnpm_current', '$pnpm_desired') ? 0 : 1)"; then
msg_error "Updating pnpm from version $pnpm_current to $pnpm_desired..."
NODE_VERSION="22" NODE_MODULE="pnpm@$pnpm_desired" setup_nodejs
else
msg_ok "pnpm is already installed and satisfies version $pnpm_desired."
fi
systemctl stop jellyseerr
rm -rf dist .next node_modules
export CYPRESS_INSTALL_BINARY=0
cd /opt/jellyseerr
$STD pnpm install --frozen-lockfile
export NODE_OPTIONS="--max-old-space-size=3072"
$STD pnpm build
msg_info "Updating $APP"
if echo "$output" | grep -q "Already up to date."; then
msg_ok "$APP is already up to date."
exit
fi
cat <<EOF >/etc/systemd/system/jellyseerr.service
systemctl stop jellyseerr
rm -rf dist .next node_modules
export CYPRESS_INSTALL_BINARY=0
cd /opt/jellyseerr
$STD pnpm install --frozen-lockfile
export NODE_OPTIONS="--max-old-space-size=3072"
$STD pnpm build
cat <<EOF >/etc/systemd/system/jellyseerr.service
[Unit]
Description=jellyseerr Service
After=network.target
@@ -86,10 +91,10 @@ ExecStart=/usr/bin/node dist/index.js
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl start jellyseerr
msg_ok "Updated $APP"
exit
systemctl daemon-reload
systemctl start jellyseerr
msg_ok "Updated $APP"
exit
}
start

View File

@@ -82,7 +82,7 @@ function update_script() {
cd /opt/karakeep/packages/db
$STD pnpm migrate
$STD pnpm store prune
sed -i "s/^SERVER_VERSION=.*$/SERVER_VERSION=${CHECK_UPDATE_RELEASE#v}/" /etc/karakeep/karakeep.env
sed -i "s/^SERVER_VERSION=.*$/SERVER_VERSION=${CHECK_UPDATE_RELEASE}/" /etc/karakeep/karakeep.env
msg_ok "Updated Karakeep"
msg_info "Starting Services"

View File

@@ -25,51 +25,14 @@ function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/kasm/current ]]; then
if [[ ! -d /var ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Checking for new version"
CURRENT_VERSION=$(readlink -f /opt/kasm/current | awk -F'/' '{print $4}')
KASM_URL=$(curl -fsSL "https://www.kasm.com/downloads" | tr '\n' ' ' | grep -oE 'https://kasm-static-content[^"]*kasm_release_[0-9]+\.[0-9]+\.[0-9]+\.[a-z0-9]+\.tar\.gz' | head -n 1)
if [[ -z "$KASM_URL" ]]; then
msg_error "Unable to detect latest Kasm release URL."
exit 1
fi
KASM_VERSION=$(echo "$KASM_URL" | sed -E 's/.*kasm_release_([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
msg_info "Checked for new version"
msg_info "Removing outdated docker-compose plugin"
[ -f ~/.docker/cli-plugins/docker-compose ] && rm -rf ~/.docker/cli-plugins/docker-compose
msg_ok "Removed outdated docker-compose plugin"
if [[ -z "$CURRENT_VERSION" ]] || [[ "$KASM_VERSION" != "$CURRENT_VERSION" ]]; then
msg_info "Updating Kasm"
cd /tmp
msg_warn "WARNING: This script will run an external installer from a third-party source (https://www.kasmweb.com/)."
msg_warn "The following code is NOT maintained or audited by our repository."
msg_warn "If you have any doubts or concerns, please review the installer code before proceeding:"
msg_custom "${TAB3}${GATEWAY}${BGN}${CL}" "\e[1;34m" "→ upgrade.sh inside tar.gz $KASM_URL"
echo
read -r -p "${TAB3}Do you want to continue? [y/N]: " CONFIRM
if [[ ! "$CONFIRM" =~ ^([yY][eE][sS]|[yY])$ ]]; then
msg_error "Aborted by user. No changes have been made."
exit 10
fi
curl -fsSL -o "/tmp/kasm_release_${KASM_VERSION}.tar.gz" "$KASM_URL"
tar -xf "kasm_release_${KASM_VERSION}.tar.gz"
chmod +x /tmp/kasm_release/install.sh
rm -f /tmp/kasm_release_${KASM_VERSION}.tar.gz
bash /tmp/kasm_release/upgrade.sh --proxy-port 443
rm -rf /tmp/kasm_release
msg_ok "Updated Successfully"
else
msg_ok "No update required. Kasm is already at v${KASM_VERSION}"
fi
msg_info "Updating LXC"
$STD apt update
$STD apt -y upgrade
msg_ok "Updated LXC"
exit
}

View File

@@ -49,12 +49,15 @@ function update_script() {
cp -a keycloak.old/conf/. keycloak/conf/
cp -a keycloak.old/providers/. keycloak/providers/ 2>/dev/null || true
cp -a keycloak.old/themes/. keycloak/themes/ 2>/dev/null || true
rm -rf keycloak.old
msg_ok "Updated Keycloak"
msg_info "Restarting Service"
systemctl restart keycloak
msg_ok "Restarted Service"
msg_info "Cleaning up"
rm -rf keycloak.old
msg_ok "Cleanup complete"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -50,12 +50,15 @@ function update_script() {
$STD yarn install
$STD yarn build
chown -R www-data:www-data /opt/koillection/public/uploads
rm -r /opt/koillection-backup
msg_ok "Updated Koillection"
msg_info "Starting Service"
systemctl start apache2
msg_ok "Started Service"
msg_info "Cleaning up"
rm -r /opt/koillection-backup
msg_ok "Cleaned"
msg_ok "Updated Successfully!"
fi
exit

View File

@@ -52,14 +52,17 @@ function update_script() {
$STD yarn web:build
$STD yarn prisma:deploy
[ -d /opt/data.bak ] && mv /opt/data.bak /opt/linkwarden/data
rm -rf ~/.cargo/registry ~/.cargo/git ~/.cargo/.package-cache ~/.rustup
rm -rf /root/.cache/yarn
rm -rf /opt/linkwarden/.next/cache
msg_ok "Updated ${APP}"
msg_info "Starting Service"
systemctl start linkwarden
msg_ok "Started Service"
msg_info "Cleaning up"
rm -rf ~/.cargo/registry ~/.cargo/git ~/.cargo/.package-cache ~/.rustup
rm -rf /root/.cache/yarn
rm -rf /opt/linkwarden/.next/cache
msg_ok "Cleaned"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -42,12 +42,15 @@ function update_script() {
mv /opt/listmonk-backup/config.toml /opt/listmonk/config.toml
mv /opt/listmonk-backup/uploads /opt/listmonk/uploads
$STD /opt/listmonk/listmonk --upgrade --yes --config /opt/listmonk/config.toml
rm -rf /opt/listmonk-backup/
msg_ok "Configured listmonk"
msg_info "Starting Service"
systemctl start listmonk
msg_ok "Started Service"
msg_info "Cleaning up"
rm -rf /opt/listmonk-backup/
msg_ok "Cleaned"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -53,12 +53,15 @@ function update_script() {
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_info "Cleaning up"
rm -rf /tmp/lubeloggerData
msg_ok "Cleaned"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -38,9 +38,15 @@ function update_script() {
curl -fsSL -o "$DEB_FILE" "$DEB_URL"
$STD apt install "$DEB_FILE" -y
systemctl restart lyrion
$STD rm -f "$DEB_FILE"
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated $APP to ${RELEASE}"
msg_info "Cleaning up"
$STD rm -f "$DEB_FILE"
$STD apt -y autoremove
$STD apt -y autoclean
$STD apt -y clean
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "$APP is already up to date (${RELEASE})"

View File

@@ -55,13 +55,17 @@ function update_script() {
mv /usr/local/bin/minio /usr/local/bin/minio_bak
curl -fsSL "https://dl.min.io/server/minio/release/linux-amd64/minio" -o /usr/local/bin/minio
chmod +x /usr/local/bin/minio
rm -f /usr/local/bin/minio_bak
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated ${APP}"
msg_info "Starting Service"
systemctl start minio
msg_ok "Started Service"
msg_info "Cleaning up"
rm -f /usr/local/bin/minio_bak
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"

View File

@@ -52,12 +52,15 @@ function update_script() {
$STD php artisan monica:update --force
chown -R www-data:www-data /opt/monica
chmod -R 775 /opt/monica/storage
rm -r /opt/monica-backup
msg_ok "Configured monica"
msg_info "Starting Service"
systemctl start apache2
msg_ok "Started Service"
msg_info "Cleaning up"
rm -r /opt/monica-backup
msg_ok "Cleaned"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -57,14 +57,17 @@ function update_script() {
fi
$STD /opt/netbox/upgrade.sh
rm -r "/opt/v${RELEASE}.zip"
rm -r /opt/netbox-backup
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated $APP to v${RELEASE}"
msg_info "Starting Service"
systemctl start netbox netbox-rq
msg_ok "Started Service"
msg_info "Cleaning up"
rm -r "/opt/v${RELEASE}.zip"
rm -r /opt/netbox-backup
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at v${RELEASE}"

View File

@@ -51,7 +51,7 @@ function update_script() {
cp /opt/netvisor.env.bak /opt/netvisor/.env
LOCAL_IP="$(hostname -I | awk '{print $1}')"
if ! grep -q "PUBLIC_URL" /opt/netvisor/.env; then
if ! grep -q "PUBLIC_URL"; then
sed -i "\|_PATH=|a\NETVISOR_PUBLIC_URL=http://${LOCAL_IP}:60072" /opt/netvisor/.env
fi
sed -i 's|_TARGET=.*$|_URL=http://127.0.0.1:60072|' /opt/netvisor/.env

View File

@@ -21,34 +21,37 @@ color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/nextpvr ]]; then
msg_error "No ${APP} Installation Found!"
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/nextpvr ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Stopping Service"
systemctl stop nextpvr-server
msg_ok "Stopped Service"
msg_info "Updating LXC packages"
$STD apt update
$STD apt -y upgrade
msg_ok "Updated LXC packages"
msg_info "Updating ${APP}"
cd /opt
curl -fsSL "https://nextpvr.com/nextpvr-helper.deb" -o $(basename "https://nextpvr.com/nextpvr-helper.deb")
$STD dpkg -i nextpvr-helper.deb
msg_ok "Updated ${APP}"
msg_info "Starting Service"
systemctl start nextpvr-server
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -rf /opt/nextpvr-helper.deb
msg_ok "Cleaned"
msg_ok "Updated successfully!"
exit
fi
msg_info "Stopping Service"
systemctl stop nextpvr-server
msg_ok "Stopped Service"
msg_info "Updating LXC packages"
$STD apt update
$STD apt -y upgrade
msg_ok "Updated LXC packages"
msg_info "Updating ${APP}"
cd /opt
curl -fsSL "https://nextpvr.com/nextpvr-helper.deb" -o $(basename "https://nextpvr.com/nextpvr-helper.deb")
$STD dpkg -i nextpvr-helper.deb
rm -rf /opt/nextpvr-helper.deb
msg_ok "Updated ${APP}"
msg_info "Starting Service"
systemctl start nextpvr-server
msg_ok "Started Service"
msg_ok "Updated successfully!"
exit
}
start

View File

@@ -42,13 +42,17 @@ function update_script() {
export DEBIAN_FRONTEND=noninteractive
export DEBCONF_NOWARNINGS=yes
$STD dpkg -i nxwitness-server-$RELEASE-linux_x64.deb
rm -f /tmp/nxwitness-server-$RELEASE-linux_x64.deb
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated ${APP}"
msg_info "Starting Service"
systemctl start networkoptix-root-tool networkoptix-mediaserver
msg_ok "Started Service"
msg_info "Cleaning up"
rm -f /tmp/nxwitness-server-$RELEASE-linux_x64.deb
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"

View File

@@ -50,13 +50,17 @@ function update_script() {
msg_info "Updating ${APP} to ${LATEST_VERSION}"
curl -fsSL https://nightly.odoo.com/${RELEASE}/nightly/deb/odoo_${RELEASE}.latest_all.deb -o /opt/odoo.deb
$STD apt install -y /opt/odoo.deb
rm -f /opt/odoo.deb
echo "$LATEST_VERSION" >/opt/${APP}_version.txt
msg_ok "Updated ${APP} to ${LATEST_VERSION}"
msg_info "Starting Service"
msg_info "Starting ${APP} service"
systemctl start odoo
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -f /opt/odoo.deb
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at ${LATEST_VERSION}"

View File

@@ -43,13 +43,16 @@ function update_script() {
mkdir -p /usr/local/lib/ollama
tar -xzf "${TMP_TAR}" -C /usr/local/lib/ollama
ln -sf /usr/local/lib/ollama/bin/ollama /usr/local/bin/ollama
rm -f "${TMP_TAR}"
echo "${RELEASE}" >/opt/Ollama_version.txt
msg_ok "Updated Ollama to ${RELEASE}"
msg_info "Starting Services"
systemctl start ollama
msg_ok "Started Services"
msg_info "Cleaning Up"
rm -f "${TMP_TAR}"
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. Ollama is already at ${RELEASE}"

View File

@@ -39,14 +39,17 @@ function update_script() {
tar -xzf onedev-latest.tar.gz
$STD /opt/onedev-latest/bin/upgrade.sh /opt/onedev
RELEASE=$(cat /opt/onedev/release.properties | grep "version" | cut -d'=' -f2)
rm -rf /opt/onedev-latest
rm -rf /opt/onedev-latest.tar.gz
echo "${RELEASE}" >"/opt/${APP}_version.txt"
msg_ok "Updated ${APP} to v${RELEASE}"
msg_info "Starting Service"
systemctl start onedev
msg_ok "Started Service"
msg_info "Cleaning up"
rm -rf /opt/onedev-latest
rm -rf /opt/onedev-latest.tar.gz
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at v${RELEASE}."

View File

@@ -65,14 +65,17 @@ EOF
$STD pip install --no-cache-dir -r requirements.txt
mkdir -p data/chromadb
$STD npm install
rm -rf /opt/v${RELEASE}.zip
rm -rf /opt/paperless-ai_bak
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated $APP to v${RELEASE}"
msg_info "Starting Service"
systemctl start paperless-ai
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -rf /opt/v${RELEASE}.zip
rm -rf /opt/paperless-ai_bak
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at v${RELEASE}"

View File

@@ -20,45 +20,48 @@ color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/paperless-gpt ]]; then
msg_error "No Paperless-GPT installation found!"
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/paperless-gpt ]]; then
msg_error "No Paperless-GPT installation found!"
exit
fi
RELEASE=$(curl -fsSL https://api.github.com/repos/icereed/paperless-gpt/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 Service"
systemctl stop paperless-gpt
msg_ok "Service Stopped"
msg_info "Updating Paperless-GPT to ${RELEASE}"
temp_file=$(mktemp)
curl -fsSL "https://github.com/icereed/paperless-gpt/archive/refs/tags/v${RELEASE}.tar.gz" -o "$temp_file"
tar zxf $temp_file
rm -rf /opt/paperless-gpt
mv paperless-gpt-${RELEASE} /opt/paperless-gpt
cd /opt/paperless-gpt/web-app
$STD npm install
$STD npm run build
cd /opt/paperless-gpt
go mod download
export CC=musl-gcc
CGO_ENABLED=1 go build -tags musl -o /dev/null github.com/mattn/go-sqlite3
CGO_ENABLED=1 go build -tags musl -o paperless-gpt .
echo "${RELEASE}" >"/opt/paperless-gpt_version.txt"
msg_ok "Updated Paperless-GPT to ${RELEASE}"
msg_info "Starting Service"
systemctl start paperless-gpt
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -f $temp_file
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"
fi
exit
fi
RELEASE=$(curl -fsSL https://api.github.com/repos/icereed/paperless-gpt/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 Service"
systemctl stop paperless-gpt
msg_ok "Service Stopped"
msg_info "Updating Paperless-GPT to ${RELEASE}"
temp_file=$(mktemp)
curl -fsSL "https://github.com/icereed/paperless-gpt/archive/refs/tags/v${RELEASE}.tar.gz" -o "$temp_file"
tar zxf $temp_file
rm -rf /opt/paperless-gpt
mv paperless-gpt-${RELEASE} /opt/paperless-gpt
cd /opt/paperless-gpt/web-app
$STD npm install
$STD npm run build
cd /opt/paperless-gpt
go mod download
export CC=musl-gcc
CGO_ENABLED=1 go build -tags musl -o /dev/null github.com/mattn/go-sqlite3
CGO_ENABLED=1 go build -tags musl -o paperless-gpt .
rm -f $temp_file
echo "${RELEASE}" >"/opt/paperless-gpt_version.txt"
msg_ok "Updated Paperless-GPT to ${RELEASE}"
msg_info "Starting Service"
systemctl start paperless-gpt
msg_ok "Started Service"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"
fi
exit
}
start

View File

@@ -52,14 +52,17 @@ function update_script() {
$STD php bin/console cache:clear
$STD php bin/console doctrine:migrations:migrate -n
chown -R www-data:www-data /opt/partdb
rm -r "/opt/v${RELEASE}.zip"
rm -r /opt/partdb-backup
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated $APP to v${RELEASE}"
msg_info "Starting Service"
systemctl start apache2
msg_ok "Started Service"
msg_info "Cleaning up"
rm -r "/opt/v${RELEASE}.zip"
rm -r /opt/partdb-backup
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at v${RELEASE}"

View File

@@ -67,7 +67,6 @@ function update_script() {
$STD php artisan migrate --seed --force
chown -R www-data:www-data /opt/pelican-panel
chmod -R 755 /opt/pelican-panel/storage /opt/pelican-panel/bootstrap/cache/
rm -rf "/opt/pelican-panel/panel.tar.gz"
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated $APP to v${RELEASE}"
@@ -75,6 +74,10 @@ function update_script() {
$STD php artisan queue:restart
$STD php artisan up
msg_ok "Started Service"
msg_info "Cleaning up"
rm -rf "/opt/pelican-panel/panel.tar.gz"
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at v${RELEASE}"

View File

@@ -23,8 +23,7 @@ function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -f /etc/apt/sources.list.d/plexmediaserver.list ]] \
&& [[ ! -f /etc/apt/sources.list.d/plexmediaserver.sources ]]; then
if [ ! -f /etc/apt/sources.list.d/plexmediaserver.list ]] && [[ ! -f /etc/apt/sources.list.d/plexmediaserver.sources ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi

View File

@@ -70,7 +70,6 @@ EOF
$STD php artisan migrate --seed --force --no-interaction
chown -R www-data:www-data /opt/pterodactyl-panel/*
chmod -R 755 /opt/pterodactyl-panel/storage /opt/pterodactyl-panel/bootstrap/cache/
rm -rf "/opt/pterodactyl-panel/panel.tar.gz"
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated $APP to v${RELEASE}"
@@ -78,6 +77,10 @@ EOF
$STD php artisan queue:restart
$STD php artisan up
msg_ok "Started Service"
msg_info "Cleaning up"
rm -rf "/opt/pterodactyl-panel/panel.tar.gz"
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at v${RELEASE}"

View File

@@ -43,11 +43,14 @@ function update_script() {
$STD apt remove --purge -y dotnet-sdk-8.0
$STD apt install -y dotnet-sdk-9.0
fi
rm -rf /opt/rdtc-backup
msg_info "Starting Service"
systemctl start rdtc
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -rf /opt/rdtc-backup
msg_ok "Cleaned"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -54,7 +54,6 @@ function update_script() {
cd /tmp
curl -fsSL https://dl.min.io/server/minio/release/linux-amd64/minio.deb -o minio.deb
$STD dpkg -i minio.deb
rm -f /tmp/minio.deb
msg_ok "Updated Minio"
msg_info "Updating Browserless (Patience)"
@@ -76,12 +75,16 @@ function update_script() {
$STD npm run build:function
$STD npm prune production
mv /opt/browserless.env /opt/browserless/.env
rm -f "$brwsr_tmp"
msg_ok "Updated Browserless"
msg_info "Restarting services"
systemctl start minio Reactive-Resume browserless
msg_ok "Restarted services"
msg_info "Cleaning Up"
rm -f /tmp/minio.deb
rm -f "$brwsr_tmp"
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -41,12 +41,15 @@ function update_script() {
$STD npm install
cp -f /opt/index.html /opt/revealjs
sed -i '25s/localhost/0.0.0.0/g' /opt/revealjs/gulpfile.js
rm -f /opt/index.html
msg_ok "Updated $APP"
msg_info "Starting Service"
systemctl start revealjs
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -f /opt/index.html
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -61,12 +61,15 @@ function update_script() {
$STD pip install -r requirements.txt
mv /opt/config.ini.bak /opt/soularr/config.ini
mv /opt/run.sh.bak /opt/soularr/run.sh
rm -rf /tmp/main.zip
msg_ok "Updated soularr"
msg_info "Starting soularr timer"
systemctl start soularr.timer
msg_ok "Started soularr timer"
msg_info "Cleaning Up"
rm -rf /tmp/main.zip
msg_ok "Cleanup Completed"
exit
}

View File

@@ -31,7 +31,7 @@ function update_script() {
sed -i '/index index.php;/i \ client_max_body_size 100M;' /etc/nginx/conf.d/snipeit.conf
fi
if check_for_gh_release "snipe-it" "grokability/snipe-it"; then
if check_for_gh_release "snipe-it" "snipe/snipe-it"; then
msg_info "Stopping Services"
systemctl stop nginx
msg_ok "Services Stopped"
@@ -40,7 +40,7 @@ function update_script() {
mv /opt/snipe-it /opt/snipe-it-backup
msg_ok "Backup created"
fetch_and_deploy_gh_release "snipe-it" "grokability/snipe-it" "tarball"
fetch_and_deploy_gh_release "snipe-it" "snipe/snipe-it" "tarball"
[[ "$(php -v 2>/dev/null)" == PHP\ 8.2* ]] && PHP_VERSION="8.3" PHP_MODULE="common,ctype,ldap,fileinfo,iconv,mysql,soap,xsl" PHP_FPM="YES" setup_php
sed -i 's/php8.2/php8.3/g' /etc/nginx/conf.d/snipeit.conf
setup_composer

View File

@@ -43,13 +43,17 @@ function update_script() {
cd spoolman
$STD pip3 install -r requirements.txt
curl -fsSL "https://raw.githubusercontent.com/Donkie/Spoolman/master/.env.example" -o ".env"
rm -rf /opt/spoolman.zip
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated ${APP} to ${RELEASE}"
msg_info "Starting Service"
systemctl start spoolman
msg_ok "Started Service"
msg_info "Cleaning up"
rm -rf /opt/spoolman.zip
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"

View File

@@ -64,13 +64,16 @@ EOF
cd /opt/tandoor
$STD /opt/tandoor/.venv/bin/python manage.py migrate
$STD /opt/tandoor/.venv/bin/python manage.py collectstatic --no-input
rm -rf /opt/tandoor.bak
msg_ok "Updated Trandoor"
msg_info "Starting Service"
systemctl start tandoor
systemctl reload nginx
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -rf /opt/tandoor.bak
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -45,13 +45,16 @@ function update_script() {
export NODE_OPTIONS=--openssl-legacy-provider
$STD npm i
$STD yarn build
rm -r "/opt/v${RELEASE}.tar.gz"
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated TasmoCompiler"
msg_info "Starting Service"
systemctl start tasmocompiler
msg_ok "Started Service"
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_info "Cleaning up"
rm -r "/opt/v${RELEASE}.tar.gz"
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at v${RELEASE}"

View File

@@ -37,8 +37,11 @@ function update_script() {
$STD unzip Tdarr_Updater.zip
chmod +x Tdarr_Updater
$STD ./Tdarr_Updater
rm -rf /opt/tdarr/Tdarr_Updater.zip
msg_ok "Updated Tdarr"
msg_info "Cleaning up"
rm -rf /opt/tdarr/Tdarr_Updater.zip
msg_ok "Cleaned up"
msg_ok "Updated successfully!"
exit
}

View File

@@ -38,8 +38,11 @@ function update_script() {
msg_info "Updating Technitium DNS"
curl -fsSL "https://download.technitium.com/dns/DnsServerPortable.tar.gz" -o /opt/DnsServerPortable.tar.gz
$STD tar zxvf /opt/DnsServerPortable.tar.gz -C /opt/technitium/dns/
rm -f /opt/DnsServerPortable.tar.gz
msg_ok "Updated Technitium DNS"
msg_info "Cleaning up"
rm -f /opt/DnsServerPortable.tar.gz
msg_ok "Cleaned up"
msg_ok "Updated successfully!"
else
msg_ok "No update required. Technitium DNS is already at v${RELEASE}."

View File

@@ -41,12 +41,15 @@ function update_script() {
msg_info "Restoring data"
cp -R /opt/teddycloud_bak/certs /opt/teddycloud_bak/config /opt/teddycloud_bak/data /opt/teddycloud
rm -rf /opt/teddycloud_bak
msg_ok "Data restored"
msg_info "Starting Service"
systemctl start teddycloud
msg_ok "Started Service"
msg_info "Cleaning up"
rm -rf /opt/teddycloud_bak
msg_ok "Cleaned up"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -43,7 +43,7 @@ function update_script() {
fetch_and_deploy_gh_release "tianji" "msgbyte/tianji"
msg_info "Updating Tianji"
msg_info "Updating ${APP}"
cd /opt/tianji
export NODE_OPTIONS="--max_old_space_size=4096"
$STD pnpm install --filter @tianji/client... --config.dedupe-peer-dependents=false --frozen-lockfile
@@ -55,11 +55,7 @@ function update_script() {
mv /opt/.env /opt/tianji/src/server/.env
cd src/server
$STD pnpm db:migrate:apply
rm -rf /opt/tianji_bak
rm -rf /opt/tianji/src/client
rm -rf /opt/tianji/website
rm -rf /opt/tianji/reporter
msg_ok "Updated Tianji"
msg_ok "Updated ${APP}"
msg_info "Updating AppRise"
$STD uv pip install apprise cryptography --system
@@ -68,6 +64,13 @@ function update_script() {
msg_info "Starting Service"
systemctl start tianji
msg_ok "Started Service"
msg_info "Cleaning up"
rm -rf /opt/tianji_bak
rm -rf /opt/tianji/src/client
rm -rf /opt/tianji/website
rm -rf /opt/tianji/reporter
msg_ok "Cleaned"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -50,12 +50,18 @@ function update_script() {
mv /opt/traccar.xml /opt/traccar/conf
[[ -d /opt/data ]] && mv /opt/data /opt/traccar
[[ -d /opt/media ]] && mv /opt/media /opt/traccar
[ -f README.txt ] || [ -f traccar.run ] && rm -f README.txt traccar.run
msg_ok "Data restored"
msg_info "Starting Service"
systemctl start traccar
msg_ok "Started Service"
msg_info "Cleaning up"
[ -f README.txt ] || [ -f traccar.run ] && rm -f README.txt traccar.run
$STD apt -y autoremove
$STD apt -y autoclean
$STD apt -y clean
msg_ok "Cleaned up"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -55,9 +55,15 @@ function update_script() {
msg_info "Restoring Database"
mkdir -p "$(dirname "${DB_RESTORE_PATH}")"
cp -r /opt/trilium_backup/$(basename "${DB_PATH}") "${DB_RESTORE_PATH}"
rm -rf /opt/trilium_backup
msg_ok "Restored Database"
msg_info "Cleaning up"
rm -rf /opt/trilium_backup
$STD apt -y autoremove
$STD apt -y autoclean
$STD apt -y clean
msg_ok "Cleaned"
msg_info "Starting Service"
systemctl start trilium
sleep 1

View File

@@ -43,6 +43,12 @@ function update_script() {
msg_info "Starting Service"
systemctl start uhf-server
msg_ok "Started Service"
msg_info "Cleaning up"
$STD apt -y autoremove
$STD apt -y autoclean
$STD apt -y clean
msg_ok "Cleaned"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -1,54 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2025 community-scripts ORG
# Author: Eduard González (wanetty)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/wanetty/upgopher
APP="Upgopher"
var_tags="${var_tags:-file-sharing}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
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/upgopher ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "upgopher" "wanetty/upgopher"; then
msg_info "Stopping Service"
systemctl stop upgopher
msg_ok "Stopped Service"
fetch_and_deploy_gh_release "upgopher" "wanetty/upgopher" "prebuild" "latest" "/opt/upgopher" "upgopher_*_linux_amd64.tar.gz"
chmod +x /opt/upgopher/upgopher
msg_info "Starting Service"
systemctl start upgopher
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}:9090${CL}"

View File

@@ -57,9 +57,12 @@ function update_script() {
else
cp target/release/vaultwarden /opt/vaultwarden/bin/
fi
cd ~ && rm -rf vaultwarden
msg_ok "Updated VaultWarden"
msg_info "Cleaning up"
cd ~ && rm -rf vaultwarden
msg_ok "Cleaned"
msg_info "Starting Service"
systemctl start vaultwarden
msg_ok "Started Service"
@@ -74,9 +77,12 @@ function update_script() {
msg_info "Updating Web-Vault to $WVRELEASE"
$STD curl -fsSLO https://github.com/dani-garcia/bw_web_builds/releases/download/"$WVRELEASE"/bw_web_"$WVRELEASE".tar.gz
$STD tar -zxf bw_web_"$WVRELEASE".tar.gz -C /opt/vaultwarden/
rm bw_web_"$WVRELEASE".tar.gz
msg_ok "Updated Web-Vault"
msg_info "Cleaning up"
rm bw_web_"$WVRELEASE".tar.gz
msg_ok "Cleaned"
msg_info "Starting Service"
systemctl start vaultwarden
msg_ok "Started Service"

View File

@@ -39,13 +39,16 @@ function update_script() {
curl -fsSL "https://dl.vikunja.io/vikunja/$RELEASE/vikunja-$RELEASE-amd64.deb" -o $(basename "https://dl.vikunja.io/vikunja/$RELEASE/vikunja-$RELEASE-amd64.deb")
export DEBIAN_FRONTEND=noninteractive
$STD dpkg -i vikunja-"$RELEASE"-amd64.deb
rm -rf /opt/vikunja-"$RELEASE"-amd64.deb
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated ${APP}"
msg_info "Starting Service"
systemctl start vikunja
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -rf /opt/vikunja-"$RELEASE"-amd64.deb
msg_ok "Cleaned"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"

View File

@@ -73,13 +73,16 @@ EOF
cp -f wastebin* /opt/wastebin/
chmod +x /opt/wastebin/wastebin
chmod +x /opt/wastebin/wastebin-ctl
rm -f "$temp_file"
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated Wastebin"
msg_info "Starting Wastebin"
systemctl start wastebin
msg_ok "Started Wastebin"
msg_info "Cleaning Up"
rm -f "$temp_file"
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at v${RELEASE}"

View File

@@ -37,7 +37,10 @@ function update_script() {
fetch_and_deploy_gh_release "watchyourlan" "aceberg/WatchYourLAN" "binary"
cp -R config.yaml /data/config.yaml
sed -i 's|/etc/watchyourlan/config.yaml|/data/config.yaml|' /lib/systemd/system/watchyourlan.service
msg_info "Cleaning up"
rm ~/config.yaml
msg_ok "Cleaned up"
msg_info "Starting service"
systemctl enable -q --now watchyourlan

View File

@@ -45,13 +45,16 @@ function update_script() {
$STD python3 manage.py collectstatic --no-input
$STD yarn install
$STD yarn build:css:sass
rm -rf "$temp_file"
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated $APP to v${RELEASE}"
msg_info "Starting Service"
systemctl start wger
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -rf "$temp_file"
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at v${RELEASE}"

View File

@@ -54,12 +54,15 @@ function update_script() {
msg_info "Restoring Data"
cp -R /opt/wikijs-backup/* /opt/wikijs
$SQLITE_INSTALL && $STD npm rebuild sqlite3
rm -rf /opt/wikijs-backup
msg_ok "Restored Data"
msg_info "Starting Service"
systemctl start wikijs
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -rf /opt/wikijs-backup
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -56,12 +56,15 @@ function update_script() {
if ! grep -q 'frozen' /opt/wizarr/start.sh; then
sed -i 's/run/& --frozen/' /opt/wizarr/start.sh
fi
rm -rf "$BACKUP_FILE"
msg_ok "Updated Wizarr"
msg_info "Starting Service"
systemctl start wizarr
msg_ok "Started Service"
msg_info "Cleaning Up"
rm -rf "$BACKUP_FILE"
msg_ok "Cleanup Completed"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -59,7 +59,6 @@ function update_script() {
xargs -I{} echo "https://repo.zabbix.com/zabbix/{}/release/debian/pool/main/z/zabbix-release/zabbix-release_latest+debian13_all.deb")" \
-o /tmp/zabbix-release_latest+debian13_all.deb
$STD dpkg -i zabbix-release_latest+debian13_all.deb
rm -rf /tmp/zabbix-release_latest+debian13_all.deb
$STD apt update
$STD apt install --only-upgrade zabbix-server-pgsql zabbix-frontend-php php8.4-pgsql
@@ -89,6 +88,13 @@ function update_script() {
systemctl start "$AGENT_SERVICE"
systemctl restart apache2
msg_ok "Started Services"
msg_info "Cleaning Up"
rm -rf /tmp/zabbix-release_latest+debian13_all.deb
$STD apt -y autoremove
$STD apt -y autoclean
$STD apt -y clean
msg_ok "Cleaned"
msg_ok "Updated successfully!"
exit
}

View File

@@ -47,15 +47,18 @@ function update_script() {
rm -rf /opt/zigbee2mqtt/data
mv /opt/z2m_backup/data /opt/zigbee2mqtt
cd /opt/zigbee2mqtt
grep -q "^packageImportMethod" ./pnpm-workspace.yaml || echo "packageImportMethod: hardlink" >>./pnpm-workspace.yaml
grep -q "^packageImportMethod" ./pnpm-workspace.yaml || echo "packageImportMethod: hardlink" >> ./pnpm-workspace.yaml
$STD pnpm install --frozen-lockfile
$STD pnpm build
rm -rf /opt/z2m_backup
msg_ok "Updated Zigbee2MQTT"
msg_info "Starting Service"
systemctl start zigbee2mqtt
msg_ok "Started Service"
msg_info "Cleaning up"
rm -rf /opt/z2m_backup
msg_ok "Cleaned up"
msg_ok "Updated successfully!"
fi
exit

934
frontend/bun.lock generated

File diff suppressed because it is too large Load Diff

10
frontend/package.json generated
View File

@@ -23,7 +23,6 @@
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-navigation-menu": "^1.2.5",
"@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
@@ -37,24 +36,27 @@
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"framer-motion": "^11.18.2",
"fuse.js": "^7.1.0",
"lucide-react": "^0.554.0",
"lucide-react": "^0.542.0",
"mini-svg-data-uri": "^1.4.4",
"motion": "^12.23.12",
"next": "15.5.2",
"next-themes": "^0.4.4",
"nuqs": "^2.4.1",
"pocketbase": "^0.21.5",
"prettier-plugin-organize-imports": "^4.1.0",
"react": "19.0.0",
"react-chartjs-2": "^5.3.0",
"react-code-blocks": "^0.1.6",
"react-datepicker": "^7.6.0",
"react-day-picker": "^9.4.3",
"react-dom": "19.0.0",
"react-icons": "^5.5.0",
"react-simple-typewriter": "^5.0.1",
"react-use-measure": "^2.1.7",
"recharts": "2.15.4",
"sharp": "^0.33.5",
"simple-icons": "^13.21.0",
"sonner": "^1.7.4",
"tailwind-merge": "^2.6.0",
"zod": "^3.24.2"

View File

@@ -11,7 +11,7 @@
"privileged": false,
"interface_port": 8080,
"documentation": null,
"website": "https://filebrowser.org/index.html#features",
"website": "https://filebrowser.org/features",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/file-browser.webp",
"config_path": "",
"description": "File Browser offers a user-friendly web interface for managing files within a designated directory. It allows you to perform various actions such as uploading, deleting, previewing, renaming, and editing files.",

View File

@@ -6,7 +6,7 @@
],
"date_created": "2025-05-26",
"type": "ct",
"updateable": true,
"updateable": false,
"privileged": true,
"config_path": "",
"interface_port": 443,

View File

@@ -23,7 +23,7 @@
"ram": 2048,
"hdd": 4,
"os": "Debian",
"version": "13"
"version": "12"
}
}
],

View File

@@ -1,43 +0,0 @@
{
"name": "OPNsense",
"slug": "opnsense-vm",
"categories": [
4,
2
],
"date_created": "2025-11-23",
"type": "vm",
"updateable": true,
"privileged": false,
"interface_port": 443,
"documentation": "https://docs.opnsense.org/",
"website": "https://opnsense.org/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/opnsense.webp",
"config_path": "",
"description": "OPNsense is an open-source firewall and routing platform based on FreeBSD. It provides advanced security features, including intrusion detection, VPN support, traffic shaping, and web filtering, with an intuitive web interface for easy management. Known for its reliability and regular updates, OPNsense is a popular choice for both businesses and home networks.",
"disable": true,
"disable_description": "This script has been temporarily disabled due to installation failures. The OPNsense bootstrap process was not completing successfully, resulting in a plain FreeBSD VM instead of a functional OPNsense installation. The issue is being investigated and the script will be re-enabled once resolved. For more details, see: https://github.com/community-scripts/ProxmoxVE/issues/6183",
"install_methods": [
{
"type": "default",
"script": "vm/opnsense-vm.sh",
"resources": {
"cpu": 4,
"ram": 8192,
"hdd": 10,
"os": "FreeBSD",
"version": "latest"
}
}
],
"default_credentials": {
"username": "root",
"password": "opnsense"
},
"notes": [
{
"text": "It will fail with default settings if there is no vmbr0 and vmbr1 on your node. Use advanced settings in this case.",
"type": "warning"
}
]
}

View File

@@ -6,7 +6,7 @@
],
"date_created": "2025-08-26",
"type": "ct",
"updateable": false,
"updateable": true,
"privileged": false,
"interface_port": 8888,
"documentation": "https://docs.searxng.org/",

View File

@@ -1,44 +0,0 @@
{
"name": "Upgopher",
"slug": "upgopher",
"categories": [
11
],
"date_created": "2025-11-22",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 9090,
"documentation": "https://github.com/wanetty/upgopher#readme",
"config_path": "",
"website": "https://github.com/wanetty/upgopher",
"logo": "https://raw.githubusercontent.com/wanetty/upgopher/main/static/logopher.webp",
"description": "A simple Go web server for file upload, download, and browsing. Cross-platform alternative to Python-based file servers with no library dependencies. Features file upload via web interface, directory navigation, URL copying to clipboard, optional basic authentication, HTTPS support, and hidden files toggle.",
"install_methods": [
{
"type": "default",
"script": "ct/upgopher.sh",
"resources": {
"cpu": 1,
"ram": 512,
"hdd": 4,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "Default uploads directory: `/opt/upgopher/uploads`",
"type": "info"
},
{
"text": "Set application startup options by editing `/etc/systemd/system/upgopher.service`. Read documentation for available options.",
"type": "info"
}
]
}

View File

@@ -1,309 +1,14 @@
[
{
"name": "rcourtman/Pulse",
"version": "v4.32.4",
"date": "2025-11-23T11:05:14Z"
},
{
"name": "readeck/readeck",
"version": "0.21.2",
"date": "2025-11-23T10:56:43Z"
},
{
"name": "verdaccio/verdaccio",
"version": "generator-verdaccio-plugin@6.0.0-next-8.26",
"date": "2025-11-23T07:46:06Z"
},
{
"name": "morpheus65535/bazarr",
"version": "v1.5.3",
"date": "2025-09-20T12:12:33Z"
},
{
"name": "Jackett/Jackett",
"version": "v0.24.340",
"date": "2025-11-23T06:00:18Z"
},
{
"name": "tobychui/zoraxy",
"version": "v3.3.0-rc1",
"date": "2025-11-23T05:42:35Z"
},
{
"name": "hyperion-project/hyperion.ng",
"version": "2.1.1",
"date": "2025-06-14T17:45:06Z"
},
{
"name": "BerriAI/litellm",
"version": "v1.80.5.rc.2",
"date": "2025-11-23T00:31:24Z"
},
{
"name": "steveiliop56/tinyauth",
"version": "v4.0.1",
"date": "2025-10-15T16:53:55Z"
},
{
"name": "jeedom/core",
"version": "4.4.20",
"date": "2025-11-23T00:27:04Z"
},
{
"name": "paperless-ngx/paperless-ngx",
"version": "v2.20.0",
"date": "2025-11-22T21:56:00Z"
},
{
"name": "OliveTin/OliveTin",
"version": "2025.11.25",
"date": "2025-11-22T21:00:52Z"
},
{
"name": "Cleanuparr/Cleanuparr",
"version": "v2.4.6",
"date": "2025-11-22T20:32:09Z"
},
{
"name": "go-gitea/gitea",
"version": "v1.25.2",
"date": "2025-11-22T19:37:02Z"
},
{
"name": "msgbyte/tianji",
"version": "v1.30.13",
"date": "2025-11-22T18:22:32Z"
},
{
"name": "nzbgetcom/nzbget",
"version": "v25.4",
"date": "2025-10-09T10:27:01Z"
},
{
"name": "bunkerity/bunkerweb",
"version": "v1.6.6",
"date": "2025-11-22T16:10:29Z"
},
{
"name": "TechnitiumSoftware/DnsServer",
"version": "v14.2.0",
"date": "2025-11-22T12:54:08Z"
},
{
"name": "keycloak/keycloak",
"version": "26.4.5",
"date": "2025-11-12T15:24:23Z"
},
{
"name": "pocketbase/pocketbase",
"version": "v0.34.0",
"date": "2025-11-22T09:18:14Z"
},
{
"name": "laurent22/joplin",
"version": "server-v3.4.4",
"date": "2025-09-25T13:19:26Z"
},
{
"name": "zwave-js/zwave-js-ui",
"version": "v11.8.0",
"date": "2025-11-22T07:54:42Z"
},
{
"name": "inventree/InvenTree",
"version": "1.1.5",
"date": "2025-11-22T07:02:52Z"
},
{
"name": "theonedev/onedev",
"version": "v13.1.0",
"date": "2025-11-22T04:29:25Z"
},
{
"name": "TwiN/gatus",
"version": "v5.33.0",
"date": "2025-11-21T22:54:49Z"
},
{
"name": "homarr-labs/homarr",
"version": "v1.44.0",
"date": "2025-11-21T19:17:09Z"
},
{
"name": "gelbphoenix/autocaliweb",
"version": "v0.11.0",
"date": "2025-11-21T18:42:15Z"
},
{
"name": "rclone/rclone",
"version": "v1.72.0",
"date": "2025-11-21T18:20:58Z"
},
{
"name": "mattermost/mattermost",
"version": "v10.11.8",
"date": "2025-11-21T17:06:07Z"
},
{
"name": "home-assistant/core",
"version": "2025.11.3",
"date": "2025-11-21T17:03:22Z"
},
{
"name": "Bubka/2FAuth",
"version": "v5.6.1",
"date": "2025-11-21T16:51:21Z"
},
{
"name": "BookStackApp/BookStack",
"version": "v25.11.3",
"date": "2025-11-21T14:06:50Z"
},
{
"name": "pommee/goaway",
"version": "v0.62.19",
"date": "2025-11-21T13:24:09Z"
},
{
"name": "fuma-nama/fumadocs",
"version": "fumadocs-ui@16.1.0",
"date": "2025-11-21T13:02:57Z"
},
{
"name": "forgejo/forgejo",
"version": "v13.0.3",
"date": "2025-11-21T12:43:04Z"
},
{
"name": "chrisbenincasa/tunarr",
"version": "v0.23.0-alpha.24",
"date": "2025-11-21T12:16:39Z"
},
{
"name": "seriousm4x/UpSnap",
"version": "5.2.4",
"date": "2025-11-21T10:25:05Z"
},
{
"name": "emqx/emqx",
"version": "e6.0.2-beta.2",
"date": "2025-11-21T10:20:27Z"
},
{
"name": "sabnzbd/sabnzbd",
"version": "4.5.5",
"date": "2025-10-24T11:12:22Z"
},
{
"name": "openobserve/openobserve",
"version": "v0.16.3",
"date": "2025-11-21T08:54:06Z"
},
{
"name": "booklore-app/booklore",
"version": "v1.11.0",
"date": "2025-11-21T08:09:03Z"
},
{
"name": "MariaDB/server",
"version": "mariadb-12.1.2",
"date": "2025-11-18T15:16:21Z"
},
{
"name": "bluenviron/mediamtx",
"version": "v1.15.4",
"date": "2025-11-21T01:21:03Z"
},
{
"name": "TasmoAdmin/TasmoAdmin",
"version": "v4.3.2",
"date": "2025-10-18T12:11:00Z"
},
{
"name": "ollama/ollama",
"version": "v0.13.1-rc0",
"date": "2025-11-20T21:30:35Z"
},
{
"name": "project-zot/zot",
"version": "v2.1.11",
"date": "2025-11-20T20:14:44Z"
},
{
"name": "saltstack/salt",
"version": "v3007.9",
"date": "2025-11-20T17:58:32Z"
},
{
"name": "neo4j/neo4j",
"version": "5.26.17",
"date": "2025-11-20T16:09:28Z"
},
{
"name": "meilisearch/meilisearch",
"version": "prototype-v1.26.0.arm-native-docker-build-1",
"date": "2025-11-20T15:38:39Z"
},
{
"name": "duplicati/duplicati",
"version": "v2.2.0.101-2.2.0.101_canary_2025-11-20",
"date": "2025-11-20T14:50:45Z"
},
{
"name": "NodeBB/NodeBB",
"version": "v4.6.3",
"date": "2025-11-20T14:13:22Z"
},
{
"name": "n8n-io/n8n",
"version": "n8n@1.120.4",
"date": "2025-11-19T12:57:55Z"
},
{
"name": "SigNoz/signoz",
"version": "v0.102.1",
"date": "2025-11-20T12:20:18Z"
},
{
"name": "kimai/kimai",
"version": "2.44.0",
"date": "2025-11-20T11:01:01Z"
},
{
"name": "itsmng/itsm-ng",
"version": "v2.1.1",
"date": "2025-11-20T10:54:07Z"
},
{
"name": "Athou/commafeed",
"version": "5.12.0",
"date": "2025-11-20T06:18:58Z"
},
{
"name": "documenso/documenso",
"version": "v2.0.14",
"date": "2025-11-20T04:12:40Z"
},
{
"name": "immich-app/immich",
"version": "v2.3.1",
"date": "2025-11-20T03:10:27Z"
},
{
"name": "nextcloud/nextcloudpi",
"version": "v1.56.0",
"date": "2025-11-20T02:28:35Z"
},
{
"name": "coder/code-server",
"version": "v4.106.2",
"date": "2025-11-20T00:23:05Z"
},
{
"name": "esphome/esphome",
"version": "2025.11.0",
"date": "2025-11-19T22:37:46Z"
},
{
"name": "immich-app/immich",
"version": "v2.3.0",
"date": "2025-11-19T22:36:35Z"
},
{
"name": "TandoorRecipes/recipes",
"version": "2.3.5",
@@ -324,6 +29,16 @@
"version": "v2.7.12",
"date": "2025-05-29T17:08:26Z"
},
{
"name": "BerriAI/litellm",
"version": "v1.77.3-stable-patch-2",
"date": "2025-11-19T19:00:37Z"
},
{
"name": "coder/code-server",
"version": "v4.106.0",
"date": "2025-11-19T18:01:23Z"
},
{
"name": "tailscale/tailscale",
"version": "v1.90.8",
@@ -334,6 +49,16 @@
"version": "0.51.3",
"date": "2025-11-19T15:45:28Z"
},
{
"name": "NodeBB/NodeBB",
"version": "v4.6.2",
"date": "2025-11-19T15:31:59Z"
},
{
"name": "BookStackApp/BookStack",
"version": "v25.11.2",
"date": "2025-11-19T15:26:29Z"
},
{
"name": "grafana/grafana",
"version": "v12.3.0",
@@ -354,6 +79,26 @@
"version": "2025.5",
"date": "2025-11-19T14:48:47Z"
},
{
"name": "ollama/ollama",
"version": "v0.13.0",
"date": "2025-11-19T14:16:07Z"
},
{
"name": "nextcloud/nextcloudpi",
"version": "v1.56.0",
"date": "2025-11-19T14:11:24Z"
},
{
"name": "mattermost/mattermost",
"version": "v10.11.7",
"date": "2025-11-17T08:40:53Z"
},
{
"name": "n8n-io/n8n",
"version": "n8n@1.120.4",
"date": "2025-11-19T12:57:55Z"
},
{
"name": "AdguardTeam/AdGuardHome",
"version": "v0.107.69",
@@ -369,21 +114,66 @@
"version": "v7.5.0",
"date": "2025-11-19T08:36:29Z"
},
{
"name": "SigNoz/signoz",
"version": "v0.102.0",
"date": "2025-11-19T06:53:59Z"
},
{
"name": "morpheus65535/bazarr",
"version": "v1.5.3",
"date": "2025-09-20T12:12:33Z"
},
{
"name": "Jackett/Jackett",
"version": "v0.24.327",
"date": "2025-11-19T05:55:51Z"
},
{
"name": "element-hq/synapse",
"version": "v1.143.0rc1",
"date": "2025-11-19T00:09:31Z"
},
{
"name": "hyperion-project/hyperion.ng",
"version": "2.1.1",
"date": "2025-06-14T17:45:06Z"
},
{
"name": "jeedom/core",
"version": "4.4.20",
"date": "2025-11-19T00:27:10Z"
},
{
"name": "steveiliop56/tinyauth",
"version": "v4.0.1",
"date": "2025-10-15T16:53:55Z"
},
{
"name": "gtsteffaniak/filebrowser",
"version": "v1.0.1-stable",
"date": "2025-11-10T16:51:44Z"
},
{
"name": "bunkerity/bunkerweb",
"version": "testing",
"date": "2025-11-17T16:24:26Z"
},
{
"name": "chrisbenincasa/tunarr",
"version": "v0.23.0-alpha.23",
"date": "2025-11-18T21:36:15Z"
},
{
"name": "umami-software/umami",
"version": "v3.0.1",
"date": "2025-11-18T18:50:35Z"
},
{
"name": "keycloak/keycloak",
"version": "26.4.5",
"date": "2025-11-12T15:24:23Z"
},
{
"name": "seerr-team/seerr",
"version": "preview-test-fix-subscriptions",
@@ -394,11 +184,21 @@
"version": "v3.6.2",
"date": "2025-11-18T16:25:16Z"
},
{
"name": "MariaDB/server",
"version": "mariadb-12.1.2",
"date": "2025-11-18T15:16:21Z"
},
{
"name": "redis/redis",
"version": "8.4.0",
"date": "2025-11-18T15:06:37Z"
},
{
"name": "fuma-nama/fumadocs",
"version": "create-fumadocs-app@16.0.17",
"date": "2025-11-18T12:28:43Z"
},
{
"name": "NginxProxyManager/nginx-proxy-manager",
"version": "v2.13.5",
@@ -409,16 +209,31 @@
"version": "v0.7.5",
"date": "2025-11-18T10:50:21Z"
},
{
"name": "emqx/emqx",
"version": "e6.1.0-alpha.1",
"date": "2025-11-18T10:33:45Z"
},
{
"name": "wazuh/wazuh",
"version": "coverity-w47-4.14.2",
"date": "2025-11-18T07:24:43Z"
},
{
"name": "documenso/documenso",
"version": "v2.0.13",
"date": "2025-11-18T05:59:02Z"
},
{
"name": "0xERR0R/blocky",
"version": "v0.28.2",
"date": "2025-11-18T05:51:46Z"
},
{
"name": "theonedev/onedev",
"version": "v13.1.0",
"date": "2025-11-18T00:06:49Z"
},
{
"name": "ipfs/kubo",
"version": "v0.38.2",
@@ -444,6 +259,11 @@
"version": "v1.4.5",
"date": "2025-11-17T15:20:35Z"
},
{
"name": "meilisearch/meilisearch",
"version": "latest",
"date": "2025-11-17T09:29:20Z"
},
{
"name": "rabbitmq/rabbitmq-server",
"version": "v4.2.1",
@@ -474,6 +294,11 @@
"version": "v0.21.0",
"date": "2025-08-23T18:33:53Z"
},
{
"name": "TwiN/gatus",
"version": "v5.32.0",
"date": "2025-11-16T21:08:56Z"
},
{
"name": "binwiederhier/ntfy",
"version": "v2.15.0",
@@ -484,6 +309,11 @@
"version": "3.3.8",
"date": "2025-11-16T17:04:37Z"
},
{
"name": "msgbyte/tianji",
"version": "v1.30.11",
"date": "2025-11-16T16:21:04Z"
},
{
"name": "AlexxIT/go2rtc",
"version": "v1.9.12",
@@ -504,11 +334,26 @@
"version": "v25.11.1",
"date": "2025-11-16T13:04:21Z"
},
{
"name": "TechnitiumSoftware/DnsServer",
"version": "v14.1.0",
"date": "2025-11-16T11:32:10Z"
},
{
"name": "FlowiseAI/Flowise",
"version": "flowise@3.0.11",
"date": "2025-11-16T01:29:06Z"
},
{
"name": "OliveTin/OliveTin",
"version": "3000.4.0",
"date": "2025-11-16T01:02:49Z"
},
{
"name": "paperless-ngx/paperless-ngx",
"version": "v2.19.6",
"date": "2025-11-15T22:56:45Z"
},
{
"name": "karakeep-app/karakeep",
"version": "android/v1.8.2-2",
@@ -524,6 +369,21 @@
"version": "v0.16.1",
"date": "2025-11-14T22:50:06Z"
},
{
"name": "home-assistant/core",
"version": "2025.11.2",
"date": "2025-11-14T22:10:50Z"
},
{
"name": "homarr-labs/homarr",
"version": "v1.43.3",
"date": "2025-11-14T19:16:17Z"
},
{
"name": "kimai/kimai",
"version": "2.43.0",
"date": "2025-11-14T17:20:16Z"
},
{
"name": "mealie-recipes/mealie",
"version": "v3.5.0",
@@ -534,11 +394,26 @@
"version": "22.0.3",
"date": "2025-11-14T14:49:25Z"
},
{
"name": "openobserve/openobserve",
"version": "v0.16.2",
"date": "2025-11-14T12:05:36Z"
},
{
"name": "zitadel/zitadel",
"version": "v4.7.0",
"date": "2025-11-14T09:45:13Z"
},
{
"name": "nzbgetcom/nzbget",
"version": "v25.4",
"date": "2025-10-09T10:27:01Z"
},
{
"name": "verdaccio/verdaccio",
"version": "generator-verdaccio-plugin@6.0.0-next-8.25",
"date": "2025-11-14T08:14:55Z"
},
{
"name": "runtipi/runtipi",
"version": "v4.6.5",
@@ -549,11 +424,26 @@
"version": "v0.86.1",
"date": "2025-11-14T05:05:44Z"
},
{
"name": "rcourtman/Pulse",
"version": "v4.31.0",
"date": "2025-11-14T00:18:50Z"
},
{
"name": "gristlabs/grist-core",
"version": "v1.7.7",
"date": "2025-11-13T21:28:44Z"
},
{
"name": "pommee/goaway",
"version": "v0.62.18",
"date": "2025-11-13T19:49:21Z"
},
{
"name": "pocketbase/pocketbase",
"version": "v0.33.0",
"date": "2025-11-13T14:09:14Z"
},
{
"name": "PrivateBin/PrivateBin",
"version": "1.7.9",
@@ -574,6 +464,11 @@
"version": "v1.0.25",
"date": "2025-11-12T16:57:54Z"
},
{
"name": "zwave-js/zwave-js-ui",
"version": "v11.7.0",
"date": "2025-11-12T14:09:36Z"
},
{
"name": "cockpit-project/cockpit",
"version": "351",
@@ -659,6 +554,16 @@
"version": "v1.0.0-beta19",
"date": "2025-11-09T17:26:30Z"
},
{
"name": "duplicati/duplicati",
"version": "v2.2.0.1_stable_2025-11-09",
"date": "2025-11-09T17:21:54Z"
},
{
"name": "Cleanuparr/Cleanuparr",
"version": "v2.4.5",
"date": "2025-11-09T17:14:01Z"
},
{
"name": "authelia/authelia",
"version": "v4.39.14",
@@ -669,6 +574,11 @@
"version": "3.5.1",
"date": "2025-11-09T05:09:28Z"
},
{
"name": "inventree/InvenTree",
"version": "1.1.3",
"date": "2025-11-09T00:28:21Z"
},
{
"name": "raydak-labs/configarr",
"version": "v1.17.2",
@@ -744,6 +654,11 @@
"version": "v0.9.1",
"date": "2025-11-06T02:26:53Z"
},
{
"name": "neo4j/neo4j",
"version": "5.26.16",
"date": "2025-11-05T20:41:40Z"
},
{
"name": "leiweibau/Pi.Alert",
"version": "v2025-11-05",
@@ -789,6 +704,11 @@
"version": "v4.0.16.2944",
"date": "2025-11-05T01:56:48Z"
},
{
"name": "go-gitea/gitea",
"version": "v1.25.1",
"date": "2025-11-04T20:01:09Z"
},
{
"name": "jhuckaby/Cronicle",
"version": "v0.9.100",
@@ -839,6 +759,11 @@
"version": "v1.9.1",
"date": "2025-11-02T21:14:50Z"
},
{
"name": "tobychui/zoraxy",
"version": "v3.2.9",
"date": "2025-11-02T05:48:39Z"
},
{
"name": "9001/copyparty",
"version": "v1.19.20",
@@ -889,11 +814,21 @@
"version": "v1.11.1",
"date": "2025-10-29T22:09:26Z"
},
{
"name": "laurent22/joplin",
"version": "server-v3.4.4",
"date": "2025-09-25T13:19:26Z"
},
{
"name": "apache/cassandra",
"version": "cassandra-5.0.6",
"date": "2025-10-29T07:40:47Z"
},
{
"name": "booklore-app/booklore",
"version": "v1.10.0",
"date": "2025-10-28T19:04:35Z"
},
{
"name": "pi-hole/pi-hole",
"version": "v6.2.2",
@@ -919,11 +854,21 @@
"version": "v25.8.0",
"date": "2025-10-26T14:23:37Z"
},
{
"name": "forgejo/forgejo",
"version": "v13.0.2",
"date": "2025-10-26T06:33:05Z"
},
{
"name": "usememos/memos",
"version": "v0.25.2",
"date": "2025-10-24T12:30:04Z"
},
{
"name": "sabnzbd/sabnzbd",
"version": "4.5.5",
"date": "2025-10-24T11:12:22Z"
},
{
"name": "crowdsecurity/crowdsec",
"version": "v1.7.3",
@@ -949,11 +894,26 @@
"version": "release-1.24.1",
"date": "2025-10-22T10:28:00Z"
},
{
"name": "bluenviron/mediamtx",
"version": "v1.15.3",
"date": "2025-10-21T19:56:55Z"
},
{
"name": "kyantech/Palmr",
"version": "v3.2.5-beta",
"date": "2025-10-21T16:49:14Z"
},
{
"name": "rclone/rclone",
"version": "v1.71.2",
"date": "2025-10-20T15:25:52Z"
},
{
"name": "seriousm4x/UpSnap",
"version": "5.2.3",
"date": "2025-10-19T20:50:15Z"
},
{
"name": "Part-DB/Part-DB-server",
"version": "v2.2.1",
@@ -969,6 +929,21 @@
"version": "v4.5.0",
"date": "2025-10-18T22:00:50Z"
},
{
"name": "project-zot/zot",
"version": "v2.1.10",
"date": "2025-10-18T18:46:36Z"
},
{
"name": "TasmoAdmin/TasmoAdmin",
"version": "v4.3.2",
"date": "2025-10-18T12:11:00Z"
},
{
"name": "readeck/readeck",
"version": "0.20.4",
"date": "2025-10-18T10:00:42Z"
},
{
"name": "diced/zipline",
"version": "v4.3.2",
@@ -1019,6 +994,11 @@
"version": "v5.0.85",
"date": "2025-10-12T19:55:18Z"
},
{
"name": "gelbphoenix/autocaliweb",
"version": "v0.10.4",
"date": "2025-10-11T19:53:39Z"
},
{
"name": "projectsend/projectsend",
"version": "r1945",
@@ -1099,6 +1079,16 @@
"version": "2025.09.24",
"date": "2025-09-24T13:51:23Z"
},
{
"name": "itsmng/itsm-ng",
"version": "v2.1.0",
"date": "2025-09-22T09:23:37Z"
},
{
"name": "Athou/commafeed",
"version": "5.11.1",
"date": "2025-09-22T02:21:27Z"
},
{
"name": "gotify/server",
"version": "v2.7.3",
@@ -1114,6 +1104,11 @@
"version": "v0.17.0",
"date": "2025-09-19T22:23:28Z"
},
{
"name": "saltstack/salt",
"version": "v3007.8",
"date": "2025-09-18T18:19:04Z"
},
{
"name": "docmost/docmost",
"version": "v0.23.2",
@@ -1319,6 +1314,11 @@
"version": "v1.11.11",
"date": "2025-06-18T18:04:50Z"
},
{
"name": "Bubka/2FAuth",
"version": "v5.6.0",
"date": "2025-06-18T12:19:54Z"
},
{
"name": "TriliumNext/Notes",
"version": "v0.95.0",

View File

@@ -1,67 +1,9 @@
"use client";
import {
ArrowUpDown,
Box,
CheckCircle2,
ChevronLeft,
ChevronRight,
List,
Loader2,
Trophy,
XCircle,
} from "lucide-react";
import {
Bar,
BarChart,
CartesianGrid,
Cell,
LabelList,
XAxis,
} from "recharts";
import React, { useEffect, useMemo, useState } from "react";
import React, { useEffect, useState } from "react";
import "react-datepicker/dist/react-datepicker.css";
import type { ChartConfig } from "@/components/ui/chart";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { formattedBadge } from "@/components/command-menu";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import ApplicationChart from "../../components/application-chart";
type DataModel = {
id: number;
@@ -88,76 +30,42 @@ type SummaryData = {
nsapp_count: Record<string, number>;
};
// Chart colors optimized for both light and dark modes
// Medium-toned colors that are visible and not too flashy in both themes
const CHART_COLORS = [
"#5B8DEF", // blue - medium tone
"#4ECDC4", // teal - medium tone
"#FF8C42", // orange - medium tone
"#A78BFA", // purple - medium tone
"#F472B6", // pink - medium tone
"#38BDF8", // cyan - medium tone
"#4ADE80", // green - medium tone
"#FBBF24", // yellow - medium tone
"#818CF8", // indigo - medium tone
"#FB7185", // rose - medium tone
"#2DD4BF", // turquoise - medium tone
"#C084FC", // violet - medium tone
"#60A5FA", // sky blue - medium tone
"#84CC16", // lime - medium tone
"#F59E0B", // amber - medium tone
"#A855F7", // purple - medium tone
"#10B981", // emerald - medium tone
"#EAB308", // gold - medium tone
"#3B82F6", // royal blue - medium tone
"#EF4444", // red - medium tone
];
const chartConfigApps = {
count: {
label: "Installations",
color: "hsl(var(--chart-1))",
},
} satisfies ChartConfig;
export default function DataPage() {
const DataFetcher: React.FC = () => {
const [data, setData] = useState<DataModel[]>([]);
const [summary, setSummary] = useState<SummaryData | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(25);
const [sortConfig, setSortConfig] = useState<{
key: string;
direction: "ascending" | "descending";
} | null>(null);
const [sortConfig, setSortConfig] = useState<{ key: string; direction: "ascending" | "descending" } | null>(null);
const nf = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 });
useEffect(() => {
const fetchData = async () => {
const fetchSummary = async () => {
try {
const response = await fetch("https://api.htl-braunau.at/data/summary");
if (!response.ok)
throw new Error(`Failed to fetch summary: ${response.statusText}`);
const result: SummaryData = await response.json();
setSummary(result);
}
catch (err) {
setError((err as Error).message);
}
};
fetchSummary();
}, []);
useEffect(() => {
const fetchPaginatedData = async () => {
setLoading(true);
try {
const [summaryRes, dataRes] = await Promise.all([
fetch("https://api.htl-braunau.at/data/summary"),
fetch(
`https://api.htl-braunau.at/data/paginated?page=${currentPage}&limit=${itemsPerPage === 0 ? "" : itemsPerPage
}`,
),
]);
if (!summaryRes.ok) {
throw new Error(`Failed to fetch summary: ${summaryRes.statusText}`);
}
if (!dataRes.ok) {
throw new Error(`Failed to fetch data: ${dataRes.statusText}`);
}
const summaryData: SummaryData = await summaryRes.json();
const pageData: DataModel[] = await dataRes.json();
setSummary(summaryData);
setData(pageData);
const response = await fetch(`https://api.htl-braunau.at/data/paginated?page=${currentPage}&limit=${itemsPerPage === 0 ? "" : itemsPerPage}`);
if (!response.ok)
throw new Error(`Failed to fetch data: ${response.statusText}`);
const result: DataModel[] = await response.json();
setData(result);
}
catch (err) {
setError((err as Error).message);
@@ -167,13 +75,13 @@ export default function DataPage() {
}
};
fetchData();
fetchPaginatedData();
}, [currentPage, itemsPerPage]);
const sortedData = useMemo(() => {
const sortedData = React.useMemo(() => {
if (!sortConfig)
return data;
return [...data].sort((a, b) => {
const sorted = [...data].sort((a, b) => {
if (a[sortConfig.key] < b[sortConfig.key]) {
return sortConfig.direction === "ascending" ? -1 : 1;
}
@@ -182,15 +90,23 @@ export default function DataPage() {
}
return 0;
});
return sorted;
}, [data, sortConfig]);
if (loading)
return <p>Loading...</p>;
if (error) {
return (
<p>
Error:
{error}
</p>
);
}
const requestSort = (key: string) => {
let direction: "ascending" | "descending" = "ascending";
if (
sortConfig
&& sortConfig.key === key
&& sortConfig.direction === "ascending"
) {
if (sortConfig && sortConfig.key === key && sortConfig.direction === "ascending") {
direction = "descending";
}
setSortConfig({ key, direction });
@@ -198,447 +114,135 @@ export default function DataPage() {
const formatDate = (dateString: string): string => {
const date = new Date(dateString);
return new Intl.DateTimeFormat("en-US", {
dateStyle: "medium",
timeStyle: "short",
}).format(date);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const timezoneOffset = dateString.slice(-6);
return `${day}.${month}.${year} ${hours}:${minutes} ${timezoneOffset} GMT`;
};
const getTypeBadge = (type: string) => {
if (type === "lxc")
return formattedBadge("ct");
if (type === "vm")
return formattedBadge("vm");
return null;
};
// Stats calculations
const successCount = summary?.status_count.done ?? 0;
const failureCount = summary?.status_count.failed ?? 0;
const totalCount = summary?.total_entries ?? 0;
const successRate = totalCount > 0 ? (successCount / totalCount) * 100 : 0;
const allApps = useMemo(() => {
if (!summary?.nsapp_count)
return [];
return Object.entries(summary.nsapp_count).sort(([, a], [, b]) => b - a);
}, [summary]);
const topApps = useMemo(() => {
return allApps.slice(0, 15);
}, [allApps]);
const mostPopularApp = topApps[0];
// Chart Data
const appsChartData = topApps.map(([name, count], index) => ({
app: name,
count,
fill: CHART_COLORS[index % CHART_COLORS.length],
}));
if (error) {
return (
<div className="p-6 text-center text-red-500">
<p>
Error loading data:
{error}
</p>
</div>
);
}
return (
<div className="mb-3">
<div className="mt-20 flex sm:px-4 xl:px-0">
<div className="mx-4 w-full sm:mx-0 space-y-8">
{/* Header */}
<div>
<h1 className="text-3xl font-bold tracking-tight">Analytics</h1>
<p className="text-muted-foreground">
Overview of container installations and system statistics.
</p>
</div>
{/* Widgets */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Created</CardTitle>
<Box className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{nf.format(totalCount)}</div>
<p className="text-xs text-muted-foreground">
Total LXC/VM entries found
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Success Rate</CardTitle>
<CheckCircle2 className="h-4 w-4 text-green-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{successRate.toFixed(1)}
%
</div>
<p className="text-xs text-muted-foreground">
{nf.format(successCount)}
{" "}
successful installations
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Failures</CardTitle>
<XCircle className="h-4 w-4 text-red-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{nf.format(failureCount)}</div>
<p className="text-xs text-muted-foreground">
Installations encountered errors
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Most Popular</CardTitle>
<Trophy className="h-4 w-4 text-yellow-500" />
</CardHeader>
<CardContent>
<div className="truncate text-2xl font-bold">
{mostPopularApp ? mostPopularApp[0] : "N/A"}
</div>
<p className="text-xs text-muted-foreground">
{mostPopularApp ? nf.format(mostPopularApp[1]) : 0}
{" "}
installations
</p>
</CardContent>
</Card>
</div>
{/* Graphs */}
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<div className="space-y-1.5">
<CardTitle>Top Applications</CardTitle>
<CardDescription>
The most frequently installed applications.
</CardDescription>
</div>
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" size="sm" className="ml-auto">
<List className="mr-2 h-4 w-4" />
View All
</Button>
</DialogTrigger>
<DialogContent className="max-h-[80vh] sm:max-w-md">
<DialogHeader>
<DialogTitle>Application Statistics</DialogTitle>
<DialogDescription>
Installation counts for all
{" "}
{allApps.length}
{" "}
applications.
</DialogDescription>
</DialogHeader>
<ScrollArea className="h-[60vh] w-full rounded-md border p-4">
<div className="space-y-4">
{allApps.map(([name, count], index) => (
<div
key={name}
className="flex items-center justify-between text-sm"
>
<div className="flex items-center gap-2">
<span className="w-8 font-mono text-muted-foreground">
{index + 1}
.
</span>
<span className="font-medium">{name}</span>
</div>
<span className="font-mono">{nf.format(count)}</span>
</div>
))}
</div>
</ScrollArea>
</DialogContent>
</Dialog>
</CardHeader>
<CardContent className="pl-2">
<div className="h-[300px] w-full">
{loading
? (
<div className="flex h-full w-full items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
)
: (
<ChartContainer config={chartConfigApps} className="h-full w-full">
<BarChart
accessibilityLayer
data={appsChartData}
margin={{
top: 20,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="app"
tickLine={false}
tickMargin={10}
axisLine={false}
tickFormatter={value => (value.length > 8 ? `${value.slice(0, 8)}...` : value)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent nameKey="app" />}
/>
<Bar dataKey="count" radius={8}>
{appsChartData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.fill} />
))}
<LabelList
position="top"
offset={12}
className="fill-foreground"
fontSize={12}
/>
</Bar>
</BarChart>
</ChartContainer>
)}
</div>
</CardContent>
</Card>
{/* Data Table */}
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle>Installation Log</CardTitle>
<CardDescription>
Detailed records of all container creation attempts.
</CardDescription>
</div>
<div className="flex items-center gap-2">
<Select
value={String(itemsPerPage)}
onValueChange={val => setItemsPerPage(Number(val))}
>
<SelectTrigger className="w-[80px]">
<SelectValue placeholder="Limit" />
</SelectTrigger>
<SelectContent>
<SelectItem value="10">10</SelectItem>
<SelectItem value="25">25</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="100">100</SelectItem>
</SelectContent>
</Select>
</div>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead
className="w-[100px] cursor-pointer"
onClick={() => requestSort("status")}
>
Status
{sortConfig?.key === "status" && (
<ArrowUpDown className="ml-2 inline h-4 w-4" />
)}
</TableHead>
<TableHead
className="cursor-pointer"
onClick={() => requestSort("type")}
>
Type
{sortConfig?.key === "type" && (
<ArrowUpDown className="ml-2 inline h-4 w-4" />
)}
</TableHead>
<TableHead
className="cursor-pointer"
onClick={() => requestSort("nsapp")}
>
Application
{sortConfig?.key === "nsapp" && (
<ArrowUpDown className="ml-2 inline h-4 w-4" />
)}
</TableHead>
<TableHead
className="hidden cursor-pointer md:table-cell"
onClick={() => requestSort("os_type")}
>
OS
{sortConfig?.key === "os_type" && (
<ArrowUpDown className="ml-2 inline h-4 w-4" />
)}
</TableHead>
<TableHead
className="hidden cursor-pointer md:table-cell"
onClick={() => requestSort("disk_size")}
>
Disk Size
{sortConfig?.key === "disk_size" && (
<ArrowUpDown className="ml-2 inline h-4 w-4" />
)}
</TableHead>
<TableHead
className="hidden cursor-pointer lg:table-cell"
onClick={() => requestSort("core_count")}
>
Core Count
{sortConfig?.key === "core_count" && (
<ArrowUpDown className="ml-2 inline h-4 w-4" />
)}
</TableHead>
<TableHead
className="hidden cursor-pointer lg:table-cell"
onClick={() => requestSort("ram_size")}
>
RAM Size
{sortConfig?.key === "ram_size" && (
<ArrowUpDown className="ml-2 inline h-4 w-4" />
)}
</TableHead>
<TableHead
className="cursor-pointer text-right"
onClick={() => requestSort("created_at")}
>
Created At
{sortConfig?.key === "created_at" && (
<ArrowUpDown className="ml-2 inline h-4 w-4" />
)}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading
<div className="p-6 mt-20">
<h1 className="text-2xl font-bold mb-4 text-center">Created LXCs</h1>
<ApplicationChart data={summary} />
<p className="text-lg font-bold mt-4"> </p>
<div className="mb-4 flex justify-between items-center">
<p className="text-lg font-bold">
{nf.format(
summary?.total_entries ?? 0,
)}
{" "}
results found
</p>
<p className="text-lg font">
Status Legend: 🔄 installing
{" "}
{nf.format(summary?.status_count.installing ?? 0)}
{" "}
| completed
{" "}
{nf.format(summary?.status_count.done ?? 0)}
{" "}
| failed
{" "}
{nf.format(summary?.status_count.failed ?? 0)}
{" "}
| unknown
</p>
</div>
<div className="overflow-x-auto">
<div className="overflow-y-auto lg:overflow-y-visible">
<table className="min-w-full table-auto border-collapse">
<thead>
<tr>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("status")}>Status</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("type")}>Type</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("nsapp")}>Application</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("os_type")}>OS</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("os_version")}>OS Version</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("disk_size")}>Disk Size</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("core_count")}>Core Count</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("ram_size")}>RAM Size</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("method")}>Method</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("pve_version")}>PVE Version</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("error")}>Error Message</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("created_at")}>Created At</th>
</tr>
</thead>
<tbody>
{sortedData.map((item, index) => (
<tr key={index}>
<td className="px-4 py-2 border-b">
{item.status === "done"
? (
<TableRow>
<TableCell colSpan={8} className="h-24 text-center">
<div className="flex items-center justify-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
{" "}
Loading data...
</div>
</TableCell>
</TableRow>
"✔️"
)
: sortedData.length > 0
: item.status === "failed"
? (
sortedData.map((item, idx) => (
<TableRow key={`${item.id}-${idx}`}>
<TableCell>
{item.status === "done"
? (
<Badge className="text-green-500/75 border-green-500/75">
Success
</Badge>
)
: item.status === "failed"
? (
<Badge className="text-red-500/75 border-red-500/75">
Failed
</Badge>
)
: item.status === "installing"
? (
<Badge className="text-blue-500/75 border-blue-500/75">
Installing
</Badge>
)
: (
<Badge variant="outline">
{item.status}
</Badge>
)}
</TableCell>
<TableCell>
{getTypeBadge(item.type) || (
<Badge variant="outline">
{item.type}
</Badge>
)}
</TableCell>
<TableCell className="font-medium">
{item.nsapp}
</TableCell>
<TableCell className="hidden md:table-cell">
{item.os_type}
{" "}
{item.os_version}
</TableCell>
<TableCell className="hidden md:table-cell">
{item.disk_size}
MB
</TableCell>
<TableCell className="hidden lg:table-cell">
{item.core_count}
</TableCell>
<TableCell className="hidden lg:table-cell">
{item.ram_size}
MB
</TableCell>
<TableCell className="text-right">
{formatDate(item.created_at)}
</TableCell>
</TableRow>
))
"❌"
)
: item.status === "installing"
? (
"🔄"
)
: (
item.status
)}
</td>
<td className="px-4 py-2 border-b">
{item.type === "lxc"
? (
"📦"
)
: item.type === "vm"
? (
"🖥️"
)
: (
<TableRow>
<TableCell colSpan={8} className="h-24 text-center">
No results found.
</TableCell>
</TableRow>
item.type
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
disabled={currentPage === 1 || loading}
>
<ChevronLeft className="mr-2 h-4 w-4" />
Previous
</Button>
<div className="text-sm text-muted-foreground">
Page
{" "}
{currentPage}
</div>
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(prev => prev + 1)}
disabled={loading || sortedData.length < itemsPerPage}
>
Next
<ChevronRight className="ml-2 h-4 w-4" />
</Button>
</div>
</CardContent>
</Card>
</td>
<td className="px-4 py-2 border-b">{item.nsapp}</td>
<td className="px-4 py-2 border-b">{item.os_type}</td>
<td className="px-4 py-2 border-b">{item.os_version}</td>
<td className="px-4 py-2 border-b">{item.disk_size}</td>
<td className="px-4 py-2 border-b">{item.core_count}</td>
<td className="px-4 py-2 border-b">{item.ram_size}</td>
<td className="px-4 py-2 border-b">{item.method}</td>
<td className="px-4 py-2 border-b">{item.pve_version}</td>
<td className="px-4 py-2 border-b">{item.error}</td>
<td className="px-4 py-2 border-b">{formatDate(item.created_at)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className="mt-4 flex justify-between items-center">
<button onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))} disabled={currentPage === 1} className="p-2 border">Previous</button>
<span>
Page
{currentPage}
</span>
<button onClick={() => setCurrentPage(prev => prev + 1)} className="p-2 border">Next</button>
<select
value={itemsPerPage}
onChange={e => setItemsPerPage(Number(e.target.value))}
className="p-2 border"
>
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
<option value={100}>100</option>
<option value={250}>250</option>
<option value={500}>500</option>
<option value={5000}>5000</option>
</select>
</div>
</div>
);
}
};
export default DataFetcher;

View File

@@ -35,22 +35,12 @@ export const ScriptSchema = z.object({
logo: z.string().url().nullable(),
config_path: z.string(),
description: z.string().min(1, "Description is required"),
disable: z.boolean().optional(),
disable_description: z.string().optional(),
install_methods: z.array(InstallMethodSchema).min(1, "At least one install method is required"),
default_credentials: z.object({
username: z.string().nullable(),
password: z.string().nullable(),
}),
notes: z.array(NoteSchema),
}).refine((data) => {
if (data.disable === true && !data.disable_description) {
return false;
}
return true;
}, {
message: "disable_description is required when disable is true",
path: ["disable_description"],
});
export type Script = z.infer<typeof ScriptSchema>;

View File

@@ -42,8 +42,6 @@ const initialScript: Script = {
website: null,
logo: null,
description: "",
disable: undefined,
disable_description: undefined,
install_methods: [],
default_credentials: {
username: null,
@@ -263,25 +261,7 @@ export default function JSONGenerator() {
<Switch checked={script.privileged} onCheckedChange={checked => updateScript("privileged", checked)} />
<label>Privileged</label>
</div>
<div className="flex items-center space-x-2">
<Switch checked={script.disable || false} onCheckedChange={checked => updateScript("disable", checked)} />
<label>Disabled</label>
</div>
</div>
{script.disable && (
<div>
<Label>
Disable Description
{" "}
<span className="text-red-500">*</span>
</Label>
<Textarea
placeholder="Explain why this script is disabled..."
value={script.disable_description || ""}
onChange={e => updateScript("disable_description", e.target.value)}
/>
</div>
)}
<Input
placeholder="Interface Port"
type="number"

View File

@@ -1,6 +1,5 @@
"use client";
import { Button } from "@/components/ui/button";
import { basePath } from "@/config/site-config";
export default function NotFoundPage() {
return (
@@ -13,17 +12,7 @@ export default function NotFoundPage() {
Oops, the page you are looking for could not be found.
</p>
</div>
<Button
onClick={() => {
if (window.history.length > 1) {
window.history.back();
}
else {
window.location.href = `/${basePath}`;
}
}}
variant="secondary"
>
<Button onClick={() => window.history.back()} variant="secondary">
Go Back
</Button>
</div>

View File

@@ -123,7 +123,7 @@ export default function ScriptAccordion({
className={`flex cursor-pointer items-center justify-between gap-1 px-1 py-1 text-muted-foreground hover:rounded-lg hover:bg-accent/60 hover:dark:bg-accent/20 ${selectedScript === script.slug
? "rounded-lg bg-accent font-semibold dark:bg-accent/30 dark:text-white"
: ""
} ${script.disable ? "opacity-60" : ""}`}
}`}
onClick={() => {
handleSelected(script.slug);
setSelectedCategory(category.name);
@@ -143,9 +143,7 @@ export default function ScriptAccordion({
alt={script.name}
className="mr-1 w-4 h-4 rounded-full"
/>
<span className="flex items-center gap-2">
{script.name}
</span>
<span className="flex items-center gap-2">{script.name}</span>
</div>
{formattedBadge(script.type)}
</Link>

View File

@@ -12,7 +12,6 @@ import { useVersions } from "@/hooks/use-versions";
import { basePath } from "@/config/site-config";
import { extractDate } from "@/lib/time";
import DisableDescription from "./script-items/disable-description";
import { getDisplayValueFromType } from "./script-info-blocks";
import DefaultPassword from "./script-items/default-password";
import InstallCommand from "./script-items/install-command";
@@ -147,45 +146,37 @@ export function ScriptItem({ item, setSelectedScript }: ScriptItemProps) {
<ScriptHeader item={item} />
</Suspense>
{item.disable && item.disable_description && (
<DisableDescription item={item} />
) }
<Description item={item} />
<Alerts item={item} />
{!item.disable && (
<>
<Description item={item} />
<Alerts item={item} />
<div className="mt-4 rounded-lg border shadow-sm">
<div className="mt-4 rounded-lg border shadow-sm">
<div className="flex gap-3 px-4 py-2 bg-accent/25">
<h2 className="text-lg font-semibold">
How to
{" "}
{item.type === "pve" ? "use" : item.type === "addon" ? "apply" : "install"}
</h2>
<Tooltips item={item} />
</div>
<Separator />
<div className="">
<InstallCommand item={item} />
</div>
{item.config_path && (
<>
<Separator />
<div className="flex gap-3 px-4 py-2 bg-accent/25">
<h2 className="text-lg font-semibold">
How to
{" "}
{item.type === "pve" ? "use" : item.type === "addon" ? "apply" : "install"}
</h2>
<Tooltips item={item} />
<h2 className="text-lg font-semibold">Location of config file</h2>
</div>
<Separator />
<div className="">
<InstallCommand item={item} />
<ConfigFile configPath={item.config_path} />
</div>
{item.config_path && (
<>
<Separator />
<div className="flex gap-3 px-4 py-2 bg-accent/25">
<h2 className="text-lg font-semibold">Location of config file</h2>
</div>
<Separator />
<div className="">
<ConfigFile configPath={item.config_path} />
</div>
</>
)}
</div>
</>
)}
</div>
<DefaultPassword item={item} />
</>
)}
<DefaultPassword item={item} />
</div>
</div>
</div>

View File

@@ -1,26 +0,0 @@
import { AlertCircle } from "lucide-react";
import type { Script } from "@/lib/types";
import TextParseLinks from "@/components/text-parse-links";
import { AlertColors } from "@/config/site-config";
import { cn } from "@/lib/utils";
export default function DisableDescription({ item }: { item: Script }) {
return (
<div className="mt-4 flex flex-col shadow-sm gap-2">
<div
className={cn(
"flex items-start gap-3 rounded-lg border p-4 text-sm",
AlertColors.warning,
)}
>
<AlertCircle className="h-5 min-h-5 w-5 min-w-5 mt-0.5" />
<div className="flex flex-col gap-2">
<h3 className="font-semibold text-base">Script Disabled</h3>
<p>{TextParseLinks(item.disable_description!)}</p>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,199 @@
"use client";
import { ArcElement, Chart as ChartJS, Tooltip as ChartTooltip, Legend } from "chart.js";
import ChartDataLabels from "chartjs-plugin-datalabels";
import { BarChart3, PieChart } from "lucide-react";
import React, { useState } from "react";
import { Pie } from "react-chartjs-2";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
ChartJS.register(ArcElement, ChartTooltip, Legend, ChartDataLabels);
type SummaryData = {
nsapp_count: Record<string, number>;
};
type ApplicationChartProps = {
data: SummaryData | null;
};
const ITEMS_PER_PAGE = 20;
const CHART_COLORS = [
"#ff6384",
"#36a2eb",
"#ffce56",
"#4bc0c0",
"#9966ff",
"#ff9f40",
"#4dc9f6",
"#f67019",
"#537bc4",
"#acc236",
"#166a8f",
"#00a950",
"#58595b",
"#8549ba",
];
export default function ApplicationChart({ data }: ApplicationChartProps) {
const [isChartOpen, setIsChartOpen] = useState(false);
const [isTableOpen, setIsTableOpen] = useState(false);
const [chartStartIndex, setChartStartIndex] = useState(0);
const [tableLimit, setTableLimit] = useState(ITEMS_PER_PAGE);
if (!data)
return null;
const sortedApps = Object.entries(data.nsapp_count)
.sort(([, a], [, b]) => b - a);
const chartApps = sortedApps.slice(
chartStartIndex,
chartStartIndex + ITEMS_PER_PAGE,
);
const chartData = {
labels: chartApps.map(([name]) => name),
datasets: [
{
data: chartApps.map(([, count]) => count),
backgroundColor: CHART_COLORS,
},
],
};
const chartOptions = {
plugins: {
legend: { display: false },
datalabels: {
color: "white",
font: { weight: "bold" as const },
formatter: (value: number, context: any) => {
const label = context.chart.data.labels?.[context.dataIndex];
return `${label}\n(${value})`;
},
},
},
responsive: true,
maintainAspectRatio: false,
};
return (
<div className="mt-6 flex justify-center gap-4">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() => setIsChartOpen(true)}
>
<PieChart className="h-5 w-5" />
</Button>
</TooltipTrigger>
<TooltipContent>Open Chart View</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() => setIsTableOpen(true)}
>
<BarChart3 className="h-5 w-5" />
</Button>
</TooltipTrigger>
<TooltipContent>Open Table View</TooltipContent>
</Tooltip>
</TooltipProvider>
<Dialog open={isChartOpen} onOpenChange={setIsChartOpen}>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>Applications Distribution</DialogTitle>
</DialogHeader>
<div className="h-[60vh] w-full">
<Pie data={chartData} options={chartOptions} />
</div>
<div className="flex justify-center gap-4">
<Button
variant="outline"
onClick={() => setChartStartIndex(Math.max(0, chartStartIndex - ITEMS_PER_PAGE))}
disabled={chartStartIndex === 0}
>
Previous
{" "}
{ITEMS_PER_PAGE}
</Button>
<Button
variant="outline"
onClick={() => setChartStartIndex(chartStartIndex + ITEMS_PER_PAGE)}
disabled={chartStartIndex + ITEMS_PER_PAGE >= sortedApps.length}
>
Next
{" "}
{ITEMS_PER_PAGE}
</Button>
</div>
</DialogContent>
</Dialog>
<Dialog open={isTableOpen} onOpenChange={setIsTableOpen}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Applications Count</DialogTitle>
</DialogHeader>
<div className="max-h-[60vh] overflow-y-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Application</TableHead>
<TableHead className="text-right">Count</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{sortedApps.slice(0, tableLimit).map(([name, count]) => (
<TableRow key={name}>
<TableCell>{name}</TableCell>
<TableCell className="text-right">{count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
{tableLimit < sortedApps.length && (
<Button
variant="outline"
className="w-full"
onClick={() => setTableLimit(prev => prev + ITEMS_PER_PAGE)}
>
Load More
</Button>
)}
</DialogContent>
</Dialog>
</div>
);
}

View File

@@ -1,52 +0,0 @@
import { ClipboardIcon, ExternalLink } from "lucide-react";
import { Fragment } from "react";
import handleCopy from "./handle-copy";
const URL_PATTERN = /(https?:\/\/[^\s,]+)/;
const CODE_PATTERN = /`([^`]*)`/;
export default function TextParseLinks(text: string) {
const codeParts = text.split(CODE_PATTERN);
return codeParts.map((part: string, codeIndex: number) => {
if (codeIndex % 2 === 1) {
return (
<span
key={`code-${codeIndex}`}
className="bg-secondary py-1 px-2 rounded-lg inline-flex items-center gap-2"
>
{part}
<ClipboardIcon
className="size-3 cursor-pointer"
onClick={() => handleCopy("command", part)}
/>
</span>
);
}
const urlParts = part.split(URL_PATTERN);
return (
<Fragment key={`text-${codeIndex}`}>
{urlParts.map((urlPart: string, urlIndex: number) => {
if (urlIndex % 2 === 1) {
return (
<a
key={`url-${codeIndex}-${urlIndex}`}
href={urlPart}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-blue-600 dark:text-blue-400 hover:underline font-medium transition-colors"
>
{urlPart}
<ExternalLink className="size-3" />
</a>
);
}
return <Fragment key={`plain-${codeIndex}-${urlIndex}`}>{urlPart}</Fragment>;
})}
</Fragment>
);
});
}

View File

@@ -1,369 +0,0 @@
"use client"
import * as React from "react"
import * as RechartsPrimitive from "recharts"
import { cn } from "@/lib/utils"
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const
export type ChartConfig = {
[k in string]: {
label?: React.ReactNode
icon?: React.ComponentType
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
)
}
type ChartContextProps = {
config: ChartConfig
}
const ChartContext = React.createContext<ChartContextProps | null>(null)
function useChart() {
const context = React.useContext(ChartContext)
if (!context) {
throw new Error("useChart must be used within a <ChartContainer />")
}
return context
}
const ChartContainer = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
config: ChartConfig
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>["children"]
}
>(({ id, className, children, config, ...props }, ref) => {
const uniqueId = React.useId()
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
return (
<ChartContext.Provider value={{ config }}>
<div
data-chart={chartId}
ref={ref}
className={cn(
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
className
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
)
})
ChartContainer.displayName = "Chart"
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
([, config]) => config.theme || config.color
)
if (!colorConfig.length) {
return null
}
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
itemConfig.color
return color ? ` --color-${key}: ${color};` : null
})
.join("\n")}
}
`
)
.join("\n"),
}}
/>
)
}
const ChartTooltip = RechartsPrimitive.Tooltip
const ChartTooltipContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
React.ComponentProps<"div"> & {
hideLabel?: boolean
hideIndicator?: boolean
indicator?: "line" | "dot" | "dashed"
nameKey?: string
labelKey?: string
}
>(
(
{
active,
payload,
className,
indicator = "dot",
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
},
ref
) => {
const { config } = useChart()
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null
}
const [item] = payload
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const value =
!labelKey && typeof label === "string"
? config[label as keyof typeof config]?.label || label
: itemConfig?.label
if (labelFormatter) {
return (
<div className={cn("font-medium", labelClassName)}>
{labelFormatter(value, payload)}
</div>
)
}
if (!value) {
return null
}
return <div className={cn("font-medium", labelClassName)}>{value}</div>
}, [
label,
labelFormatter,
payload,
hideLabel,
labelClassName,
config,
labelKey,
])
if (!active || !payload?.length) {
return null
}
const nestLabel = payload.length === 1 && indicator !== "dot"
return (
<div
ref={ref}
className={cn(
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
className
)}
>
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload
.filter((item) => item.type !== "none")
.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const indicatorColor = color || item.payload.fill || item.color
return (
<div
key={item.dataKey}
className={cn(
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
indicator === "dot" && "items-center"
)}
>
{formatter && item?.value !== undefined && item.name ? (
formatter(item.value, item.name, item, index, item.payload)
) : (
<>
{itemConfig?.icon ? (
<itemConfig.icon />
) : (
!hideIndicator && (
<div
className={cn(
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
{
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent":
indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed",
}
)}
style={
{
"--color-bg": indicatorColor,
"--color-border": indicatorColor,
} as React.CSSProperties
}
/>
)
)}
<div
className={cn(
"flex flex-1 justify-between leading-none",
nestLabel ? "items-end" : "items-center"
)}
>
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label || item.name}
</span>
</div>
{item.value && (
<span className="font-mono font-medium tabular-nums text-foreground">
{item.value.toLocaleString()}
</span>
)}
</div>
</>
)}
</div>
)
})}
</div>
</div>
)
}
)
ChartTooltipContent.displayName = "ChartTooltip"
const ChartLegend = RechartsPrimitive.Legend
const ChartLegendContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> &
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean
nameKey?: string
}
>(
(
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
ref
) => {
const { config } = useChart()
if (!payload?.length) {
return null
}
return (
<div
ref={ref}
className={cn(
"flex items-center justify-center gap-4",
verticalAlign === "top" ? "pb-3" : "pt-3",
className
)}
>
{payload
.filter((item) => item.type !== "none")
.map((item) => {
const key = `${nameKey || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
return (
<div
key={item.value}
className={cn(
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
)}
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}}
/>
)}
{itemConfig?.label}
</div>
)
})}
</div>
)
}
)
ChartLegendContent.displayName = "ChartLegend"
// Helper to extract item config from a payload.
function getPayloadConfigFromPayload(
config: ChartConfig,
payload: unknown,
key: string
) {
if (typeof payload !== "object" || payload === null) {
return undefined
}
const payloadPayload =
"payload" in payload &&
typeof payload.payload === "object" &&
payload.payload !== null
? payload.payload
: undefined
let configLabelKey: string = key
if (
key in payload &&
typeof payload[key as keyof typeof payload] === "string"
) {
configLabelKey = payload[key as keyof typeof payload] as string
} else if (
payloadPayload &&
key in payloadPayload &&
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
) {
configLabelKey = payloadPayload[
key as keyof typeof payloadPayload
] as string
}
return configLabelKey in config
? config[configLabelKey]
: config[key as keyof typeof config]
}
export {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartStyle,
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useInView, useMotionValue, useSpring } from "motion/react";
import { useInView, useMotionValue, useSpring } from "framer-motion";
import { useEffect, useRef } from "react";
import { cn } from "@/lib/utils";

View File

@@ -1,48 +0,0 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }

View File

@@ -5,7 +5,7 @@ import React from "react";
import type { OperatingSystem } from "@/lib/types";
// eslint-disable-next-line node/no-process-env
export const basePath = process.env.BASE_PATH || "ProxmoxVE";
export const basePath = process.env.BASE_PATH || "";
export const navbarLinks = [
{

View File

@@ -14,8 +14,6 @@ export type Script = {
logo: string | null;
config_path: string;
description: string;
disable?: boolean;
disable_description?: string;
install_methods: {
type: "default" | "alpine";
script: string;

View File

@@ -97,4 +97,9 @@ msg_ok "Configured Service"
motd_ssh
customize
cleanup_lxc
msg_info "Cleaning up"
$STD apt -y autoremove
$STD apt -y autoclean
$STD apt -y clean
msg_ok "Cleaned"

View File

@@ -89,4 +89,9 @@ msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
msg_info "Cleaning up"
$STD apt -y autoremove
$STD apt -y autoclean
$STD apt -y clean
msg_ok "Cleaned"

View File

@@ -41,4 +41,9 @@ msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
msg_info "Cleaning up"
$STD apt -y autoremove
$STD apt -y autoclean
$STD apt -y clean
msg_ok "Cleaned"

View File

@@ -146,4 +146,9 @@ msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
msg_info "Cleaning up"
$STD apt -y autoremove
$STD apt -y autoclean
$STD apt -y clean
msg_ok "Cleaned"

View File

@@ -30,7 +30,6 @@ curl -fsSL "$RELEASE" -o $(basename "$RELEASE")
$STD unzip Agent_Linux64*.zip
chmod +x ./Agent
echo $RELEASE >~/.agentdvr
rm -rf Agent_Linux64*.zip
msg_ok "Installed AgentDVR"
msg_info "Creating Service"
@@ -54,4 +53,10 @@ msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
msg_info "Cleaning up"
rm -rf Agent_Linux64*.zip
$STD apt -y autoremove
$STD apt -y autoclean
$STD apt -y clean
msg_ok "Cleaned"

View File

@@ -34,4 +34,8 @@ msg_ok "Installed Apache Cassandra"
motd_ssh
customize
cleanup_lxc
msg_info "Cleaning up"
$STD apt-get -y autoremove
$STD apt-get -y autoclean
msg_ok "Cleaned"

View File

@@ -36,4 +36,8 @@ msg_ok "Installed Apache CouchDB."
motd_ssh
customize
cleanup_lxc
msg_info "Cleaning up"
$STD apt-get -y autoremove
$STD apt-get -y autoclean
msg_ok "Cleaned"

View File

@@ -71,8 +71,6 @@ mv ~/mysql-connector-j-9.3.0/mysql-connector-j-9.3.0.jar /etc/guacamole/lib/
curl -fsSL "https://downloads.apache.org/guacamole/${RELEASE_SERVER}/binary/guacamole-auth-jdbc-${RELEASE_SERVER}.tar.gz" -o "/root/guacamole-auth-jdbc-${RELEASE_SERVER}.tar.gz"
$STD tar -xf ~/guacamole-auth-jdbc-$RELEASE_SERVER.tar.gz
mv ~/guacamole-auth-jdbc-$RELEASE_SERVER/mysql/guacamole-auth-jdbc-mysql-$RELEASE_SERVER.jar /etc/guacamole/extensions/
rm -rf ~/mysql-connector-j-9.3.0{,.tar.gz}
rm -rf ~/guacamole-auth-jdbc-$RELEASE_SERVER{,.tar.gz}
msg_ok "Setup Apache Guacamole"
msg_info "Setup Database"
@@ -148,4 +146,10 @@ msg_ok "Setup Service"
motd_ssh
customize
cleanup_lxc
msg_info "Cleaning up"
rm -rf ~/mysql-connector-j-9.3.0{,.tar.gz}
rm -rf ~/guacamole-auth-jdbc-$RELEASE_SERVER{,.tar.gz}
$STD apt -y autoremove
$STD apt -y autoclean
msg_ok "Cleaned"

View File

@@ -69,4 +69,8 @@ msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
msg_info "Cleaning up"
$STD apt-get -y autoremove
$STD apt-get -y autoclean
msg_ok "Cleaned"

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