Compare commits

..

49 Commits

Author SHA1 Message Date
Tobias
1a010573ac Update gitea-mirror-install.sh 2026-03-04 09:54:49 +01:00
community-scripts-pr-app[bot]
c70c488648 Update CHANGELOG.md (#12545)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-04 07:40:41 +00:00
CanbiZ (MickLesk)
91db277446 jellyseer/overseer migration corrupting /usr/bin/update (#12539) 2026-03-04 08:40:15 +01:00
community-scripts-pr-app[bot]
35ce20391c Update CHANGELOG.md (#12544)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-04 07:40:00 +00:00
CanbiZ (MickLesk)
783ba03e92 fix(postgresql): fall back to distro packages instead of bookworm-pgdg on Trixie (#12524) (#12542) 2026-03-04 08:39:33 +01:00
community-scripts-pr-app[bot]
e344d3661c Update CHANGELOG.md (#12543)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-04 07:12:38 +00:00
CanbiZ (MickLesk)
c62418069d fix(powerdns): use gsqlite3 backend instead of BIND (#12533) (#12538)
- Comment out empty 'launch=' in pdns.conf that overrides launch+= directives
- Remove default bind.conf and create gsqlite3.conf pointing to the
  Poweradmin SQLite database (/opt/poweradmin/powerdns.db)
- Set correct ownership (pdns:pdns) and permissions (664) on the
  database file so pdns_server can read/write it
- Add www-data to pdns group for shared database access
- Restart pdns service alongside apache2
2026-03-04 08:12:11 +01:00
community-scripts-pr-app[bot]
41611d2682 Update CHANGELOG.md (#12541)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-04 07:00:19 +00:00
CanbiZ (MickLesk)
cf8043a22e fix(migration): atomic /usr/bin/update replacement to prevent syntax errors (#12540) 2026-03-04 07:59:51 +01:00
community-scripts-pr-app[bot]
2521412747 Update CHANGELOG.md (#12537)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-04 06:31:35 +00:00
Tom
199483be82 fix: whitelist var_searchdomain and fix the handling of var_ns and var_searchdomain in build.func (#12521) 2026-03-04 07:31:11 +01:00
community-scripts-pr-app[bot]
821b2b4415 chore: update github-versions.json (#12536)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-04 06:16:45 +00:00
community-scripts-pr-app[bot]
866ae47dd4 Update CHANGELOG.md (#12535)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-04 04:50:05 +00:00
Denislav Denev
fbc8133a86 fix(immich): correct LibRaw clone URL to official upstream (#12526)
* fix(immich): correct LibRaw clone URL to official upstream

The Immich helper script clones LibRaw from:

  https://github.com/libraw/libraw.git

This repository does not exist and results in:

  fatal: unable to access ... error: 500
  exit code: 128

The correct upstream repository is:

  https://github.com/LibRaw/LibRaw.git

This should resolve installation failure during the LibRaw compilation stage.

* Fix repository URL for libraw in install script

---------

Co-authored-by: Chris <punk.sand7393@fastmail.com>
2026-03-04 05:49:45 +01:00
community-scripts-pr-app[bot]
7044b6e017 Update CHANGELOG.md (#12534)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-04 00:21:05 +00:00
community-scripts-pr-app[bot]
3ff140101d chore: update github-versions.json (#12532)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-04 00:20:41 +00:00
community-scripts-pr-app[bot]
c4b6528bf0 Update CHANGELOG.md (#12530)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 22:38:41 +00:00
Cybirdtech
7a85e33791 Fix config_path casing in reactive-resume.json (#12525) 2026-03-03 23:38:15 +01:00
community-scripts-pr-app[bot]
bbfffb97a5 Update CHANGELOG.md (#12527)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 20:55:33 +00:00
Copilot
4306b4018d cross-seed: install build-essential to resolve missing make error (#12522)
* Initial plan

* fix(cross-seed): add build-essential to resolve missing make error

Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>

* fix(cross-seed): use apt and ensure_dependencies per review feedback

Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>
2026-03-03 21:55:06 +01:00
community-scripts-pr-app[bot]
3722533431 chore: update github-versions.json (#12523)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 18:16:29 +00:00
community-scripts-pr-app[bot]
f773af17b2 Update CHANGELOG.md (#12517)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 14:49:20 +00:00
CanbiZ (MickLesk)
56b4490554 opnsense-vm: harden temp dir, bridge detection and network selection (#12513) 2026-03-03 15:48:50 +01:00
community-scripts-pr-app[bot]
b45842d76a Update CHANGELOG.md (#12516)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 14:34:30 +00:00
community-scripts-pr-app[bot]
ea279ace89 Update CHANGELOG.md (#12515)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 14:34:19 +00:00
CanbiZ (MickLesk)
034061e744 meshcentral: increased disk space to 4GB (#12509) 2026-03-03 15:34:02 +01:00
community-scripts-pr-app[bot]
dd07ba4453 Update CHANGELOG.md (#12514)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 14:33:48 +00:00
CanbiZ (MickLesk)
380aa4bc0f feat(recovery): add ENOSPC disk-full detection with auto-retry using doubled disk size (#12511) 2026-03-03 15:33:19 +01:00
community-scripts-pr-app[bot]
aca721e9ee chore: update github-versions.json (#12507)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 12:12:27 +00:00
community-scripts-pr-app[bot]
42e546904f Update .app files (#12504)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-03-03 11:05:11 +01:00
community-scripts-pr-app[bot]
4045824bf1 Update CHANGELOG.md (#12506)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 10:03:23 +00:00
community-scripts-pr-app[bot]
738cbfd1ae Update CHANGELOG.md (#12505)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 10:03:06 +00:00
community-scripts-pr-app[bot]
278c3cc2d8 Update date in json (#12503)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-03-03 10:02:59 +00:00
CanbiZ (MickLesk)
14a7ac2618 Tinyauth: v5 Support & add Debian Version (#12501) 2026-03-03 11:02:38 +01:00
community-scripts-pr-app[bot]
a7699361c1 Update CHANGELOG.md (#12502)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 09:26:38 +00:00
Copilot
82a0893036 Remove Unifi Network Server scripts (dead APT repo) (#12500)
* Initial plan

* remove Unifi (not unifi-os-server) CT, install, JSON, and header files

Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>
2026-03-03 10:26:15 +01:00
community-scripts-pr-app[bot]
f9b59d7634 chore: update github-versions.json (#12499)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 06:18:03 +00:00
community-scripts-pr-app[bot]
f279536eb7 Update CHANGELOG.md (#12497)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 05:15:00 +00:00
Bram
8f34c7cd2e Refactor JSON editor and command menu components (#12492)
- Moved the search function to the JSON editor for better organization.
- Improved search functionality in the command menu with a new filtering approach.
- Cleaned up unused imports and optimized state management.
- Enhanced user experience by refining the search results display and handling.
- Updated error handling for JSON import and file reading processes.
2026-03-03 06:14:36 +01:00
community-scripts-pr-app[bot]
f922f669a8 Update CHANGELOG.md (#12495)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 00:22:05 +00:00
community-scripts-pr-app[bot]
2694d60faf chore: update github-versions.json (#12494)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-03 00:21:42 +00:00
community-scripts-pr-app[bot]
a326994459 Update .app files (#12485)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-03-02 20:35:25 +01:00
community-scripts-pr-app[bot]
4c8d1ef010 chore: update github-versions.json (#12491)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 18:14:27 +00:00
community-scripts-pr-app[bot]
86a9a0aac6 Update CHANGELOG.md (#12487)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 17:34:35 +00:00
community-scripts-pr-app[bot]
5059ed2320 Update CHANGELOG.md (#12486)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-02 17:34:13 +00:00
community-scripts-pr-app[bot]
df65c60fe4 Update date in json (#12484)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-03-02 17:33:59 +00:00
push-app-to-main[bot]
cff2c90041 Add powerdns (ct) (#12481)
Co-authored-by: push-app-to-main[bot] <203845782+push-app-to-main[bot]@users.noreply.github.com>
2026-03-02 18:33:26 +01:00
Michel Roegl-Brunner
88d1494e46 Adapt workflow 2026-03-02 16:28:35 +01:00
CanbiZ (MickLesk)
8b62b8f3c5 fix(api): rewrite json_escape to use awk for reliable JSON escaping 2026-03-02 16:25:22 +01:00
39 changed files with 719 additions and 480 deletions

View File

@@ -105,7 +105,11 @@ jobs:
const typesRes = await request(apiBase + '/collections/z_ref_script_types/records?perPage=500', { headers: { 'Authorization': token } });
if (typesRes.ok) {
const typesData = JSON.parse(typesRes.body);
(typesData.items || []).forEach(function(item) { if (item.type) typeValueToId[item.type] = item.id; });
(typesData.items || []).forEach(function(item) {
if (item.type != null) typeValueToId[item.type] = item.id;
if (item.name != null) typeValueToId[item.name] = item.id;
if (item.value != null) typeValueToId[item.value] = item.id;
});
}
} catch (e) { console.warn('Could not fetch z_ref_script_types:', e.message); }
try {
@@ -115,6 +119,33 @@ jobs:
(catData.items || []).forEach(function(item) { if (item.name) categoryNameToPbId[item.name] = item.id; });
}
} catch (e) { console.warn('Could not fetch script_categories:', e.message); }
var noteTypeToId = {};
var installMethodTypeToId = {};
var osToId = {};
var osVersionToId = {};
try {
const res = await request(apiBase + '/collections/z_ref_note_types/records?perPage=500', { headers: { 'Authorization': token } });
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.type != null) noteTypeToId[item.type] = item.id; });
} catch (e) { console.warn('z_ref_note_types:', e.message); }
try {
const res = await request(apiBase + '/collections/z_ref_install_method_types/records?perPage=500', { headers: { 'Authorization': token } });
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.type != null) installMethodTypeToId[item.type] = item.id; });
} catch (e) { console.warn('z_ref_install_method_types:', e.message); }
try {
const res = await request(apiBase + '/collections/z_ref_os/records?perPage=500', { headers: { 'Authorization': token } });
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.os != null) osToId[item.os] = item.id; });
} catch (e) { console.warn('z_ref_os:', e.message); }
try {
const res = await request(apiBase + '/collections/z_ref_os_version/records?perPage=500&expand=os', { headers: { 'Authorization': token } });
if (res.ok) {
(JSON.parse(res.body).items || []).forEach(function(item) {
var osName = item.expand && item.expand.os && item.expand.os.os != null ? item.expand.os.os : null;
if (osName != null && item.version != null) osVersionToId[osName + '|' + item.version] = item.id;
});
}
} catch (e) { console.warn('z_ref_os_version:', e.message); }
var notesCollUrl = apiBase + '/collections/script_notes/records';
var installMethodsCollUrl = apiBase + '/collections/script_install_methods/records';
for (const file of files) {
if (!fs.existsSync(file)) continue;
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
@@ -137,6 +168,7 @@ jobs:
is_dev: false
};
var resolvedType = typeValueToId[data.type];
if (resolvedType == null && data.type === 'ct') resolvedType = typeValueToId['lxc'];
if (resolvedType) payload.type = resolvedType;
var resolvedCats = (data.categories || []).map(function(n) { return categoryNameToPbId[categoryIdToName[n]]; }).filter(Boolean);
if (resolvedCats.length) payload.categories = resolvedCats;
@@ -149,7 +181,49 @@ jobs:
});
const list = JSON.parse(listRes.body);
const existingId = list.items && list.items[0] && list.items[0].id;
async function resolveNotesAndInstallMethods(scriptId) {
var noteIds = [];
for (var i = 0; i < (data.notes || []).length; i++) {
var note = data.notes[i];
var typeId = noteTypeToId[note.type];
if (typeId == null) continue;
var postRes = await request(notesCollUrl, {
method: 'POST',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ text: note.text || '', type: typeId })
});
if (postRes.ok) noteIds.push(JSON.parse(postRes.body).id);
}
var installMethodIds = [];
for (var j = 0; j < (data.install_methods || []).length; j++) {
var im = data.install_methods[j];
var typeId = installMethodTypeToId[im.type];
var res = im.resources || {};
var osId = osToId[res.os];
var osVersionKey = (res.os != null && res.version != null) ? res.os + '|' + res.version : null;
var osVersionId = osVersionKey ? osVersionToId[osVersionKey] : null;
var imBody = {
script: scriptId,
resources_cpu: res.cpu != null ? res.cpu : 0,
resources_ram: res.ram != null ? res.ram : 0,
resources_hdd: res.hdd != null ? res.hdd : 0
};
if (typeId) imBody.type = typeId;
if (osId) imBody.os = osId;
if (osVersionId) imBody.os_version = osVersionId;
var imPostRes = await request(installMethodsCollUrl, {
method: 'POST',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify(imBody)
});
if (imPostRes.ok) installMethodIds.push(JSON.parse(imPostRes.body).id);
}
return { noteIds: noteIds, installMethodIds: installMethodIds };
}
if (existingId) {
var resolved = await resolveNotesAndInstallMethods(existingId);
payload.notes = resolved.noteIds;
payload.install_methods = resolved.installMethodIds;
console.log('Updating', file, '(slug=' + data.slug + ')');
const r = await request(recordsUrl + '/' + existingId, {
method: 'PATCH',
@@ -165,6 +239,14 @@ jobs:
body: JSON.stringify(payload)
});
if (!r.ok) throw new Error('POST failed: ' + r.body);
var scriptId = JSON.parse(r.body).id;
var resolved = await resolveNotesAndInstallMethods(scriptId);
var patchRes = await request(recordsUrl + '/' + scriptId, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ install_methods: resolved.installMethodIds, notes: resolved.noteIds })
});
if (!patchRes.ok) throw new Error('PATCH relations failed: ' + patchRes.body);
}
}
console.log('Done.');

View File

@@ -410,11 +410,67 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details>
## 2026-03-04
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- jellyseer/overseer migration corrupting /usr/bin/update [@MickLesk](https://github.com/MickLesk) ([#12539](https://github.com/community-scripts/ProxmoxVE/pull/12539))
- PowerDNS: use gsqlite3 backend instead of BIND [@MickLesk](https://github.com/MickLesk) ([#12538](https://github.com/community-scripts/ProxmoxVE/pull/12538))
- addon migrations: /usr/bin/update replacement to prevent syntax error [@MickLesk](https://github.com/MickLesk) ([#12540](https://github.com/community-scripts/ProxmoxVE/pull/12540))
- fix(immich): correct LibRaw clone URL to official upstream [@DenislavDenev](https://github.com/DenislavDenev) ([#12526](https://github.com/community-scripts/ProxmoxVE/pull/12526))
### 💾 Core
- #### 🐞 Bug Fixes
- tools: fall back to distro packages for psql [@MickLesk](https://github.com/MickLesk) ([#12542](https://github.com/community-scripts/ProxmoxVE/pull/12542))
- fix: whitelist var_searchdomain and fix the handling of var_ns and va… [@tommoyer](https://github.com/tommoyer) ([#12521](https://github.com/community-scripts/ProxmoxVE/pull/12521))
## 2026-03-03
### 🆕 New Scripts
- Tinyauth: v5 Support & add Debian Version [@MickLesk](https://github.com/MickLesk) ([#12501](https://github.com/community-scripts/ProxmoxVE/pull/12501))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- cross-seed: install build-essential to resolve missing `make` error [@Copilot](https://github.com/Copilot) ([#12522](https://github.com/community-scripts/ProxmoxVE/pull/12522))
- meshcentral: increased disk space to 4GB [@MickLesk](https://github.com/MickLesk) ([#12509](https://github.com/community-scripts/ProxmoxVE/pull/12509))
- #### 🔧 Refactor
- opnsense-vm: harden temp dir, bridge detection and network selection [@MickLesk](https://github.com/MickLesk) ([#12513](https://github.com/community-scripts/ProxmoxVE/pull/12513))
### 🗑️ Deleted Scripts
- Remove Unifi Network Server scripts (dead APT repo) [@Copilot](https://github.com/Copilot) ([#12500](https://github.com/community-scripts/ProxmoxVE/pull/12500))
### 💾 Core
- #### ✨ New Features
- core: recovery - add ENOSPC disk-full detection with auto-retry using * 2 hdd [@MickLesk](https://github.com/MickLesk) ([#12511](https://github.com/community-scripts/ProxmoxVE/pull/12511))
### 📚 Documentation
- Fix config_path casing in reactive-resume.json [@ScubyG](https://github.com/ScubyG) ([#12525](https://github.com/community-scripts/ProxmoxVE/pull/12525))
### 🌐 Website
- #### 🐞 Bug Fixes
- Revert #11534 PR that messed up search [@BramSuurdje](https://github.com/BramSuurdje) ([#12492](https://github.com/community-scripts/ProxmoxVE/pull/12492))
## 2026-03-02
### 🆕 New Scripts
- Profilarr ([#12441](https://github.com/community-scripts/ProxmoxVE/pull/12441))
- PowerDNS ([#12481](https://github.com/community-scripts/ProxmoxVE/pull/12481))
- Profilarr ([#12441](https://github.com/community-scripts/ProxmoxVE/pull/12441))
### 🚀 Updated Scripts

View File

@@ -48,9 +48,11 @@ function update_script() {
fi
msg_info "Migrating update function"
cat <<'MIGRATION_EOF' >/usr/bin/update
TMP_UPDATE=$(mktemp)
cat <<'MIGRATION_EOF' >"$TMP_UPDATE"
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/komodo.sh)"
MIGRATION_EOF
mv "$TMP_UPDATE" /usr/bin/update
chmod +x /usr/bin/update
ln -sf /usr/bin/update /usr/bin/update_komodo 2>/dev/null || true

View File

@@ -35,6 +35,20 @@ function update_script() {
$STD service tinyauth stop
msg_ok "Service Stopped"
if [[ -f /opt/tinyauth/.env ]] && ! grep -q "^TINYAUTH_" /opt/tinyauth/.env; then
msg_info "Migrating .env to v5 format"
sed -i \
-e 's/^DATABASE_PATH=/TINYAUTH_DATABASE_PATH=/' \
-e 's/^USERS=/TINYAUTH_AUTH_USERS=/' \
-e "s/^USERS='/TINYAUTH_AUTH_USERS='/" \
-e 's/^APP_URL=/TINYAUTH_APPURL=/' \
-e 's/^SECRET=/TINYAUTH_AUTH_SECRET=/' \
-e 's/^PORT=/TINYAUTH_SERVER_PORT=/' \
-e 's/^ADDRESS=/TINYAUTH_SERVER_ADDRESS=/' \
/opt/tinyauth/.env
msg_ok "Migrated .env to v5 format"
fi
msg_info "Updating Tinyauth"
rm -f /opt/tinyauth/tinyauth
curl -fsSL "https://github.com/steveiliop56/tinyauth/releases/download/v${RELEASE}/tinyauth-amd64" -o /opt/tinyauth/tinyauth

View File

@@ -46,9 +46,11 @@ function update_script() {
fi
msg_info "Migrating update function"
cat <<'MIGRATION_EOF' >/usr/bin/update
TMP_UPDATE=$(mktemp)
cat <<'MIGRATION_EOF' >"$TMP_UPDATE"
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/coolify.sh)"
MIGRATION_EOF
mv "$TMP_UPDATE" /usr/bin/update
chmod +x /usr/bin/update
ln -sf /usr/bin/update /usr/bin/update_coolify 2>/dev/null || true

View File

@@ -25,6 +25,7 @@ function update_script() {
check_container_resources
NODE_VERSION="24" setup_nodejs
ensure_dependencies build-essential
if command -v cross-seed &>/dev/null; then
current_version=$(cross-seed --version)

View File

@@ -48,9 +48,11 @@ function update_script() {
fi
msg_info "Migrating update function"
cat <<'MIGRATION_EOF' >/usr/bin/update
TMP_UPDATE=$(mktemp)
cat <<'MIGRATION_EOF' >"$TMP_UPDATE"
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/dockge.sh)"
MIGRATION_EOF
mv "$TMP_UPDATE" /usr/bin/update
chmod +x /usr/bin/update
ln -sf /usr/bin/update /usr/bin/update_dockge 2>/dev/null || true

View File

@@ -46,9 +46,11 @@ function update_script() {
fi
msg_info "Migrating update function"
cat <<'MIGRATION_EOF' >/usr/bin/update
TMP_UPDATE=$(mktemp)
cat <<'MIGRATION_EOF' >"$TMP_UPDATE"
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/dokploy.sh)"
MIGRATION_EOF
mv "$TMP_UPDATE" /usr/bin/update
chmod +x /usr/bin/update
ln -sf /usr/bin/update /usr/bin/update_dokploy 2>/dev/null || true

6
ct/headers/powerdns Normal file
View File

@@ -0,0 +1,6 @@
____ ____ _ _______
/ __ \____ _ _____ _____/ __ \/ | / / ___/
/ /_/ / __ \ | /| / / _ \/ ___/ / / / |/ /\__ \
/ ____/ /_/ / |/ |/ / __/ / / /_/ / /| /___/ /
/_/ \____/|__/|__/\___/_/ /_____/_/ |_//____/

6
ct/headers/tinyauth Normal file
View File

@@ -0,0 +1,6 @@
_______ __ __
/_ __(_)___ __ ______ ___ __/ /_/ /_
/ / / / __ \/ / / / __ `/ / / / __/ __ \
/ / / / / / / /_/ / /_/ / /_/ / /_/ / / /
/_/ /_/_/ /_/\__, /\__,_/\__,_/\__/_/ /_/
/____/

View File

@@ -1,6 +0,0 @@
__ __ _ _____
/ / / /___ (_) __(_)
/ / / / __ \/ / /_/ /
/ /_/ / / / / / __/ /
\____/_/ /_/_/_/ /_/

View File

@@ -337,7 +337,7 @@ function compile_libraw() {
if [[ "$LIBRAW_REVISION" != "$(grep 'libraw' ~/.immich_library_revisions | awk '{print $2}')" ]]; then
msg_info "Recompiling libraw"
[[ -d "$SOURCE" ]] && rm -rf "$SOURCE"
$STD git clone https://github.com/libraw/libraw.git "$SOURCE"
$STD git clone https://github.com/LibRaw/LibRaw.git "$SOURCE"
cd "$SOURCE"
$STD git reset --hard "$LIBRAW_REVISION"
$STD autoreconf --install

View File

@@ -45,14 +45,15 @@ function update_script() {
fi
msg_info "Switching update script to Seerr"
cat <<'EOF' >/usr/bin/update
#!/usr/bin/env bash
TMP_UPDATE=$(mktemp)
cat <<'EOF' >"$TMP_UPDATE"
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/seerr.sh)"
EOF
mv "$TMP_UPDATE" /usr/bin/update
chmod +x /usr/bin/update
msg_ok "Switched update script to Seerr"
msg_warn "Please type 'update' again to complete the migration"
exit
exit 0
fi
msg_info "Updating Jellyseerr"

View File

@@ -52,9 +52,11 @@ function update_script() {
fi
msg_info "Migrating update function"
cat <<'MIGRATION_EOF' >/usr/bin/update
TMP_UPDATE=$(mktemp)
cat <<'MIGRATION_EOF' >"$TMP_UPDATE"
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/komodo.sh)"
MIGRATION_EOF
mv "$TMP_UPDATE" /usr/bin/update
chmod +x /usr/bin/update
ln -sf /usr/bin/update /usr/bin/update_komodo 2>/dev/null || true

View File

@@ -9,7 +9,7 @@ APP="MeshCentral"
var_tags="${var_tags:-remote-management}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-2}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"

View File

@@ -44,10 +44,11 @@ function update_script() {
fi
msg_info "Switching update script to Seerr"
cat <<'EOF' >/usr/bin/update
#!/usr/bin/env bash
TMP_UPDATE=$(mktemp)
cat <<'EOF' >"$TMP_UPDATE"
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/seerr.sh)"
EOF
mv "$TMP_UPDATE" /usr/bin/update
chmod +x /usr/bin/update
msg_ok "Switched update script to Seerr"
msg_warn "Please type 'update' again to complete the migration"

View File

@@ -46,9 +46,11 @@ function update_script() {
fi
msg_info "Migrating update function"
cat <<'MIGRATION_EOF' >/usr/bin/update
TMP_UPDATE=$(mktemp)
cat <<'MIGRATION_EOF' >"$TMP_UPDATE"
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/runtipi.sh)"
MIGRATION_EOF
mv "$TMP_UPDATE" /usr/bin/update
chmod +x /usr/bin/update
ln -sf /usr/bin/update /usr/bin/update_runtipi 2>/dev/null || true

53
ct/tinyauth.sh Normal file
View File

@@ -0,0 +1,53 @@
#!/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: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/steveiliop56/tinyauth
APP="Tinyauth"
var_tags="${var_tags:-auth}"
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/tinyauth ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "tinyauth" "steveiliop56/tinyauth"; then
msg_info "Stopping Service"
systemctl stop tinyauth
msg_ok "Stopped Service"
fetch_and_deploy_gh_release "tinyauth" "steveiliop56/tinyauth" "singlefile" "latest" "/opt/tinyauth" "tinyauth-amd64"
msg_info "Starting Service"
systemctl start tinyauth
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}:3000${CL}"

View File

@@ -1,47 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://ui.com/download/unifi
APP="Unifi"
var_tags="${var_tags:-network;unifi}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-8}"
var_os="${var_os:-debian}"
var_version="${var_version:-12}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /usr/lib/unifi ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
JAVA_VERSION="21" setup_java
msg_info "Updating ${APP}"
$STD apt update --allow-releaseinfo-change
ensure_dependencies unifi
msg_ok "Updated successfully!"
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}https://${IP}:8443${CL}"

View File

@@ -13,7 +13,7 @@
"website": "https://2fauth.app/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/2fauth.webp",
"config_path": "cat /opt/2fauth/.env",
"description": "2FAuth is a web based self-hosted alternative to One Time Passcode (OTP) generators like Google Authenticator, designed for both mobile and desktop. It aims to ease you perform your 2FA authentication steps whatever the device you handle, with a clean and suitable interface. - Workflow - Test",
"description": "2FAuth is a web based self-hosted alternative to One Time Passcode (OTP) generators like Google Authenticator, designed for both mobile and desktop. It aims to ease you perform your 2FA authentication steps whatever the device you handle, with a clean and suitable interface.",
"install_methods": [
{
"type": "default",

View File

@@ -1,5 +1,5 @@
{
"generated": "2026-03-02T13:48:26Z",
"generated": "2026-03-04T06:16:35Z",
"versions": [
{
"slug": "2fauth",
@@ -242,9 +242,9 @@
{
"slug": "cosmos",
"repo": "azukaar/Cosmos-Server",
"version": "v0.21.6",
"version": "v0.21.7",
"pinned": false,
"date": "2026-02-28T22:00:49Z"
"date": "2026-03-03T18:15:29Z"
},
{
"slug": "cronicle",
@@ -291,16 +291,16 @@
{
"slug": "dispatcharr",
"repo": "Dispatcharr/Dispatcharr",
"version": "v0.20.1",
"version": "v0.20.2",
"pinned": false,
"date": "2026-02-26T21:38:19Z"
"date": "2026-03-03T01:40:33Z"
},
{
"slug": "docmost",
"repo": "docmost/docmost",
"version": "v0.25.3",
"version": "v0.70.0",
"pinned": false,
"date": "2026-02-10T02:58:23Z"
"date": "2026-03-03T20:33:08Z"
},
{
"slug": "domain-locker",
@@ -382,9 +382,9 @@
{
"slug": "firefly",
"repo": "firefly-iii/firefly-iii",
"version": "v6.5.1",
"version": "v6.5.2",
"pinned": false,
"date": "2026-02-27T20:55:55Z"
"date": "2026-03-03T05:42:27Z"
},
{
"slug": "fladder",
@@ -438,9 +438,9 @@
{
"slug": "ghostfolio",
"repo": "ghostfolio/ghostfolio",
"version": "2.245.0",
"version": "2.246.0",
"pinned": false,
"date": "2026-03-01T09:09:57Z"
"date": "2026-03-03T19:37:20Z"
},
{
"slug": "gitea",
@@ -452,9 +452,9 @@
{
"slug": "gitea-mirror",
"repo": "RayLabsHQ/gitea-mirror",
"version": "v3.11.0",
"version": "v3.12.0",
"pinned": false,
"date": "2026-03-02T10:19:59Z"
"date": "2026-03-04T02:53:42Z"
},
{
"slug": "glance",
@@ -557,9 +557,9 @@
{
"slug": "homebox",
"repo": "sysadminsmedia/homebox",
"version": "v0.23.1",
"version": "v0.24.0",
"pinned": false,
"date": "2026-02-01T22:53:32Z"
"date": "2026-03-03T16:09:55Z"
},
{
"slug": "homepage",
@@ -585,9 +585,9 @@
{
"slug": "immich-public-proxy",
"repo": "alangrainger/immich-public-proxy",
"version": "v1.15.3",
"version": "v1.15.4",
"pinned": false,
"date": "2026-02-16T22:54:27Z"
"date": "2026-03-02T21:28:06Z"
},
{
"slug": "inspircd",
@@ -613,9 +613,9 @@
{
"slug": "jackett",
"repo": "Jackett/Jackett",
"version": "v0.24.1247",
"version": "v0.24.1275",
"pinned": false,
"date": "2026-03-02T05:56:37Z"
"date": "2026-03-04T05:53:40Z"
},
{
"slug": "jellystat",
@@ -669,9 +669,9 @@
{
"slug": "kima-hub",
"repo": "Chevron7Locked/kima-hub",
"version": "v1.6.0",
"version": "v1.6.1",
"pinned": false,
"date": "2026-03-02T05:43:31Z"
"date": "2026-03-03T16:13:53Z"
},
{
"slug": "kimai",
@@ -753,9 +753,9 @@
{
"slug": "libretranslate",
"repo": "LibreTranslate/LibreTranslate",
"version": "v1.9.4",
"version": "v1.9.5",
"pinned": false,
"date": "2026-02-24T17:06:05Z"
"date": "2026-03-03T18:25:04Z"
},
{
"slug": "lidarr",
@@ -872,9 +872,9 @@
{
"slug": "metube",
"repo": "alexta69/metube",
"version": "2026.02.27",
"version": "2026.03.03",
"pinned": false,
"date": "2026-02-27T11:47:02Z"
"date": "2026-03-03T19:15:55Z"
},
{
"slug": "miniflux",
@@ -921,9 +921,9 @@
{
"slug": "netbox",
"repo": "netbox-community/netbox",
"version": "v4.5.3",
"version": "v4.5.4",
"pinned": false,
"date": "2026-02-17T15:39:18Z"
"date": "2026-03-03T20:32:16Z"
},
{
"slug": "nextcloud-exporter",
@@ -942,9 +942,9 @@
{
"slug": "nightscout",
"repo": "nightscout/cgm-remote-monitor",
"version": "v15.0.5",
"version": "v15.0.6",
"pinned": false,
"date": "2026-03-01T21:22:37Z"
"date": "2026-03-03T23:04:35Z"
},
{
"slug": "nocodb",
@@ -1149,6 +1149,13 @@
"pinned": false,
"date": "2026-02-23T19:50:48Z"
},
{
"slug": "powerdns",
"repo": "poweradmin/poweradmin",
"version": "v4.0.7",
"pinned": false,
"date": "2026-02-15T20:09:48Z"
},
{
"slug": "privatebin",
"repo": "PrivateBin/PrivateBin",
@@ -1222,9 +1229,9 @@
{
"slug": "pulse",
"repo": "rcourtman/Pulse",
"version": "v5.1.16",
"version": "v5.1.18",
"pinned": false,
"date": "2026-03-01T23:13:09Z"
"date": "2026-03-03T22:09:15Z"
},
{
"slug": "pve-scripts-local",
@@ -1348,9 +1355,9 @@
{
"slug": "scanopy",
"repo": "scanopy/scanopy",
"version": "v0.14.10",
"version": "v0.14.11",
"pinned": false,
"date": "2026-02-28T21:05:12Z"
"date": "2026-03-02T08:48:42Z"
},
{
"slug": "scraparr",
@@ -1439,9 +1446,9 @@
{
"slug": "sonobarr",
"repo": "Dodelidoo-Labs/sonobarr",
"version": "0.11.0",
"version": "0.12.1",
"pinned": false,
"date": "2026-01-21T19:07:21Z"
"date": "2026-03-03T13:43:02Z"
},
{
"slug": "speedtest-tracker",
@@ -1460,9 +1467,9 @@
{
"slug": "sportarr",
"repo": "Sportarr/Sportarr",
"version": "v4.0.983.1057",
"version": "v4.0.985.1060",
"pinned": false,
"date": "2026-01-26T18:54:50Z"
"date": "2026-03-04T01:00:04Z"
},
{
"slug": "stirling-pdf",
@@ -1555,6 +1562,13 @@
"pinned": false,
"date": "2026-02-13T16:30:09Z"
},
{
"slug": "tinyauth",
"repo": "steveiliop56/tinyauth",
"version": "v5.0.0",
"pinned": false,
"date": "2026-03-02T18:43:57Z"
},
{
"slug": "traccar",
"repo": "traccar/traccar",
@@ -1565,9 +1579,9 @@
{
"slug": "tracearr",
"repo": "connorgallopo/Tracearr",
"version": "v1.4.19",
"version": "v1.4.21",
"pinned": false,
"date": "2026-02-28T21:25:47Z"
"date": "2026-03-03T18:43:20Z"
},
{
"slug": "tracktor",
@@ -1635,9 +1649,9 @@
{
"slug": "upgopher",
"repo": "wanetty/upgopher",
"version": "v1.14.0",
"version": "v1.15.2",
"pinned": false,
"date": "2026-02-24T22:43:34Z"
"date": "2026-03-03T13:40:45Z"
},
{
"slug": "upsnap",
@@ -1726,9 +1740,9 @@
{
"slug": "wealthfolio",
"repo": "afadil/wealthfolio",
"version": "v3.0.0",
"version": "v3.0.3",
"pinned": false,
"date": "2026-02-24T22:37:05Z"
"date": "2026-03-03T21:47:55Z"
},
{
"slug": "web-check",

View File

@@ -21,7 +21,7 @@
"resources": {
"cpu": 1,
"ram": 512,
"hdd": 2,
"hdd": 4,
"os": "debian",
"version": "13"
}

View File

@@ -4,7 +4,7 @@
"categories": [
5
],
"date_created": "2026-02-11",
"date_created": "2026-03-02",
"type": "ct",
"updateable": true,
"privileged": false,
@@ -37,4 +37,4 @@
"type": "info"
}
]
}
}

View File

@@ -12,7 +12,7 @@
"documentation": "https://docs.rxresume.org/",
"website": "https://rxresume.org",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/reactive-resume.webp",
"config_path": "/opt/reactive-resume/.env",
"config_path": "/opt/Reactive-Resume/.env",
"description": "A one-of-a-kind resume builder that keeps your privacy in mind. Completely secure, customizable, portable, open-source and free forever.",
"install_methods": [
{

View File

@@ -1,10 +1,10 @@
{
"name": "Alpine-Tinyauth",
"slug": "alpine-tinyauth",
"name": "Tinyauth",
"slug": "tinyauth",
"categories": [
6
],
"date_created": "2025-05-06",
"date_created": "2026-03-03",
"type": "ct",
"updateable": true,
"privileged": false,
@@ -17,13 +17,13 @@
"install_methods": [
{
"type": "default",
"script": "ct/alpine-tinyauth.sh",
"script": "ct/tinyauth.sh",
"resources": {
"cpu": 1,
"ram": 256,
"hdd": 2,
"os": "alpine",
"version": "3.23"
"ram": 512,
"hdd": 4,
"os": "debian",
"version": "13"
}
},
{

View File

@@ -1,42 +0,0 @@
{
"name": "UniFi Network Server",
"slug": "unifi",
"categories": [
4
],
"date_created": "2024-05-02",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 8443,
"documentation": "https://help.ui.com/hc/en-us/articles/360012282453-Self-Hosting-a-UniFi-Network-Server",
"website": "https://www.ui.com/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/ubiquiti-unifi.webp",
"config_path": "",
"description": "UniFi Network Server is a software that helps manage and monitor UniFi networks (Wi-Fi, Ethernet, etc.) by providing an intuitive user interface and advanced features. It allows network administrators to configure, monitor, and upgrade network devices, as well as view network statistics, client devices, and historical events. The aim of the application is to make the management of UniFi networks easier and more efficient.",
"disable": true,
"disable_description": "This script is disabled because UniFi no longer delivers APT packages for Debian systems. The installation relies on APT repositories that are no longer maintained or available. For more details, see: https://github.com/community-scripts/ProxmoxVE/issues/11876",
"install_methods": [
{
"type": "default",
"script": "ct/unifi.sh",
"resources": {
"cpu": 2,
"ram": 2048,
"hdd": 8,
"os": "debian",
"version": "12"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "For non-AVX CPUs, MongoDB 4.4 is installed. Please note this is a legacy solution that may present security risks and could become unsupported in future updates.",
"type": "warning"
}
]
}

View File

@@ -2,42 +2,68 @@
import type { z } from "zod";
import { githubGist, nord } from "react-syntax-highlighter/dist/esm/styles/hljs";
import { CalendarIcon, Check, Clipboard, Download } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import SyntaxHighlighter from "react-syntax-highlighter";
import { useTheme } from "next-themes";
import { format } from "date-fns";
import { toast } from "sonner";
import Image from "next/image";
import type { Category } from "@/lib/types";
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Calendar } from "@/components/ui/calendar";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { basePath } from "@/config/site-config";
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";
import type { Script } from "./_schemas/schemas";
import { ScriptItem } from "../scripts/_components/script-item";
import InstallMethod from "./_components/install-method";
import { ScriptSchema } from "./_schemas/schemas";
import Categories from "./_components/categories";
import Note from "./_components/note";
import { githubGist, nord } from "react-syntax-highlighter/dist/esm/styles/hljs";
import SyntaxHighlighter from "react-syntax-highlighter";
import { ScriptItem } from "../scripts/_components/script-item";
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { search } from "@/components/command-menu";
import { basePath } from "@/config/site-config";
import Image from "next/image";
import { useTheme } from "next-themes";
function search(scripts: Script[], query: string): Script[] {
const queryLower = query.toLowerCase().trim();
const searchWords = queryLower.split(/\s+/).filter(Boolean);
return scripts
.map((script) => {
const nameLower = script.name.toLowerCase();
const descriptionLower = (script.description || "").toLowerCase();
let score = 0;
for (const word of searchWords) {
if (nameLower.includes(word)) {
score += 10;
}
if (descriptionLower.includes(word)) {
score += 5;
}
}
return { script, score };
})
.filter(({ score }) => score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, 20)
.map(({ script }) => script);
}
const initialScript: Script = {
name: "",
@@ -77,32 +103,32 @@ export default function JSONGenerator() {
const selectedCategoryObj = useMemo(
() => categories.find(cat => cat.id.toString() === selectedCategory),
[categories, selectedCategory]
[categories, selectedCategory],
);
const allScripts = useMemo(
() => categories.flatMap(cat => cat.scripts || []),
[categories]
[categories],
);
const scripts = useMemo(() => {
const query = searchQuery.trim()
const query = searchQuery.trim();
if (query) {
return search(allScripts, query)
return search(allScripts, query);
}
if (selectedCategoryObj) {
return selectedCategoryObj.scripts || []
return selectedCategoryObj.scripts || [];
}
return []
return [];
}, [allScripts, selectedCategoryObj, searchQuery]);
useEffect(() => {
fetchCategories()
.then(setCategories)
.catch((error) => console.error("Error fetching categories:", error));
.catch(error => console.error("Error fetching categories:", error));
}, []);
useEffect(() => {
@@ -122,11 +148,14 @@ export default function JSONGenerator() {
if (updated.type === "pve") {
scriptPath = `tools/pve/${updated.slug}.sh`;
} else if (updated.type === "addon") {
}
else if (updated.type === "addon") {
scriptPath = `tools/addon/${updated.slug}.sh`;
} else if (method.type === "alpine") {
}
else if (method.type === "alpine") {
scriptPath = `${updated.type}/alpine-${updated.slug}.sh`;
} else {
}
else {
scriptPath = `${updated.type}/${updated.slug}.sh`;
}
@@ -145,11 +174,13 @@ export default function JSONGenerator() {
}, []);
const handleCopy = useCallback(() => {
if (!isValid) toast.warning("JSON schema is invalid. Copying anyway.");
if (!isValid)
toast.warning("JSON schema is invalid. Copying anyway.");
navigator.clipboard.writeText(JSON.stringify(script, null, 2));
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
if (isValid) toast.success("Copied metadata to clipboard");
if (isValid)
toast.success("Copied metadata to clipboard");
}, [script]);
const importScript = (script: Script) => {
@@ -166,11 +197,11 @@ export default function JSONGenerator() {
setIsValid(true);
setZodErrors(null);
toast.success("Imported JSON successfully");
} catch (error) {
}
catch (error) {
toast.error("Failed to read or parse the JSON file.");
}
}
};
const handleFileImport = useCallback(() => {
const input = document.createElement("input");
@@ -180,7 +211,8 @@ export default function JSONGenerator() {
input.onchange = (e: Event) => {
const target = e.target as HTMLInputElement;
const file = target.files?.[0];
if (!file) return;
if (!file)
return;
const reader = new FileReader();
reader.onload = (event) => {
@@ -189,7 +221,8 @@ export default function JSONGenerator() {
const parsed = JSON.parse(content);
importScript(parsed);
toast.success("Imported JSON successfully");
} catch (error) {
}
catch (error) {
toast.error("Failed to read the JSON file.");
}
};
@@ -243,7 +276,10 @@ export default function JSONGenerator() {
<div className="mt-2 space-y-1">
{zodErrors.issues.map((error, index) => (
<AlertDescription key={index} className="p-1 text-red-500">
{error.path.join(".")} -{error.message}
{error.path.join(".")}
{" "}
-
{error.message}
</AlertDescription>
))}
</div>
@@ -270,7 +306,7 @@ export default function JSONGenerator() {
onOpenChange={setIsImportDialogOpen}
>
<DialogTrigger asChild>
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
<DropdownMenuItem onSelect={e => e.preventDefault()}>
Import existing script
</DropdownMenuItem>
</DialogTrigger>
@@ -292,7 +328,7 @@ export default function JSONGenerator() {
<SelectValue placeholder="Category" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
{categories.map(category => (
<SelectItem key={category.id} value={category.id.toString()}>
{category.name}
</SelectItem>
@@ -302,40 +338,44 @@ export default function JSONGenerator() {
<Input
placeholder="Search for a script..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onChange={e => setSearchQuery(e.target.value)}
/>
{!selectedCategory && !searchQuery ? (
<p className="text-muted-foreground text-sm text-center">
Select a category or search for a script
</p>
) : scripts.length === 0 ? (
<p className="text-muted-foreground text-sm text-center">
No scripts found
</p>
) : (
<div className="grid grid-cols-3 auto-rows-min h-64 overflow-y-auto gap-4">
{scripts.map(script => (
<div
key={script.slug}
className="p-2 border rounded cursor-pointer hover:bg-accent hover:text-accent-foreground"
onClick={() => {
importScript(script);
setIsImportDialogOpen(false);
}}
>
<Image
src={script.logo || `/${basePath}/logo.png`}
alt={script.name}
className="w-full h-12 object-contain mb-2"
width={16}
height={16}
unoptimized
/>
<p className="text-sm text-center">{script.name}</p>
</div>
))}
</div>
)}
{!selectedCategory && !searchQuery
? (
<p className="text-muted-foreground text-sm text-center">
Select a category or search for a script
</p>
)
: scripts.length === 0
? (
<p className="text-muted-foreground text-sm text-center">
No scripts found
</p>
)
: (
<div className="grid grid-cols-3 auto-rows-min h-64 overflow-y-auto gap-4">
{scripts.map(script => (
<div
key={script.slug}
className="p-2 border rounded cursor-pointer hover:bg-accent hover:text-accent-foreground"
onClick={() => {
importScript(script);
setIsImportDialogOpen(false);
}}
>
<Image
src={script.logo || `/${basePath}/logo.png`}
alt={script.name}
className="w-full h-12 object-contain mb-2"
width={16}
height={16}
unoptimized
/>
<p className="text-sm text-center">{script.name}</p>
</div>
))}
</div>
)}
</div>
</div>
</DialogContent>
@@ -348,15 +388,19 @@ export default function JSONGenerator() {
<div className="grid grid-cols-2 gap-4">
<div>
<Label>
Name <span className="text-red-500">*</span>
Name
{" "}
<span className="text-red-500">*</span>
</Label>
<Input placeholder="Example" value={script.name} onChange={(e) => updateScript("name", e.target.value)} />
<Input placeholder="Example" value={script.name} onChange={e => updateScript("name", e.target.value)} />
</div>
<div>
<Label>
Slug <span className="text-red-500">*</span>
Slug
{" "}
<span className="text-red-500">*</span>
</Label>
<Input placeholder="example" value={script.slug} onChange={(e) => updateScript("slug", e.target.value)} />
<Input placeholder="example" value={script.slug} onChange={e => updateScript("slug", e.target.value)} />
</div>
</div>
<div>
@@ -366,7 +410,7 @@ export default function JSONGenerator() {
<Input
placeholder="Full logo URL"
value={script.logo || ""}
onChange={(e) => updateScript("logo", e.target.value || null)}
onChange={e => updateScript("logo", e.target.value || null)}
/>
</div>
<div>
@@ -374,24 +418,28 @@ 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 || "")}
/>
</div>
<div>
<Label>
Description <span className="text-red-500">*</span>
Description
{" "}
<span className="text-red-500">*</span>
</Label>
<Textarea
placeholder="Example"
value={script.description}
onChange={(e) => updateScript("description", e.target.value)}
onChange={e => updateScript("description", e.target.value)}
/>
</div>
<Categories script={script} setScript={setScript} categories={categories} />
<div className="flex gap-2">
<div className="flex flex-col gap-2 w-full">
<Label>
Date Created <span className="text-red-500">*</span>
Date Created
{" "}
<span className="text-red-500">*</span>
</Label>
<Popover>
<PopoverTrigger asChild className="flex-1">
@@ -415,7 +463,7 @@ export default function JSONGenerator() {
</div>
<div className="flex flex-col gap-2 w-full">
<Label>Type</Label>
<Select value={script.type} onValueChange={(value) => updateScript("type", value)}>
<Select value={script.type} onValueChange={value => updateScript("type", value)}>
<SelectTrigger className="flex-1">
<SelectValue placeholder="Type" />
</SelectTrigger>
@@ -430,17 +478,17 @@ export default function JSONGenerator() {
</div>
<div className="w-full flex gap-5">
<div className="flex items-center space-x-2">
<Switch checked={script.updateable} onCheckedChange={(checked) => updateScript("updateable", checked)} />
<Switch checked={script.updateable} onCheckedChange={checked => updateScript("updateable", checked)} />
<label>Updateable</label>
</div>
<div className="flex items-center space-x-2">
<Switch checked={script.privileged} onCheckedChange={(checked) => updateScript("privileged", checked)} />
<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)}
onCheckedChange={checked => updateScript("disable", checked)}
/>
<label>Disabled</label>
</div>
@@ -448,12 +496,14 @@ export default function JSONGenerator() {
{script.disable && (
<div>
<Label>
Disable Description <span className="text-red-500">*</span>
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)}
onChange={e => updateScript("disable_description", e.target.value)}
/>
</div>
)}
@@ -461,18 +511,18 @@ export default function JSONGenerator() {
placeholder="Interface Port"
type="number"
value={script.interface_port || ""}
onChange={(e) => updateScript("interface_port", e.target.value ? Number(e.target.value) : null)}
onChange={e => updateScript("interface_port", e.target.value ? Number(e.target.value) : null)}
/>
<div className="flex gap-2">
<Input
placeholder="Website URL"
value={script.website || ""}
onChange={(e) => updateScript("website", e.target.value || null)}
onChange={e => updateScript("website", e.target.value || null)}
/>
<Input
placeholder="Documentation URL"
value={script.documentation || ""}
onChange={(e) => updateScript("documentation", e.target.value || null)}
onChange={e => updateScript("documentation", e.target.value || null)}
/>
</div>
<InstallMethod script={script} setScript={setScript} setIsValid={setIsValid} setZodErrors={setZodErrors} />
@@ -480,22 +530,20 @@ export default function JSONGenerator() {
<Input
placeholder="Username"
value={script.default_credentials.username || ""}
onChange={(e) =>
onChange={e =>
updateScript("default_credentials", {
...script.default_credentials,
username: e.target.value || null,
})
}
})}
/>
<Input
placeholder="Password"
value={script.default_credentials.password || ""}
onChange={(e) =>
onChange={e =>
updateScript("default_credentials", {
...script.default_credentials,
password: e.target.value || null,
})
}
})}
/>
<Note script={script} setScript={setScript} setIsValid={setIsValid} setZodErrors={setZodErrors} />
</form>
@@ -504,7 +552,7 @@ export default function JSONGenerator() {
<Tabs
defaultValue="json"
className="w-full"
onValueChange={(value) => setCurrentTab(value as "json" | "preview")}
onValueChange={value => setCurrentTab(value as "json" | "preview")}
value={currentTab}
>
<TabsList className="grid w-full grid-cols-2">

View File

@@ -1,6 +1,7 @@
import { useRouter } from "next/navigation";
import { ArrowRightIcon, Sparkles } from "lucide-react";
import { useRouter } from "next/navigation";
import Image from "next/image";
import Link from "next/link";
import React from "react";
import type { Category, Script } from "@/lib/types";
@@ -21,35 +22,6 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/t
import { DialogTitle } from "./ui/dialog";
import { Button } from "./ui/button";
import { Badge } from "./ui/badge";
import Link from "next/link";
export function search(scripts: Script[], query: string): Script[] {
const queryLower = query.toLowerCase().trim();
const searchWords = queryLower.split(/\s+/).filter(Boolean);
return scripts
.map(script => {
const nameLower = script.name.toLowerCase();
const descriptionLower = (script.description || "").toLowerCase();
let score = 0;
for (const word of searchWords) {
if (nameLower.includes(word)) {
score += 10;
}
if (descriptionLower.includes(word)) {
score += 5;
}
}
return { script, score };
})
.filter(({ score }) => score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, 20)
.map(({ script }) => script);
}
export function formattedBadge(type: string) {
switch (type) {
@@ -79,11 +51,9 @@ function getRandomScript(categories: Category[], previouslySelected: Set<string>
}
function CommandMenu() {
const [query, setQuery] = React.useState("");
const [open, setOpen] = React.useState(false);
const [links, setLinks] = React.useState<Category[]>([]);
const [isLoading, setIsLoading] = React.useState(false);
const [results, setResults] = React.useState<Script[]>([]);
const [selectedScripts, setSelectedScripts] = React.useState<Set<string>>(new Set());
const router = useRouter();
@@ -100,27 +70,6 @@ function CommandMenu() {
});
};
React.useEffect(() => {
if (query.trim() === "") {
fetchSortedCategories();
}
else {
const scriptMap = new Map<string, Script>();
for (const category of links) {
for (const script of category.scripts || []) {
if (!scriptMap.has(script.slug)) {
scriptMap.set(script.slug, script);
}
}
}
const uniqueScripts = Array.from(scriptMap.values());
const filteredResults = search(uniqueScripts, query);
setResults(filteredResults);
}
}, [query]);
React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
@@ -248,46 +197,49 @@ function CommandMenu() {
<CommandDialog
open={open}
onOpenChange={(open) => {
setOpen(open);
if (open) {
setQuery("");
setResults([]);
}
onOpenChange={setOpen}
filter={(value: string, search: string) => {
const searchLower = search.toLowerCase().trim();
if (!searchLower)
return 1;
const valueLower = value.toLowerCase();
const searchWords = searchLower.split(/\s+/).filter(Boolean);
// All search words must appear somewhere in the value (name + description)
const allWordsMatch = searchWords.every((word: string) => valueLower.includes(word));
return allWordsMatch ? 1 : 0;
}}
>
<DialogTitle className="sr-only">Search scripts</DialogTitle>
<CommandInput
placeholder="Search for a script..."
onValueChange={setQuery}
value={query}
/>
<CommandInput placeholder="Search for a script..." />
<CommandList>
<CommandEmpty>
{isLoading ? (
"Searching..."
) : (
<div className="flex flex-col items-center justify-center py-6 text-center">
<p className="text-sm text-muted-foreground">No scripts match your search.</p>
<div className="mt-4">
<p className="text-xs text-muted-foreground mb-2">Want to add a new script?</p>
<Button variant="outline" size="sm" asChild>
<Link
href={`https://github.com/community-scripts/${basePath}/tree/main/docs/contribution/GUIDE.md`}
target="_blank"
rel="noopener noreferrer"
>
Documentation <ArrowRightIcon className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</div>
)}
{isLoading
? (
"Searching..."
)
: (
<div className="flex flex-col items-center justify-center py-6 text-center">
<p className="text-sm text-muted-foreground">No scripts match your search.</p>
<div className="mt-4">
<p className="text-xs text-muted-foreground mb-2">Want to add a new script?</p>
<Button variant="outline" size="sm" asChild>
<Link
href={`https://github.com/community-scripts/${basePath}/tree/main/docs/contribution/GUIDE.md`}
target="_blank"
rel="noopener noreferrer"
>
Documentation
{" "}
<ArrowRightIcon className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</div>
)}
</CommandEmpty>
{results.length > 0 ? (
<CommandGroup heading="Search Results">
{results.map(script => (
{Object.entries(uniqueScriptsByCategory).map(([categoryName, scripts]) => (
<CommandGroup key={`category:${categoryName}`} heading={categoryName}>
{scripts.map(script => (
<CommandItem
key={`script:${script.slug}`}
value={`${script.name} ${script.type} ${script.description || ""}`}
@@ -320,44 +272,7 @@ function CommandMenu() {
</CommandItem>
))}
</CommandGroup>
) : ( // When no search results, show all scripts grouped by category
Object.entries(uniqueScriptsByCategory).map(([categoryName, scripts]) => (
<CommandGroup key={`category:${categoryName}`} heading={categoryName}>
{scripts.map(script => (
<CommandItem
key={`script:${script.slug}`}
value={`${script.name} ${script.type} ${script.description || ""}`}
onSelect={() => {
setOpen(false);
router.push(`/scripts?id=${script.slug}`);
}}
tabIndex={0}
aria-label={`Open script ${script.name}`}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
setOpen(false);
router.push(`/scripts?id=${script.slug}`);
}
}}
>
<div className="flex gap-2" onClick={() => setOpen(false)}>
<Image
src={script.logo || `/${basePath}/logo.png`}
onError={e => ((e.currentTarget as HTMLImageElement).src = `/${basePath}/logo.png`)}
unoptimized
width={16}
height={16}
alt=""
className="h-5 w-5"
/>
<span>{script.name}</span>
<span>{formattedBadge(script.type)}</span>
</div>
</CommandItem>
))}
</CommandGroup>
))
)}
))}
</CommandList>
</CommandDialog>
</>

View File

@@ -36,9 +36,9 @@ msg_ok "Installed Tinyauth"
read -r -p "${TAB3}Enter your Tinyauth subdomain (e.g. https://tinyauth.example.com): " app_url
cat <<EOF >/opt/tinyauth/.env
DATABASE_PATH=/opt/tinyauth/database.db
USERS='${USER}'
APP_URL=${app_url}
TINYAUTH_DATABASE_PATH=/opt/tinyauth/database.db
TINYAUTH_AUTH_USERS='${USER}'
TINYAUTH_APPURL=${app_url}
EOF
msg_info "Creating Service"

View File

@@ -13,6 +13,10 @@ setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
$STD apt install -y build-essential
msg_ok "Installed Dependencies"
NODE_VERSION="24" setup_nodejs
msg_info "Setup Cross-Seed"

View File

@@ -15,6 +15,7 @@ update_os
msg_info "Installing dependencies"
$STD apt install -y \
git \
build-essential \
openssl \
sqlite3 \

View File

@@ -232,7 +232,7 @@ msg_ok "(2/5) Compiled libheif"
msg_info "(3/5) Compiling libraw"
SOURCE=${SOURCE_DIR}/libraw
: "${LIBRAW_REVISION:=$(jq -cr '.revision' $BASE_DIR/server/sources/libraw.json)}"
$STD git clone https://github.com/libraw/libraw.git "$SOURCE"
$STD git clone https://github.com/LibRaw/LibRaw.git "$SOURCE"
cd "$SOURCE"
$STD git reset --hard "$LIBRAW_REVISION"
$STD autoreconf --install

View File

@@ -38,6 +38,12 @@ msg_info "Setting up PowerDNS"
$STD apt install -y \
pdns-server \
pdns-backend-sqlite3
sed -i 's/^launch=$/# launch=/' /etc/powerdns/pdns.conf
rm -f /etc/powerdns/pdns.d/bind.conf
cat <<EOF >/etc/powerdns/pdns.d/gsqlite3.conf
launch+=gsqlite3
gsqlite3-database=/opt/poweradmin/powerdns.db
EOF
msg_ok "Setup PowerDNS"
fetch_and_deploy_gh_release "poweradmin" "poweradmin/poweradmin" "tarball"
@@ -126,7 +132,10 @@ cat <<EOF >/etc/apache2/sites-enabled/poweradmin.conf
EOF
$STD a2enmod rewrite headers
chown -R www-data:www-data /opt/poweradmin
$STD systemctl restart apache2
chown pdns:pdns /opt/poweradmin/powerdns.db
chmod 664 /opt/poweradmin/powerdns.db
usermod -aG pdns www-data
$STD systemctl restart pdns apache2
msg_info "Created Service"
motd_ssh

View File

@@ -0,0 +1,62 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/steveiliop56/tinyauth
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 \
openssl \
apache2-utils
msg_ok "Installed Dependencies"
fetch_and_deploy_gh_release "tinyauth" "steveiliop56/tinyauth" "singlefile" "latest" "/opt/tinyauth" "tinyauth-amd64"
msg_info "Setting up Tinyauth"
PASS=$(openssl rand -base64 8 | tr -dc 'a-zA-Z0-9' | head -c 8)
USER=$(htpasswd -Bbn "tinyauth" "${PASS}")
cat <<EOF >/opt/tinyauth/credentials.txt
Tinyauth Credentials
Username: tinyauth
Password: ${PASS}
EOF
msg_ok "Set up Tinyauth"
read -r -p "${TAB3}Enter your Tinyauth subdomain (e.g. https://tinyauth.example.com): " app_url
msg_info "Creating Service"
cat <<EOF >/opt/tinyauth/.env
TINYAUTH_DATABASE_PATH=/opt/tinyauth/database.db
TINYAUTH_AUTH_USERS='${USER}'
TINYAUTH_APPURL=${app_url}
EOF
cat <<EOF >/etc/systemd/system/tinyauth.service
[Unit]
Description=Tinyauth Service
After=network.target
[Service]
Type=simple
EnvironmentFile=/opt/tinyauth/.env
ExecStart=/opt/tinyauth/tinyauth
WorkingDirectory=/opt/tinyauth
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now tinyauth
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -1,53 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://ui.com/download/unifi
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 apt-transport-https
curl -fsSL "https://dl.ui.com/unifi/unifi-repo.gpg" -o "/usr/share/keyrings/unifi-repo.gpg"
cat <<EOF | sudo tee /etc/apt/sources.list.d/100-ubnt-unifi.sources >/dev/null
Types: deb
URIs: https://www.ui.com/downloads/unifi/debian
Suites: stable
Components: ubiquiti
Architectures: amd64
Signed-By: /usr/share/keyrings/unifi-repo.gpg
EOF
$STD apt update
msg_ok "Installed Dependencies"
JAVA_VERSION="21" setup_java
if lscpu | grep -q 'avx'; then
MONGO_VERSION="8.0" setup_mongodb
else
msg_error "No AVX detected (CPU-Flag)! We have discontinued support for this. You are welcome to try it manually with a Debian LXC, but due to the many issues with Unifi, we currently only support AVX CPUs."
exit 10
fi
if ! dpkg -l | grep -q 'libssl1.1'; then
msg_info "Installing libssl (if needed)"
curl -fsSL "https://security.debian.org/debian-security/pool/updates/main/o/openssl/libssl1.1_1.1.1w-0+deb11u4_amd64.deb" -o "/tmp/libssl.deb"
$STD dpkg -i /tmp/libssl.deb
rm -f /tmp/libssl.deb
msg_ok "Installed libssl1.1"
fi
msg_info "Installing UniFi Network Server"
$STD apt install -y unifi
msg_ok "Installed UniFi Network Server"
motd_ssh
customize
cleanup_lxc

View File

@@ -346,18 +346,20 @@ explain_exit_code() {
# - Handles backslashes, quotes, newlines, tabs, and carriage returns
# ------------------------------------------------------------------------------
json_escape() {
local s="$1"
# Strip ANSI escape sequences (color codes etc.)
s=$(printf '%s' "$s" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g')
s=${s//\\/\\\\}
s=${s//"/\\"/}
s=${s//$'\n'/\\n}
s=${s//$'\r'/}
s=${s//$'\t'/\\t}
# Remove any remaining control characters (0x00-0x1F except those already handled)
# Also remove DEL (0x7F) and invalid high bytes that break JSON parsers
s=$(printf '%s' "$s" | tr -d '\000-\010\013\014\016-\037\177')
printf '%s' "$s"
# Escape a string for safe JSON embedding using awk (handles any input size).
# Pipeline: strip ANSI → remove control chars → escape \ " TAB → join lines with \n
printf '%s' "$1" \
| sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' \
| tr -d '\000-\010\013\014\016-\037\177\r' \
| awk '
BEGIN { ORS = "" }
{
gsub(/\\/, "\\\\") # backslash → \\
gsub(/"/, "\\\"") # double quote → \"
gsub(/\t/, "\\t") # tab → \t
if (NR > 1) printf "\\n"
printf "%s", $0
}'
}
# ------------------------------------------------------------------------------

View File

@@ -1033,7 +1033,7 @@ load_vars_file() {
var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_gpu var_keyctl
var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu
var_net var_nesting var_ns var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged
var_verbose var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage
var_verbose var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_searchdomain
)
# Whitelist check helper
@@ -3620,13 +3620,13 @@ $PCT_OPTIONS_STRING"
# Add storage if specified
if [ -n "$SD" ]; then
PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING
$SD"
-searchdomain $SD"
fi
# Add nameserver if specified
if [ -n "$NS" ]; then
PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING
$NS"
-nameserver $NS"
fi
# Network configuration
@@ -4222,6 +4222,7 @@ EOF'
local is_network_issue=false
local is_apt_issue=false
local is_cmd_not_found=false
local is_disk_full=false
local error_explanation=""
if declare -f explain_exit_code >/dev/null 2>&1; then
error_explanation="$(explain_exit_code "$install_exit_code")"
@@ -4242,6 +4243,14 @@ EOF'
;;
esac
# Disk full / ENOSPC detection: errno -28 (ENOSPC), exit 228 (custom handler), exit 23 (curl write error)
if [[ $install_exit_code -eq 228 || $install_exit_code -eq 23 ]]; then
is_disk_full=true
fi
if [[ -f "$combined_log" ]] && grep -qiE 'ENOSPC|no space left on device|No space left on device|Disk quota exceeded|errno -28' "$combined_log"; then
is_disk_full=true
fi
# Command not found detection
if [[ $install_exit_code -eq 127 ]]; then
is_cmd_not_found=true
@@ -4278,6 +4287,9 @@ EOF'
if grep -qiE ': command not found|No such file or directory.*/s?bin/' "$combined_log"; then
is_cmd_not_found=true
fi
if grep -qiE 'ENOSPC|no space left on device|Disk quota exceeded|errno -28' "$combined_log"; then
is_disk_full=true
fi
fi
# Show error explanation if available
@@ -4299,6 +4311,12 @@ EOF'
echo ""
fi
if [[ "$is_disk_full" == true ]]; then
echo -e "${TAB}${INFO} The container ran out of disk space during installation (${GN}ENOSPC${CL})."
echo -e "${TAB}${INFO} Current disk size: ${GN}${DISK_SIZE} GB${CL}. A rebuild with doubled disk may resolve this."
echo ""
fi
if [[ "$is_cmd_not_found" == true ]]; then
local missing_cmd=""
if [[ -f "$combined_log" ]]; then
@@ -4318,7 +4336,7 @@ EOF'
echo -e " ${GN}3)${CL} Retry with verbose mode (full rebuild)"
local next_option=4
local APT_OPTION="" OOM_OPTION="" DNS_OPTION=""
local APT_OPTION="" OOM_OPTION="" DNS_OPTION="" DISK_OPTION=""
if [[ "$is_apt_issue" == true ]]; then
if [[ "$var_os" == "alpine" ]]; then
@@ -4343,6 +4361,18 @@ EOF'
fi
fi
if [[ "$is_disk_full" == true ]]; then
local disk_recovery_attempt="${DISK_RECOVERY_ATTEMPT:-0}"
if [[ $disk_recovery_attempt -lt 2 ]]; then
local new_disk=$((DISK_SIZE * 2))
echo -e " ${GN}${next_option})${CL} Retry with more disk space (Disk: ${DISK_SIZE}${new_disk} GB)"
DISK_OPTION=$next_option
next_option=$((next_option + 1))
else
echo -e " ${DGN}-)${CL} ${DGN}Disk resize retry exhausted (already retried ${disk_recovery_attempt}x)${CL}"
fi
fi
if [[ "$is_network_issue" == true ]]; then
echo -e " ${GN}${next_option})${CL} Retry with DNS override in LXC (8.8.8.8 / 1.1.1.1)"
DNS_OPTION=$next_option
@@ -4503,6 +4533,35 @@ EOF'
return $?
fi
if [[ -n "${DISK_OPTION}" && "${response}" == "${DISK_OPTION}" ]]; then
# Retry with doubled disk size
handled=true
echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild with more disk space...${CL}"
pct stop "$CTID" &>/dev/null || true
pct destroy "$CTID" &>/dev/null || true
echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}"
echo ""
local old_ctid="$CTID"
local old_disk="$DISK_SIZE"
export CTID=$(get_valid_container_id "$CTID")
export DISK_SIZE=$((DISK_SIZE * 2))
export var_disk="$DISK_SIZE"
export VERBOSE="yes"
export var_verbose="yes"
export DISK_RECOVERY_ATTEMPT=$((${DISK_RECOVERY_ATTEMPT:-0} + 1))
echo -e "${YW}Rebuilding with increased disk space (attempt ${DISK_RECOVERY_ATTEMPT}/2):${CL}"
echo -e " Container ID: ${old_ctid}${CTID}"
echo -e " Disk: ${old_disk}${GN}${DISK_SIZE}${CL} GB (x2)"
echo -e " RAM: ${RAM_SIZE} MiB | CPU: ${CORE_COUNT} cores"
echo -e " Network: ${NET:-dhcp} | Bridge: ${BRG:-vmbr0}"
echo -e " Verbose: ${GN}enabled${CL}"
echo ""
msg_info "Restarting installation..."
build_container
return $?
fi
if [[ -n "${DNS_OPTION}" && "${response}" == "${DNS_OPTION}" ]]; then
# Retry with DNS override in LXC
handled=true

View File

@@ -6500,14 +6500,13 @@ function setup_postgresql() {
local SUITE
case "$DISTRO_CODENAME" in
trixie | forky | sid)
if verify_repo_available "https://apt.postgresql.org/pub/repos/apt" "trixie-pgdg"; then
SUITE="trixie-pgdg"
else
SUITE="bookworm-pgdg"
msg_warn "PGDG repo not available for ${DISTRO_CODENAME}, falling back to distro packages"
USE_PGDG_REPO=false setup_postgresql
return $?
fi
;;
*)
SUITE=$(get_fallback_suite "$DISTRO_ID" "$DISTRO_CODENAME" "https://apt.postgresql.org/pub/repos/apt")

View File

@@ -105,7 +105,15 @@ function check_disk_space() {
return 0
}
TEMP_DIR=$(mktemp -d)
# Use disk-backed temp directory to avoid tmpfs/RAM size limits in /tmp
if [ -d "/var/tmp" ] && check_disk_space "/var/tmp" 20; then
TEMP_DIR=$(mktemp -d /var/tmp/opnsense-vm.XXXXXX)
elif [ -d "/tmp" ] && check_disk_space "/tmp" 20; then
TEMP_DIR=$(mktemp -d)
else
# Fallback: try /var/tmp anyway, disk space check will catch it later
TEMP_DIR=$(mktemp -d /var/tmp/opnsense-vm.XXXXXX)
fi
pushd $TEMP_DIR >/dev/null
function send_line_to_vm() {
echo -e "${DGN}Sending line: ${YW}$1${CL}"
@@ -260,6 +268,10 @@ function exit-script() {
exit
}
function get_available_bridges() {
ip -o link show type bridge 2>/dev/null | awk -F': ' '{print $2}' | sort
}
function default_settings() {
VMID=$(get_valid_nextid)
FORMAT=",efitype=4m"
@@ -279,11 +291,17 @@ function default_settings() {
VLAN=""
MAC=$GEN_MAC
WAN_MAC=$GEN_MAC_LAN
WAN_BRG="vmbr1"
WAN_BRG=""
MTU=""
START_VM="yes"
METHOD="default"
# Detect available bridges
local AVAILABLE_BRIDGES
AVAILABLE_BRIDGES=$(get_available_bridges)
local BRIDGE_COUNT
BRIDGE_COUNT=$(echo "$AVAILABLE_BRIDGES" | wc -l)
echo -e "${DGN}Using Virtual Machine ID: ${BGN}${VMID}${CL}"
echo -e "${DGN}Using Hostname: ${BGN}${HN}${CL}"
echo -e "${DGN}Allocated Cores: ${BGN}${CORE_COUNT}${CL}"
@@ -297,26 +315,34 @@ function default_settings() {
echo -e "${DGN}Using LAN VLAN: ${BGN}Default${CL}"
echo -e "${DGN}Using LAN MAC Address: ${BGN}${MAC}${CL}"
if NETWORK_MODE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "NETWORK CONFIGURATION" --radiolist --cancel-button Exit-Script \
"Choose network setup mode for OPNsense:\n" 14 70 2 \
"dual" "Dual Interface (Traditional Firewall/Router)" ON \
"single" "Single Interface (Proxy/VPN/IDS Server)" OFF \
3>&1 1>&2 2>&3); then
if [ "$NETWORK_MODE" = "dual" ]; then
echo -e "${DGN}Network Mode: ${BGN}Dual Interface (Firewall)${CL}"
echo -e "${DGN}Using WAN MAC Address: ${BGN}${WAN_MAC}${CL}"
if ! ip link show "${WAN_BRG}" &>/dev/null; then
msg_error "Bridge '${WAN_BRG}' does not exist"
exit
else
# Determine available network modes based on bridge count
local DEFAULT_WAN_BRG
DEFAULT_WAN_BRG=$(echo "$AVAILABLE_BRIDGES" | grep -v "^${BRG}$" | head -n1)
if [ "$BRIDGE_COUNT" -ge 2 ]; then
# Multiple bridges available - offer dual or single mode
if NETWORK_MODE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "NETWORK CONFIGURATION" --radiolist --cancel-button Exit-Script \
"Choose network setup mode for OPNsense:\n" 14 70 2 \
"dual" "Dual Interface (Firewall/Router) - uses ${DEFAULT_WAN_BRG}" ON \
"single" "Single Interface (Proxy/VPN/IDS Server)" OFF \
3>&1 1>&2 2>&3); then
if [ "$NETWORK_MODE" = "dual" ]; then
WAN_BRG="$DEFAULT_WAN_BRG"
echo -e "${DGN}Network Mode: ${BGN}Dual Interface (Firewall)${CL}"
echo -e "${DGN}Using WAN Bridge: ${BGN}${WAN_BRG}${CL}"
echo -e "${DGN}Using WAN MAC Address: ${BGN}${WAN_MAC}${CL}"
else
echo -e "${DGN}Network Mode: ${BGN}Single Interface (Proxy/VPN/IDS)${CL}"
WAN_BRG=""
fi
else
echo -e "${DGN}Network Mode: ${BGN}Single Interface (Proxy/VPN/IDS)${CL}"
WAN_BRG=""
exit-script
fi
else
exit-script
# Only one bridge available - single interface mode only
echo -e "${DGN}Network Mode: ${BGN}Single Interface (Proxy/VPN/IDS)${CL}"
echo -e "${YW} (Only one bridge detected, dual interface requires a second bridge)${CL}"
WAN_BRG=""
fi
echo -e "${DGN}Using Interface MTU Size: ${BGN}Default${CL}"
echo -e "${DGN}Start VM when completed: ${BGN}yes${CL}"
@@ -470,13 +496,29 @@ function advanced_settings() {
exit-script
fi
if WAN_BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a WAN Bridge" 8 58 vmbr1 --title "WAN BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z $WAN_BRG ]; then
WAN_BRG="vmbr1"
# Build WAN bridge selection from available bridges (excluding LAN bridge)
local WAN_BRIDGES
WAN_BRIDGES=$(get_available_bridges | grep -v "^${BRG}$")
if [ -z "$WAN_BRIDGES" ]; then
msg_error "No additional bridge available for WAN. Only '${BRG}' exists."
msg_error "Create a second bridge (e.g. vmbr1) in Proxmox network config first."
exit
fi
local WAN_MENU=()
local first=true
while IFS= read -r brg; do
if $first; then
WAN_MENU+=("$brg" "" "ON")
first=false
else
WAN_MENU+=("$brg" "" "OFF")
fi
if ! ip link show "${WAN_BRG}" &>/dev/null; then
msg_error "WAN Bridge '${WAN_BRG}' does not exist"
exit
done <<<"$WAN_BRIDGES"
if WAN_BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "WAN BRIDGE" --radiolist "Select WAN Bridge" 14 58 6 \
"${WAN_MENU[@]}" 3>&1 1>&2 2>&3); then
if [ -z "$WAN_BRG" ]; then
WAN_BRG=$(echo "$WAN_BRIDGES" | head -n1)
fi
echo -e "${DGN}Using WAN Bridge: ${BGN}$WAN_BRG${CL}"
else