mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-02-04 12:23:26 +01:00
Compare commits
3 Commits
automated/
...
add-script
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c71a0e094b | ||
|
|
30893fd41f | ||
|
|
5905e008a7 |
83
.github/workflows/lock-issue.yaml
generated
vendored
83
.github/workflows/lock-issue.yaml
generated
vendored
@@ -18,6 +18,7 @@ jobs:
|
||||
with:
|
||||
script: |
|
||||
const daysBeforeLock = 3;
|
||||
const cutoffDate = new Date('2026-01-27T00:00:00Z');
|
||||
const lockDate = new Date();
|
||||
lockDate.setDate(lockDate.getDate() - daysBeforeLock);
|
||||
|
||||
@@ -28,50 +29,48 @@ jobs:
|
||||
/dependabot/i
|
||||
];
|
||||
|
||||
// Search for closed, unlocked issues older than 3 days (paginated, oldest first)
|
||||
let page = 1;
|
||||
let totalLocked = 0;
|
||||
// Search for closed, unlocked issues older than 3 days
|
||||
const issues = await github.rest.search.issuesAndPullRequests({
|
||||
q: `repo:${context.repo.owner}/${context.repo.repo} is:closed is:unlocked updated:<${lockDate.toISOString().split('T')[0]}`,
|
||||
per_page: 50
|
||||
});
|
||||
|
||||
while (true) {
|
||||
const issues = await github.rest.search.issuesAndPullRequests({
|
||||
q: `repo:${context.repo.owner}/${context.repo.repo} is:closed is:unlocked updated:<${lockDate.toISOString().split('T')[0]}`,
|
||||
sort: 'updated',
|
||||
order: 'asc',
|
||||
per_page: 100,
|
||||
page: page
|
||||
});
|
||||
|
||||
if (issues.data.items.length === 0) break;
|
||||
|
||||
console.log(`Page ${page}: ${issues.data.items.length} items (total available: ${issues.data.total_count})`);
|
||||
|
||||
for (const item of issues.data.items) {
|
||||
// Skip excluded items
|
||||
const shouldExclude = excludePatterns.some(pattern => pattern.test(item.title));
|
||||
if (shouldExclude) {
|
||||
console.log(`Skipped #${item.number}: "${item.title}" (matches exclude pattern)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Lock the issue/PR silently
|
||||
await github.rest.issues.lock({
|
||||
...context.repo,
|
||||
issue_number: item.number,
|
||||
lock_reason: 'resolved'
|
||||
});
|
||||
|
||||
totalLocked++;
|
||||
console.log(`Locked #${item.number} (${item.pull_request ? 'PR' : 'Issue'})`);
|
||||
} catch (error) {
|
||||
console.log(`Failed to lock #${item.number}: ${error.message}`);
|
||||
}
|
||||
console.log(`Found ${issues.data.items.length} issues/PRs to process`);
|
||||
|
||||
for (const item of issues.data.items) {
|
||||
// Skip excluded items
|
||||
const shouldExclude = excludePatterns.some(pattern => pattern.test(item.title));
|
||||
if (shouldExclude) {
|
||||
console.log(`Skipped #${item.number}: "${item.title}" (matches exclude pattern)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
page++;
|
||||
const createdAt = new Date(item.created_at);
|
||||
const isNew = createdAt >= cutoffDate;
|
||||
|
||||
// GitHub search API limit: max 10000 results (100 pages * 100) - temporarily increased
|
||||
if (page > 100) break;
|
||||
try {
|
||||
// Add comment only for new issues (created after 2026-01-27)
|
||||
if (isNew) {
|
||||
const comment = item.pull_request
|
||||
? 'This pull request has been automatically locked. Please open a new issue for related bugs.'
|
||||
: 'This issue has been automatically locked. Please open a new issue for related bugs and reference this issue if needed.';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
...context.repo,
|
||||
issue_number: item.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
|
||||
// Lock the issue/PR
|
||||
await github.rest.issues.lock({
|
||||
...context.repo,
|
||||
issue_number: item.number,
|
||||
lock_reason: 'resolved'
|
||||
});
|
||||
|
||||
console.log(`Locked #${item.number} (${item.pull_request ? 'PR' : 'Issue'})`);
|
||||
} catch (error) {
|
||||
console.log(`Failed to lock #${item.number}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Total locked: ${totalLocked} issues/PRs`);
|
||||
|
||||
62
CHANGELOG.md
62
CHANGELOG.md
@@ -772,71 +772,11 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
</details>
|
||||
|
||||
## 2026-02-04
|
||||
|
||||
## 2026-02-03
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- Wealthfolio ([#11511](https://github.com/community-scripts/ProxmoxVE/pull/11511))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- [FIX] Shelfmark: unpin Chromium version [@vhsdream](https://github.com/vhsdream) ([#11505](https://github.com/community-scripts/ProxmoxVE/pull/11505))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- [FEAT] Scanopy: automatically update integrated daemon [@vhsdream](https://github.com/vhsdream) ([#11506](https://github.com/community-scripts/ProxmoxVE/pull/11506))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- [FIX] tools.func: trim spaces in app_lc when checking for gh release [@vhsdream](https://github.com/vhsdream) ([#11512](https://github.com/community-scripts/ProxmoxVE/pull/11512))
|
||||
|
||||
### 🌐 Website
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- fix(frontend): decouple table pagination from summary fetching [@ls-root](https://github.com/ls-root) ([#11495](https://github.com/community-scripts/ProxmoxVE/pull/11495))
|
||||
|
||||
## 2026-02-02
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- rustypaste | Alpine-rustypaste ([#11457](https://github.com/community-scripts/ProxmoxVE/pull/11457))
|
||||
- KitchenOwl ([#11453](https://github.com/community-scripts/ProxmoxVE/pull/11453))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- Grist: Update dependencies [@tremor021](https://github.com/tremor021) ([#11489](https://github.com/community-scripts/ProxmoxVE/pull/11489))
|
||||
- Allow "downgrade" of libigdgmm12 [@vhsdream](https://github.com/vhsdream) ([#11478](https://github.com/community-scripts/ProxmoxVE/pull/11478))
|
||||
- Disable NPM install and update due to OpenResty SHA-1 signature issues [@MickLesk](https://github.com/MickLesk) ([#11471](https://github.com/community-scripts/ProxmoxVE/pull/11471))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- Refactor: Forgejo & readeck - migrate to codeberg functions [@MickLesk](https://github.com/MickLesk) ([#11460](https://github.com/community-scripts/ProxmoxVE/pull/11460))
|
||||
|
||||
- #### 💥 Breaking Changes
|
||||
|
||||
- [FIX] Scanopy: remove daemon build [@vhsdream](https://github.com/vhsdream) ([#11444](https://github.com/community-scripts/ProxmoxVE/pull/11444))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Refactor: Vaultwarden [@MickLesk](https://github.com/MickLesk) ([#11445](https://github.com/community-scripts/ProxmoxVE/pull/11445))
|
||||
- various scripts: use ensure_dependencies instead of apt [@MickLesk](https://github.com/MickLesk) ([#11463](https://github.com/community-scripts/ProxmoxVE/pull/11463))
|
||||
|
||||
### 🌐 Website
|
||||
|
||||
- cleanup(frontend): remove unused /category-view route [@ls-root](https://github.com/ls-root) ([#11461](https://github.com/community-scripts/ProxmoxVE/pull/11461))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- feat(frontend): preview tab [@ls-root](https://github.com/ls-root) ([#11475](https://github.com/community-scripts/ProxmoxVE/pull/11475))
|
||||
- KitchenOwl ([#11453](https://github.com/community-scripts/ProxmoxVE/pull/11453))
|
||||
|
||||
## 2026-02-01
|
||||
|
||||
|
||||
@@ -27,7 +27,10 @@ function update_script() {
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
ensure_dependencies memcached libmemcached-tools
|
||||
if ! command -v memcached >/dev/null 2>&1; then
|
||||
$STD apt update
|
||||
$STD apt install -y memcached libmemcached-tools
|
||||
fi
|
||||
if check_for_gh_release "adventurelog" "seanmorley15/adventurelog"; then
|
||||
msg_info "Stopping Services"
|
||||
systemctl stop adventurelog-backend
|
||||
|
||||
@@ -31,7 +31,11 @@ function update_script() {
|
||||
NODE_VERSION="22" NODE_MODULE="@postlight/parser@latest,single-file-cli@latest" setup_nodejs
|
||||
PYTHON_VERSION="3.13" setup_uv
|
||||
|
||||
ensure_dependencies chromium
|
||||
if ! dpkg -l | grep -q "^ii chromium "; then
|
||||
msg_info "Installing System Dependencies"
|
||||
$STD apt-get install -y chromium
|
||||
msg_ok "Installed System Dependencies"
|
||||
fi
|
||||
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop archivebox
|
||||
|
||||
@@ -29,7 +29,12 @@ function update_script() {
|
||||
exit
|
||||
fi
|
||||
|
||||
ensure_dependencies libjpeg-dev
|
||||
if ! dpkg -s libjpeg-dev >/dev/null 2>&1; then
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt-get update
|
||||
$STD apt-get install -y libjpeg-dev
|
||||
msg_ok "Updated Dependencies"
|
||||
fi
|
||||
|
||||
NODE_VERSION="24" setup_nodejs
|
||||
|
||||
|
||||
@@ -34,7 +34,12 @@ function update_script() {
|
||||
systemctl stop commafeed
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
ensure_dependencies rsync
|
||||
if ! [[ $(dpkg -s rsync 2>/dev/null) ]]; then
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt update
|
||||
$STD apt install -y rsync
|
||||
msg_ok "Installed Dependencies"
|
||||
fi
|
||||
|
||||
if [ -d /opt/commafeed/data ] && [ "$(ls -A /opt/commafeed/data)" ]; then
|
||||
msg_info "Backing up existing data"
|
||||
|
||||
@@ -44,7 +44,10 @@ function update_script() {
|
||||
NODE_VERSION="22" setup_nodejs
|
||||
if check_for_gh_release "cronicle" "jhuckaby/Cronicle"; then
|
||||
msg_info "Installing Dependencies"
|
||||
ensure_dependencies git build-essential ca-certificates
|
||||
$STD apt install -y \
|
||||
git \
|
||||
build-essential \
|
||||
ca-certificates
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
NODE_VERSION="22" setup_nodejs
|
||||
|
||||
@@ -38,7 +38,9 @@ function update_script() {
|
||||
systemctl reload nginx
|
||||
fi
|
||||
|
||||
ensure_dependencies vlc-bin vlc-plugin-base
|
||||
if ! dpkg -s vlc-bin vlc-plugin-base &>/dev/null; then
|
||||
$STD apt update && $STD apt install -y vlc-bin vlc-plugin-base
|
||||
fi
|
||||
|
||||
if check_for_gh_release "Dispatcharr" "Dispatcharr/Dispatcharr"; then
|
||||
msg_info "Stopping Services"
|
||||
|
||||
@@ -27,28 +27,35 @@ function update_script() {
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
if check_for_codeberg_release "forgejo" "forgejo/forgejo"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop forgejo
|
||||
msg_ok "Stopped Service"
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop forgejo
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
fetch_and_deploy_codeberg_release "forgejo" "forgejo/forgejo" "singlefile" "latest" "/opt/forgejo" "forgejo-*-linux-amd64"
|
||||
ln -sf /opt/forgejo/forgejo /usr/local/bin/forgejo
|
||||
msg_info "Updating ${APP}"
|
||||
RELEASE=$(curl -fsSL https://codeberg.org/api/v1/repos/forgejo/forgejo/releases/latest | grep -oP '"tag_name":\s*"\K[^"]+' | sed 's/^v//')
|
||||
curl -fsSL "https://codeberg.org/forgejo/forgejo/releases/download/v${RELEASE}/forgejo-${RELEASE}-linux-amd64" -o "forgejo-$RELEASE-linux-amd64"
|
||||
rm -rf /opt/forgejo/*
|
||||
cp -r forgejo-$RELEASE-linux-amd64 /opt/forgejo/forgejo-$RELEASE-linux-amd64
|
||||
chmod +x /opt/forgejo/forgejo-$RELEASE-linux-amd64
|
||||
ln -sf /opt/forgejo/forgejo-$RELEASE-linux-amd64 /usr/local/bin/forgejo
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
if grep -q "GITEA_WORK_DIR" /etc/systemd/system/forgejo.service; then
|
||||
msg_info "Updating Service File"
|
||||
sed -i "s/GITEA_WORK_DIR/FORGEJO_WORK_DIR/g" /etc/systemd/system/forgejo.service
|
||||
systemctl daemon-reload
|
||||
msg_ok "Updated Service File"
|
||||
fi
|
||||
msg_info "Cleaning"
|
||||
rm -rf forgejo-$RELEASE-linux-amd64
|
||||
msg_ok "Cleaned"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start forgejo
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at the latest version."
|
||||
# Fix env var from older version of community script
|
||||
if grep -q "GITEA_WORK_DIR" /etc/systemd/system/forgejo.service; then
|
||||
msg_info "Updating Service File"
|
||||
sed -i "s/GITEA_WORK_DIR/FORGEJO_WORK_DIR/g" /etc/systemd/system/forgejo.service
|
||||
systemctl daemon-reload
|
||||
msg_ok "Updated Service File"
|
||||
fi
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start forgejo
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,9 @@ function update_script() {
|
||||
msg_error "Project directory does not exist: $PROJECT_DIR"
|
||||
exit
|
||||
fi
|
||||
ensure_dependencies git
|
||||
if ! command -v git &>/dev/null; then
|
||||
$STD apt install -y git
|
||||
fi
|
||||
|
||||
msg_info "Stopping service $SERVICE_NAME"
|
||||
systemctl stop "$SERVICE_NAME"
|
||||
|
||||
@@ -45,7 +45,7 @@ function update_script() {
|
||||
curl -fsSL "https://packages.graylog2.org/repo/packages/graylog-7.0-repository_latest.deb" -o "graylog-7.0-repository_latest.deb"
|
||||
$STD dpkg -i graylog-7.0-repository_latest.deb
|
||||
$STD apt update
|
||||
ensure_dependencies graylog-server graylog-datanode
|
||||
$STD apt install -y graylog-server graylog-datanode
|
||||
rm -f graylog-7.0-repository_latest.deb
|
||||
msg_ok "Updated Graylog"
|
||||
elif dpkg --compare-versions "$CURRENT_VERSION" ge "7.0"; then
|
||||
|
||||
@@ -29,8 +29,6 @@ function update_script() {
|
||||
exit
|
||||
fi
|
||||
|
||||
ensure_dependencies git
|
||||
|
||||
if check_for_gh_release "grist" "gristlabs/grist-core"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop grist
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
___ __ _ ____ __ ____ __
|
||||
/ | / /___ (_)___ ___ / __ \__ _______/ /___ __/ __ \____ ______/ /____
|
||||
/ /| | / / __ \/ / __ \/ _ \______/ /_/ / / / / ___/ __/ / / / /_/ / __ `/ ___/ __/ _ \
|
||||
/ ___ |/ / /_/ / / / / / __/_____/ _, _/ /_/ (__ ) /_/ /_/ / ____/ /_/ (__ ) /_/ __/
|
||||
/_/ |_/_/ .___/_/_/ /_/\___/ /_/ |_|\__,_/____/\__/\__, /_/ \__,_/____/\__/\___/
|
||||
/_/ /____/
|
||||
@@ -1,4 +1,4 @@
|
||||
__ __ _ __ __ ____ __
|
||||
__ __ _ __ __ ____ __
|
||||
/ //_/(_) /______/ /_ ___ ____ / __ \_ __/ /
|
||||
/ ,< / / __/ ___/ __ \/ _ \/ __ \/ / / / | /| / / /
|
||||
/ /| |/ / /_/ /__/ / / / __/ / / / /_/ /| |/ |/ / /
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__ __
|
||||
__ __
|
||||
_______ _______/ /___ ______ ____ ______/ /____
|
||||
/ ___/ / / / ___/ __/ / / / __ \/ __ `/ ___/ __/ _ \
|
||||
/ / / /_/ (__ ) /_/ /_/ / /_/ / /_/ (__ ) /_/ __/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
_ __ ____ __ ____ ___
|
||||
| | / /__ ____ _/ / /_/ /_ / __/___ / (_)___
|
||||
| | /| / / _ \/ __ `/ / __/ __ \/ /_/ __ \/ / / __ \
|
||||
| |/ |/ / __/ /_/ / / /_/ / / / __/ /_/ / / / /_/ /
|
||||
|__/|__/\___/\__,_/_/\__/_/ /_/_/ \____/_/_/\____/
|
||||
|
||||
@@ -30,7 +30,14 @@ function update_script() {
|
||||
|
||||
get_lxc_ip
|
||||
NODE_VERSION="22" NODE_MODULE="pnpm@latest" setup_nodejs
|
||||
ensure_dependencies jq
|
||||
if ! command -v jq &>/dev/null; then
|
||||
$STD msg_info "Installing jq..."
|
||||
$STD apt-get update -qq &>/dev/null
|
||||
$STD apt-get install -y jq &>/dev/null || {
|
||||
msg_error "Failed to install jq"
|
||||
exit
|
||||
}
|
||||
fi
|
||||
|
||||
if check_for_gh_release "homepage" "gethomepage/homepage"; then
|
||||
msg_info "Stopping service"
|
||||
|
||||
14
ct/immich.sh
14
ct/immich.sh
@@ -67,7 +67,8 @@ EOF
|
||||
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" >/etc/apt/sources.list.d/mise.list
|
||||
ensure_dependencies mise
|
||||
$STD apt update
|
||||
$STD apt install -y mise
|
||||
msg_ok "Installed Mise"
|
||||
fi
|
||||
|
||||
@@ -88,7 +89,7 @@ EOF
|
||||
curl -fsSLO "$url"
|
||||
done
|
||||
$STD apt-mark unhold libigdgmm12
|
||||
$STD apt install -y --allow-downgrades ./libigdgmm12*.deb
|
||||
$STD apt install -y ./libigdgmm12*.deb
|
||||
rm ./libigdgmm12*.deb
|
||||
$STD apt install -y ./*.deb
|
||||
rm ./*.deb
|
||||
@@ -133,7 +134,9 @@ EOF
|
||||
$STD sudo -u postgres psql -d immich -c "REINDEX INDEX face_index;"
|
||||
$STD sudo -u postgres psql -d immich -c "REINDEX INDEX clip_index;"
|
||||
fi
|
||||
ensure_dependencies ccache
|
||||
if ! dpkg -l | grep -q ccache; then
|
||||
$STD apt install -yqq ccache
|
||||
fi
|
||||
|
||||
INSTALL_DIR="/opt/${APP}"
|
||||
UPLOAD_DIR="$(sed -n '/^IMMICH_MEDIA_LOCATION/s/[^=]*=//p' /opt/immich/.env)"
|
||||
@@ -301,7 +304,10 @@ function compile_libjxl() {
|
||||
|
||||
function compile_libheif() {
|
||||
SOURCE=${SOURCE_DIR}/libheif
|
||||
ensure_dependencies libaom-dev
|
||||
if ! dpkg -l | grep -q libaom; then
|
||||
$STD apt install -y libaom-dev
|
||||
local update="required"
|
||||
fi
|
||||
: "${LIBHEIF_REVISION:=$(jq -cr '.revision' "$BASE_DIR"/server/sources/libheif.json)}"
|
||||
if [[ "${update:-}" ]] || [[ "$LIBHEIF_REVISION" != "$(grep 'libheif' ~/.immich_library_revisions | awk '{print $2}')" ]]; then
|
||||
msg_info "Recompiling libheif"
|
||||
|
||||
@@ -40,7 +40,9 @@ function update_script() {
|
||||
fi
|
||||
|
||||
msg_info "Updating Jellyfin"
|
||||
ensure_dependencies libjemalloc2
|
||||
if ! dpkg -s libjemalloc2 >/dev/null 2>&1; then
|
||||
$STD apt install -y libjemalloc2
|
||||
fi
|
||||
if [[ ! -f /usr/lib/libjemalloc.so ]]; then
|
||||
ln -sf /usr/lib/x86_64-linux-gnu/libjemalloc.so.2 /usr/lib/libjemalloc.so
|
||||
fi
|
||||
|
||||
@@ -38,7 +38,7 @@ function update_script() {
|
||||
msg_ok "Updated yt-dlp"
|
||||
|
||||
msg_info "Prepare update"
|
||||
ensure_dependencies graphicsmagick ghostscript
|
||||
$STD apt install -y graphicsmagick ghostscript
|
||||
if [[ -f /opt/karakeep/.env ]] && [[ ! -f /etc/karakeep/karakeep.env ]]; then
|
||||
mkdir -p /etc/karakeep
|
||||
mv /opt/karakeep/.env /etc/karakeep/karakeep.env
|
||||
|
||||
@@ -23,7 +23,9 @@ function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
ensure_dependencies lsb-release
|
||||
if ! command -v lsb_release; then
|
||||
apt install -y lsb-release
|
||||
fi
|
||||
if [[ ! -d /opt/kimai ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
|
||||
@@ -27,8 +27,9 @@ function update_script() {
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
JAVA_VERSION="21" setup_java
|
||||
|
||||
if ! dpkg -l | grep -q temurin-21-jre; then
|
||||
JAVA_VERSION="21" setup_java
|
||||
fi
|
||||
msg_info "Updating ${APP}"
|
||||
$STD apt update
|
||||
$STD apt -y upgrade
|
||||
|
||||
@@ -36,7 +36,12 @@ function update_script() {
|
||||
NODE_VERSION="24" setup_nodejs
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "scanopy" "scanopy/scanopy" "tarball" "latest" "/opt/scanopy"
|
||||
|
||||
ensure_dependencies pkg-config libssl-dev
|
||||
if ! dpkg -l | grep -q "pkg-config"; then
|
||||
$STD apt install -y pkg-config
|
||||
fi
|
||||
if ! dpkg -l | grep -q "libssl-dev"; then
|
||||
$STD apt install -y libssl-dev
|
||||
fi
|
||||
TOOLCHAIN="$(grep "channel" /opt/scanopy/backend/rust-toolchain.toml | awk -F\" '{print $2}')"
|
||||
RUST_TOOLCHAIN=$TOOLCHAIN setup_rust
|
||||
|
||||
|
||||
@@ -28,12 +28,6 @@ function update_script() {
|
||||
exit
|
||||
fi
|
||||
|
||||
msg_error "This script is currently disabled due to an external issue with the OpenResty APT repository."
|
||||
msg_error "The repository's GPG key uses SHA-1 signatures, which are no longer accepted by Debian as of February 1, 2026."
|
||||
msg_error "The issue is tracked in openresty/openresty#1097"
|
||||
msg_error "For more details, see: https://github.com/community-scripts/ProxmoxVE/issues/11406"
|
||||
exit 1
|
||||
|
||||
if [[ $(grep -E '^VERSION_ID=' /etc/os-release) == *"12"* ]]; then
|
||||
msg_error "Wrong Debian version detected!"
|
||||
msg_error "Please create a snapshot first. You must upgrade your LXC to Debian Trixie before updating. Visit: https://github.com/community-scripts/ProxmoxVE/discussions/7489"
|
||||
|
||||
@@ -28,8 +28,8 @@ function update_script() {
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
ensure_dependencies python3-lxml
|
||||
if ! [[ $(dpkg -s python3-lxml-html-clean 2>/dev/null) ]]; then
|
||||
$STD apt install python3-lxml
|
||||
curl -fsSL "http://archive.ubuntu.com/ubuntu/pool/universe/l/lxml-html-clean/python3-lxml-html-clean_0.1.1-1_all.deb" -o /opt/python3-lxml-html-clean.deb
|
||||
$STD dpkg -i /opt/python3-lxml-html-clean.deb
|
||||
rm -f /opt/python3-lxml-html-clean.deb
|
||||
|
||||
@@ -32,7 +32,11 @@ function update_script() {
|
||||
if [[ ! -f /opt/Ollama_version.txt ]]; then
|
||||
touch /opt/Ollama_version.txt
|
||||
fi
|
||||
ensure_dependencies zstd
|
||||
if ! command -v zstd &>/dev/null; then
|
||||
msg_info "Installing zstd"
|
||||
$STD apt install -y zstd
|
||||
msg_ok "Installed zstd"
|
||||
fi
|
||||
msg_info "Stopping Services"
|
||||
systemctl stop ollama
|
||||
msg_ok "Services Stopped"
|
||||
|
||||
@@ -92,7 +92,11 @@ EOF
|
||||
OLLAMA_VERSION=$(ollama -v | awk '{print $NF}')
|
||||
RELEASE=$(curl -s https://api.github.com/repos/ollama/ollama/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4)}')
|
||||
if [ "$OLLAMA_VERSION" != "$RELEASE" ]; then
|
||||
ensure_dependencies zstd
|
||||
if ! command -v zstd &>/dev/null; then
|
||||
msg_info "Installing zstd"
|
||||
$STD apt install -y zstd
|
||||
msg_ok "Installed zstd"
|
||||
fi
|
||||
msg_info "Ollama update available: v$OLLAMA_VERSION -> v$RELEASE"
|
||||
msg_info "Downloading Ollama v$RELEASE \n"
|
||||
curl -fS#LO https://github.com/ollama/ollama/releases/download/v${RELEASE}/ollama-linux-amd64.tar.zst
|
||||
|
||||
@@ -69,7 +69,7 @@ function update_script() {
|
||||
if [ "$VERSION_CODENAME" = "bookworm" ]; then
|
||||
setup_gs
|
||||
else
|
||||
ensure_dependencies ghostscript
|
||||
$STD apt install -y ghostscript
|
||||
fi
|
||||
|
||||
msg_info "Updating Paperless-ngx"
|
||||
@@ -145,7 +145,7 @@ function update_script() {
|
||||
setup_gs
|
||||
else
|
||||
msg_info "Installing Ghostscript"
|
||||
ensure_dependencies ghostscript
|
||||
$STD apt install -y ghostscript
|
||||
msg_ok "Installed Ghostscript"
|
||||
fi
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ function update_script() {
|
||||
LIBHEIF_URL=$(curl -fsSL "https://dl.photoprism.app/dist/libheif/" | grep -oP "libheif-bookworm-amd64-v[0-9\.]+\.tar\.gz" | sort -V | tail -n 1)
|
||||
if [[ "${LIBHEIF_URL}" != "$(cat ~/.photoprism_libheif 2>/dev/null)" ]] || [[ ! -f ~/.photoprism_libheif ]]; then
|
||||
msg_info "Updating PhotoPrism LibHeif"
|
||||
ensure_dependencies libvips42
|
||||
$STD apt install -y libvips42
|
||||
curl -fsSL "https://dl.photoprism.app/dist/libheif/$LIBHEIF_URL" -o /tmp/libheif.tar.gz
|
||||
tar -xzf /tmp/libheif.tar.gz -C /usr/local
|
||||
ldconfig
|
||||
|
||||
@@ -41,7 +41,7 @@ function update_script() {
|
||||
cp -R /opt/rdtc-backup/appsettings.json /opt/rdtc/
|
||||
if dpkg-query -W dotnet-sdk-8.0 >/dev/null 2>&1; then
|
||||
$STD apt remove --purge -y dotnet-sdk-8.0
|
||||
ensure_dependencies aspnetcore-runtime-9.0
|
||||
$STD apt install -y aspnetcore-runtime-9.0
|
||||
fi
|
||||
rm -rf /opt/rdtc-backup
|
||||
|
||||
|
||||
@@ -27,20 +27,22 @@ function update_script() {
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
if check_for_codeberg_release "readeck" "readeck/readeck"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop readeck
|
||||
msg_ok "Stopped Service"
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop readeck
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
fetch_and_deploy_codeberg_release "readeck" "readeck/readeck" "singlefile" "latest" "/opt/readeck" "readeck-*-linux-amd64"
|
||||
msg_info "Updating Readeck"
|
||||
LATEST=$(curl -fsSL https://codeberg.org/readeck/readeck/releases/ | grep -oP '/releases/tag/\K\d+\.\d+\.\d+' | head -1)
|
||||
rm -rf /opt/readeck/readeck
|
||||
cd /opt/readeck
|
||||
curl -fsSL "https://codeberg.org/readeck/readeck/releases/download/${LATEST}/readeck-${LATEST}-linux-amd64" -o "readeck"
|
||||
chmod a+x readeck
|
||||
msg_ok "Updated Readeck"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start readeck
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at the latest version."
|
||||
fi
|
||||
msg_info "Starting Service"
|
||||
systemctl start readeck
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,12 @@ function update_script() {
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "scanopy" "scanopy/scanopy" "tarball" "latest" "/opt/scanopy"
|
||||
|
||||
ensure_dependencies pkg-config libssl-dev
|
||||
if ! dpkg -l | grep -q "pkg-config"; then
|
||||
$STD apt install -y pkg-config
|
||||
fi
|
||||
if ! dpkg -l | grep -q "libssl-dev"; then
|
||||
$STD apt install -y libssl-dev
|
||||
fi
|
||||
TOOLCHAIN="$(grep "channel" /opt/scanopy/backend/rust-toolchain.toml | awk -F\" '{print $2}')"
|
||||
RUST_TOOLCHAIN=$TOOLCHAIN setup_rust
|
||||
|
||||
@@ -67,13 +72,10 @@ function update_script() {
|
||||
mv ./target/release/server /usr/bin/scanopy-server
|
||||
msg_ok "Built scanopy-server"
|
||||
|
||||
[[ -f /etc/systemd/system/scanopy-daemon.service ]] &&
|
||||
fetch_and_deploy_gh_release "scanopy" "scanopy/scanopy" "singlefile" "latest" "/usr/local/bin" "scanopy-daemon-linux-amd64" &&
|
||||
rm -f /usr/bin/scanopy-daemon ~/configure_daemon.sh &&
|
||||
sed -i -e 's|usr/bin|usr/local/bin|' \
|
||||
-e 's/push/daemon_poll/' \
|
||||
-e 's/pull/server_poll/' /etc/systemd/system/scanopy-daemon.service &&
|
||||
systemctl daemon-reload
|
||||
msg_info "Building scanopy-daemon"
|
||||
$STD cargo build --release --bin daemon
|
||||
cp ./target/release/daemon /usr/bin/scanopy-daemon
|
||||
msg_ok "Built scanopy-daemon"
|
||||
|
||||
msg_info "Starting services"
|
||||
systemctl start scanopy-server
|
||||
|
||||
@@ -30,7 +30,8 @@ function update_script() {
|
||||
fi
|
||||
|
||||
msg_info "Updating ${APP}"
|
||||
ensure_dependencies twingate-connector
|
||||
$STD apt update
|
||||
$STD apt install -yq twingate-connector
|
||||
$STD systemctl restart twingate-connector
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
|
||||
@@ -32,7 +32,7 @@ function update_script() {
|
||||
|
||||
msg_info "Updating ${APP}"
|
||||
$STD apt update --allow-releaseinfo-change
|
||||
ensure_dependencies unifi
|
||||
$STD apt install -y unifi
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
}
|
||||
|
||||
@@ -30,9 +30,12 @@ function update_script() {
|
||||
|
||||
NODE_VERSION="22" setup_nodejs
|
||||
|
||||
ensure_dependencies chromium
|
||||
if [[ ! -L /opt/uptime-kuma/chromium ]]; then
|
||||
if ! dpkg -s chromium >/dev/null 2>&1; then
|
||||
msg_info "Installing Chromium"
|
||||
$STD apt update
|
||||
$STD apt install -y chromium
|
||||
ln -s /usr/bin/chromium /opt/uptime-kuma/chromium
|
||||
msg_ok "Installed Chromium"
|
||||
fi
|
||||
|
||||
if check_for_gh_release "uptime-kuma" "louislam/uptime-kuma"; then
|
||||
|
||||
@@ -28,8 +28,12 @@ function update_script() {
|
||||
exit
|
||||
fi
|
||||
|
||||
VAULT=$(get_latest_github_release "dani-garcia/vaultwarden")
|
||||
WVRELEASE=$(get_latest_github_release "dani-garcia/bw_web_builds")
|
||||
VAULT=$(curl -fsSL https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest |
|
||||
grep "tag_name" |
|
||||
awk '{print substr($2, 2, length($2)-3) }')
|
||||
WVRELEASE=$(curl -fsSL https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest |
|
||||
grep "tag_name" |
|
||||
awk '{print substr($2, 2, length($2)-3) }')
|
||||
|
||||
UPD=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "SUPPORT" --radiolist --cancel-button Exit-Script "Spacebar = Select" 11 58 3 \
|
||||
"1" "VaultWarden $VAULT" ON \
|
||||
@@ -38,70 +42,57 @@ function update_script() {
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
if [ "$UPD" == "1" ]; then
|
||||
if check_for_gh_release "vaultwarden" "dani-garcia/vaultwarden"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop vaultwarden
|
||||
msg_ok "Stopped Service"
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop vaultwarden
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
fetch_and_deploy_gh_release "vaultwarden" "dani-garcia/vaultwarden" "tarball" "latest" "/tmp/vaultwarden-src"
|
||||
|
||||
msg_info "Updating VaultWarden to $VAULT (Patience)"
|
||||
cd /tmp/vaultwarden-src
|
||||
$STD cargo build --features "sqlite,mysql,postgresql" --release
|
||||
if [[ -f /usr/bin/vaultwarden ]]; then
|
||||
cp target/release/vaultwarden /usr/bin/
|
||||
else
|
||||
cp target/release/vaultwarden /opt/vaultwarden/bin/
|
||||
fi
|
||||
cd ~ && rm -rf /tmp/vaultwarden-src
|
||||
msg_ok "Updated VaultWarden to ${VAULT}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start vaultwarden
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
msg_info "Updating VaultWarden to $VAULT (Patience)"
|
||||
cd ~ && rm -rf vaultwarden
|
||||
$STD git clone https://github.com/dani-garcia/vaultwarden
|
||||
cd vaultwarden
|
||||
$STD cargo build --features "sqlite,mysql,postgresql" --release
|
||||
DIR=/usr/bin/vaultwarden
|
||||
if [ -d "$DIR" ]; then
|
||||
cp target/release/vaultwarden /usr/bin/
|
||||
else
|
||||
msg_ok "VaultWarden is already up-to-date"
|
||||
cp target/release/vaultwarden /opt/vaultwarden/bin/
|
||||
fi
|
||||
cd ~ && rm -rf vaultwarden
|
||||
msg_ok "Updated VaultWarden"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start vaultwarden
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ "$UPD" == "2" ]; then
|
||||
if check_for_gh_release "vaultwarden_webvault" "dani-garcia/bw_web_builds"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop vaultwarden
|
||||
msg_ok "Stopped Service"
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop vaultwarden
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
msg_info "Updating Web-Vault to $WVRELEASE"
|
||||
rm -rf /opt/vaultwarden/web-vault
|
||||
mkdir -p /opt/vaultwarden/web-vault
|
||||
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"
|
||||
|
||||
fetch_and_deploy_gh_release "vaultwarden_webvault" "dani-garcia/bw_web_builds" "prebuild" "latest" "/opt/vaultwarden/web-vault" "bw_web_*.tar.gz"
|
||||
|
||||
chown -R root:root /opt/vaultwarden/web-vault/
|
||||
msg_ok "Updated Web-Vault to ${WVRELEASE}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start vaultwarden
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "Web-Vault is already up-to-date"
|
||||
fi
|
||||
msg_info "Starting Service"
|
||||
systemctl start vaultwarden
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ "$UPD" == "3" ]; then
|
||||
if NEWTOKEN=$(whiptail --backtitle "Proxmox VE Helper Scripts" --passwordbox "Set the ADMIN_TOKEN" 10 58 3>&1 1>&2 2>&3); then
|
||||
if [[ -z "$NEWTOKEN" ]]; then exit; fi
|
||||
ensure_dependencies argon2
|
||||
if ! command -v argon2 >/dev/null 2>&1; then $STD apt-get install -y argon2; fi
|
||||
TOKEN=$(echo -n "${NEWTOKEN}" | argon2 "$(openssl rand -base64 32)" -t 2 -m 16 -p 4 -l 64 -e)
|
||||
sed -i "s|ADMIN_TOKEN=.*|ADMIN_TOKEN='${TOKEN}'|" /opt/vaultwarden/.env
|
||||
if [[ -f /opt/vaultwarden/data/config.json ]]; then
|
||||
sed -i "s|\"admin_token\":.*|\"admin_token\": \"${TOKEN}\"|" /opt/vaultwarden/data/config.json
|
||||
fi
|
||||
systemctl restart vaultwarden
|
||||
msg_ok "Admin token updated"
|
||||
fi
|
||||
exit
|
||||
fi
|
||||
|
||||
@@ -27,7 +27,10 @@ function update_script() {
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
ensure_dependencies zstd
|
||||
if ! [[ $(dpkg -s zstd 2>/dev/null) ]]; then
|
||||
$STD apt update
|
||||
$STD apt install -y zstd
|
||||
fi
|
||||
RELEASE=$(curl -fsSL https://api.github.com/repos/matze/wastebin/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
|
||||
# Dirty-Fix 03/2025 for missing APP_version.txt on old installations, set to pre-latest release
|
||||
msg_info "Running Migration"
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: CrazyWolf13
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://wealthfolio.app/
|
||||
|
||||
APP="Wealthfolio"
|
||||
var_tags="${var_tags:-finance;portfolio}"
|
||||
var_cpu="${var_cpu:-4}"
|
||||
var_ram="${var_ram:-4096}"
|
||||
var_disk="${var_disk:-10}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
|
||||
if [[ ! -d /opt/wealthfolio ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "wealthfolio" "afadil/wealthfolio"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop wealthfolio
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
msg_info "Backing up Data"
|
||||
cp -r /opt/wealthfolio_data /opt/wealthfolio_data_backup
|
||||
cp /opt/wealthfolio/.env /opt/wealthfolio_env_backup
|
||||
msg_ok "Backed up Data"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "wealthfolio" "afadil/wealthfolio" "tarball"
|
||||
|
||||
msg_info "Building Frontend (patience)"
|
||||
cd /opt/wealthfolio
|
||||
$STD pnpm install --frozen-lockfile
|
||||
$STD pnpm tsc
|
||||
$STD pnpm vite build
|
||||
msg_ok "Built Frontend"
|
||||
|
||||
msg_info "Building Backend (patience)"
|
||||
cd /opt/wealthfolio/src-server
|
||||
source ~/.cargo/env
|
||||
$STD cargo build --release --manifest-path Cargo.toml
|
||||
cp /opt/wealthfolio/src-server/target/release/wealthfolio-server /usr/local/bin/wealthfolio-server
|
||||
chmod +x /usr/local/bin/wealthfolio-server
|
||||
msg_ok "Built Backend"
|
||||
|
||||
msg_info "Restoring Data"
|
||||
cp -r /opt/wealthfolio_data_backup/. /opt/wealthfolio_data
|
||||
cp /opt/wealthfolio_env_backup /opt/wealthfolio/.env
|
||||
rm -rf /opt/wealthfolio_data_backup /opt/wealthfolio_env_backup
|
||||
msg_ok "Restored Data"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /opt/wealthfolio/src-server/target
|
||||
rm -rf /root/.cargo/registry
|
||||
rm -rf /opt/wealthfolio/node_modules
|
||||
msg_ok "Cleaned Up"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start wealthfolio
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}"
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-02-04T06:18:03Z",
|
||||
"generated": "2026-02-02T06:25:10Z",
|
||||
"versions": [
|
||||
{
|
||||
"slug": "2fauth",
|
||||
@@ -95,9 +95,9 @@
|
||||
{
|
||||
"slug": "bar-assistant",
|
||||
"repo": "karlomikus/bar-assistant",
|
||||
"version": "v5.13.1",
|
||||
"version": "v5.13.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-02T18:47:43Z"
|
||||
"date": "2026-02-01T15:49:21Z"
|
||||
},
|
||||
{
|
||||
"slug": "bazarr",
|
||||
@@ -109,9 +109,9 @@
|
||||
{
|
||||
"slug": "bentopdf",
|
||||
"repo": "alam00000/bentopdf",
|
||||
"version": "v2.1.0",
|
||||
"version": "v2.0.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-02T14:30:55Z"
|
||||
"date": "2026-01-31T10:13:47Z"
|
||||
},
|
||||
{
|
||||
"slug": "beszel",
|
||||
@@ -158,9 +158,9 @@
|
||||
{
|
||||
"slug": "bytestash",
|
||||
"repo": "jordan-dalby/ByteStash",
|
||||
"version": "v1.5.11",
|
||||
"version": "v1.5.10",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T22:12:19Z"
|
||||
"date": "2026-01-26T14:07:59Z"
|
||||
},
|
||||
{
|
||||
"slug": "caddy",
|
||||
@@ -186,9 +186,9 @@
|
||||
{
|
||||
"slug": "comfyui",
|
||||
"repo": "comfyanonymous/ComfyUI",
|
||||
"version": "v0.12.2",
|
||||
"version": "v0.11.1",
|
||||
"pinned": false,
|
||||
"date": "2026-02-04T06:09:31Z"
|
||||
"date": "2026-01-29T07:52:21Z"
|
||||
},
|
||||
{
|
||||
"slug": "commafeed",
|
||||
@@ -256,9 +256,9 @@
|
||||
{
|
||||
"slug": "docmost",
|
||||
"repo": "docmost/docmost",
|
||||
"version": "v0.25.0",
|
||||
"version": "v0.24.1",
|
||||
"pinned": false,
|
||||
"date": "2026-02-04T00:33:45Z"
|
||||
"date": "2025-12-14T13:49:16Z"
|
||||
},
|
||||
{
|
||||
"slug": "domain-locker",
|
||||
@@ -291,9 +291,9 @@
|
||||
{
|
||||
"slug": "elementsynapse",
|
||||
"repo": "etkecc/synapse-admin",
|
||||
"version": "v0.11.1-etke53",
|
||||
"version": "v0.11.1-etke52",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T20:38:15Z"
|
||||
"date": "2026-01-09T08:41:29Z"
|
||||
},
|
||||
{
|
||||
"slug": "emby",
|
||||
@@ -312,9 +312,9 @@
|
||||
{
|
||||
"slug": "ersatztv",
|
||||
"repo": "ErsatzTV/ErsatzTV",
|
||||
"version": "v26.2.0",
|
||||
"version": "v26.1.1",
|
||||
"pinned": false,
|
||||
"date": "2026-02-02T20:54:26Z"
|
||||
"date": "2026-01-08T22:02:15Z"
|
||||
},
|
||||
{
|
||||
"slug": "excalidraw",
|
||||
@@ -375,9 +375,9 @@
|
||||
{
|
||||
"slug": "ghostfolio",
|
||||
"repo": "ghostfolio/ghostfolio",
|
||||
"version": "2.235.0",
|
||||
"version": "2.234.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T19:27:17Z"
|
||||
"date": "2026-01-30T19:00:22Z"
|
||||
},
|
||||
{
|
||||
"slug": "gitea",
|
||||
@@ -515,9 +515,9 @@
|
||||
{
|
||||
"slug": "huntarr",
|
||||
"repo": "plexguide/Huntarr.io",
|
||||
"version": "9.1.9.1",
|
||||
"version": "9.1.8",
|
||||
"pinned": false,
|
||||
"date": "2026-02-04T01:08:22Z"
|
||||
"date": "2026-02-02T01:29:45Z"
|
||||
},
|
||||
{
|
||||
"slug": "inspircd",
|
||||
@@ -536,16 +536,16 @@
|
||||
{
|
||||
"slug": "invoiceninja",
|
||||
"repo": "invoiceninja/invoiceninja",
|
||||
"version": "v5.12.53",
|
||||
"version": "v5.12.52",
|
||||
"pinned": false,
|
||||
"date": "2026-02-04T00:52:01Z"
|
||||
"date": "2026-02-01T02:08:10Z"
|
||||
},
|
||||
{
|
||||
"slug": "jackett",
|
||||
"repo": "Jackett/Jackett",
|
||||
"version": "v0.24.1027",
|
||||
"version": "v0.24.1008",
|
||||
"pinned": false,
|
||||
"date": "2026-02-04T05:56:22Z"
|
||||
"date": "2026-02-02T05:55:21Z"
|
||||
},
|
||||
{
|
||||
"slug": "joplin-server",
|
||||
@@ -596,13 +596,6 @@
|
||||
"pinned": false,
|
||||
"date": "2026-01-31T18:10:59Z"
|
||||
},
|
||||
{
|
||||
"slug": "kitchenowl",
|
||||
"repo": "TomBursch/kitchenowl",
|
||||
"version": "v0.7.6",
|
||||
"pinned": false,
|
||||
"date": "2026-01-24T01:21:14Z"
|
||||
},
|
||||
{
|
||||
"slug": "koel",
|
||||
"repo": "koel/koel",
|
||||
@@ -669,9 +662,9 @@
|
||||
{
|
||||
"slug": "libretranslate",
|
||||
"repo": "LibreTranslate/LibreTranslate",
|
||||
"version": "v1.8.4",
|
||||
"version": "v1.8.3",
|
||||
"pinned": false,
|
||||
"date": "2026-02-02T17:45:16Z"
|
||||
"date": "2025-12-04T21:07:00Z"
|
||||
},
|
||||
{
|
||||
"slug": "lidarr",
|
||||
@@ -746,9 +739,9 @@
|
||||
{
|
||||
"slug": "mealie",
|
||||
"repo": "mealie-recipes/mealie",
|
||||
"version": "v3.10.1",
|
||||
"version": "v3.9.2",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T01:04:38Z"
|
||||
"date": "2026-01-02T19:40:09Z"
|
||||
},
|
||||
{
|
||||
"slug": "mediamanager",
|
||||
@@ -767,9 +760,9 @@
|
||||
{
|
||||
"slug": "meilisearch",
|
||||
"repo": "riccox/meilisearch-ui",
|
||||
"version": "v0.15.1",
|
||||
"version": "v0.15.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-04T03:56:59Z"
|
||||
"date": "2026-01-29T03:54:27Z"
|
||||
},
|
||||
{
|
||||
"slug": "memos",
|
||||
@@ -781,9 +774,9 @@
|
||||
{
|
||||
"slug": "metube",
|
||||
"repo": "alexta69/metube",
|
||||
"version": "2026.02.03",
|
||||
"version": "2026.02.01",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T21:49:49Z"
|
||||
"date": "2026-02-01T00:20:00Z"
|
||||
},
|
||||
{
|
||||
"slug": "miniflux",
|
||||
@@ -823,16 +816,16 @@
|
||||
{
|
||||
"slug": "navidrome",
|
||||
"repo": "navidrome/navidrome",
|
||||
"version": "v0.60.0",
|
||||
"version": "v0.59.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T18:57:04Z"
|
||||
"date": "2025-12-06T18:08:42Z"
|
||||
},
|
||||
{
|
||||
"slug": "netbox",
|
||||
"repo": "netbox-community/netbox",
|
||||
"version": "v4.5.2",
|
||||
"version": "v4.5.1",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T13:54:26Z"
|
||||
"date": "2026-01-20T19:45:05Z"
|
||||
},
|
||||
{
|
||||
"slug": "nocodb",
|
||||
@@ -879,9 +872,9 @@
|
||||
{
|
||||
"slug": "opengist",
|
||||
"repo": "thomiceli/opengist",
|
||||
"version": "v1.12.1",
|
||||
"version": "v1.12.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T09:00:43Z"
|
||||
"date": "2026-01-27T15:31:57Z"
|
||||
},
|
||||
{
|
||||
"slug": "ots",
|
||||
@@ -1054,9 +1047,9 @@
|
||||
{
|
||||
"slug": "prometheus-alertmanager",
|
||||
"repo": "prometheus/alertmanager",
|
||||
"version": "v0.31.0",
|
||||
"version": "v0.30.1",
|
||||
"pinned": false,
|
||||
"date": "2026-02-02T13:34:15Z"
|
||||
"date": "2026-01-12T23:30:06Z"
|
||||
},
|
||||
{
|
||||
"slug": "prometheus-blackbox-exporter",
|
||||
@@ -1184,13 +1177,6 @@
|
||||
"pinned": false,
|
||||
"date": "2026-01-12T05:38:30Z"
|
||||
},
|
||||
{
|
||||
"slug": "rustypaste",
|
||||
"repo": "orhun/rustypaste",
|
||||
"version": "v0.16.1",
|
||||
"pinned": false,
|
||||
"date": "2025-03-21T20:44:47Z"
|
||||
},
|
||||
{
|
||||
"slug": "sabnzbd",
|
||||
"repo": "sabnzbd/sabnzbd",
|
||||
@@ -1201,9 +1187,9 @@
|
||||
{
|
||||
"slug": "scanopy",
|
||||
"repo": "scanopy/scanopy",
|
||||
"version": "v0.14.3",
|
||||
"version": "v0.14.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-04T01:41:01Z"
|
||||
"date": "2026-02-01T17:02:37Z"
|
||||
},
|
||||
{
|
||||
"slug": "scraparr",
|
||||
@@ -1271,16 +1257,16 @@
|
||||
{
|
||||
"slug": "speedtest-tracker",
|
||||
"repo": "alexjustesen/speedtest-tracker",
|
||||
"version": "v1.13.6",
|
||||
"version": "v1.13.5",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T21:20:51Z"
|
||||
"date": "2026-01-08T22:35:28Z"
|
||||
},
|
||||
{
|
||||
"slug": "spoolman",
|
||||
"repo": "Donkie/Spoolman",
|
||||
"version": "v0.23.1",
|
||||
"version": "v0.23.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T19:03:55Z"
|
||||
"date": "2026-01-23T20:42:34Z"
|
||||
},
|
||||
{
|
||||
"slug": "sportarr",
|
||||
@@ -1355,9 +1341,9 @@
|
||||
{
|
||||
"slug": "thingsboard",
|
||||
"repo": "thingsboard/thingsboard",
|
||||
"version": "v4.3.0.1",
|
||||
"version": "v4.3",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T12:39:14Z"
|
||||
"date": "2026-01-20T14:27:07Z"
|
||||
},
|
||||
{
|
||||
"slug": "threadfin",
|
||||
@@ -1425,9 +1411,9 @@
|
||||
{
|
||||
"slug": "tunarr",
|
||||
"repo": "chrisbenincasa/tunarr",
|
||||
"version": "v1.1.12",
|
||||
"version": "v1.1.11",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T20:19:00Z"
|
||||
"date": "2026-01-30T22:34:30Z"
|
||||
},
|
||||
{
|
||||
"slug": "uhf",
|
||||
@@ -1471,19 +1457,12 @@
|
||||
"pinned": false,
|
||||
"date": "2025-10-22T17:03:54Z"
|
||||
},
|
||||
{
|
||||
"slug": "vaultwarden",
|
||||
"repo": "dani-garcia/vaultwarden",
|
||||
"version": "1.35.2",
|
||||
"pinned": false,
|
||||
"date": "2026-01-09T18:37:04Z"
|
||||
},
|
||||
{
|
||||
"slug": "victoriametrics",
|
||||
"repo": "VictoriaMetrics/VictoriaMetrics",
|
||||
"version": "v1.135.0",
|
||||
"version": "v1.134.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-02T14:20:15Z"
|
||||
"date": "2026-01-19T13:29:43Z"
|
||||
},
|
||||
{
|
||||
"slug": "vikunja",
|
||||
@@ -1509,9 +1488,9 @@
|
||||
{
|
||||
"slug": "wanderer",
|
||||
"repo": "meilisearch/meilisearch",
|
||||
"version": "v1.35.0",
|
||||
"version": "v1.34.3",
|
||||
"pinned": false,
|
||||
"date": "2026-02-02T09:57:03Z"
|
||||
"date": "2026-01-28T17:52:24Z"
|
||||
},
|
||||
{
|
||||
"slug": "warracker",
|
||||
@@ -1541,13 +1520,6 @@
|
||||
"pinned": false,
|
||||
"date": "2025-12-31T16:53:34Z"
|
||||
},
|
||||
{
|
||||
"slug": "wealthfolio",
|
||||
"repo": "afadil/wealthfolio",
|
||||
"version": "v2.1.0",
|
||||
"pinned": false,
|
||||
"date": "2025-12-01T21:57:36Z"
|
||||
},
|
||||
{
|
||||
"slug": "web-check",
|
||||
"repo": "CrazyWolf13/web-check",
|
||||
@@ -1614,9 +1586,9 @@
|
||||
{
|
||||
"slug": "zwave-js-ui",
|
||||
"repo": "zwave-js/zwave-js-ui",
|
||||
"version": "v11.11.0",
|
||||
"version": "v11.10.1",
|
||||
"pinned": false,
|
||||
"date": "2026-02-03T13:13:05Z"
|
||||
"date": "2026-01-15T15:58:06Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
"website": "https://nginxproxymanager.com/",
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/nginx-proxy-manager.webp",
|
||||
"config_path": "",
|
||||
"disable": true,
|
||||
"disable_description": "This script is temporarily disabled due to an external issue with the OpenResty APT repository. The repository's GPG key uses SHA-1 signatures, which are no longer accepted by Debian as of February 1, 2026. This causes installation to fail with APT errors. The issue is tracked in openresty/openresty#1097. A workaround exists but requires manual configuration. The script will be re-enabled once OpenResty updates their repository signing key. For more details, see: https://github.com/community-scripts/ProxmoxVE/issues/11406",
|
||||
"description": "Nginx Proxy Manager is a tool that provides a web-based interface to manage Nginx reverse proxies. It enables users to easily and securely expose their services to the internet by providing features such as HTTPS encryption, domain mapping, and access control. It eliminates the need for manual configuration of Nginx reverse proxies, making it easy for users to quickly and securely expose their services to the public.",
|
||||
"install_methods": [
|
||||
{
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
{
|
||||
"name": "RustyPaste",
|
||||
"slug": "rustypaste",
|
||||
"categories": [
|
||||
11
|
||||
],
|
||||
"date_created": "2026-02-02",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 8000,
|
||||
"documentation": "https://github.com/orhun/rustypaste",
|
||||
"config_path": "/opt/rustypaste/config.toml",
|
||||
"website": "https://github.com/orhun/rustypaste",
|
||||
"logo": "https://github.com/orhun/rustypaste/raw/master/img/rustypaste_logo.png",
|
||||
"description": "Rustypaste is a minimal file upload/pastebin service.",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/rustypaste.sh",
|
||||
"resources": {
|
||||
"cpu": 1,
|
||||
"ram": 1024,
|
||||
"hdd": 20,
|
||||
"os": "Debian",
|
||||
"version": "13"
|
||||
}
|
||||
"name": "RustyPaste",
|
||||
"slug": "rustypaste",
|
||||
"categories": [
|
||||
11
|
||||
],
|
||||
"date_created": "2025-12-22",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 8000,
|
||||
"documentation": "https://github.com/orhun/rustypaste",
|
||||
"config_path": "/opt/rustypaste/config.toml",
|
||||
"website": "https://github.com/orhun/rustypaste",
|
||||
"logo": "https://github.com/orhun/rustypaste/raw/master/img/rustypaste_logo.png",
|
||||
"description": "Rustypaste is a minimal file upload/pastebin service.",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/rustypaste.sh",
|
||||
"resources": {
|
||||
"cpu": 1,
|
||||
"ram": 1024,
|
||||
"hdd": 20,
|
||||
"os": "Debian",
|
||||
"version": "13"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "alpine",
|
||||
"script": "ct/alpine-rustypaste.sh",
|
||||
"resources": {
|
||||
"cpu": 1,
|
||||
"ram": 256,
|
||||
"hdd": 4,
|
||||
"os": "Alpine",
|
||||
"version": "3.23"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": null,
|
||||
"password": null
|
||||
},
|
||||
{
|
||||
"type": "alpine",
|
||||
"script": "ct/alpine-rustypaste.sh",
|
||||
"resources": {
|
||||
"cpu": 1,
|
||||
"ram": 256,
|
||||
"hdd": 4,
|
||||
"os": "Alpine",
|
||||
"version": "3.23"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": null,
|
||||
"password": null
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "When updating the script it will backup the whole project including all the uploaded files, make sure to extract it to a safe location or remove",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
"notes": [
|
||||
{
|
||||
"text": "When updating the script it will backup the whole project including all the uploaded files, make sure to extract it to a safe location or remove",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"type": "info"
|
||||
},
|
||||
{
|
||||
"text": "The integrated daemon config is located at `/root/.config/daemon/`",
|
||||
"text": "The integrated daemon config is located at `/root/.config/daemon/config.json`",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"name": "Wealthfolio",
|
||||
"slug": "wealthfolio",
|
||||
"categories": [
|
||||
23
|
||||
],
|
||||
"date_created": "2026-02-03",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 8080,
|
||||
"documentation": "https://wealthfolio.app/docs/introduction/",
|
||||
"config_path": "/opt/wealthfolio/.env",
|
||||
"website": "https://wealthfolio.app/",
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/wealthfolio.webp",
|
||||
"description": "Wealthfolio is a beautiful, privacy-focused investment tracker with local data storage. Track your portfolio across multiple accounts and asset types with detailed performance analytics, goal planning, and multi-currency support.",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/wealthfolio.sh",
|
||||
"resources": {
|
||||
"cpu": 4,
|
||||
"ram": 4096,
|
||||
"hdd": 10,
|
||||
"os": "Debian",
|
||||
"version": "13"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": null,
|
||||
"password": "See ~/wealthfolio.creds"
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "Login password is stored in ~/wealthfolio.creds",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
||||
320
frontend/src/app/category-view/page.tsx
Normal file
320
frontend/src/app/category-view/page.tsx
Normal file
@@ -0,0 +1,320 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import type { Category } from "@/lib/types";
|
||||
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
const defaultLogo = "/default-logo.png"; // Fallback logo path
|
||||
const MAX_DESCRIPTION_LENGTH = 100; // Set max length for description
|
||||
const MAX_LOGOS = 5; // Max logos to display at once
|
||||
|
||||
function formattedBadge(type: string) {
|
||||
switch (type) {
|
||||
case "vm":
|
||||
return <Badge className="text-blue-500/75 border-blue-500/75 badge">VM</Badge>;
|
||||
case "ct":
|
||||
return <Badge className="text-yellow-500/75 border-yellow-500/75 badge">LXC</Badge>;
|
||||
case "pve":
|
||||
return <Badge className="text-orange-500/75 border-orange-500/75 badge">PVE</Badge>;
|
||||
case "addon":
|
||||
return <Badge className="text-green-500/75 border-green-500/75 badge">ADDON</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function CategoryView() {
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [selectedCategoryIndex, setSelectedCategoryIndex] = useState<number | null>(null);
|
||||
const [currentScripts, setCurrentScripts] = useState<any[]>([]);
|
||||
const [logoIndices, setLogoIndices] = useState<{ [key: string]: number }>({});
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
// eslint-disable-next-line node/no-process-env
|
||||
const basePath = process.env.NODE_ENV === "production" ? "/ProxmoxVE" : "";
|
||||
const response = await fetch(`${basePath}/api/categories`);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch categories");
|
||||
}
|
||||
const data = await response.json();
|
||||
setCategories(data);
|
||||
|
||||
// Initialize logo indices
|
||||
const initialLogoIndices: { [key: string]: number } = {};
|
||||
data.forEach((category: any) => {
|
||||
initialLogoIndices[category.name] = 0;
|
||||
});
|
||||
setLogoIndices(initialLogoIndices);
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error fetching categories:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchCategories();
|
||||
}, []);
|
||||
|
||||
const handleCategoryClick = (index: number) => {
|
||||
setSelectedCategoryIndex(index);
|
||||
setCurrentScripts(categories[index]?.scripts || []); // Update scripts for the selected category
|
||||
};
|
||||
|
||||
const handleBackClick = () => {
|
||||
setSelectedCategoryIndex(null);
|
||||
setCurrentScripts([]); // Clear scripts when going back
|
||||
};
|
||||
|
||||
const handleScriptClick = (scriptSlug: string) => {
|
||||
// Include category context when navigating to scripts
|
||||
const categoryName = selectedCategoryIndex !== null ? categories[selectedCategoryIndex]?.name : null;
|
||||
const queryParams = new URLSearchParams({ id: scriptSlug });
|
||||
if (categoryName) {
|
||||
queryParams.append("category", categoryName);
|
||||
}
|
||||
router.push(`/scripts?${queryParams.toString()}`);
|
||||
};
|
||||
|
||||
const navigateCategory = (direction: "prev" | "next") => {
|
||||
if (selectedCategoryIndex !== null) {
|
||||
const newIndex
|
||||
= direction === "prev"
|
||||
? (selectedCategoryIndex - 1 + categories.length) % categories.length
|
||||
: (selectedCategoryIndex + 1) % categories.length;
|
||||
setSelectedCategoryIndex(newIndex);
|
||||
setCurrentScripts(categories[newIndex]?.scripts || []); // Update scripts for the new category
|
||||
}
|
||||
};
|
||||
|
||||
const switchLogos = (categoryName: string, direction: "prev" | "next") => {
|
||||
setLogoIndices((prev) => {
|
||||
const currentIndex = prev[categoryName] || 0;
|
||||
const category = categories.find(cat => cat.name === categoryName);
|
||||
if (!category || !category.scripts)
|
||||
return prev;
|
||||
|
||||
const totalLogos = category.scripts.length;
|
||||
const newIndex
|
||||
= direction === "prev"
|
||||
? (currentIndex - MAX_LOGOS + totalLogos) % totalLogos
|
||||
: (currentIndex + MAX_LOGOS) % totalLogos;
|
||||
|
||||
return { ...prev, [categoryName]: newIndex };
|
||||
});
|
||||
};
|
||||
|
||||
const truncateDescription = (text: string) => {
|
||||
return text.length > MAX_DESCRIPTION_LENGTH ? `${text.slice(0, MAX_DESCRIPTION_LENGTH)}...` : text;
|
||||
};
|
||||
|
||||
const renderResources = (script: any) => {
|
||||
const cpu = script.install_methods[0]?.resources.cpu;
|
||||
const ram = script.install_methods[0]?.resources.ram;
|
||||
const hdd = script.install_methods[0]?.resources.hdd;
|
||||
|
||||
const resourceParts = [];
|
||||
if (cpu) {
|
||||
resourceParts.push(
|
||||
<span key="cpu">
|
||||
<b>CPU:</b>
|
||||
{" "}
|
||||
{cpu}
|
||||
vCPU
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
if (ram) {
|
||||
resourceParts.push(
|
||||
<span key="ram">
|
||||
<b>RAM:</b>
|
||||
{" "}
|
||||
{ram}
|
||||
MB
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
if (hdd) {
|
||||
resourceParts.push(
|
||||
<span key="hdd">
|
||||
<b>HDD:</b>
|
||||
{" "}
|
||||
{hdd}
|
||||
GB
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
|
||||
return resourceParts.length > 0
|
||||
? (
|
||||
<div className="text-sm text-gray-400">
|
||||
{resourceParts.map((part, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{part}
|
||||
{index < resourceParts.length - 1 && " | "}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
: null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 mt-20">
|
||||
{categories.length === 0 && (
|
||||
<p className="text-center text-gray-500">No categories available. Please check the API endpoint.</p>
|
||||
)}
|
||||
{selectedCategoryIndex !== null
|
||||
? (
|
||||
<div>
|
||||
{/* Header with Navigation */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => navigateCategory("prev")}
|
||||
className="p-2 transition-transform duration-300 hover:scale-105"
|
||||
>
|
||||
<ChevronLeft className="h-6 w-6" />
|
||||
</Button>
|
||||
<h2 className="text-3xl font-semibold transition-opacity duration-300 hover:opacity-90">
|
||||
{categories[selectedCategoryIndex].name}
|
||||
</h2>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => navigateCategory("next")}
|
||||
className="p-2 transition-transform duration-300 hover:scale-105"
|
||||
>
|
||||
<ChevronRight className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Scripts Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
||||
{currentScripts
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map(script => (
|
||||
<Card
|
||||
key={script.name}
|
||||
className="p-4 cursor-pointer hover:shadow-md transition-shadow duration-300"
|
||||
onClick={() => handleScriptClick(script.slug)}
|
||||
>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<h3 className="text-lg font-bold script-text text-center hover:text-blue-600 transition-colors duration-300">
|
||||
{script.name}
|
||||
</h3>
|
||||
<img
|
||||
src={script.logo || defaultLogo}
|
||||
alt={script.name || "Script logo"}
|
||||
className="h-12 w-12 object-contain mx-auto"
|
||||
/>
|
||||
<p className="text-sm text-gray-500 text-center">
|
||||
<b>Created at:</b>
|
||||
{" "}
|
||||
{script.date_created || "No date available"}
|
||||
</p>
|
||||
<p
|
||||
className="text-sm text-gray-700 hover:text-gray-900 text-center transition-colors duration-300"
|
||||
title={script.description || "No description available."}
|
||||
>
|
||||
{truncateDescription(script.description || "No description available.")}
|
||||
</p>
|
||||
{renderResources(script)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Back to Categories Button */}
|
||||
<div className="mt-8 text-center">
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={handleBackClick}
|
||||
className="px-6 py-2 text-white bg-blue-600 hover:bg-blue-700 rounded-lg shadow-md transition-transform duration-300 hover:scale-105"
|
||||
>
|
||||
Back to Categories
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div>
|
||||
{/* Categories Grid */}
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<h1 className="text-3xl font-semibold mb-4">Categories</h1>
|
||||
<p className="text-sm text-gray-500">
|
||||
{categories.reduce((total, category) => total + (category.scripts?.length || 0), 0)}
|
||||
{" "}
|
||||
Total scripts
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8">
|
||||
{categories.map((category, index) => (
|
||||
<Card
|
||||
key={category.name}
|
||||
onClick={() => handleCategoryClick(index)}
|
||||
className="cursor-pointer hover:shadow-lg flex flex-col items-center justify-center py-6 transition-shadow duration-300"
|
||||
>
|
||||
<CardContent className="flex flex-col items-center">
|
||||
<h3 className="text-xl font-bold mb-4 category-title transition-colors duration-300 hover:text-blue-600">
|
||||
{category.name}
|
||||
</h3>
|
||||
<div className="flex justify-center items-center gap-2 mb-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
switchLogos(category.name, "prev");
|
||||
}}
|
||||
className="p-1 transition-transform duration-300 hover:scale-110"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
{category.scripts
|
||||
&& category.scripts
|
||||
.slice(logoIndices[category.name] || 0, (logoIndices[category.name] || 0) + MAX_LOGOS)
|
||||
.map((script, i) => (
|
||||
<div key={i} className="flex flex-col items-center">
|
||||
<img
|
||||
src={script.logo || defaultLogo}
|
||||
alt={script.name || "Script logo"}
|
||||
title={script.name}
|
||||
className="h-8 w-8 object-contain cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleScriptClick(script.slug);
|
||||
}}
|
||||
/>
|
||||
{formattedBadge(script.type)}
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
switchLogos(category.name, "next");
|
||||
}}
|
||||
className="p-1 transition-transform duration-300 hover:scale-110"
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400 text-center">
|
||||
{(category as any).description || "No description available."}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CategoryView;
|
||||
@@ -94,8 +94,7 @@ const chartConfigApps = {
|
||||
export default function DataPage() {
|
||||
const [data, setData] = useState<DataModel[]>([]);
|
||||
const [summary, setSummary] = useState<SummaryData | null>(null);
|
||||
const [summaryLoading, setSummaryLoading] = useState<boolean>(true);
|
||||
const [dataLoading, setDataLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(25);
|
||||
@@ -106,40 +105,35 @@ export default function DataPage() {
|
||||
|
||||
const nf = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 });
|
||||
|
||||
// Fetch summary only once on mount
|
||||
useEffect(() => {
|
||||
const fetchSummary = async () => {
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const summaryRes = await fetch("https://api.htl-braunau.at/data/summary");
|
||||
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}`);
|
||||
}
|
||||
const summaryData: SummaryData = await summaryRes.json();
|
||||
setSummary(summaryData);
|
||||
} catch (err) {
|
||||
setError((err as Error).message);
|
||||
} finally {
|
||||
setSummaryLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSummary();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
setDataLoading(true);
|
||||
try {
|
||||
const dataRes = await fetch(`https://api.htl-braunau.at/data/paginated?page=${currentPage}&limit=${itemsPerPage}`);
|
||||
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);
|
||||
} catch (err) {
|
||||
setError((err as Error).message);
|
||||
} finally {
|
||||
setDataLoading(false);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -312,7 +306,7 @@ export default function DataPage() {
|
||||
</CardHeader>
|
||||
<CardContent className="pl-2">
|
||||
<div className="h-[300px] w-full">
|
||||
{summaryLoading ? (
|
||||
{loading ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
@@ -417,7 +411,7 @@ export default function DataPage() {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{dataLoading ? (
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="h-24 text-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
@@ -484,7 +478,7 @@ export default function DataPage() {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}
|
||||
disabled={currentPage === 1 || dataLoading}
|
||||
disabled={currentPage === 1 || loading}
|
||||
>
|
||||
<ChevronLeft className="mr-2 h-4 w-4" />
|
||||
Previous
|
||||
@@ -494,7 +488,7 @@ export default function DataPage() {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setCurrentPage((prev) => prev + 1)}
|
||||
disabled={dataLoading || sortedData.length < itemsPerPage}
|
||||
disabled={loading || sortedData.length < itemsPerPage}
|
||||
>
|
||||
Next
|
||||
<ChevronRight className="ml-2 h-4 w-4" />
|
||||
|
||||
@@ -105,7 +105,7 @@ function Note({
|
||||
const addNote = useCallback(() => {
|
||||
setScript({
|
||||
...script,
|
||||
notes: [...script.notes, { text: "", type: "info" }],
|
||||
notes: [...script.notes, { text: "", type: "" }],
|
||||
});
|
||||
}, [script, setScript]);
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { AlertColors } from "@/config/site-config";
|
||||
|
||||
export const InstallMethodSchema = z.object({
|
||||
type: z.enum(["default", "alpine"], {
|
||||
@@ -17,9 +16,7 @@ export const InstallMethodSchema = z.object({
|
||||
|
||||
const NoteSchema = z.object({
|
||||
text: z.string().min(1, "Note text cannot be empty"),
|
||||
type: z.enum(Object.keys(AlertColors) as [keyof typeof AlertColors, ...(keyof typeof AlertColors)[]], {
|
||||
message: `Type must be one of: ${Object.keys(AlertColors).join(", ")}`,
|
||||
}),
|
||||
type: z.string().min(1, "Note type cannot be empty"),
|
||||
});
|
||||
|
||||
export const ScriptSchema = z.object({
|
||||
@@ -45,7 +42,7 @@ export const ScriptSchema = z.object({
|
||||
username: z.string().nullable(),
|
||||
password: z.string().nullable(),
|
||||
}),
|
||||
notes: z.array(NoteSchema).optional().default([]),
|
||||
notes: z.array(NoteSchema),
|
||||
}).refine((data) => {
|
||||
if (data.disable === true && !data.disable_description) {
|
||||
return false;
|
||||
|
||||
@@ -18,7 +18,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { fetchCategories } from "@/lib/data";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -31,7 +30,6 @@ import Note from "./_components/note";
|
||||
|
||||
import { nord } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
import { ScriptItem } from "../scripts/_components/script-item";
|
||||
|
||||
const initialScript: Script = {
|
||||
name: "",
|
||||
@@ -62,7 +60,6 @@ export default function JSONGenerator() {
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [currentTab, setCurrentTab] = useState<"json" | "preview">("json");
|
||||
const [zodErrors, setZodErrors] = useState<z.ZodError | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -71,13 +68,6 @@ export default function JSONGenerator() {
|
||||
.catch((error) => console.error("Error fetching categories:", error));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isValid && currentTab === "preview") {
|
||||
setCurrentTab("json");
|
||||
toast.error("Switched to JSON tab due to invalid configuration.");
|
||||
}
|
||||
}, [isValid, currentTab]);
|
||||
|
||||
const updateScript = useCallback((key: keyof Script, value: Script[keyof Script]) => {
|
||||
setScript((prev) => {
|
||||
const updated = { ...prev, [key]: value };
|
||||
@@ -206,7 +196,7 @@ export default function JSONGenerator() {
|
||||
<Input
|
||||
placeholder="Path to config file"
|
||||
value={script.config_path || ""}
|
||||
onChange={(e) => updateScript("config_path", e.target.value || "")}
|
||||
onChange={(e) => updateScript("config_path", e.target.value || null)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -333,41 +323,25 @@ export default function JSONGenerator() {
|
||||
</form>
|
||||
</div>
|
||||
<div className="w-1/2 p-4 bg-background overflow-y-auto">
|
||||
<Tabs
|
||||
defaultValue="json"
|
||||
className="w-full"
|
||||
onValueChange={(value) => setCurrentTab(value as "json" | "preview")}
|
||||
value={currentTab}
|
||||
>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="json">JSON</TabsTrigger>
|
||||
<TabsTrigger disabled={!isValid} value="preview">Preview</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="json" className="h-full w-full">
|
||||
{validationAlert}
|
||||
<div className="relative">
|
||||
<div className="absolute right-2 top-2 flex gap-1">
|
||||
<Button size="icon" variant="outline" onClick={handleCopy}>
|
||||
{isCopied ? <Check className="h-4 w-4" /> : <Clipboard className="h-4 w-4" />}
|
||||
</Button>
|
||||
<Button size="icon" variant="outline" onClick={handleDownload}>
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
{validationAlert}
|
||||
<div className="relative">
|
||||
<div className="absolute right-2 top-2 flex gap-1">
|
||||
<Button size="icon" variant="outline" onClick={handleCopy}>
|
||||
{isCopied ? <Check className="h-4 w-4" /> : <Clipboard className="h-4 w-4" />}
|
||||
</Button>
|
||||
<Button size="icon" variant="outline" onClick={handleDownload}>
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<SyntaxHighlighter
|
||||
language="json"
|
||||
style={nord}
|
||||
className="mt-4 p-4 bg-secondary rounded shadow overflow-x-scroll"
|
||||
>
|
||||
{JSON.stringify(script, null, 2)}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="preview" className="h-full w-full">
|
||||
<ScriptItem item={script} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<SyntaxHighlighter
|
||||
language="json"
|
||||
style={nord}
|
||||
className="mt-4 p-4 bg-secondary rounded shadow overflow-x-scroll"
|
||||
>
|
||||
{JSON.stringify(script, null, 2)}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,8 +4,7 @@ import { X, HelpCircle } from "lucide-react";
|
||||
import { Suspense } from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { AppVersion } from "@/lib/types";
|
||||
import type { Script } from "@/app/json-editor/_schemas/schemas";
|
||||
import type { AppVersion, Script } from "@/lib/types";
|
||||
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
@@ -27,6 +26,7 @@ import Alerts from "./script-items/alerts";
|
||||
|
||||
type ScriptItemProps = {
|
||||
item: Script;
|
||||
setSelectedScript: (script: string | null) => void;
|
||||
};
|
||||
|
||||
function ScriptHeader({ item }: { item: Script }) {
|
||||
@@ -135,10 +135,25 @@ function VersionInfo({ item }: { item: Script }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function ScriptItem({ item }: ScriptItemProps) {
|
||||
export function ScriptItem({ item, setSelectedScript }: ScriptItemProps) {
|
||||
const closeScript = () => {
|
||||
window.history.pushState({}, document.title, window.location.pathname);
|
||||
setSelectedScript(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full mx-auto">
|
||||
<div className="flex w-full flex-col">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-foreground/90">Selected Script</h2>
|
||||
<button
|
||||
onClick={closeScript}
|
||||
className="rounded-full p-2 text-muted-foreground hover:bg-card/50 transition-colors"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-border bg-accent/30 backdrop-blur-sm shadow-sm">
|
||||
<div className="p-6 space-y-6">
|
||||
<Suspense fallback={<div className="animate-pulse h-32 bg-accent/20 rounded-xl" />}>
|
||||
@@ -147,7 +162,7 @@ export function ScriptItem({ item }: ScriptItemProps) {
|
||||
|
||||
{item.disable && item.disable_description && (
|
||||
<DisableDescription item={item} />
|
||||
)}
|
||||
) }
|
||||
|
||||
{!item.disable && (
|
||||
<>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
import { Loader2, X } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useQueryState } from "nuqs";
|
||||
|
||||
import type { Category, Script } from "@/lib/types";
|
||||
@@ -20,11 +20,6 @@ function ScriptContent() {
|
||||
const [item, setItem] = useState<Script>();
|
||||
const [latestPage, setLatestPage] = useState(1);
|
||||
|
||||
const closeScript = () => {
|
||||
window.history.pushState({}, document.title, window.location.pathname);
|
||||
setSelectedScript(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedScript && links.length > 0) {
|
||||
const script = links
|
||||
@@ -58,18 +53,7 @@ function ScriptContent() {
|
||||
<div className="px-4 w-full sm:max-w-[calc(100%-350px-16px)]">
|
||||
{selectedScript && item
|
||||
? (
|
||||
<div className="flex w-full flex-col">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-foreground/90">Selected Script</h2>
|
||||
<button
|
||||
onClick={closeScript}
|
||||
className="rounded-full p-2 text-muted-foreground hover:bg-card/50 transition-colors"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
<ScriptItem item={item} />
|
||||
</div>
|
||||
<ScriptItem item={item} setSelectedScript={setSelectedScript} />
|
||||
)
|
||||
: (
|
||||
<div className="flex w-full flex-col gap-5">
|
||||
|
||||
@@ -119,6 +119,7 @@ function MobileSidebar() {
|
||||
<p className="text-sm font-medium">Last Viewed</p>
|
||||
<ScriptItem
|
||||
item={lastViewedScript}
|
||||
setSelectedScript={isOnScriptsPage ? setSelectedScript : setTempSelectedScript}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -130,4 +131,3 @@ function MobileSidebar() {
|
||||
}
|
||||
|
||||
export default MobileSidebar;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ export type Script = {
|
||||
slug: string;
|
||||
categories: number[];
|
||||
date_created: string;
|
||||
type: "vm" | "ct" | "pve" | "addon" | "turnkey";
|
||||
type: "vm" | "ct" | "pve" | "addon";
|
||||
updateable: boolean;
|
||||
privileged: boolean;
|
||||
interface_port: number | null;
|
||||
@@ -31,10 +31,12 @@ export type Script = {
|
||||
username: string | null;
|
||||
password: string | null;
|
||||
};
|
||||
notes: {
|
||||
text: string;
|
||||
type: keyof typeof AlertColors;
|
||||
}[];
|
||||
notes: [
|
||||
{
|
||||
text: string;
|
||||
type: keyof typeof AlertColors;
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export type Category = {
|
||||
|
||||
@@ -14,13 +14,17 @@ network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
git \
|
||||
git-lfs
|
||||
$STD apt-get install -y git
|
||||
$STD apt-get install -y git-lfs
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
fetch_and_deploy_codeberg_release "forgejo" "forgejo/forgejo" "singlefile" "latest" "/opt/forgejo" "forgejo-*-linux-amd64"
|
||||
ln -sf /opt/forgejo/forgejo /usr/local/bin/forgejo
|
||||
msg_info "Installing Forgejo"
|
||||
mkdir -p /opt/forgejo
|
||||
RELEASE=$(curl -fsSL https://codeberg.org/api/v1/repos/forgejo/forgejo/releases/latest | grep -oP '"tag_name":\s*"\K[^"]+' | sed 's/^v//')
|
||||
curl -fsSL "https://codeberg.org/forgejo/forgejo/releases/download/v${RELEASE}/forgejo-${RELEASE}-linux-amd64" -o "/opt/forgejo/forgejo-$RELEASE-linux-amd64"
|
||||
chmod +x /opt/forgejo/forgejo-$RELEASE-linux-amd64
|
||||
ln -sf /opt/forgejo/forgejo-$RELEASE-linux-amd64 /usr/local/bin/forgejo
|
||||
msg_ok "Installed Forgejo"
|
||||
|
||||
msg_info "Setting up Forgejo"
|
||||
$STD adduser --system --shell /bin/bash --gecos 'Git Version Control' --group --disabled-password --home /home/git git
|
||||
|
||||
@@ -17,8 +17,7 @@ msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
make \
|
||||
ca-certificates \
|
||||
python3-venv \
|
||||
git
|
||||
python3-venv
|
||||
msg_ok "Installed Dependencies"
|
||||
NODE_VERSION="22" NODE_MODULE="yarn@latest" setup_nodejs
|
||||
fetch_and_deploy_gh_release "grist" "gristlabs/grist-core" "tarball"
|
||||
|
||||
@@ -13,7 +13,13 @@ setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
fetch_and_deploy_codeberg_release "readeck" "readeck/readeck" "singlefile" "latest" "/opt/readeck" "readeck-*-linux-amd64"
|
||||
msg_info "Installing Readeck"
|
||||
LATEST=$(curl -fsSL https://codeberg.org/readeck/readeck/releases/ | grep -oP '/releases/tag/\K\d+\.\d+\.\d+' | head -1)
|
||||
mkdir -p /opt/readeck
|
||||
cd /opt/readeck
|
||||
curl -fsSL "https://codeberg.org/readeck/readeck/releases/download/${LATEST}/readeck-${LATEST}-linux-amd64" -o "readeck"
|
||||
chmod a+x readeck
|
||||
msg_ok "Installed Readeck"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/etc/systemd/system/readeck.service
|
||||
|
||||
@@ -41,34 +41,39 @@ $STD cargo build --release --bin server
|
||||
mv ./target/release/server /usr/bin/scanopy-server
|
||||
msg_ok "Built scanopy-server"
|
||||
|
||||
msg_info "Building scanopy-daemon"
|
||||
$STD cargo build --release --bin daemon
|
||||
cp ./target/release/daemon /usr/bin/scanopy-daemon
|
||||
msg_ok "Built scanopy-daemon"
|
||||
|
||||
msg_info "Configuring server for first-run"
|
||||
cat <<EOF >/opt/scanopy/.env
|
||||
### - SERVER
|
||||
SCANOPY_DATABASE_URL=postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME
|
||||
SCANOPY_WEB_EXTERNAL_PATH="/opt/scanopy/ui/build"
|
||||
SCANOPY_PUBLIC_URL=http://${LOCAL_IP}:60072
|
||||
SCANOPY_SERVER_PORT=60072
|
||||
SCANOPY_LOG_LEVEL=info
|
||||
SCANOPY_INTEGRATED_DAEMON_URL=http://127.0.0.1:60073
|
||||
scanopy_DATABASE_URL=postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME
|
||||
scanopy_WEB_EXTERNAL_PATH="/opt/scanopy/ui/build"
|
||||
scanopy_PUBLIC_URL=http://${LOCAL_IP}:60072
|
||||
scanopy_SERVER_PORT=60072
|
||||
scanopy_LOG_LEVEL=info
|
||||
scanopy_INTEGRATED_DAEMON_URL=http://127.0.0.1:60073
|
||||
## - uncomment to disable signups
|
||||
# SCANOPY_DISABLE_REGISTRATION=true
|
||||
# scanopy_DISABLE_REGISTRATION=true
|
||||
## - uncomment when using TLS
|
||||
# SCANOPY_USE_SECURE_SESSION_COOKIES=true
|
||||
# scanopy_USE_SECURE_SESSION_COOKIES=true
|
||||
## - see https://github.com/imbolc/axum-client-ip?tab=readme-ov-file#configurable-vs-specific-extractors
|
||||
## - before uncommenting the below
|
||||
# SCANOPY_CLIENT_IP_SOURCE=
|
||||
# scanopy_CLIENT_IP_SOURCE=
|
||||
|
||||
### - SMTP (password reset and notifications - optional)
|
||||
# SCANOPY_SMTP_RELAY=smtp.gmail.com:587
|
||||
# SCANOPY_SMTP_USERNAME=your-email@gmail.com
|
||||
# SCANOPY_SMTP_PASSWORD=your-app-password
|
||||
# SCANOPY_SMTP_EMAIL=scanopy@yourdomain.tld
|
||||
# scanopy_SMTP_RELAY=smtp.gmail.com:587
|
||||
# scanopy_SMTP_USERNAME=your-email@gmail.com
|
||||
# scanopy_SMTP_PASSWORD=your-app-password
|
||||
# scanopy_SMTP_EMAIL=scanopy@yourdomain.tld
|
||||
|
||||
### - INTEGRATED DAEMON
|
||||
SCANOPY_SERVER_URL=http://127.0.0.1:60072
|
||||
SCANOPY_BIND_ADDRESS=0.0.0.0
|
||||
SCANOPY_NAME="scanopy-daemon"
|
||||
SCANOPY_HEARTBEAT_INTERVAL=30
|
||||
scanopy_SERVER_URL=http://127.0.0.1:60072
|
||||
scanopy_BIND_ADDRESS=0.0.0.0
|
||||
scanopy_NAME="scanopy-daemon"
|
||||
scanopy_HEARTBEAT_INTERVAL=30
|
||||
|
||||
### - see https://github.com/scanopy/scanopy/blob/main/docs/CONFIGURATION.md for more options
|
||||
EOF
|
||||
|
||||
@@ -105,13 +105,14 @@ elif [[ "$DEPLOYMENT_TYPE" == "4" ]]; then
|
||||
sed -i '/_BYPASS=/s/true/false/' /etc/shelfmark/.env
|
||||
else
|
||||
DEPLOYMENT_TYPE="1"
|
||||
CHROME_VERSION=$(curl -fsSL https://raw.githubusercontent.com/calibrain/shelfmark/refs/heads/main/Dockerfile | sed -n '/chromium=/s/[^=]*=//p' | awk '{print $1}')
|
||||
msg_info "Installing internal bypasser dependencies"
|
||||
$STD apt install -y --no-install-recommends \
|
||||
xvfb \
|
||||
ffmpeg \
|
||||
chromium-common \
|
||||
chromium \
|
||||
chromium-driver \
|
||||
chromium-common=${CHROME_VERSION} \
|
||||
chromium=${CHROME_VERSION} \
|
||||
chromium-driver=${CHROME_VERSION} \
|
||||
python3-tk
|
||||
msg_ok "Installed internal bypasser dependencies"
|
||||
fi
|
||||
|
||||
@@ -14,7 +14,7 @@ network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
$STD apt install -y git \
|
||||
build-essential \
|
||||
pkgconf \
|
||||
libssl-dev \
|
||||
@@ -24,25 +24,34 @@ $STD apt install -y \
|
||||
ssl-cert
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
setup_rust
|
||||
fetch_and_deploy_gh_release "vaultwarden" "dani-garcia/vaultwarden" "tarball" "latest" "/tmp/vaultwarden-src"
|
||||
WEBVAULT=$(curl -fsSL https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
|
||||
VAULT=$(curl -fsSL https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
|
||||
|
||||
msg_info "Building Vaultwarden (Patience)"
|
||||
cd /tmp/vaultwarden-src
|
||||
msg_info "Installing Rust"
|
||||
curl -fsSL https://sh.rustup.rs -o rustup-init.sh
|
||||
$STD bash rustup-init.sh -y --profile minimal
|
||||
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >>~/.bashrc
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
rm rustup-init.sh
|
||||
msg_ok "Installed Rust"
|
||||
|
||||
msg_info "Building Vaultwarden ${VAULT} (Patience)"
|
||||
$STD git clone https://github.com/dani-garcia/vaultwarden
|
||||
cd vaultwarden
|
||||
$STD cargo build --features "sqlite,mysql,postgresql" --release
|
||||
msg_ok "Built Vaultwarden"
|
||||
msg_ok "Built Vaultwarden ${VAULT}"
|
||||
|
||||
msg_info "Setting up Vaultwarden"
|
||||
$STD addgroup --system vaultwarden
|
||||
$STD adduser --system --home /opt/vaultwarden --shell /usr/sbin/nologin --no-create-home --gecos 'vaultwarden' --ingroup vaultwarden --disabled-login --disabled-password vaultwarden
|
||||
mkdir -p /opt/vaultwarden/{bin,data,web-vault}
|
||||
mkdir -p /opt/vaultwarden/bin
|
||||
mkdir -p /opt/vaultwarden/data
|
||||
cp target/release/vaultwarden /opt/vaultwarden/bin/
|
||||
cd ~ && rm -rf /tmp/vaultwarden-src
|
||||
msg_ok "Set up Vaultwarden"
|
||||
|
||||
fetch_and_deploy_gh_release "vaultwarden_webvault" "dani-garcia/bw_web_builds" "prebuild" "latest" "/opt/vaultwarden/web-vault" "bw_web_*.tar.gz"
|
||||
msg_info "Downloading Web-Vault ${WEBVAULT}"
|
||||
$STD curl -fsSLO https://github.com/dani-garcia/bw_web_builds/releases/download/"$WEBVAULT"/bw_web_"$WEBVAULT".tar.gz
|
||||
$STD tar -xzf bw_web_"$WEBVAULT".tar.gz -C /opt/vaultwarden/
|
||||
msg_ok "Downloaded Web-Vault ${WEBVAULT}"
|
||||
|
||||
msg_info "Configuring Vaultwarden"
|
||||
cat <<EOF >/opt/vaultwarden/.env
|
||||
ADMIN_TOKEN=''
|
||||
ROCKET_ADDRESS=0.0.0.0
|
||||
@@ -52,23 +61,22 @@ DATABASE_MAX_CONNS=10
|
||||
WEB_VAULT_FOLDER=/opt/vaultwarden/web-vault
|
||||
WEB_VAULT_ENABLED=true
|
||||
EOF
|
||||
|
||||
mv /etc/ssl/certs/ssl-cert-snakeoil.pem /opt/vaultwarden/
|
||||
mv /etc/ssl/private/ssl-cert-snakeoil.key /opt/vaultwarden/
|
||||
|
||||
msg_info "Creating Service"
|
||||
chown -R vaultwarden:vaultwarden /opt/vaultwarden/
|
||||
chown root:root /opt/vaultwarden/bin/vaultwarden
|
||||
chmod +x /opt/vaultwarden/bin/vaultwarden
|
||||
chown -R root:root /opt/vaultwarden/web-vault/
|
||||
chmod +r /opt/vaultwarden/.env
|
||||
msg_ok "Configured Vaultwarden"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/etc/systemd/system/vaultwarden.service
|
||||
[Unit]
|
||||
service_path="/etc/systemd/system/vaultwarden.service"
|
||||
echo "[Unit]
|
||||
Description=Bitwarden Server (Powered by Vaultwarden)
|
||||
Documentation=https://github.com/dani-garcia/vaultwarden
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=vaultwarden
|
||||
Group=vaultwarden
|
||||
@@ -91,11 +99,10 @@ LockPersonality=yes
|
||||
WorkingDirectory=/opt/vaultwarden
|
||||
ReadWriteDirectories=/opt/vaultwarden/data
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now vaultwarden
|
||||
WantedBy=multi-user.target" >$service_path
|
||||
systemctl daemon-reload
|
||||
$STD systemctl enable --now vaultwarden
|
||||
msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: CrazyWolf13
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://wealthfolio.app/
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
build-essential \
|
||||
libsqlite3-dev \
|
||||
argon2
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
setup_rust
|
||||
NODE_MODULE="pnpm" setup_nodejs
|
||||
fetch_and_deploy_gh_release "wealthfolio" "afadil/wealthfolio" "tarball"
|
||||
|
||||
msg_info "Building Frontend (patience)"
|
||||
cd /opt/wealthfolio
|
||||
$STD pnpm install --frozen-lockfile
|
||||
$STD pnpm tsc
|
||||
$STD pnpm vite build
|
||||
msg_ok "Built Frontend"
|
||||
|
||||
msg_info "Building Backend (patience)"
|
||||
cd /opt/wealthfolio/src-server
|
||||
$STD cargo build --release --manifest-path Cargo.toml
|
||||
cp /opt/wealthfolio/src-server/target/release/wealthfolio-server /usr/local/bin/wealthfolio-server
|
||||
chmod +x /usr/local/bin/wealthfolio-server
|
||||
msg_ok "Built Backend"
|
||||
|
||||
msg_info "Configuring Wealthfolio"
|
||||
mkdir -p /opt/wealthfolio_data
|
||||
SECRET_KEY=$(openssl rand -base64 32)
|
||||
WF_PASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-16)
|
||||
WF_PASSWORD_HASH=$(echo -n "$WF_PASSWORD" | argon2 "$(openssl rand -base64 16)" -id -e)
|
||||
cat <<EOF >/opt/wealthfolio/.env
|
||||
WF_LISTEN_ADDR=0.0.0.0:8080
|
||||
WF_DB_PATH=/opt/wealthfolio_data/wealthfolio.db
|
||||
WF_SECRET_KEY=${SECRET_KEY}
|
||||
WF_AUTH_PASSWORD_HASH=${WF_PASSWORD_HASH}
|
||||
WF_STATIC_DIR=/opt/wealthfolio/dist
|
||||
WF_CORS_ALLOW_ORIGINS=*
|
||||
WF_REQUEST_TIMEOUT_MS=30000
|
||||
EOF
|
||||
echo "WF_PASSWORD=${WF_PASSWORD}" >~/wealthfolio.creds
|
||||
msg_ok "Configured Wealthfolio"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /opt/wealthfolio/src-server/target
|
||||
rm -rf /root/.cargo/registry
|
||||
rm -rf /opt/wealthfolio/node_modules
|
||||
msg_ok "Cleaned Up"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/etc/systemd/system/wealthfolio.service
|
||||
[Unit]
|
||||
Description=Wealthfolio Investment Tracker
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/wealthfolio
|
||||
EnvironmentFile=/opt/wealthfolio/.env
|
||||
ExecStart=/usr/local/bin/wealthfolio-server
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now wealthfolio
|
||||
msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -1531,8 +1531,7 @@ check_for_gh_release() {
|
||||
local app="$1"
|
||||
local source="$2"
|
||||
local pinned_version_in="${3:-}" # optional
|
||||
local app_lc=""
|
||||
app_lc="$(echo "${app,,}" | tr -d ' ')"
|
||||
local app_lc="${app,,}"
|
||||
local current_file="$HOME/.${app_lc}"
|
||||
|
||||
msg_info "Checking for update: ${app}"
|
||||
|
||||
Reference in New Issue
Block a user