mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-03-19 16:33:01 +01:00
Compare commits
1 Commits
github-act
...
MickLesk-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f227ab2cd |
10
.github/workflows/delete-pocketbase-entry-on-removal.yml
generated
vendored
10
.github/workflows/delete-pocketbase-entry-on-removal.yml
generated
vendored
@@ -75,8 +75,7 @@ jobs:
|
|||||||
const http = require('http');
|
const http = require('http');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
|
||||||
function request(fullUrl, opts, redirectCount) {
|
function request(fullUrl, opts) {
|
||||||
redirectCount = redirectCount || 0;
|
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
const u = url.parse(fullUrl);
|
const u = url.parse(fullUrl);
|
||||||
const isHttps = u.protocol === 'https:';
|
const isHttps = u.protocol === 'https:';
|
||||||
@@ -91,13 +90,6 @@ jobs:
|
|||||||
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
|
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
|
||||||
const lib = isHttps ? https : http;
|
const lib = isHttps ? https : http;
|
||||||
const req = lib.request(options, function(res) {
|
const req = lib.request(options, function(res) {
|
||||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
||||||
if (redirectCount >= 5) return reject(new Error('Too many redirects from ' + fullUrl));
|
|
||||||
const redirectUrl = url.resolve(fullUrl, res.headers.location);
|
|
||||||
res.resume();
|
|
||||||
resolve(request(redirectUrl, opts, redirectCount + 1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let data = '';
|
let data = '';
|
||||||
res.on('data', function(chunk) { data += chunk; });
|
res.on('data', function(chunk) { data += chunk; });
|
||||||
res.on('end', function() {
|
res.on('end', function() {
|
||||||
|
|||||||
675
.github/workflows/pocketbase-bot.yml
generated
vendored
675
.github/workflows/pocketbase-bot.yml
generated
vendored
@@ -1,675 +0,0 @@
|
|||||||
name: PocketBase Bot
|
|
||||||
|
|
||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
pocketbase-bot:
|
|
||||||
runs-on: self-hosted
|
|
||||||
|
|
||||||
# Only act on /pocketbase commands
|
|
||||||
if: startsWith(github.event.comment.body, '/pocketbase')
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Execute PocketBase bot command
|
|
||||||
env:
|
|
||||||
POCKETBASE_URL: ${{ secrets.POCKETBASE_URL }}
|
|
||||||
POCKETBASE_COLLECTION: ${{ secrets.POCKETBASE_COLLECTION }}
|
|
||||||
POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }}
|
|
||||||
POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }}
|
|
||||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
|
||||||
COMMENT_ID: ${{ github.event.comment.id }}
|
|
||||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
||||||
REPO_OWNER: ${{ github.repository_owner }}
|
|
||||||
REPO_NAME: ${{ github.event.repository.name }}
|
|
||||||
ACTOR: ${{ github.event.comment.user.login }}
|
|
||||||
ACTOR_ASSOCIATION: ${{ github.event.comment.author_association }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
node << 'ENDSCRIPT'
|
|
||||||
(async function () {
|
|
||||||
const https = require('https');
|
|
||||||
const http = require('http');
|
|
||||||
const url = require('url');
|
|
||||||
|
|
||||||
// ── HTTP helper with redirect following ────────────────────────────
|
|
||||||
function request(fullUrl, opts, redirectCount) {
|
|
||||||
redirectCount = redirectCount || 0;
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
const u = url.parse(fullUrl);
|
|
||||||
const isHttps = u.protocol === 'https:';
|
|
||||||
const body = opts.body;
|
|
||||||
const options = {
|
|
||||||
hostname: u.hostname,
|
|
||||||
port: u.port || (isHttps ? 443 : 80),
|
|
||||||
path: u.path,
|
|
||||||
method: opts.method || 'GET',
|
|
||||||
headers: opts.headers || {}
|
|
||||||
};
|
|
||||||
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
|
|
||||||
const lib = isHttps ? https : http;
|
|
||||||
const req = lib.request(options, function (res) {
|
|
||||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
||||||
if (redirectCount >= 5) return reject(new Error('Too many redirects from ' + fullUrl));
|
|
||||||
const redirectUrl = url.resolve(fullUrl, res.headers.location);
|
|
||||||
res.resume();
|
|
||||||
resolve(request(redirectUrl, opts, redirectCount + 1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let data = '';
|
|
||||||
res.on('data', function (chunk) { data += chunk; });
|
|
||||||
res.on('end', function () {
|
|
||||||
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.on('error', reject);
|
|
||||||
if (body) req.write(body);
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── GitHub API helpers ─────────────────────────────────────────────
|
|
||||||
const owner = process.env.REPO_OWNER;
|
|
||||||
const repo = process.env.REPO_NAME;
|
|
||||||
const issueNumber = parseInt(process.env.ISSUE_NUMBER, 10);
|
|
||||||
const commentId = parseInt(process.env.COMMENT_ID, 10);
|
|
||||||
const actor = process.env.ACTOR;
|
|
||||||
|
|
||||||
function ghRequest(path, method, body) {
|
|
||||||
const headers = {
|
|
||||||
'Authorization': 'Bearer ' + process.env.GITHUB_TOKEN,
|
|
||||||
'Accept': 'application/vnd.github+json',
|
|
||||||
'X-GitHub-Api-Version': '2022-11-28',
|
|
||||||
'User-Agent': 'PocketBase-Bot'
|
|
||||||
};
|
|
||||||
const bodyStr = body ? JSON.stringify(body) : undefined;
|
|
||||||
if (bodyStr) headers['Content-Type'] = 'application/json';
|
|
||||||
return request('https://api.github.com' + path, { method: method || 'GET', headers, body: bodyStr });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addReaction(content) {
|
|
||||||
try {
|
|
||||||
await ghRequest(
|
|
||||||
'/repos/' + owner + '/' + repo + '/issues/comments/' + commentId + '/reactions',
|
|
||||||
'POST', { content }
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Could not add reaction:', e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function postComment(text) {
|
|
||||||
const res = await ghRequest(
|
|
||||||
'/repos/' + owner + '/' + repo + '/issues/' + issueNumber + '/comments',
|
|
||||||
'POST', { body: text }
|
|
||||||
);
|
|
||||||
if (!res.ok) console.warn('Could not post comment:', res.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Permission check ───────────────────────────────────────────────
|
|
||||||
// author_association: OWNER = repo/org owner, MEMBER = org member (includes Contributors team)
|
|
||||||
const association = process.env.ACTOR_ASSOCIATION;
|
|
||||||
if (association !== 'OWNER' && association !== 'MEMBER') {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment(
|
|
||||||
'❌ **PocketBase Bot**: @' + actor + ' is not authorized to use this command.\n' +
|
|
||||||
'Only org members (Contributors team) can use `/pocketbase`.'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Acknowledge ────────────────────────────────────────────────────
|
|
||||||
await addReaction('eyes');
|
|
||||||
|
|
||||||
// ── Parse command ──────────────────────────────────────────────────
|
|
||||||
// Formats (first line of comment):
|
|
||||||
// /pocketbase <slug> field=value [field=value ...] ← field updates (simple values)
|
|
||||||
// /pocketbase <slug> set <field> ← value from code block below
|
|
||||||
// /pocketbase <slug> note list|add|edit|remove ... ← note management
|
|
||||||
// /pocketbase <slug> method list ← list install methods
|
|
||||||
// /pocketbase <slug> method <type> cpu=N ram=N hdd=N ← edit install method resources
|
|
||||||
const commentBody = process.env.COMMENT_BODY || '';
|
|
||||||
const lines = commentBody.trim().split('\n');
|
|
||||||
const firstLine = lines[0].trim();
|
|
||||||
const withoutCmd = firstLine.replace(/^\/pocketbase\s+/, '').trim();
|
|
||||||
|
|
||||||
// Extract code block content from comment body (```...``` or ```lang\n...```)
|
|
||||||
function extractCodeBlock(body) {
|
|
||||||
const m = body.match(/```[^\n]*\n([\s\S]*?)```/);
|
|
||||||
return m ? m[1].trim() : null;
|
|
||||||
}
|
|
||||||
const codeBlockValue = extractCodeBlock(commentBody);
|
|
||||||
|
|
||||||
const HELP_TEXT =
|
|
||||||
'**Field update (simple):** `/pocketbase <slug> field=value [field=value ...]`\n\n' +
|
|
||||||
'**Field update (HTML/multiline) — value from code block:**\n' +
|
|
||||||
'````\n' +
|
|
||||||
'/pocketbase <slug> set description\n' +
|
|
||||||
'```html\n' +
|
|
||||||
'<p>Your <b>HTML</b> or multi-line content here</p>\n' +
|
|
||||||
'```\n' +
|
|
||||||
'````\n\n' +
|
|
||||||
'**Note management:**\n' +
|
|
||||||
'```\n' +
|
|
||||||
'/pocketbase <slug> note list\n' +
|
|
||||||
'/pocketbase <slug> note add <type> "<text>"\n' +
|
|
||||||
'/pocketbase <slug> note edit <type> "<old text>" "<new text>"\n' +
|
|
||||||
'/pocketbase <slug> note remove <type> "<text>"\n' +
|
|
||||||
'```\n\n' +
|
|
||||||
'**Install method resources:**\n' +
|
|
||||||
'```\n' +
|
|
||||||
'/pocketbase <slug> method list\n' +
|
|
||||||
'/pocketbase <slug> method <type> hdd=10\n' +
|
|
||||||
'/pocketbase <slug> method <type> cpu=4 ram=2048 hdd=20\n' +
|
|
||||||
'```\n\n' +
|
|
||||||
'**Editable fields:** `name` `description` `logo` `documentation` `website` `project_url` `github` ' +
|
|
||||||
'`config_path` `port` `default_user` `default_passwd` ' +
|
|
||||||
'`updateable` `privileged` `has_arm` `is_dev` ' +
|
|
||||||
'`is_disabled` `disable_message` `is_deleted` `deleted_message`';
|
|
||||||
|
|
||||||
if (!withoutCmd) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment('❌ **PocketBase Bot**: No slug or command specified.\n\n' + HELP_TEXT);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const spaceIdx = withoutCmd.indexOf(' ');
|
|
||||||
const slug = (spaceIdx === -1 ? withoutCmd : withoutCmd.substring(0, spaceIdx)).trim();
|
|
||||||
const rest = spaceIdx === -1 ? '' : withoutCmd.substring(spaceIdx + 1).trim();
|
|
||||||
|
|
||||||
if (!rest) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment('❌ **PocketBase Bot**: No command specified for slug `' + slug + '`.\n\n' + HELP_TEXT);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Allowed fields and their types ─────────────────────────────────
|
|
||||||
// ── PocketBase: authenticate (shared by all paths) ─────────────────
|
|
||||||
const raw = process.env.POCKETBASE_URL.replace(/\/$/, '');
|
|
||||||
const apiBase = /\/api$/i.test(raw) ? raw : raw + '/api';
|
|
||||||
const coll = process.env.POCKETBASE_COLLECTION;
|
|
||||||
|
|
||||||
const authRes = await request(apiBase + '/collections/users/auth-with-password', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
identity: process.env.POCKETBASE_ADMIN_EMAIL,
|
|
||||||
password: process.env.POCKETBASE_ADMIN_PASSWORD
|
|
||||||
})
|
|
||||||
});
|
|
||||||
if (!authRes.ok) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment('❌ **PocketBase Bot**: PocketBase authentication failed. CC @' + owner + '/maintainers');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
const token = JSON.parse(authRes.body).token;
|
|
||||||
|
|
||||||
// ── PocketBase: find record by slug (shared by all paths) ──────────
|
|
||||||
const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records';
|
|
||||||
const filter = "(slug='" + slug.replace(/'/g, "''") + "')";
|
|
||||||
const listRes = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', {
|
|
||||||
headers: { 'Authorization': token }
|
|
||||||
});
|
|
||||||
const list = JSON.parse(listRes.body);
|
|
||||||
const record = list.items && list.items[0];
|
|
||||||
|
|
||||||
if (!record) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment(
|
|
||||||
'❌ **PocketBase Bot**: No record found for slug `' + slug + '`.\n\n' +
|
|
||||||
'Make sure the script was already pushed to PocketBase (JSON must exist and have been synced).'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Route: dispatch to subcommand handler ──────────────────────────
|
|
||||||
const noteMatch = rest.match(/^note\s+(list|add|edit|remove)\b/i);
|
|
||||||
const methodMatch = rest.match(/^method\b/i);
|
|
||||||
const setMatch = rest.match(/^set\s+(\S+)/i);
|
|
||||||
|
|
||||||
if (noteMatch) {
|
|
||||||
// ── NOTE SUBCOMMAND (reads/writes notes_json on script record) ────
|
|
||||||
const noteAction = noteMatch[1].toLowerCase();
|
|
||||||
const noteArgsStr = rest.substring(noteMatch[0].length).trim();
|
|
||||||
|
|
||||||
// Parse notes_json from the already-fetched script record
|
|
||||||
// PocketBase may return JSON fields as already-parsed objects
|
|
||||||
let notesArr = [];
|
|
||||||
try {
|
|
||||||
const rawNotes = record.notes_json;
|
|
||||||
notesArr = Array.isArray(rawNotes) ? rawNotes : JSON.parse(rawNotes || '[]');
|
|
||||||
} catch (e) { notesArr = []; }
|
|
||||||
|
|
||||||
// Token parser: unquoted-word OR "quoted string" (supports \" escapes)
|
|
||||||
function parseNoteTokens(str) {
|
|
||||||
const tokens = [];
|
|
||||||
let pos = 0;
|
|
||||||
while (pos < str.length) {
|
|
||||||
while (pos < str.length && /\s/.test(str[pos])) pos++;
|
|
||||||
if (pos >= str.length) break;
|
|
||||||
if (str[pos] === '"') {
|
|
||||||
pos++;
|
|
||||||
let start = pos;
|
|
||||||
while (pos < str.length && str[pos] !== '"') {
|
|
||||||
if (str[pos] === '\\') pos++;
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
tokens.push(str.substring(start, pos).replace(/\\"/g, '"'));
|
|
||||||
if (pos < str.length) pos++;
|
|
||||||
} else {
|
|
||||||
let start = pos;
|
|
||||||
while (pos < str.length && !/\s/.test(str[pos])) pos++;
|
|
||||||
tokens.push(str.substring(start, pos));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatNotesList(arr) {
|
|
||||||
if (arr.length === 0) return '*None*';
|
|
||||||
return arr.map(function (n, i) {
|
|
||||||
return (i + 1) + '. **`' + (n.type || '?') + '`**: ' + (n.text || '');
|
|
||||||
}).join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function patchNotesJson(arr) {
|
|
||||||
const res = await request(recordsUrl + '/' + record.id, {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ notes_json: JSON.stringify(arr) })
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment('❌ **PocketBase Bot**: Failed to update `notes_json`:\n```\n' + res.body + '\n```');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (noteAction === 'list') {
|
|
||||||
await addReaction('+1');
|
|
||||||
await postComment(
|
|
||||||
'ℹ️ **PocketBase Bot**: Notes for **`' + slug + '`** (' + notesArr.length + ' total)\n\n' +
|
|
||||||
formatNotesList(notesArr)
|
|
||||||
);
|
|
||||||
|
|
||||||
} else if (noteAction === 'add') {
|
|
||||||
const tokens = parseNoteTokens(noteArgsStr);
|
|
||||||
if (tokens.length < 2) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment(
|
|
||||||
'❌ **PocketBase Bot**: `note add` requires `<type>` and `"<text>"`.\n\n' +
|
|
||||||
'**Usage:** `/pocketbase ' + slug + ' note add <type> "<text>"`'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
const noteType = tokens[0].toLowerCase();
|
|
||||||
const noteText = tokens.slice(1).join(' ');
|
|
||||||
notesArr.push({ type: noteType, text: noteText });
|
|
||||||
await patchNotesJson(notesArr);
|
|
||||||
await addReaction('+1');
|
|
||||||
await postComment(
|
|
||||||
'✅ **PocketBase Bot**: Added note to **`' + slug + '`**\n\n' +
|
|
||||||
'- **Type:** `' + noteType + '`\n' +
|
|
||||||
'- **Text:** ' + noteText + '\n\n' +
|
|
||||||
'*Executed by @' + actor + '*'
|
|
||||||
);
|
|
||||||
|
|
||||||
} else if (noteAction === 'edit') {
|
|
||||||
const tokens = parseNoteTokens(noteArgsStr);
|
|
||||||
if (tokens.length < 3) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment(
|
|
||||||
'❌ **PocketBase Bot**: `note edit` requires `<type>`, `"<old text>"`, and `"<new text>"`.\n\n' +
|
|
||||||
'**Usage:** `/pocketbase ' + slug + ' note edit <type> "<old text>" "<new text>"`\n\n' +
|
|
||||||
'Use `/pocketbase ' + slug + ' note list` to see current notes.'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
const noteType = tokens[0].toLowerCase();
|
|
||||||
const oldText = tokens[1];
|
|
||||||
const newText = tokens[2];
|
|
||||||
const idx = notesArr.findIndex(function (n) {
|
|
||||||
return n.type.toLowerCase() === noteType && n.text === oldText;
|
|
||||||
});
|
|
||||||
if (idx === -1) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment(
|
|
||||||
'❌ **PocketBase Bot**: No `' + noteType + '` note found with that exact text.\n\n' +
|
|
||||||
'**Current notes for `' + slug + '`:**\n' + formatNotesList(notesArr)
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
notesArr[idx].text = newText;
|
|
||||||
await patchNotesJson(notesArr);
|
|
||||||
await addReaction('+1');
|
|
||||||
await postComment(
|
|
||||||
'✅ **PocketBase Bot**: Edited note in **`' + slug + '`**\n\n' +
|
|
||||||
'- **Type:** `' + noteType + '`\n' +
|
|
||||||
'- **Old:** ' + oldText + '\n' +
|
|
||||||
'- **New:** ' + newText + '\n\n' +
|
|
||||||
'*Executed by @' + actor + '*'
|
|
||||||
);
|
|
||||||
|
|
||||||
} else if (noteAction === 'remove') {
|
|
||||||
const tokens = parseNoteTokens(noteArgsStr);
|
|
||||||
if (tokens.length < 2) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment(
|
|
||||||
'❌ **PocketBase Bot**: `note remove` requires `<type>` and `"<text>"`.\n\n' +
|
|
||||||
'**Usage:** `/pocketbase ' + slug + ' note remove <type> "<text>"`\n\n' +
|
|
||||||
'Use `/pocketbase ' + slug + ' note list` to see current notes.'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
const noteType = tokens[0].toLowerCase();
|
|
||||||
const noteText = tokens[1];
|
|
||||||
const before = notesArr.length;
|
|
||||||
notesArr = notesArr.filter(function (n) {
|
|
||||||
return !(n.type.toLowerCase() === noteType && n.text === noteText);
|
|
||||||
});
|
|
||||||
if (notesArr.length === before) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment(
|
|
||||||
'❌ **PocketBase Bot**: No `' + noteType + '` note found with that exact text.\n\n' +
|
|
||||||
'**Current notes for `' + slug + '`:**\n' + formatNotesList(notesArr)
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
await patchNotesJson(notesArr);
|
|
||||||
await addReaction('+1');
|
|
||||||
await postComment(
|
|
||||||
'✅ **PocketBase Bot**: Removed note from **`' + slug + '`**\n\n' +
|
|
||||||
'- **Type:** `' + noteType + '`\n' +
|
|
||||||
'- **Text:** ' + noteText + '\n\n' +
|
|
||||||
'*Executed by @' + actor + '*'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (methodMatch) {
|
|
||||||
// ── METHOD SUBCOMMAND (reads/writes install_methods_json on script record) ──
|
|
||||||
const methodArgs = rest.replace(/^method\s*/i, '').trim();
|
|
||||||
const methodListMode = !methodArgs || methodArgs.toLowerCase() === 'list';
|
|
||||||
|
|
||||||
// Parse install_methods_json from the already-fetched script record
|
|
||||||
// PocketBase may return JSON fields as already-parsed objects
|
|
||||||
let methodsArr = [];
|
|
||||||
try {
|
|
||||||
const rawMethods = record.install_methods_json;
|
|
||||||
methodsArr = Array.isArray(rawMethods) ? rawMethods : JSON.parse(rawMethods || '[]');
|
|
||||||
} catch (e) { methodsArr = []; }
|
|
||||||
|
|
||||||
function formatMethodsList(arr) {
|
|
||||||
if (arr.length === 0) return '*None*';
|
|
||||||
return arr.map(function (im, i) {
|
|
||||||
const r = im.resources || {};
|
|
||||||
return (i + 1) + '. **`' + (im.type || '?') + '`** — CPU: `' + (r.cpu != null ? r.cpu : '?') +
|
|
||||||
'` · RAM: `' + (r.ram != null ? r.ram : '?') + ' MB` · HDD: `' + (r.hdd != null ? r.hdd : '?') + ' GB`';
|
|
||||||
}).join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function patchInstallMethodsJson(arr) {
|
|
||||||
const res = await request(recordsUrl + '/' + record.id, {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ install_methods_json: JSON.stringify(arr) })
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment('❌ **PocketBase Bot**: Failed to update `install_methods_json`:\n```\n' + res.body + '\n```');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (methodListMode) {
|
|
||||||
await addReaction('+1');
|
|
||||||
await postComment(
|
|
||||||
'ℹ️ **PocketBase Bot**: Install methods for **`' + slug + '`** (' + methodsArr.length + ' total)\n\n' +
|
|
||||||
formatMethodsList(methodsArr)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Parse: <type> cpu=N ram=N hdd=N
|
|
||||||
const methodParts = methodArgs.match(/^(\S+)\s+(.+)$/);
|
|
||||||
if (!methodParts) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment(
|
|
||||||
'❌ **PocketBase Bot**: Invalid `method` syntax.\n\n' +
|
|
||||||
'**Usage:**\n```\n/pocketbase ' + slug + ' method list\n/pocketbase ' + slug + ' method <type> hdd=10\n/pocketbase ' + slug + ' method <type> cpu=4 ram=2048 hdd=20\n```'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
const targetType = methodParts[1].toLowerCase();
|
|
||||||
const resourcesStr = methodParts[2];
|
|
||||||
|
|
||||||
// Parse resource fields (only cpu/ram/hdd allowed)
|
|
||||||
const RESOURCE_FIELDS = { cpu: true, ram: true, hdd: true };
|
|
||||||
const resourceChanges = {};
|
|
||||||
const rePairs = /([a-z]+)=(\d+)/gi;
|
|
||||||
let m;
|
|
||||||
while ((m = rePairs.exec(resourcesStr)) !== null) {
|
|
||||||
const key = m[1].toLowerCase();
|
|
||||||
if (RESOURCE_FIELDS[key]) resourceChanges[key] = parseInt(m[2], 10);
|
|
||||||
}
|
|
||||||
if (Object.keys(resourceChanges).length === 0) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment('❌ **PocketBase Bot**: No valid resource fields found. Use `cpu=N`, `ram=N`, `hdd=N`.');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find matching method by type name (case-insensitive)
|
|
||||||
const idx = methodsArr.findIndex(function (im) {
|
|
||||||
return (im.type || '').toLowerCase() === targetType;
|
|
||||||
});
|
|
||||||
if (idx === -1) {
|
|
||||||
await addReaction('-1');
|
|
||||||
const availableTypes = methodsArr.map(function (im) { return im.type || '?'; });
|
|
||||||
await postComment(
|
|
||||||
'❌ **PocketBase Bot**: No install method with type `' + targetType + '` found for `' + slug + '`.\n\n' +
|
|
||||||
'**Available types:** `' + (availableTypes.length ? availableTypes.join('`, `') : '(none)') + '`\n\n' +
|
|
||||||
'Use `/pocketbase ' + slug + ' method list` to see all methods.'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!methodsArr[idx].resources) methodsArr[idx].resources = {};
|
|
||||||
if (resourceChanges.cpu != null) methodsArr[idx].resources.cpu = resourceChanges.cpu;
|
|
||||||
if (resourceChanges.ram != null) methodsArr[idx].resources.ram = resourceChanges.ram;
|
|
||||||
if (resourceChanges.hdd != null) methodsArr[idx].resources.hdd = resourceChanges.hdd;
|
|
||||||
|
|
||||||
await patchInstallMethodsJson(methodsArr);
|
|
||||||
|
|
||||||
const changesLines = Object.entries(resourceChanges)
|
|
||||||
.map(function ([k, v]) { return '- `' + k + '` → `' + v + (k === 'ram' ? ' MB' : k === 'hdd' ? ' GB' : '') + '`'; })
|
|
||||||
.join('\n');
|
|
||||||
await addReaction('+1');
|
|
||||||
await postComment(
|
|
||||||
'✅ **PocketBase Bot**: Updated install method **`' + methodsArr[idx].type + '`** for **`' + slug + '`**\n\n' +
|
|
||||||
'**Changes applied:**\n' + changesLines + '\n\n' +
|
|
||||||
'*Executed by @' + actor + '*'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (setMatch) {
|
|
||||||
// ── SET SUBCOMMAND (multi-line / HTML / special chars via code block) ──
|
|
||||||
const fieldName = setMatch[1].toLowerCase();
|
|
||||||
const SET_ALLOWED = {
|
|
||||||
name: 'string', description: 'string', logo: 'string',
|
|
||||||
documentation: 'string', website: 'string', project_url: 'string', github: 'string',
|
|
||||||
config_path: 'string', disable_message: 'string', deleted_message: 'string'
|
|
||||||
};
|
|
||||||
if (!SET_ALLOWED[fieldName]) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment(
|
|
||||||
'❌ **PocketBase Bot**: `set` only supports text fields.\n\n' +
|
|
||||||
'**Allowed:** `' + Object.keys(SET_ALLOWED).join('`, `') + '`\n\n' +
|
|
||||||
'For boolean/number fields use `field=value` syntax instead.'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
if (!codeBlockValue) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment(
|
|
||||||
'❌ **PocketBase Bot**: `set` requires a code block with the value.\n\n' +
|
|
||||||
'**Usage:**\n````\n/pocketbase ' + slug + ' set ' + fieldName + '\n```\nYour content here (HTML, multiline, special chars all fine)\n```\n````'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
const setPayload = {};
|
|
||||||
setPayload[fieldName] = codeBlockValue;
|
|
||||||
const setPatchRes = await request(recordsUrl + '/' + record.id, {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(setPayload)
|
|
||||||
});
|
|
||||||
if (!setPatchRes.ok) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment('❌ **PocketBase Bot**: PATCH failed for `' + slug + '`:\n```\n' + setPatchRes.body + '\n```');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
const preview = codeBlockValue.length > 300 ? codeBlockValue.substring(0, 300) + '…' : codeBlockValue;
|
|
||||||
await addReaction('+1');
|
|
||||||
await postComment(
|
|
||||||
'✅ **PocketBase Bot**: Set `' + fieldName + '` for **`' + slug + '`**\n\n' +
|
|
||||||
'**Value set:**\n```\n' + preview + '\n```\n\n' +
|
|
||||||
'*Executed by @' + actor + '*'
|
|
||||||
);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// ── FIELD=VALUE PATH ─────────────────────────────────────────────
|
|
||||||
const fieldsStr = rest;
|
|
||||||
|
|
||||||
// Skipped: slug, script_created/updated, created (auto), categories/
|
|
||||||
// install_methods/notes/type (relations), github_data/install_methods_json/
|
|
||||||
// notes_json (auto-generated), execute_in (select relation), last_update_commit (auto)
|
|
||||||
const ALLOWED_FIELDS = {
|
|
||||||
name: 'string',
|
|
||||||
description: 'string',
|
|
||||||
logo: 'string',
|
|
||||||
documentation: 'string',
|
|
||||||
website: 'string',
|
|
||||||
project_url: 'string',
|
|
||||||
github: 'string',
|
|
||||||
config_path: 'string',
|
|
||||||
port: 'number',
|
|
||||||
default_user: 'nullable_string',
|
|
||||||
default_passwd: 'nullable_string',
|
|
||||||
updateable: 'boolean',
|
|
||||||
privileged: 'boolean',
|
|
||||||
has_arm: 'boolean',
|
|
||||||
is_dev: 'boolean',
|
|
||||||
is_disabled: 'boolean',
|
|
||||||
disable_message: 'string',
|
|
||||||
is_deleted: 'boolean',
|
|
||||||
deleted_message: 'string',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Field=value parser (handles quoted values and empty=null)
|
|
||||||
function parseFields(str) {
|
|
||||||
const fields = {};
|
|
||||||
let pos = 0;
|
|
||||||
while (pos < str.length) {
|
|
||||||
while (pos < str.length && /\s/.test(str[pos])) pos++;
|
|
||||||
if (pos >= str.length) break;
|
|
||||||
let keyStart = pos;
|
|
||||||
while (pos < str.length && str[pos] !== '=' && !/\s/.test(str[pos])) pos++;
|
|
||||||
const key = str.substring(keyStart, pos).trim();
|
|
||||||
if (!key || pos >= str.length || str[pos] !== '=') { pos++; continue; }
|
|
||||||
pos++;
|
|
||||||
let value;
|
|
||||||
if (str[pos] === '"') {
|
|
||||||
pos++;
|
|
||||||
let valStart = pos;
|
|
||||||
while (pos < str.length && str[pos] !== '"') {
|
|
||||||
if (str[pos] === '\\') pos++;
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
value = str.substring(valStart, pos).replace(/\\"/g, '"');
|
|
||||||
if (pos < str.length) pos++;
|
|
||||||
} else {
|
|
||||||
let valStart = pos;
|
|
||||||
while (pos < str.length && !/\s/.test(str[pos])) pos++;
|
|
||||||
value = str.substring(valStart, pos);
|
|
||||||
}
|
|
||||||
fields[key] = value;
|
|
||||||
}
|
|
||||||
return fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedFields = parseFields(fieldsStr);
|
|
||||||
|
|
||||||
const unknownFields = Object.keys(parsedFields).filter(function (f) { return !ALLOWED_FIELDS[f]; });
|
|
||||||
if (unknownFields.length > 0) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment(
|
|
||||||
'❌ **PocketBase Bot**: Unknown field(s): `' + unknownFields.join('`, `') + '`\n\n' +
|
|
||||||
'**Allowed fields:** `' + Object.keys(ALLOWED_FIELDS).join('`, `') + '`'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(parsedFields).length === 0) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment('❌ **PocketBase Bot**: Could not parse any valid `field=value` pairs.\n\n' + HELP_TEXT);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cast values to correct types
|
|
||||||
const payload = {};
|
|
||||||
for (const [key, rawVal] of Object.entries(parsedFields)) {
|
|
||||||
const type = ALLOWED_FIELDS[key];
|
|
||||||
if (type === 'boolean') {
|
|
||||||
if (rawVal === 'true') payload[key] = true;
|
|
||||||
else if (rawVal === 'false') payload[key] = false;
|
|
||||||
else {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment('❌ **PocketBase Bot**: `' + key + '` must be `true` or `false`, got: `' + rawVal + '`');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
} else if (type === 'number') {
|
|
||||||
const n = parseInt(rawVal, 10);
|
|
||||||
if (isNaN(n)) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment('❌ **PocketBase Bot**: `' + key + '` must be a number, got: `' + rawVal + '`');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
payload[key] = n;
|
|
||||||
} else if (type === 'nullable_string') {
|
|
||||||
payload[key] = rawVal === '' ? null : rawVal;
|
|
||||||
} else {
|
|
||||||
payload[key] = rawVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const patchRes = await request(recordsUrl + '/' + record.id, {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
});
|
|
||||||
if (!patchRes.ok) {
|
|
||||||
await addReaction('-1');
|
|
||||||
await postComment('❌ **PocketBase Bot**: PATCH failed for `' + slug + '`:\n```\n' + patchRes.body + '\n```');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
await addReaction('+1');
|
|
||||||
const changesLines = Object.entries(payload)
|
|
||||||
.map(function ([k, v]) { return '- `' + k + '` → `' + JSON.stringify(v) + '`'; })
|
|
||||||
.join('\n');
|
|
||||||
await postComment(
|
|
||||||
'✅ **PocketBase Bot**: Updated **`' + slug + '`** successfully!\n\n' +
|
|
||||||
'**Changes applied:**\n' + changesLines + '\n\n' +
|
|
||||||
'*Executed by @' + actor + '*'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Done.');
|
|
||||||
})().catch(function (e) {
|
|
||||||
console.error('Fatal error:', e.message || e);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
ENDSCRIPT
|
|
||||||
shell: bash
|
|
||||||
24
.github/workflows/push-json-to-pocketbase.yml
generated
vendored
24
.github/workflows/push-json-to-pocketbase.yml
generated
vendored
@@ -48,8 +48,7 @@ jobs:
|
|||||||
const https = require('https');
|
const https = require('https');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
function request(fullUrl, opts, redirectCount) {
|
function request(fullUrl, opts) {
|
||||||
redirectCount = redirectCount || 0;
|
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
const u = url.parse(fullUrl);
|
const u = url.parse(fullUrl);
|
||||||
const isHttps = u.protocol === 'https:';
|
const isHttps = u.protocol === 'https:';
|
||||||
@@ -64,13 +63,6 @@ jobs:
|
|||||||
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
|
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
|
||||||
const lib = isHttps ? https : http;
|
const lib = isHttps ? https : http;
|
||||||
const req = lib.request(options, function(res) {
|
const req = lib.request(options, function(res) {
|
||||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
||||||
if (redirectCount >= 5) return reject(new Error('Too many redirects from ' + fullUrl));
|
|
||||||
const redirectUrl = url.resolve(fullUrl, res.headers.location);
|
|
||||||
res.resume();
|
|
||||||
resolve(request(redirectUrl, opts, redirectCount + 1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let data = '';
|
let data = '';
|
||||||
res.on('data', function(chunk) { data += chunk; });
|
res.on('data', function(chunk) { data += chunk; });
|
||||||
res.on('end', function() {
|
res.on('end', function() {
|
||||||
@@ -133,15 +125,15 @@ jobs:
|
|||||||
var osVersionToId = {};
|
var osVersionToId = {};
|
||||||
try {
|
try {
|
||||||
const res = await request(apiBase + '/collections/z_ref_note_types/records?perPage=500', { headers: { 'Authorization': token } });
|
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; noteTypeToId[item.type.toLowerCase()] = item.id; } });
|
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); }
|
} catch (e) { console.warn('z_ref_note_types:', e.message); }
|
||||||
try {
|
try {
|
||||||
const res = await request(apiBase + '/collections/z_ref_install_method_types/records?perPage=500', { headers: { 'Authorization': token } });
|
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; installMethodTypeToId[item.type.toLowerCase()] = item.id; } });
|
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); }
|
} catch (e) { console.warn('z_ref_install_method_types:', e.message); }
|
||||||
try {
|
try {
|
||||||
const res = await request(apiBase + '/collections/z_ref_os/records?perPage=500', { headers: { 'Authorization': token } });
|
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; osToId[item.os.toLowerCase()] = item.id; } });
|
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); }
|
} catch (e) { console.warn('z_ref_os:', e.message); }
|
||||||
try {
|
try {
|
||||||
const res = await request(apiBase + '/collections/z_ref_os_version/records?perPage=500&expand=os', { headers: { 'Authorization': token } });
|
const res = await request(apiBase + '/collections/z_ref_os_version/records?perPage=500&expand=os', { headers: { 'Authorization': token } });
|
||||||
@@ -162,7 +154,7 @@ jobs:
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
slug: data.slug,
|
slug: data.slug,
|
||||||
script_created: data.date_created || data.script_created,
|
script_created: data.date_created || data.script_created,
|
||||||
script_updated: new Date().toISOString().split('T')[0],
|
script_updated: data.date_created || data.script_updated,
|
||||||
updateable: data.updateable,
|
updateable: data.updateable,
|
||||||
privileged: data.privileged,
|
privileged: data.privileged,
|
||||||
port: data.interface_port != null ? data.interface_port : data.port,
|
port: data.interface_port != null ? data.interface_port : data.port,
|
||||||
@@ -171,8 +163,8 @@ jobs:
|
|||||||
logo: data.logo,
|
logo: data.logo,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
config_path: data.config_path,
|
config_path: data.config_path,
|
||||||
default_user: (data.default_credentials && data.default_credentials.username) || data.default_user || null,
|
default_user: (data.default_credentials && data.default_credentials.username) || data.default_user,
|
||||||
default_passwd: (data.default_credentials && data.default_credentials.password) || data.default_passwd || null,
|
default_passwd: (data.default_credentials && data.default_credentials.password) || data.default_passwd,
|
||||||
is_dev: false
|
is_dev: false
|
||||||
};
|
};
|
||||||
var resolvedType = typeValueToId[data.type];
|
var resolvedType = typeValueToId[data.type];
|
||||||
@@ -198,7 +190,7 @@ jobs:
|
|||||||
var postRes = await request(notesCollUrl, {
|
var postRes = await request(notesCollUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ text: note.text || '', type: typeId, script: scriptId })
|
body: JSON.stringify({ text: note.text || '', type: typeId })
|
||||||
});
|
});
|
||||||
if (postRes.ok) noteIds.push(JSON.parse(postRes.body).id);
|
if (postRes.ok) noteIds.push(JSON.parse(postRes.body).id);
|
||||||
}
|
}
|
||||||
|
|||||||
12
.github/workflows/update-script-timestamp-on-sh-change.yml
generated
vendored
12
.github/workflows/update-script-timestamp-on-sh-change.yml
generated
vendored
@@ -83,8 +83,7 @@ jobs:
|
|||||||
const http = require('http');
|
const http = require('http');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
|
||||||
function request(fullUrl, opts, redirectCount) {
|
function request(fullUrl, opts) {
|
||||||
redirectCount = redirectCount || 0;
|
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
const u = url.parse(fullUrl);
|
const u = url.parse(fullUrl);
|
||||||
const isHttps = u.protocol === 'https:';
|
const isHttps = u.protocol === 'https:';
|
||||||
@@ -99,13 +98,6 @@ jobs:
|
|||||||
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
|
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
|
||||||
const lib = isHttps ? https : http;
|
const lib = isHttps ? https : http;
|
||||||
const req = lib.request(options, function(res) {
|
const req = lib.request(options, function(res) {
|
||||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
||||||
if (redirectCount >= 5) return reject(new Error('Too many redirects from ' + fullUrl));
|
|
||||||
const redirectUrl = url.resolve(fullUrl, res.headers.location);
|
|
||||||
res.resume();
|
|
||||||
resolve(request(redirectUrl, opts, redirectCount + 1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let data = '';
|
let data = '';
|
||||||
res.on('data', function(chunk) { data += chunk; });
|
res.on('data', function(chunk) { data += chunk; });
|
||||||
res.on('end', function() {
|
res.on('end', function() {
|
||||||
@@ -159,7 +151,7 @@ jobs:
|
|||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
script_updated: new Date().toISOString().split('T')[0],
|
name: record.name || record.slug,
|
||||||
last_update_commit: process.env.PR_URL || process.env.COMMIT_URL || ''
|
last_update_commit: process.env.PR_URL || process.env.COMMIT_URL || ''
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
65
CHANGELOG.md
65
CHANGELOG.md
@@ -423,73 +423,8 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## 2026-03-19
|
|
||||||
|
|
||||||
### 🚀 Updated Scripts
|
|
||||||
|
|
||||||
- Owncast: increase default disk size from 2GB to 10GB [@Copilot](https://github.com/Copilot) ([#13079](https://github.com/community-scripts/ProxmoxVE/pull/13079))
|
|
||||||
|
|
||||||
- #### 🐞 Bug Fixes
|
|
||||||
|
|
||||||
- Increase Tracearr RAM; derive APP_VERSION [@MickLesk](https://github.com/MickLesk) ([#13087](https://github.com/community-scripts/ProxmoxVE/pull/13087))
|
|
||||||
- ProjectSend: Update application access URL [@tremor021](https://github.com/tremor021) ([#13078](https://github.com/community-scripts/ProxmoxVE/pull/13078))
|
|
||||||
- Dispatcharr: use npm install --no-audit --progress=false [@MickLesk](https://github.com/MickLesk) ([#13074](https://github.com/community-scripts/ProxmoxVE/pull/13074))
|
|
||||||
- core: reorder hwaccel setup and adjust GPU group usermod [@MickLesk](https://github.com/MickLesk) ([#13072](https://github.com/community-scripts/ProxmoxVE/pull/13072))
|
|
||||||
|
|
||||||
### 📚 Documentation
|
|
||||||
|
|
||||||
- github: add PocketBase bot workflow [@MickLesk](https://github.com/MickLesk) ([#13075](https://github.com/community-scripts/ProxmoxVE/pull/13075))
|
|
||||||
|
|
||||||
## 2026-03-18
|
|
||||||
|
|
||||||
### 🆕 New Scripts
|
|
||||||
|
|
||||||
- Alpine-Ntfy [@MickLesk](https://github.com/MickLesk) ([#13048](https://github.com/community-scripts/ProxmoxVE/pull/13048))
|
|
||||||
- Split-Pro ([#12975](https://github.com/community-scripts/ProxmoxVE/pull/12975))
|
|
||||||
|
|
||||||
### 🚀 Updated Scripts
|
|
||||||
|
|
||||||
- #### 🐞 Bug Fixes
|
|
||||||
|
|
||||||
- Tdarr: use curl_with_retry and correct exit code [@MickLesk](https://github.com/MickLesk) ([#13060](https://github.com/community-scripts/ProxmoxVE/pull/13060))
|
|
||||||
- reitti: fix: v4 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#13039](https://github.com/community-scripts/ProxmoxVE/pull/13039))
|
|
||||||
- Paperless-NGX: increase default RAM to 3GB [@MickLesk](https://github.com/MickLesk) ([#13018](https://github.com/community-scripts/ProxmoxVE/pull/13018))
|
|
||||||
- Plex: restart service after update to apply new version [@MickLesk](https://github.com/MickLesk) ([#13017](https://github.com/community-scripts/ProxmoxVE/pull/13017))
|
|
||||||
|
|
||||||
- #### ✨ New Features
|
|
||||||
|
|
||||||
- tools: centralize GPU group setup via setup_hwaccel [@MickLesk](https://github.com/MickLesk) ([#13044](https://github.com/community-scripts/ProxmoxVE/pull/13044))
|
|
||||||
- Termix: add guacd build and systemd integration [@MickLesk](https://github.com/MickLesk) ([#12999](https://github.com/community-scripts/ProxmoxVE/pull/12999))
|
|
||||||
|
|
||||||
- #### 🔧 Refactor
|
|
||||||
|
|
||||||
- Podman: replace deprecated commands with Quadlets [@MickLesk](https://github.com/MickLesk) ([#13052](https://github.com/community-scripts/ProxmoxVE/pull/13052))
|
|
||||||
- Refactor: Jellyfin repo, ffmpeg package and symlinks [@MickLesk](https://github.com/MickLesk) ([#13045](https://github.com/community-scripts/ProxmoxVE/pull/13045))
|
|
||||||
- pve-scripts-local: Increase default disk size from 4GB to 10GB [@MickLesk](https://github.com/MickLesk) ([#13009](https://github.com/community-scripts/ProxmoxVE/pull/13009))
|
|
||||||
|
|
||||||
### 💾 Core
|
|
||||||
|
|
||||||
- #### ✨ New Features
|
|
||||||
|
|
||||||
- tools.func Implement pg_cron setup for setup_postgresql [@MickLesk](https://github.com/MickLesk) ([#13053](https://github.com/community-scripts/ProxmoxVE/pull/13053))
|
|
||||||
- tools.func: Implement check_for_gh_tag function [@MickLesk](https://github.com/MickLesk) ([#12998](https://github.com/community-scripts/ProxmoxVE/pull/12998))
|
|
||||||
- tools.func: Implement fetch_and_deploy_gh_tag function [@MickLesk](https://github.com/MickLesk) ([#13000](https://github.com/community-scripts/ProxmoxVE/pull/13000))
|
|
||||||
|
|
||||||
## 2026-03-17
|
## 2026-03-17
|
||||||
|
|
||||||
### 🚀 Updated Scripts
|
|
||||||
|
|
||||||
- #### 🐞 Bug Fixes
|
|
||||||
|
|
||||||
- Gluetun: add OpenVPN process user and cleanup stale config [@MickLesk](https://github.com/MickLesk) ([#13016](https://github.com/community-scripts/ProxmoxVE/pull/13016))
|
|
||||||
- Frigate: check OpenVino model files exist before configuring detector and use curl_with_retry instead of default wget [@MickLesk](https://github.com/MickLesk) ([#13019](https://github.com/community-scripts/ProxmoxVE/pull/13019))
|
|
||||||
|
|
||||||
### 💾 Core
|
|
||||||
|
|
||||||
- #### 🔧 Refactor
|
|
||||||
|
|
||||||
- tools.func: Update `create_self_signed_cert()` [@tremor021](https://github.com/tremor021) ([#13008](https://github.com/community-scripts/ProxmoxVE/pull/13008))
|
|
||||||
|
|
||||||
## 2026-03-16
|
## 2026-03-16
|
||||||
|
|
||||||
### 🆕 New Scripts
|
### 🆕 New Scripts
|
||||||
|
|||||||
@@ -1,50 +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: cobalt (cobaltgit)
|
|
||||||
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
|
|
||||||
# Source: https://ntfy.sh/
|
|
||||||
|
|
||||||
APP="Alpine-ntfy"
|
|
||||||
var_tags="${var_tags:-notification}"
|
|
||||||
var_cpu="${var_cpu:-1}"
|
|
||||||
var_ram="${var_ram:-256}"
|
|
||||||
var_disk="${var_disk:-2}"
|
|
||||||
var_os="${var_os:-alpine}"
|
|
||||||
var_version="${var_version:-3.23}"
|
|
||||||
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 /etc/ntfy ]]; then
|
|
||||||
msg_error "No ${APP} Installation Found!"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
msg_info "Updating ntfy LXC"
|
|
||||||
$STD apk -U upgrade
|
|
||||||
setcap 'cap_net_bind_service=+ep' /usr/bin/ntfy
|
|
||||||
msg_ok "Updated ntfy LXC"
|
|
||||||
|
|
||||||
msg_info "Restarting ntfy"
|
|
||||||
rc-service ntfy restart
|
|
||||||
msg_ok "Restarted ntfy"
|
|
||||||
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}http://${IP}${CL}"
|
|
||||||
@@ -110,9 +110,7 @@ function update_script() {
|
|||||||
|
|
||||||
msg_info "Building Frontend"
|
msg_info "Building Frontend"
|
||||||
cd /opt/dispatcharr/frontend
|
cd /opt/dispatcharr/frontend
|
||||||
node -e "const p=require('./package.json');p.overrides=p.overrides||{};p.overrides['webworkify-webpack']='2.1.3';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2));"
|
$STD npm install --legacy-peer-deps
|
||||||
rm -f package-lock.json
|
|
||||||
$STD npm install --no-audit --progress=false
|
|
||||||
$STD npm run build
|
$STD npm run build
|
||||||
msg_ok "Built Frontend"
|
msg_ok "Built Frontend"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
___ __ _ __ ____
|
|
||||||
/ | / /___ (_)___ ___ ____ / /_/ __/_ __
|
|
||||||
/ /| | / / __ \/ / __ \/ _ \______/ __ \/ __/ /_/ / / /
|
|
||||||
/ ___ |/ / /_/ / / / / / __/_____/ / / / /_/ __/ /_/ /
|
|
||||||
/_/ |_/_/ .___/_/_/ /_/\___/ /_/ /_/\__/_/ \__, /
|
|
||||||
/_/ /____/
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
_____ ___ __ ____
|
|
||||||
/ ___/____ / (_) /_ / __ \_________
|
|
||||||
\__ \/ __ \/ / / __/_____/ /_/ / ___/ __ \
|
|
||||||
___/ / /_/ / / / /_/_____/ ____/ / / /_/ /
|
|
||||||
/____/ .___/_/_/\__/ /_/ /_/ \____/
|
|
||||||
/_/
|
|
||||||
@@ -39,23 +39,14 @@ function update_script() {
|
|||||||
msg_ok "Updated Intel Dependencies"
|
msg_ok "Updated Intel Dependencies"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
msg_info "Setting up Jellyfin Repository"
|
|
||||||
setup_deb822_repo \
|
|
||||||
"jellyfin" \
|
|
||||||
"https://repo.jellyfin.org/jellyfin_team.gpg.key" \
|
|
||||||
"https://repo.jellyfin.org/$(get_os_info id)" \
|
|
||||||
"$(get_os_info codename)"
|
|
||||||
msg_ok "Set up Jellyfin Repository"
|
|
||||||
|
|
||||||
msg_info "Updating Jellyfin"
|
msg_info "Updating Jellyfin"
|
||||||
ensure_dependencies libjemalloc2
|
ensure_dependencies libjemalloc2
|
||||||
if [[ ! -f /usr/lib/libjemalloc.so ]]; then
|
if [[ ! -f /usr/lib/libjemalloc.so ]]; then
|
||||||
ln -sf /usr/lib/x86_64-linux-gnu/libjemalloc.so.2 /usr/lib/libjemalloc.so
|
ln -sf /usr/lib/x86_64-linux-gnu/libjemalloc.so.2 /usr/lib/libjemalloc.so
|
||||||
fi
|
fi
|
||||||
|
$STD apt update
|
||||||
$STD apt -y upgrade
|
$STD apt -y upgrade
|
||||||
$STD apt -y --with-new-pkgs upgrade jellyfin jellyfin-server jellyfin-ffmpeg7
|
$STD apt -y --with-new-pkgs upgrade jellyfin jellyfin-server
|
||||||
ln -sf /usr/lib/jellyfin-ffmpeg/ffmpeg /usr/bin/ffmpeg
|
|
||||||
ln -sf /usr/lib/jellyfin-ffmpeg/ffprobe /usr/bin/ffprobe
|
|
||||||
msg_ok "Updated Jellyfin"
|
msg_ok "Updated Jellyfin"
|
||||||
msg_ok "Updated successfully!"
|
msg_ok "Updated successfully!"
|
||||||
exit
|
exit
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ APP="Owncast"
|
|||||||
var_tags="${var_tags:-broadcasting}"
|
var_tags="${var_tags:-broadcasting}"
|
||||||
var_cpu="${var_cpu:-2}"
|
var_cpu="${var_cpu:-2}"
|
||||||
var_ram="${var_ram:-2048}"
|
var_ram="${var_ram:-2048}"
|
||||||
var_disk="${var_disk:-10}"
|
var_disk="${var_disk:-2}"
|
||||||
var_os="${var_os:-debian}"
|
var_os="${var_os:-debian}"
|
||||||
var_version="${var_version:-13}"
|
var_version="${var_version:-13}"
|
||||||
var_unprivileged="${var_unprivileged:-1}"
|
var_unprivileged="${var_unprivileged:-1}"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxV
|
|||||||
APP="Paperless-ngx"
|
APP="Paperless-ngx"
|
||||||
var_tags="${var_tags:-document;management}"
|
var_tags="${var_tags:-document;management}"
|
||||||
var_cpu="${var_cpu:-2}"
|
var_cpu="${var_cpu:-2}"
|
||||||
var_ram="${var_ram:-3072}"
|
var_ram="${var_ram:-2048}"
|
||||||
var_disk="${var_disk:-12}"
|
var_disk="${var_disk:-12}"
|
||||||
var_os="${var_os:-debian}"
|
var_os="${var_os:-debian}"
|
||||||
var_version="${var_version:-13}"
|
var_version="${var_version:-13}"
|
||||||
|
|||||||
@@ -79,11 +79,6 @@ function update_script() {
|
|||||||
$STD apt update
|
$STD apt update
|
||||||
$STD apt install -y plexmediaserver
|
$STD apt install -y plexmediaserver
|
||||||
msg_ok "Updated Plex Media Server"
|
msg_ok "Updated Plex Media Server"
|
||||||
|
|
||||||
msg_info "Restarting Plex Media Server"
|
|
||||||
systemctl restart plexmediaserver
|
|
||||||
msg_ok "Restarted Plex Media Server"
|
|
||||||
|
|
||||||
msg_ok "Updated successfully!"
|
msg_ok "Updated successfully!"
|
||||||
exit
|
exit
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function update_script() {
|
|||||||
header_info
|
header_info
|
||||||
check_container_storage
|
check_container_storage
|
||||||
check_container_resources
|
check_container_resources
|
||||||
if [[ ! -f /etc/containers/systemd/homeassistant.container ]]; then
|
if [[ ! -f /etc/systemd/system/homeassistant.service ]]; then
|
||||||
msg_error "No ${APP} Installation Found!"
|
msg_error "No ${APP} Installation Found!"
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ description
|
|||||||
msg_ok "Completed successfully!\n"
|
msg_ok "Completed successfully!\n"
|
||||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}/install for the initial setup${CL}"
|
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL}"
|
||||||
|
|||||||
34
ct/reitti.sh
34
ct/reitti.sh
@@ -89,49 +89,17 @@ EOF
|
|||||||
msg_ok "Started Service"
|
msg_ok "Started Service"
|
||||||
msg_ok "Updated successfully!"
|
msg_ok "Updated successfully!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if check_for_gh_release "photon" "komoot/photon"; then
|
if check_for_gh_release "photon" "komoot/photon"; then
|
||||||
if [[ -f "$HOME/.photon" ]] && [[ "$(cat "$HOME/.photon")" == 0.7 ]]; then
|
|
||||||
CURRENT_VERSION="$(<"$HOME/.photon")"
|
|
||||||
echo
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
echo "Photon v1 upgrade detected (breaking change)"
|
|
||||||
echo
|
|
||||||
echo "Your current version: $CURRENT_VERSION"
|
|
||||||
echo
|
|
||||||
echo "Photon v1 requires a manual migration before updating."
|
|
||||||
echo
|
|
||||||
echo "You need to:"
|
|
||||||
echo " 1. Remove existing geocoding data (not actual reitti data):"
|
|
||||||
echo " rm -rf /opt/photon_data"
|
|
||||||
echo
|
|
||||||
echo " 2. Follow the inial setup guide again:"
|
|
||||||
echo " https://github.com/community-scripts/ProxmoxVE/discussions/8737"
|
|
||||||
echo
|
|
||||||
echo " 3. Re-download and import Photon data for v1"
|
|
||||||
echo
|
|
||||||
read -rp "Do you want to continue anyway? (y/N): " CONTINUE
|
|
||||||
echo
|
|
||||||
|
|
||||||
if [[ ! "$CONTINUE" =~ ^[Yy]$ ]]; then
|
|
||||||
msg_info "Migration required. Update cancelled."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
msg_warn "Continuing without migration may break Photon in the future!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
msg_info "Stopping Service"
|
msg_info "Stopping Service"
|
||||||
systemctl stop photon
|
systemctl stop photon
|
||||||
msg_ok "Stopped Service"
|
msg_ok "Stopped Service"
|
||||||
|
|
||||||
rm -f /opt/photon/photon.jar
|
rm -f /opt/photon/photon.jar
|
||||||
USE_ORIGINAL_FILENAME="true" fetch_and_deploy_gh_release "photon" "komoot/photon" "singlefile" "latest" "/opt/photon" "photon-*.jar"
|
USE_ORIGINAL_FILENAME="true" fetch_and_deploy_gh_release "photon" "komoot/photon" "singlefile" "latest" "/opt/photon" "photon-0*.jar"
|
||||||
mv /opt/photon/photon-*.jar /opt/photon/photon.jar
|
mv /opt/photon/photon-*.jar /opt/photon/photon.jar
|
||||||
|
|
||||||
msg_info "Starting Service"
|
msg_info "Starting Service"
|
||||||
systemctl start photon
|
systemctl start photon
|
||||||
systemctl restart nginx
|
|
||||||
msg_ok "Started Service"
|
msg_ok "Started Service"
|
||||||
msg_ok "Updated successfully!"
|
msg_ok "Updated successfully!"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function update_script() {
|
|||||||
|
|
||||||
msg_info "Updating Sparky Fitness Backend"
|
msg_info "Updating Sparky Fitness Backend"
|
||||||
cd /opt/sparkyfitness/SparkyFitnessServer
|
cd /opt/sparkyfitness/SparkyFitnessServer
|
||||||
$STD pnpm install
|
$STD npm install --legacy-peer-deps
|
||||||
msg_ok "Updated Sparky Fitness Backend"
|
msg_ok "Updated Sparky Fitness Backend"
|
||||||
|
|
||||||
msg_info "Updating Sparky Fitness Frontend (Patience)"
|
msg_info "Updating Sparky Fitness Frontend (Patience)"
|
||||||
|
|||||||
@@ -1,68 +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: johanngrobe
|
|
||||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
|
||||||
# Source: https://github.com/oss-apps/split-pro
|
|
||||||
|
|
||||||
APP="Split-Pro"
|
|
||||||
var_tags="${var_tags:-finance;expense-sharing}"
|
|
||||||
var_cpu="${var_cpu:-2}"
|
|
||||||
var_ram="${var_ram:-4096}"
|
|
||||||
var_disk="${var_disk:-6}"
|
|
||||||
var_os="${var_os:-debian}"
|
|
||||||
var_version="${var_version:-13}"
|
|
||||||
var_unprivileged="${var_unprivileged:-1}"
|
|
||||||
|
|
||||||
variables
|
|
||||||
color
|
|
||||||
catch_errors
|
|
||||||
|
|
||||||
function update_script() {
|
|
||||||
header_info
|
|
||||||
check_container_storage
|
|
||||||
check_container_resources
|
|
||||||
|
|
||||||
if [[ ! -d /opt/split-pro ]]; then
|
|
||||||
msg_error "No Split Pro Installation Found!"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
if check_for_gh_release "split-pro" "oss-apps/split-pro"; then
|
|
||||||
msg_info "Stopping Service"
|
|
||||||
systemctl stop split-pro
|
|
||||||
msg_ok "Stopped Service"
|
|
||||||
|
|
||||||
msg_info "Backing up Data"
|
|
||||||
cp /opt/split-pro/.env /opt/split-pro.env
|
|
||||||
msg_ok "Backed up Data"
|
|
||||||
|
|
||||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "split-pro" "oss-apps/split-pro" "tarball"
|
|
||||||
|
|
||||||
msg_info "Building Application"
|
|
||||||
cd /opt/split-pro
|
|
||||||
$STD pnpm install --frozen-lockfile
|
|
||||||
$STD pnpm build
|
|
||||||
cp /opt/split-pro.env /opt/split-pro/.env
|
|
||||||
rm -f /opt/split-pro.env
|
|
||||||
ln -sf /opt/split-pro_data/uploads /opt/split-pro/uploads
|
|
||||||
$STD pnpm exec prisma migrate deploy
|
|
||||||
msg_ok "Built Application"
|
|
||||||
|
|
||||||
msg_info "Starting Service"
|
|
||||||
systemctl start split-pro
|
|
||||||
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}"
|
|
||||||
@@ -33,16 +33,12 @@ function update_script() {
|
|||||||
$STD apt upgrade -y
|
$STD apt upgrade -y
|
||||||
rm -rf /opt/tdarr/Tdarr_Updater
|
rm -rf /opt/tdarr/Tdarr_Updater
|
||||||
cd /opt/tdarr
|
cd /opt/tdarr
|
||||||
RELEASE=$(curl_with_retry "https://f000.backblazeb2.com/file/tdarrs/versions.json" "-" | grep -oP '(?<="Tdarr_Updater": ")[^"]+' | grep linux_x64 | head -n 1)
|
RELEASE=$(curl -fsSL https://f000.backblazeb2.com/file/tdarrs/versions.json | grep -oP '(?<="Tdarr_Updater": ")[^"]+' | grep linux_x64 | head -n 1)
|
||||||
curl_with_retry "$RELEASE" "Tdarr_Updater.zip"
|
curl -fsSL "$RELEASE" -o Tdarr_Updater.zip
|
||||||
$STD unzip Tdarr_Updater.zip
|
$STD unzip Tdarr_Updater.zip
|
||||||
chmod +x Tdarr_Updater
|
chmod +x Tdarr_Updater
|
||||||
$STD ./Tdarr_Updater
|
$STD ./Tdarr_Updater
|
||||||
rm -rf /opt/tdarr/Tdarr_Updater.zip
|
rm -rf /opt/tdarr/Tdarr_Updater.zip
|
||||||
[[ -f /opt/tdarr/Tdarr_Server/Tdarr_Server ]] || {
|
|
||||||
msg_error "Tdarr_Updater failed — tdarr.io may be blocked by local DNS"
|
|
||||||
exit 250
|
|
||||||
}
|
|
||||||
msg_ok "Updated Tdarr"
|
msg_ok "Updated Tdarr"
|
||||||
msg_ok "Updated successfully!"
|
msg_ok "Updated successfully!"
|
||||||
exit
|
exit
|
||||||
|
|||||||
114
ct/termix.sh
114
ct/termix.sh
@@ -29,116 +29,10 @@ function update_script() {
|
|||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if check_for_gh_tag "guacd" "apache/guacamole-server"; then
|
|
||||||
msg_info "Stopping guacd"
|
|
||||||
systemctl stop guacd 2>/dev/null || true
|
|
||||||
msg_ok "Stopped guacd"
|
|
||||||
|
|
||||||
ensure_dependencies \
|
|
||||||
libcairo2-dev \
|
|
||||||
libjpeg62-turbo-dev \
|
|
||||||
libpng-dev \
|
|
||||||
libtool-bin \
|
|
||||||
uuid-dev \
|
|
||||||
libvncserver-dev \
|
|
||||||
freerdp3-dev \
|
|
||||||
libssh2-1-dev \
|
|
||||||
libtelnet-dev \
|
|
||||||
libwebsockets-dev \
|
|
||||||
libpulse-dev \
|
|
||||||
libvorbis-dev \
|
|
||||||
libwebp-dev \
|
|
||||||
libssl-dev \
|
|
||||||
libpango1.0-dev \
|
|
||||||
libswscale-dev \
|
|
||||||
libavcodec-dev \
|
|
||||||
libavutil-dev \
|
|
||||||
libavformat-dev
|
|
||||||
|
|
||||||
msg_info "Updating Guacamole Server (guacd)"
|
|
||||||
fetch_and_deploy_gh_tag "guacd" "apache/guacamole-server" "${CHECK_UPDATE_RELEASE}" "/opt/guacamole-server"
|
|
||||||
cd /opt/guacamole-server
|
|
||||||
export CPPFLAGS="-Wno-error=deprecated-declarations"
|
|
||||||
$STD autoreconf -fi
|
|
||||||
$STD ./configure --with-init-dir=/etc/init.d --enable-allow-freerdp-snapshots
|
|
||||||
$STD make
|
|
||||||
$STD make install
|
|
||||||
$STD ldconfig
|
|
||||||
cd /opt
|
|
||||||
rm -rf /opt/guacamole-server
|
|
||||||
msg_ok "Updated Guacamole Server (guacd) to ${CHECK_UPDATE_RELEASE}"
|
|
||||||
|
|
||||||
if [[ ! -f /etc/guacamole/guacd.conf ]]; then
|
|
||||||
mkdir -p /etc/guacamole
|
|
||||||
cat <<EOF >/etc/guacamole/guacd.conf
|
|
||||||
[server]
|
|
||||||
bind_host = 127.0.0.1
|
|
||||||
bind_port = 4822
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -f /etc/systemd/system/guacd.service ]] || grep -q "Type=forking" /etc/systemd/system/guacd.service 2>/dev/null; then
|
|
||||||
cat <<EOF >/etc/systemd/system/guacd.service
|
|
||||||
[Unit]
|
|
||||||
Description=Guacamole Proxy Daemon (guacd)
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=/usr/local/sbin/guacd -f -b 127.0.0.1 -l 4822
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! grep -q "guacd.service" /etc/systemd/system/termix.service 2>/dev/null; then
|
|
||||||
sed -i '/^After=network.target/s/$/ guacd.service/' /etc/systemd/system/termix.service
|
|
||||||
sed -i '/^\[Unit\]/a Wants=guacd.service' /etc/systemd/system/termix.service
|
|
||||||
fi
|
|
||||||
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl enable -q --now guacd
|
|
||||||
fi
|
|
||||||
|
|
||||||
if check_for_gh_release "termix" "Termix-SSH/Termix"; then
|
if check_for_gh_release "termix" "Termix-SSH/Termix"; then
|
||||||
msg_info "Stopping Termix"
|
msg_info "Stopping Service"
|
||||||
systemctl stop termix
|
systemctl stop termix
|
||||||
msg_ok "Stopped Termix"
|
msg_ok "Stopped Service"
|
||||||
|
|
||||||
msg_info "Migrating Configuration"
|
|
||||||
if [[ ! -f /opt/termix/.env ]]; then
|
|
||||||
cat <<EOF >/opt/termix/.env
|
|
||||||
NODE_ENV=production
|
|
||||||
DATA_DIR=/opt/termix/data
|
|
||||||
GUACD_HOST=127.0.0.1
|
|
||||||
GUACD_PORT=4822
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
if ! grep -q "EnvironmentFile" /etc/systemd/system/termix.service 2>/dev/null; then
|
|
||||||
cat <<EOF >/etc/systemd/system/termix.service
|
|
||||||
[Unit]
|
|
||||||
Description=Termix Backend
|
|
||||||
After=network.target guacd.service
|
|
||||||
Wants=guacd.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=root
|
|
||||||
WorkingDirectory=/opt/termix
|
|
||||||
EnvironmentFile=/opt/termix/.env
|
|
||||||
ExecStart=/usr/bin/node /opt/termix/dist/backend/backend/starter.js
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
systemctl daemon-reload
|
|
||||||
fi
|
|
||||||
msg_ok "Migrated Configuration"
|
|
||||||
|
|
||||||
msg_info "Backing up Data"
|
msg_info "Backing up Data"
|
||||||
cp -r /opt/termix/data /opt/termix_data_backup
|
cp -r /opt/termix/data /opt/termix_data_backup
|
||||||
@@ -208,9 +102,9 @@ EOF
|
|||||||
msg_warn "Nginx configuration not updated. If Termix doesn't work, restore from backup or update manually."
|
msg_warn "Nginx configuration not updated. If Termix doesn't work, restore from backup or update manually."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
msg_info "Starting Termix"
|
msg_info "Starting Service"
|
||||||
systemctl start termix
|
systemctl start termix
|
||||||
msg_ok "Started Termix"
|
msg_ok "Started Service"
|
||||||
msg_ok "Updated successfully!"
|
msg_ok "Updated successfully!"
|
||||||
fi
|
fi
|
||||||
exit
|
exit
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxV
|
|||||||
APP="Tracearr"
|
APP="Tracearr"
|
||||||
var_tags="${var_tags:-media}"
|
var_tags="${var_tags:-media}"
|
||||||
var_cpu="${var_cpu:-2}"
|
var_cpu="${var_cpu:-2}"
|
||||||
var_ram="${var_ram:-4096}"
|
var_ram="${var_ram:-2048}"
|
||||||
var_disk="${var_disk:-10}"
|
var_disk="${var_disk:-10}"
|
||||||
var_os="${var_os:-debian}"
|
var_os="${var_os:-debian}"
|
||||||
var_version="${var_version:-13}"
|
var_version="${var_version:-13}"
|
||||||
@@ -140,7 +140,7 @@ EOF
|
|||||||
msg_ok "Built Tracearr"
|
msg_ok "Built Tracearr"
|
||||||
|
|
||||||
msg_info "Configuring Tracearr"
|
msg_info "Configuring Tracearr"
|
||||||
sed -i "s|^APP_VERSION=.*|APP_VERSION=${CHECK_UPDATE_RELEASE#v}|" /data/tracearr/.env
|
sed -i "s/^APP_VERSION=.*/APP_VERSION=$(cat /root/.tracearr)/" /data/tracearr/.env
|
||||||
chmod 600 /data/tracearr/.env
|
chmod 600 /data/tracearr/.env
|
||||||
chown -R tracearr:tracearr /data/tracearr
|
chown -R tracearr:tracearr /data/tracearr
|
||||||
mkdir -p /data/backup
|
mkdir -p /data/backup
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Copyright (c) 2021-2026 community-scripts ORG
|
|
||||||
# Author: cobalt (cobaltgit)
|
|
||||||
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
|
|
||||||
# Source: https://ntfy.sh/
|
|
||||||
|
|
||||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
|
||||||
color
|
|
||||||
verb_ip6
|
|
||||||
catch_errors
|
|
||||||
setting_up_container
|
|
||||||
network_check
|
|
||||||
update_os
|
|
||||||
|
|
||||||
msg_info "Installing ntfy"
|
|
||||||
$STD apk add --no-cache ntfy ntfy-openrc libcap
|
|
||||||
sed -i '/^listen-http/s/^\(.*\)$/#\1\n/' /etc/ntfy/server.yml
|
|
||||||
setcap 'cap_net_bind_service=+ep' /usr/bin/ntfy
|
|
||||||
$STD rc-update add ntfy default
|
|
||||||
$STD service ntfy start
|
|
||||||
msg_ok "Installed ntfy"
|
|
||||||
|
|
||||||
motd_ssh
|
|
||||||
customize
|
|
||||||
@@ -35,6 +35,7 @@ setup_hwaccel
|
|||||||
msg_info "Installing Channels DVR Server (Patience)"
|
msg_info "Installing Channels DVR Server (Patience)"
|
||||||
cd /opt
|
cd /opt
|
||||||
$STD bash <(curl -fsSL https://getchannels.com/dvr/setup.sh)
|
$STD bash <(curl -fsSL https://getchannels.com/dvr/setup.sh)
|
||||||
|
sed -i -e 's/^sgx:x:104:$/render:x:104:root/' -e 's/^render:x:106:root$/sgx:x:106:/' /etc/group
|
||||||
msg_ok "Installed Channels DVR Server"
|
msg_ok "Installed Channels DVR Server"
|
||||||
|
|
||||||
motd_ssh
|
motd_ssh
|
||||||
|
|||||||
@@ -66,9 +66,7 @@ CELERY_BROKER_URL=redis://localhost:6379/0
|
|||||||
DJANGO_SECRET_KEY=$DJANGO_SECRET
|
DJANGO_SECRET_KEY=$DJANGO_SECRET
|
||||||
EOF
|
EOF
|
||||||
cd /opt/dispatcharr/frontend
|
cd /opt/dispatcharr/frontend
|
||||||
node -e "const p=require('./package.json');p.overrides=p.overrides||{};p.overrides['webworkify-webpack']='2.1.3';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2));"
|
$STD npm install --legacy-peer-deps
|
||||||
rm -f package-lock.json
|
|
||||||
$STD npm install --no-audit --progress=false
|
|
||||||
$STD npm run build
|
$STD npm run build
|
||||||
msg_ok "Configured Dispatcharr"
|
msg_ok "Configured Dispatcharr"
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,17 @@ setting_up_container
|
|||||||
network_check
|
network_check
|
||||||
update_os
|
update_os
|
||||||
|
|
||||||
|
setup_hwaccel
|
||||||
|
|
||||||
fetch_and_deploy_gh_release "emby" "MediaBrowser/Emby.Releases" "binary"
|
fetch_and_deploy_gh_release "emby" "MediaBrowser/Emby.Releases" "binary"
|
||||||
|
|
||||||
setup_hwaccel "emby"
|
msg_info "Configuring Emby"
|
||||||
|
if [[ "$CTTYPE" == "0" ]]; then
|
||||||
|
sed -i -e 's/^ssl-cert:x:104:$/render:x:104:root,emby/' -e 's/^render:x:108:root,emby$/ssl-cert:x:108:/' /etc/group
|
||||||
|
else
|
||||||
|
sed -i -e 's/^ssl-cert:x:104:$/render:x:104:emby/' -e 's/^render:x:108:emby$/ssl-cert:x:108:/' /etc/group
|
||||||
|
fi
|
||||||
|
msg_ok "Configured Emby"
|
||||||
|
|
||||||
motd_ssh
|
motd_ssh
|
||||||
customize
|
customize
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ ldconfig
|
|||||||
msg_ok "Built libUSB"
|
msg_ok "Built libUSB"
|
||||||
|
|
||||||
msg_info "Bootstrapping pip"
|
msg_info "Bootstrapping pip"
|
||||||
curl_with_retry "https://bootstrap.pypa.io/get-pip.py" "/tmp/get-pip.py"
|
wget -q https://bootstrap.pypa.io/get-pip.py -O /tmp/get-pip.py
|
||||||
sed -i 's/args.append("setuptools")/args.append("setuptools==77.0.3")/' /tmp/get-pip.py
|
sed -i 's/args.append("setuptools")/args.append("setuptools==77.0.3")/' /tmp/get-pip.py
|
||||||
$STD python3 /tmp/get-pip.py "pip"
|
$STD python3 /tmp/get-pip.py "pip"
|
||||||
rm -f /tmp/get-pip.py
|
rm -f /tmp/get-pip.py
|
||||||
@@ -169,13 +169,13 @@ NODE_VERSION="20" setup_nodejs
|
|||||||
|
|
||||||
msg_info "Downloading Inference Models"
|
msg_info "Downloading Inference Models"
|
||||||
mkdir -p /models /openvino-model
|
mkdir -p /models /openvino-model
|
||||||
curl_with_retry "https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess_edgetpu.tflite" "/edgetpu_model.tflite"
|
wget -q -O /edgetpu_model.tflite https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess_edgetpu.tflite
|
||||||
curl_with_retry "https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess.tflite" "/models/cpu_model.tflite"
|
wget -q -O /models/cpu_model.tflite https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess.tflite
|
||||||
cp /opt/frigate/labelmap.txt /labelmap.txt
|
cp /opt/frigate/labelmap.txt /labelmap.txt
|
||||||
msg_ok "Downloaded Inference Models"
|
msg_ok "Downloaded Inference Models"
|
||||||
|
|
||||||
msg_info "Downloading Audio Model"
|
msg_info "Downloading Audio Model"
|
||||||
curl_with_retry "https://www.kaggle.com/api/v1/models/google/yamnet/tfLite/classification-tflite/1/download" "/tmp/yamnet.tar.gz"
|
wget -q -O /tmp/yamnet.tar.gz https://www.kaggle.com/api/v1/models/google/yamnet/tfLite/classification-tflite/1/download
|
||||||
$STD tar xzf /tmp/yamnet.tar.gz -C /
|
$STD tar xzf /tmp/yamnet.tar.gz -C /
|
||||||
mv /1.tflite /cpu_audio_model.tflite
|
mv /1.tflite /cpu_audio_model.tflite
|
||||||
cp /opt/frigate/audio-labelmap.txt /audio-labelmap.txt
|
cp /opt/frigate/audio-labelmap.txt /audio-labelmap.txt
|
||||||
@@ -205,7 +205,7 @@ msg_ok "Installed OpenVino"
|
|||||||
|
|
||||||
msg_info "Building OpenVino Model"
|
msg_info "Building OpenVino Model"
|
||||||
cd /models
|
cd /models
|
||||||
curl_with_retry "http://download.tensorflow.org/models/object_detection/ssdlite_mobilenet_v2_coco_2018_05_09.tar.gz" "ssdlite_mobilenet_v2_coco_2018_05_09.tar.gz"
|
wget -q http://download.tensorflow.org/models/object_detection/ssdlite_mobilenet_v2_coco_2018_05_09.tar.gz
|
||||||
$STD tar -zxf ssdlite_mobilenet_v2_coco_2018_05_09.tar.gz --no-same-owner
|
$STD tar -zxf ssdlite_mobilenet_v2_coco_2018_05_09.tar.gz --no-same-owner
|
||||||
if python3 /opt/frigate/docker/main/build_ov_model.py &>/dev/null; then
|
if python3 /opt/frigate/docker/main/build_ov_model.py &>/dev/null; then
|
||||||
mkdir -p /openvino-model
|
mkdir -p /openvino-model
|
||||||
@@ -219,7 +219,7 @@ if python3 /opt/frigate/docker/main/build_ov_model.py &>/dev/null; then
|
|||||||
if [[ -n "$OV_LABELS" ]]; then
|
if [[ -n "$OV_LABELS" ]]; then
|
||||||
ln -sf "$OV_LABELS" /openvino-model/coco_91cl_bkgr.txt
|
ln -sf "$OV_LABELS" /openvino-model/coco_91cl_bkgr.txt
|
||||||
else
|
else
|
||||||
curl_with_retry "https://raw.githubusercontent.com/openvinotoolkit/open_model_zoo/master/data/dataset_classes/coco_91cl_bkgr.txt" "/openvino-model/coco_91cl_bkgr.txt"
|
wget -q "https://raw.githubusercontent.com/openvinotoolkit/open_model_zoo/master/data/dataset_classes/coco_91cl_bkgr.txt" -O /openvino-model/coco_91cl_bkgr.txt
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
sed -i 's/truck/car/g' /openvino-model/coco_91cl_bkgr.txt
|
sed -i 's/truck/car/g' /openvino-model/coco_91cl_bkgr.txt
|
||||||
@@ -246,7 +246,7 @@ msg_info "Configuring Frigate"
|
|||||||
mkdir -p /config /media/frigate
|
mkdir -p /config /media/frigate
|
||||||
cp -r /opt/frigate/config/. /config
|
cp -r /opt/frigate/config/. /config
|
||||||
|
|
||||||
curl_with_retry "https://github.com/intel-iot-devkit/sample-videos/raw/master/person-bicycle-car-detection.mp4" "/media/frigate/person-bicycle-car-detection.mp4"
|
curl -fsSL "https://github.com/intel-iot-devkit/sample-videos/raw/master/person-bicycle-car-detection.mp4" -o "/media/frigate/person-bicycle-car-detection.mp4"
|
||||||
|
|
||||||
echo "tmpfs /tmp/cache tmpfs defaults 0 0" >>/etc/fstab
|
echo "tmpfs /tmp/cache tmpfs defaults 0 0" >>/etc/fstab
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ detect:
|
|||||||
enabled: false
|
enabled: false
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
if grep -q -o -m1 -E 'avx[^ ]*|sse4_2' /proc/cpuinfo && [[ -f /openvino-model/ssdlite_mobilenet_v2.xml ]] && [[ -f /openvino-model/coco_91cl_bkgr.txt ]]; then
|
if grep -q -o -m1 -E 'avx[^ ]*|sse4_2' /proc/cpuinfo; then
|
||||||
cat <<EOF >>/config/config.yml
|
cat <<EOF >>/config/config.yml
|
||||||
ffmpeg:
|
ffmpeg:
|
||||||
hwaccel_args: auto
|
hwaccel_args: auto
|
||||||
|
|||||||
@@ -46,9 +46,6 @@ VPN_TYPE=openvpn
|
|||||||
OPENVPN_CUSTOM_CONFIG=/opt/gluetun-data/custom.ovpn
|
OPENVPN_CUSTOM_CONFIG=/opt/gluetun-data/custom.ovpn
|
||||||
OPENVPN_USER=
|
OPENVPN_USER=
|
||||||
OPENVPN_PASSWORD=
|
OPENVPN_PASSWORD=
|
||||||
OPENVPN_PROCESS_USER=root
|
|
||||||
PUID=0
|
|
||||||
PGID=0
|
|
||||||
HTTP_CONTROL_SERVER_ADDRESS=:8000
|
HTTP_CONTROL_SERVER_ADDRESS=:8000
|
||||||
HTTPPROXY=off
|
HTTPPROXY=off
|
||||||
SHADOWSOCKS=off
|
SHADOWSOCKS=off
|
||||||
@@ -79,7 +76,6 @@ User=root
|
|||||||
WorkingDirectory=/opt/gluetun-data
|
WorkingDirectory=/opt/gluetun-data
|
||||||
EnvironmentFile=/opt/gluetun-data/.env
|
EnvironmentFile=/opt/gluetun-data/.env
|
||||||
UnsetEnvironment=USER
|
UnsetEnvironment=USER
|
||||||
ExecStartPre=/bin/sh -c 'rm -f /etc/openvpn/target.ovpn'
|
|
||||||
ExecStart=/usr/local/bin/gluetun
|
ExecStart=/usr/local/bin/gluetun
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Copyright (c) 2021-2026 community-scripts ORG
|
# Copyright (c) 2021-2026 tteck
|
||||||
# Author: MickLesk (CanbiZ)
|
# Author: tteck (tteckster)
|
||||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||||
# Source: https://jellyfin.org/
|
# Source: https://jellyfin.org/
|
||||||
|
|
||||||
@@ -14,31 +14,31 @@ network_check
|
|||||||
update_os
|
update_os
|
||||||
|
|
||||||
msg_custom "ℹ️" "${GN}" "If NVIDIA GPU passthrough is detected, you'll be asked whether to install drivers in the container"
|
msg_custom "ℹ️" "${GN}" "If NVIDIA GPU passthrough is detected, you'll be asked whether to install drivers in the container"
|
||||||
|
setup_hwaccel
|
||||||
|
|
||||||
msg_info "Installing Dependencies"
|
msg_info "Installing Jellyfin"
|
||||||
ensure_dependencies libjemalloc2
|
VERSION="$(awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release)"
|
||||||
|
if ! dpkg -s libjemalloc2 >/dev/null 2>&1; then
|
||||||
|
$STD apt install -y libjemalloc2
|
||||||
|
fi
|
||||||
if [[ ! -f /usr/lib/libjemalloc.so ]]; then
|
if [[ ! -f /usr/lib/libjemalloc.so ]]; then
|
||||||
ln -sf /usr/lib/x86_64-linux-gnu/libjemalloc.so.2 /usr/lib/libjemalloc.so
|
ln -sf /usr/lib/x86_64-linux-gnu/libjemalloc.so.2 /usr/lib/libjemalloc.so
|
||||||
fi
|
fi
|
||||||
msg_ok "Installed Dependencies"
|
if [[ ! -d /etc/apt/keyrings ]]; then
|
||||||
|
mkdir -p /etc/apt/keyrings
|
||||||
|
fi
|
||||||
|
curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor --yes --output /etc/apt/keyrings/jellyfin.gpg
|
||||||
|
cat <<EOF >/etc/apt/sources.list.d/jellyfin.sources
|
||||||
|
Types: deb
|
||||||
|
URIs: https://repo.jellyfin.org/${PCT_OSTYPE}
|
||||||
|
Suites: ${VERSION}
|
||||||
|
Components: main
|
||||||
|
Architectures: amd64
|
||||||
|
Signed-By: /etc/apt/keyrings/jellyfin.gpg
|
||||||
|
EOF
|
||||||
|
|
||||||
msg_info "Setting up Jellyfin Repository"
|
$STD apt update
|
||||||
setup_deb822_repo \
|
$STD apt install -y jellyfin
|
||||||
"jellyfin" \
|
|
||||||
"https://repo.jellyfin.org/jellyfin_team.gpg.key" \
|
|
||||||
"https://repo.jellyfin.org/$(get_os_info id)" \
|
|
||||||
"$(get_os_info codename)"
|
|
||||||
msg_ok "Set up Jellyfin Repository"
|
|
||||||
|
|
||||||
msg_info "Installing Jellyfin"
|
|
||||||
$STD apt install -y jellyfin jellyfin-ffmpeg7
|
|
||||||
ln -sf /usr/lib/jellyfin-ffmpeg/ffmpeg /usr/bin/ffmpeg
|
|
||||||
ln -sf /usr/lib/jellyfin-ffmpeg/ffprobe /usr/bin/ffprobe
|
|
||||||
msg_ok "Installed Jellyfin"
|
|
||||||
|
|
||||||
setup_hwaccel "jellyfin"
|
|
||||||
|
|
||||||
msg_info "Configuring Jellyfin"
|
|
||||||
# Configure log rotation to prevent disk fill (keeps fail2ban compatibility) (PR: #1690 / Issue: #11224)
|
# Configure log rotation to prevent disk fill (keeps fail2ban compatibility) (PR: #1690 / Issue: #11224)
|
||||||
cat <<EOF >/etc/logrotate.d/jellyfin
|
cat <<EOF >/etc/logrotate.d/jellyfin
|
||||||
/var/log/jellyfin/*.log {
|
/var/log/jellyfin/*.log {
|
||||||
@@ -55,7 +55,12 @@ EOF
|
|||||||
chown -R jellyfin:adm /etc/jellyfin
|
chown -R jellyfin:adm /etc/jellyfin
|
||||||
sleep 10
|
sleep 10
|
||||||
systemctl restart jellyfin
|
systemctl restart jellyfin
|
||||||
msg_ok "Configured Jellyfin"
|
if [[ "$CTTYPE" == "0" ]]; then
|
||||||
|
sed -i -e 's/^ssl-cert:x:104:$/render:x:104:root,jellyfin/' -e 's/^render:x:108:root,jellyfin$/ssl-cert:x:108:/' /etc/group
|
||||||
|
else
|
||||||
|
sed -i -e 's/^ssl-cert:x:104:$/render:x:104:jellyfin/' -e 's/^render:x:108:jellyfin$/ssl-cert:x:108:/' /etc/group
|
||||||
|
fi
|
||||||
|
msg_ok "Installed Jellyfin"
|
||||||
|
|
||||||
motd_ssh
|
motd_ssh
|
||||||
customize
|
customize
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ EOF
|
|||||||
$STD apt update
|
$STD apt update
|
||||||
msg_ok "Set up Intel® Repositories"
|
msg_ok "Set up Intel® Repositories"
|
||||||
|
|
||||||
|
setup_hwaccel
|
||||||
|
|
||||||
msg_info "Installing Intel® Level Zero"
|
msg_info "Installing Intel® Level Zero"
|
||||||
# Debian 13+ has newer Level Zero packages in system repos that conflict with Intel repo packages
|
# Debian 13+ has newer Level Zero packages in system repos that conflict with Intel repo packages
|
||||||
if is_debian && [[ "$(get_os_version_major)" -ge 13 ]]; then
|
if is_debian && [[ "$(get_os_version_major)" -ge 13 ]]; then
|
||||||
@@ -87,11 +89,11 @@ msg_info "Creating ollama User and Group"
|
|||||||
if ! id ollama >/dev/null 2>&1; then
|
if ! id ollama >/dev/null 2>&1; then
|
||||||
useradd -r -s /usr/sbin/nologin -U -m -d /usr/share/ollama ollama
|
useradd -r -s /usr/sbin/nologin -U -m -d /usr/share/ollama ollama
|
||||||
fi
|
fi
|
||||||
|
$STD usermod -aG render ollama || true
|
||||||
|
$STD usermod -aG video ollama || true
|
||||||
$STD usermod -aG ollama $(id -u -n)
|
$STD usermod -aG ollama $(id -u -n)
|
||||||
msg_ok "Created ollama User and adjusted Groups"
|
msg_ok "Created ollama User and adjusted Groups"
|
||||||
|
|
||||||
setup_hwaccel "ollama"
|
|
||||||
|
|
||||||
msg_info "Creating Service"
|
msg_info "Creating Service"
|
||||||
cat <<EOF >/etc/systemd/system/ollama.service
|
cat <<EOF >/etc/systemd/system/ollama.service
|
||||||
[Unit]
|
[Unit]
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ setting_up_container
|
|||||||
network_check
|
network_check
|
||||||
update_os
|
update_os
|
||||||
|
|
||||||
|
setup_hwaccel
|
||||||
|
|
||||||
msg_info "Setting Up Plex Media Server Repository"
|
msg_info "Setting Up Plex Media Server Repository"
|
||||||
setup_deb822_repo \
|
setup_deb822_repo \
|
||||||
"plexmediaserver" \
|
"plexmediaserver" \
|
||||||
@@ -24,10 +26,13 @@ msg_ok "Set Up Plex Media Server Repository"
|
|||||||
|
|
||||||
msg_info "Installing Plex Media Server"
|
msg_info "Installing Plex Media Server"
|
||||||
$STD apt install -y plexmediaserver
|
$STD apt install -y plexmediaserver
|
||||||
|
if [[ "$CTTYPE" == "0" ]]; then
|
||||||
|
sed -i -e 's/^ssl-cert:x:104:plex$/render:x:104:root,plex/' -e 's/^render:x:108:root$/ssl-cert:x:108:plex/' /etc/group
|
||||||
|
else
|
||||||
|
sed -i -e 's/^ssl-cert:x:104:plex$/render:x:104:plex/' -e 's/^render:x:108:$/ssl-cert:x:108:/' /etc/group
|
||||||
|
fi
|
||||||
msg_ok "Installed Plex Media Server"
|
msg_ok "Installed Plex Media Server"
|
||||||
|
|
||||||
setup_hwaccel "plex"
|
|
||||||
|
|
||||||
motd_ssh
|
motd_ssh
|
||||||
customize
|
customize
|
||||||
cleanup_lxc
|
cleanup_lxc
|
||||||
|
|||||||
@@ -45,58 +45,32 @@ systemctl enable -q --now podman.socket
|
|||||||
echo -e 'unqualified-search-registries=["docker.io"]' >>/etc/containers/registries.conf
|
echo -e 'unqualified-search-registries=["docker.io"]' >>/etc/containers/registries.conf
|
||||||
msg_ok "Installed Podman"
|
msg_ok "Installed Podman"
|
||||||
|
|
||||||
mkdir -p /etc/containers/systemd
|
|
||||||
|
|
||||||
read -r -p "${TAB3}Would you like to add Portainer? <y/N> " prompt
|
read -r -p "${TAB3}Would you like to add Portainer? <y/N> " prompt
|
||||||
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
|
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
|
||||||
msg_info "Installing Portainer $PORTAINER_LATEST_VERSION"
|
msg_info "Installing Portainer $PORTAINER_LATEST_VERSION"
|
||||||
podman volume create portainer_data >/dev/null
|
podman volume create portainer_data >/dev/null
|
||||||
cat <<EOF >/etc/containers/systemd/portainer.container
|
$STD podman run -d \
|
||||||
[Unit]
|
-p 8000:8000 \
|
||||||
Description=Portainer Container
|
-p 9443:9443 \
|
||||||
After=network-online.target
|
--name=portainer \
|
||||||
|
--restart=always \
|
||||||
[Container]
|
-v /run/podman/podman.sock:/var/run/docker.sock \
|
||||||
Image=docker.io/portainer/portainer-ce:latest
|
-v portainer_data:/data \
|
||||||
ContainerName=portainer
|
portainer/portainer-ce:latest
|
||||||
PublishPort=8000:8000
|
|
||||||
PublishPort=9443:9443
|
|
||||||
Volume=/run/podman/podman.sock:/var/run/docker.sock
|
|
||||||
Volume=portainer_data:/data
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target multi-user.target
|
|
||||||
EOF
|
|
||||||
systemctl daemon-reload
|
|
||||||
$STD systemctl start portainer
|
|
||||||
msg_ok "Installed Portainer $PORTAINER_LATEST_VERSION"
|
msg_ok "Installed Portainer $PORTAINER_LATEST_VERSION"
|
||||||
else
|
else
|
||||||
read -r -p "${TAB3}Would you like to add the Portainer Agent? <y/N> " prompt
|
read -r -p "${TAB3}Would you like to add the Portainer Agent? <y/N> " prompt
|
||||||
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
|
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
|
||||||
msg_info "Installing Portainer agent $PORTAINER_AGENT_LATEST_VERSION"
|
msg_info "Installing Portainer agent $PORTAINER_AGENT_LATEST_VERSION"
|
||||||
cat <<EOF >/etc/containers/systemd/portainer-agent.container
|
podman volume create temp >/dev/null
|
||||||
[Unit]
|
podman volume remove temp >/dev/null
|
||||||
Description=Portainer Agent Container
|
$STD podman run -d \
|
||||||
After=network-online.target
|
-p 9001:9001 \
|
||||||
|
--name portainer_agent \
|
||||||
[Container]
|
--restart=always \
|
||||||
Image=docker.io/portainer/agent:latest
|
-v /run/podman/podman.sock:/var/run/docker.sock \
|
||||||
ContainerName=portainer_agent
|
-v /var/lib/containers/storage/volumes:/var/lib/docker/volumes \
|
||||||
PublishPort=9001:9001
|
portainer/agent
|
||||||
Volume=/run/podman/podman.sock:/var/run/docker.sock
|
|
||||||
Volume=/var/lib/containers/storage/volumes:/var/lib/docker/volumes
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target multi-user.target
|
|
||||||
EOF
|
|
||||||
systemctl daemon-reload
|
|
||||||
$STD systemctl start portainer-agent
|
|
||||||
msg_ok "Installed Portainer Agent $PORTAINER_AGENT_LATEST_VERSION"
|
msg_ok "Installed Portainer Agent $PORTAINER_AGENT_LATEST_VERSION"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -107,29 +81,19 @@ msg_ok "Pulled Home Assistant Image"
|
|||||||
|
|
||||||
msg_info "Installing Home Assistant"
|
msg_info "Installing Home Assistant"
|
||||||
$STD podman volume create hass_config
|
$STD podman volume create hass_config
|
||||||
cat <<EOF >/etc/containers/systemd/homeassistant.container
|
$STD podman run -d \
|
||||||
[Unit]
|
--name homeassistant \
|
||||||
Description=Home Assistant Container
|
--restart unless-stopped \
|
||||||
After=network-online.target
|
-v /dev:/dev \
|
||||||
|
-v hass_config:/config \
|
||||||
[Container]
|
-v /etc/localtime:/etc/localtime:ro \
|
||||||
Image=docker.io/homeassistant/home-assistant:stable
|
-v /etc/timezone:/etc/timezone:ro \
|
||||||
ContainerName=homeassistant
|
--net=host \
|
||||||
Volume=/dev:/dev
|
homeassistant/home-assistant:stable
|
||||||
Volume=hass_config:/config
|
podman generate systemd \
|
||||||
Volume=/etc/localtime:/etc/localtime:ro
|
--new --name homeassistant \
|
||||||
Volume=/etc/timezone:/etc/timezone:ro
|
>/etc/systemd/system/homeassistant.service
|
||||||
Network=host
|
systemctl enable -q --now homeassistant
|
||||||
|
|
||||||
[Service]
|
|
||||||
Restart=always
|
|
||||||
TimeoutStartSec=300
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target multi-user.target
|
|
||||||
EOF
|
|
||||||
systemctl daemon-reload
|
|
||||||
$STD systemctl start homeassistant
|
|
||||||
msg_ok "Installed Home Assistant"
|
msg_ok "Installed Home Assistant"
|
||||||
|
|
||||||
motd_ssh
|
motd_ssh
|
||||||
|
|||||||
@@ -45,58 +45,32 @@ systemctl enable -q --now podman.socket
|
|||||||
echo -e 'unqualified-search-registries=["docker.io"]' >>/etc/containers/registries.conf
|
echo -e 'unqualified-search-registries=["docker.io"]' >>/etc/containers/registries.conf
|
||||||
msg_ok "Installed Podman"
|
msg_ok "Installed Podman"
|
||||||
|
|
||||||
mkdir -p /etc/containers/systemd
|
|
||||||
|
|
||||||
read -r -p "${TAB3}Would you like to add Portainer? <y/N> " prompt
|
read -r -p "${TAB3}Would you like to add Portainer? <y/N> " prompt
|
||||||
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
|
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
|
||||||
msg_info "Installing Portainer $PORTAINER_LATEST_VERSION"
|
msg_info "Installing Portainer $PORTAINER_LATEST_VERSION"
|
||||||
podman volume create portainer_data >/dev/null
|
podman volume create portainer_data >/dev/null
|
||||||
cat <<EOF >/etc/containers/systemd/portainer.container
|
$STD podman run -d \
|
||||||
[Unit]
|
-p 8000:8000 \
|
||||||
Description=Portainer Container
|
-p 9443:9443 \
|
||||||
After=network-online.target
|
--name=portainer \
|
||||||
|
--restart=always \
|
||||||
[Container]
|
-v /run/podman/podman.sock:/var/run/docker.sock \
|
||||||
Image=docker.io/portainer/portainer-ce:latest
|
-v portainer_data:/data \
|
||||||
ContainerName=portainer
|
portainer/portainer-ce:latest
|
||||||
PublishPort=8000:8000
|
|
||||||
PublishPort=9443:9443
|
|
||||||
Volume=/run/podman/podman.sock:/var/run/docker.sock
|
|
||||||
Volume=portainer_data:/data
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target multi-user.target
|
|
||||||
EOF
|
|
||||||
systemctl daemon-reload
|
|
||||||
$STD systemctl start portainer
|
|
||||||
msg_ok "Installed Portainer $PORTAINER_LATEST_VERSION"
|
msg_ok "Installed Portainer $PORTAINER_LATEST_VERSION"
|
||||||
else
|
else
|
||||||
read -r -p "${TAB3}Would you like to add the Portainer Agent? <y/N> " prompt
|
read -r -p "${TAB3}Would you like to add the Portainer Agent? <y/N> " prompt
|
||||||
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
|
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
|
||||||
msg_info "Installing Portainer agent $PORTAINER_AGENT_LATEST_VERSION"
|
msg_info "Installing Portainer agent $PORTAINER_AGENT_LATEST_VERSION"
|
||||||
cat <<EOF >/etc/containers/systemd/portainer-agent.container
|
podman volume create temp >/dev/null
|
||||||
[Unit]
|
podman volume remove temp >/dev/null
|
||||||
Description=Portainer Agent Container
|
$STD podman run -d \
|
||||||
After=network-online.target
|
-p 9001:9001 \
|
||||||
|
--name portainer_agent \
|
||||||
[Container]
|
--restart=always \
|
||||||
Image=docker.io/portainer/agent:latest
|
-v /run/podman/podman.sock:/var/run/docker.sock \
|
||||||
ContainerName=portainer_agent
|
-v /var/lib/containers/storage/volumes:/var/lib/docker/volumes \
|
||||||
PublishPort=9001:9001
|
portainer/agent
|
||||||
Volume=/run/podman/podman.sock:/var/run/docker.sock
|
|
||||||
Volume=/var/lib/containers/storage/volumes:/var/lib/docker/volumes
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target multi-user.target
|
|
||||||
EOF
|
|
||||||
systemctl daemon-reload
|
|
||||||
$STD systemctl start portainer-agent
|
|
||||||
msg_ok "Installed Portainer Agent $PORTAINER_AGENT_LATEST_VERSION"
|
msg_ok "Installed Portainer Agent $PORTAINER_AGENT_LATEST_VERSION"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ msg_ok "Configured RabbitMQ"
|
|||||||
|
|
||||||
USE_ORIGINAL_FILENAME="true" fetch_and_deploy_gh_release "reitti" "dedicatedcode/reitti" "singlefile" "latest" "/opt/reitti" "reitti-app.jar"
|
USE_ORIGINAL_FILENAME="true" fetch_and_deploy_gh_release "reitti" "dedicatedcode/reitti" "singlefile" "latest" "/opt/reitti" "reitti-app.jar"
|
||||||
mv /opt/reitti/reitti-*.jar /opt/reitti/reitti.jar
|
mv /opt/reitti/reitti-*.jar /opt/reitti/reitti.jar
|
||||||
USE_ORIGINAL_FILENAME="true" fetch_and_deploy_gh_release "photon" "komoot/photon" "singlefile" "latest" "/opt/photon" "photon-*.jar"
|
USE_ORIGINAL_FILENAME="true" fetch_and_deploy_gh_release "photon" "komoot/photon" "singlefile" "latest" "/opt/photon" "photon-0*.jar"
|
||||||
mv /opt/photon/photon-*.jar /opt/photon/photon.jar
|
mv /opt/photon/photon-*.jar /opt/photon/photon.jar
|
||||||
|
|
||||||
msg_info "Installing Nginx Tile Cache"
|
msg_info "Installing Nginx Tile Cache"
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ msg_ok "Configured Sparky Fitness"
|
|||||||
|
|
||||||
msg_info "Building Backend"
|
msg_info "Building Backend"
|
||||||
cd /opt/sparkyfitness/SparkyFitnessServer
|
cd /opt/sparkyfitness/SparkyFitnessServer
|
||||||
$STD pnpm install
|
$STD npm install --legacy-peer-deps
|
||||||
msg_ok "Built Backend"
|
msg_ok "Built Backend"
|
||||||
|
|
||||||
msg_info "Building Frontend (Patience)"
|
msg_info "Building Frontend (Patience)"
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Copyright (c) 2021-2026 community-scripts ORG
|
|
||||||
# Author: johanngrobe
|
|
||||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
|
||||||
# Source: https://github.com/oss-apps/split-pro
|
|
||||||
|
|
||||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
|
||||||
color
|
|
||||||
verb_ip6
|
|
||||||
catch_errors
|
|
||||||
setting_up_container
|
|
||||||
network_check
|
|
||||||
update_os
|
|
||||||
|
|
||||||
NODE_VERSION="22" NODE_MODULE="pnpm" setup_nodejs
|
|
||||||
PG_VERSION="17" PG_MODULES="cron" setup_postgresql
|
|
||||||
|
|
||||||
msg_info "Installing Dependencies"
|
|
||||||
$STD apt install -y openssl
|
|
||||||
msg_ok "Installed Dependencies"
|
|
||||||
|
|
||||||
PG_DB_NAME="splitpro" PG_DB_USER="splitpro" PG_DB_EXTENSIONS="pg_cron" setup_postgresql_db
|
|
||||||
fetch_and_deploy_gh_release "split-pro" "oss-apps/split-pro" "tarball"
|
|
||||||
|
|
||||||
msg_info "Installing Dependencies"
|
|
||||||
cd /opt/split-pro
|
|
||||||
$STD pnpm install --frozen-lockfile
|
|
||||||
msg_ok "Installed Dependencies"
|
|
||||||
|
|
||||||
msg_info "Building Split Pro"
|
|
||||||
cd /opt/split-pro
|
|
||||||
mkdir -p /opt/split-pro_data/uploads
|
|
||||||
ln -sf /opt/split-pro_data/uploads /opt/split-pro/uploads
|
|
||||||
NEXTAUTH_SECRET=$(openssl rand -base64 32)
|
|
||||||
cp .env.example .env
|
|
||||||
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=\"postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\"|" .env
|
|
||||||
sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=\"${NEXTAUTH_SECRET}\"|" .env
|
|
||||||
sed -i "s|^NEXTAUTH_URL=.*|NEXTAUTH_URL=\"http://${LOCAL_IP}:3000\"|" .env
|
|
||||||
sed -i "s|^NEXTAUTH_URL_INTERNAL=.*|NEXTAUTH_URL_INTERNAL=\"http://localhost:3000\"|" .env
|
|
||||||
sed -i "/^POSTGRES_CONTAINER_NAME=/d" .env
|
|
||||||
sed -i "/^POSTGRES_USER=/d" .env
|
|
||||||
sed -i "/^POSTGRES_PASSWORD=/d" .env
|
|
||||||
sed -i "/^POSTGRES_DB=/d" .env
|
|
||||||
sed -i "/^POSTGRES_PORT=/d" .env
|
|
||||||
$STD pnpm build
|
|
||||||
$STD pnpm exec prisma migrate deploy
|
|
||||||
msg_ok "Built Split Pro"
|
|
||||||
|
|
||||||
msg_info "Creating Service"
|
|
||||||
cat <<EOF >/etc/systemd/system/split-pro.service
|
|
||||||
[Unit]
|
|
||||||
Description=Split Pro
|
|
||||||
After=network.target postgresql.service
|
|
||||||
Requires=postgresql.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=root
|
|
||||||
WorkingDirectory=/opt/split-pro
|
|
||||||
EnvironmentFile=/opt/split-pro/.env
|
|
||||||
ExecStart=/usr/bin/pnpm start
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
systemctl enable -q --now split-pro
|
|
||||||
msg_ok "Created Service"
|
|
||||||
|
|
||||||
motd_ssh
|
|
||||||
customize
|
|
||||||
cleanup_lxc
|
|
||||||
@@ -20,16 +20,12 @@ msg_ok "Installed Dependencies"
|
|||||||
msg_info "Installing Tdarr"
|
msg_info "Installing Tdarr"
|
||||||
mkdir -p /opt/tdarr
|
mkdir -p /opt/tdarr
|
||||||
cd /opt/tdarr
|
cd /opt/tdarr
|
||||||
RELEASE=$(curl_with_retry "https://f000.backblazeb2.com/file/tdarrs/versions.json" "-" | grep -oP '(?<="Tdarr_Updater": ")[^"]+' | grep linux_x64 | head -n 1)
|
RELEASE=$(curl -fsSL https://f000.backblazeb2.com/file/tdarrs/versions.json | grep -oP '(?<="Tdarr_Updater": ")[^"]+' | grep linux_x64 | head -n 1)
|
||||||
curl_with_retry "$RELEASE" "Tdarr_Updater.zip"
|
curl -fsSL "$RELEASE" -o Tdarr_Updater.zip
|
||||||
$STD unzip Tdarr_Updater.zip
|
$STD unzip Tdarr_Updater.zip
|
||||||
chmod +x Tdarr_Updater
|
chmod +x Tdarr_Updater
|
||||||
$STD ./Tdarr_Updater
|
$STD ./Tdarr_Updater
|
||||||
rm -rf /opt/tdarr/Tdarr_Updater.zip
|
rm -rf /opt/tdarr/Tdarr_Updater.zip
|
||||||
[[ -f /opt/tdarr/Tdarr_Server/Tdarr_Server ]] || {
|
|
||||||
msg_error "Tdarr_Updater failed — tdarr.io may be blocked by local DNS"
|
|
||||||
exit 250
|
|
||||||
}
|
|
||||||
msg_ok "Installed Tdarr"
|
msg_ok "Installed Tdarr"
|
||||||
|
|
||||||
setup_hwaccel
|
setup_hwaccel
|
||||||
|
|||||||
@@ -19,41 +19,9 @@ $STD apt install -y \
|
|||||||
python3 \
|
python3 \
|
||||||
nginx \
|
nginx \
|
||||||
openssl \
|
openssl \
|
||||||
gettext-base \
|
gettext-base
|
||||||
libcairo2-dev \
|
|
||||||
libjpeg62-turbo-dev \
|
|
||||||
libpng-dev \
|
|
||||||
libtool-bin \
|
|
||||||
uuid-dev \
|
|
||||||
libvncserver-dev \
|
|
||||||
freerdp3-dev \
|
|
||||||
libssh2-1-dev \
|
|
||||||
libtelnet-dev \
|
|
||||||
libwebsockets-dev \
|
|
||||||
libpulse-dev \
|
|
||||||
libvorbis-dev \
|
|
||||||
libwebp-dev \
|
|
||||||
libssl-dev \
|
|
||||||
libpango1.0-dev \
|
|
||||||
libswscale-dev \
|
|
||||||
libavcodec-dev \
|
|
||||||
libavutil-dev \
|
|
||||||
libavformat-dev
|
|
||||||
msg_ok "Installed Dependencies"
|
msg_ok "Installed Dependencies"
|
||||||
|
|
||||||
msg_info "Building Guacamole Server (guacd)"
|
|
||||||
fetch_and_deploy_gh_tag "guacd" "apache/guacamole-server" "latest" "/opt/guacamole-server"
|
|
||||||
cd /opt/guacamole-server
|
|
||||||
export CPPFLAGS="-Wno-error=deprecated-declarations"
|
|
||||||
$STD autoreconf -fi
|
|
||||||
$STD ./configure --with-init-dir=/etc/init.d --enable-allow-freerdp-snapshots
|
|
||||||
$STD make
|
|
||||||
$STD make install
|
|
||||||
$STD ldconfig
|
|
||||||
cd /opt
|
|
||||||
rm -rf /opt/guacamole-server
|
|
||||||
msg_ok "Built Guacamole Server (guacd)"
|
|
||||||
|
|
||||||
NODE_VERSION="22" setup_nodejs
|
NODE_VERSION="22" setup_nodejs
|
||||||
fetch_and_deploy_gh_release "termix" "Termix-SSH/Termix"
|
fetch_and_deploy_gh_release "termix" "Termix-SSH/Termix"
|
||||||
|
|
||||||
@@ -106,46 +74,17 @@ systemctl reload nginx
|
|||||||
msg_ok "Configured Nginx"
|
msg_ok "Configured Nginx"
|
||||||
|
|
||||||
msg_info "Creating Service"
|
msg_info "Creating Service"
|
||||||
mkdir -p /etc/guacamole
|
|
||||||
cat <<EOF >/etc/guacamole/guacd.conf
|
|
||||||
[server]
|
|
||||||
bind_host = 127.0.0.1
|
|
||||||
bind_port = 4822
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<EOF >/opt/termix/.env
|
|
||||||
NODE_ENV=production
|
|
||||||
DATA_DIR=/opt/termix/data
|
|
||||||
GUACD_HOST=127.0.0.1
|
|
||||||
GUACD_PORT=4822
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<EOF >/etc/systemd/system/guacd.service
|
|
||||||
[Unit]
|
|
||||||
Description=Guacamole Proxy Daemon (guacd)
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=/usr/local/sbin/guacd -f -b 127.0.0.1 -l 4822
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<EOF >/etc/systemd/system/termix.service
|
cat <<EOF >/etc/systemd/system/termix.service
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Termix Backend
|
Description=Termix Backend
|
||||||
After=network.target guacd.service
|
After=network.target
|
||||||
Wants=guacd.service
|
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=root
|
User=root
|
||||||
WorkingDirectory=/opt/termix
|
WorkingDirectory=/opt/termix
|
||||||
EnvironmentFile=/opt/termix/.env
|
Environment=NODE_ENV=production
|
||||||
|
Environment=DATA_DIR=/opt/termix/data
|
||||||
ExecStart=/usr/bin/node /opt/termix/dist/backend/backend/starter.js
|
ExecStart=/usr/bin/node /opt/termix/dist/backend/backend/starter.js
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
@@ -153,7 +92,7 @@ RestartSec=5
|
|||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
systemctl enable -q --now guacd termix
|
systemctl enable -q --now termix
|
||||||
msg_ok "Created Service"
|
msg_ok "Created Service"
|
||||||
|
|
||||||
motd_ssh
|
motd_ssh
|
||||||
|
|||||||
270
misc/tools.func
270
misc/tools.func
@@ -1979,47 +1979,6 @@ extract_version_from_json() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# Get latest GitHub tag (for repos that only publish tags, not releases).
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# get_latest_gh_tag "owner/repo" [prefix]
|
|
||||||
#
|
|
||||||
# Arguments:
|
|
||||||
# $1 - GitHub repo (owner/repo)
|
|
||||||
# $2 - Optional prefix filter (e.g., "v" to only match tags starting with "v")
|
|
||||||
#
|
|
||||||
# Returns:
|
|
||||||
# Latest tag name (stdout), or returns 1 on failure
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
get_latest_gh_tag() {
|
|
||||||
local repo="$1"
|
|
||||||
local prefix="${2:-}"
|
|
||||||
local temp_file
|
|
||||||
temp_file=$(mktemp)
|
|
||||||
|
|
||||||
if ! github_api_call "https://api.github.com/repos/${repo}/tags?per_page=50" "$temp_file"; then
|
|
||||||
rm -f "$temp_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local tag=""
|
|
||||||
if [[ -n "$prefix" ]]; then
|
|
||||||
tag=$(jq -r --arg p "$prefix" '[.[] | select(.name | startswith($p))][0].name // empty' "$temp_file")
|
|
||||||
else
|
|
||||||
tag=$(jq -r '.[0].name // empty' "$temp_file")
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f "$temp_file"
|
|
||||||
|
|
||||||
if [[ -z "$tag" ]]; then
|
|
||||||
msg_error "No tags found for ${repo}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$tag"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Get latest GitHub release version with fallback to tags
|
# Get latest GitHub release version with fallback to tags
|
||||||
# Usage: get_latest_github_release "owner/repo" [strip_v] [include_prerelease]
|
# Usage: get_latest_github_release "owner/repo" [strip_v] [include_prerelease]
|
||||||
@@ -2118,129 +2077,101 @@ verify_gpg_fingerprint() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Fetches and deploys a GitHub tag-based source tarball.
|
# Get latest GitHub tag for a repository.
|
||||||
#
|
#
|
||||||
# Description:
|
# Description:
|
||||||
# - Downloads the source tarball for a given tag from GitHub
|
# - Queries the GitHub API for tags (not releases)
|
||||||
# - Extracts to the target directory
|
# - Useful for repos that only create tags, not full releases
|
||||||
# - Writes the version to ~/.<app>
|
# - Supports optional prefix filter and version-only extraction
|
||||||
|
# - Returns the latest tag name (printed to stdout)
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# fetch_and_deploy_gh_tag "guacd" "apache/guacamole-server"
|
# MONGO_VERSION=$(get_latest_gh_tag "mongodb/mongo-tools")
|
||||||
# fetch_and_deploy_gh_tag "guacd" "apache/guacamole-server" "latest" "/opt/guacamole-server"
|
# LATEST=$(get_latest_gh_tag "owner/repo" "v") # only tags starting with "v"
|
||||||
|
# LATEST=$(get_latest_gh_tag "owner/repo" "" "true") # strip leading "v"
|
||||||
#
|
#
|
||||||
# Arguments:
|
# Arguments:
|
||||||
# $1 - App name (used for version file ~/.<app>)
|
# $1 - GitHub repo (owner/repo)
|
||||||
# $2 - GitHub repo (owner/repo)
|
# $2 - Tag prefix filter (optional, e.g. "v" or "100.")
|
||||||
# $3 - Tag version (default: "latest" → auto-detect via get_latest_gh_tag)
|
# $3 - Strip prefix from result (optional, "true" to strip $2 prefix)
|
||||||
# $4 - Target directory (default: /opt/$app)
|
#
|
||||||
|
# Returns:
|
||||||
|
# 0 on success (tag printed to stdout), 1 on failure
|
||||||
#
|
#
|
||||||
# Notes:
|
# Notes:
|
||||||
# - Supports CLEAN_INSTALL=1 to wipe target before extracting
|
# - Skips tags containing "rc", "alpha", "beta", "dev", "test"
|
||||||
# - For repos that only publish tags, not GitHub Releases
|
# - Sorts by version number (sort -V) to find the latest
|
||||||
|
# - Respects GITHUB_TOKEN for rate limiting
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
fetch_and_deploy_gh_tag() {
|
get_latest_gh_tag() {
|
||||||
local app="$1"
|
local repo="$1"
|
||||||
local repo="$2"
|
local prefix="${2:-}"
|
||||||
local version="${3:-latest}"
|
local strip_prefix="${3:-false}"
|
||||||
local target="${4:-/opt/$app}"
|
|
||||||
local app_lc=""
|
|
||||||
app_lc="$(echo "${app,,}" | tr -d ' ')"
|
|
||||||
local version_file="$HOME/.${app_lc}"
|
|
||||||
|
|
||||||
if [[ "$version" == "latest" ]]; then
|
local header_args=()
|
||||||
version=$(get_latest_gh_tag "$repo") || {
|
[[ -n "${GITHUB_TOKEN:-}" ]] && header_args=(-H "Authorization: Bearer $GITHUB_TOKEN")
|
||||||
msg_error "Failed to determine latest tag for ${repo}"
|
|
||||||
|
local http_code=""
|
||||||
|
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o /tmp/gh_tags.json \
|
||||||
|
-H 'Accept: application/vnd.github+json' \
|
||||||
|
-H 'X-GitHub-Api-Version: 2022-11-28' \
|
||||||
|
"${header_args[@]}" \
|
||||||
|
"https://api.github.com/repos/${repo}/tags?per_page=100" 2>/dev/null) || true
|
||||||
|
|
||||||
|
if [[ "$http_code" == "401" ]]; then
|
||||||
|
msg_error "GitHub API authentication failed (HTTP 401)."
|
||||||
|
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
||||||
|
msg_error "Your GITHUB_TOKEN appears to be invalid or expired."
|
||||||
|
else
|
||||||
|
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
|
||||||
|
fi
|
||||||
|
rm -f /tmp/gh_tags.json
|
||||||
return 1
|
return 1
|
||||||
}
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local current_version=""
|
if [[ "$http_code" == "403" ]]; then
|
||||||
[[ -f "$version_file" ]] && current_version=$(<"$version_file")
|
msg_error "GitHub API rate limit exceeded (HTTP 403)."
|
||||||
|
msg_error "To increase the limit, export a GitHub token before running the script:"
|
||||||
if [[ "$current_version" == "$version" ]]; then
|
msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\""
|
||||||
msg_ok "$app is already up-to-date ($version)"
|
rm -f /tmp/gh_tags.json
|
||||||
return 0
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local tmpdir
|
if [[ "$http_code" == "000" || -z "$http_code" ]]; then
|
||||||
tmpdir=$(mktemp -d) || return 1
|
msg_error "GitHub API connection failed (no response)."
|
||||||
local tarball_url="https://github.com/${repo}/archive/refs/tags/${version}.tar.gz"
|
msg_error "Check your network/DNS: curl -sSL https://api.github.com/rate_limit"
|
||||||
local filename="${app_lc}-${version}.tar.gz"
|
rm -f /tmp/gh_tags.json
|
||||||
|
|
||||||
msg_info "Fetching GitHub tag: ${app} (${version})"
|
|
||||||
|
|
||||||
download_file "$tarball_url" "$tmpdir/$filename" || {
|
|
||||||
msg_error "Download failed: $tarball_url"
|
|
||||||
rm -rf "$tmpdir"
|
|
||||||
return 1
|
return 1
|
||||||
}
|
|
||||||
|
|
||||||
mkdir -p "$target"
|
|
||||||
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
|
|
||||||
rm -rf "${target:?}/"*
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
|
if [[ "$http_code" != "200" ]] || [[ ! -s /tmp/gh_tags.json ]]; then
|
||||||
msg_error "Failed to extract tarball"
|
msg_error "Unable to fetch tags for ${repo} (HTTP ${http_code})"
|
||||||
rm -rf "$tmpdir"
|
rm -f /tmp/gh_tags.json
|
||||||
return 1
|
return 1
|
||||||
}
|
fi
|
||||||
|
|
||||||
local unpack_dir
|
local tags_json
|
||||||
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
|
tags_json=$(</tmp/gh_tags.json)
|
||||||
|
rm -f /tmp/gh_tags.json
|
||||||
shopt -s dotglob nullglob
|
|
||||||
cp -r "$unpack_dir"/* "$target/"
|
|
||||||
shopt -u dotglob nullglob
|
|
||||||
|
|
||||||
rm -rf "$tmpdir"
|
|
||||||
echo "$version" >"$version_file"
|
|
||||||
msg_ok "Deployed ${app} ${version} to ${target}"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# Checks for new GitHub tag (for repos without releases).
|
|
||||||
#
|
|
||||||
# Description:
|
|
||||||
# - Uses get_latest_gh_tag to fetch the latest tag
|
|
||||||
# - Compares it to a local cached version (~/.<app>)
|
|
||||||
# - If newer, sets global CHECK_UPDATE_RELEASE and returns 0
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# if check_for_gh_tag "guacd" "apache/guacamole-server"; then
|
|
||||||
# fetch_and_deploy_gh_tag "guacd" "apache/guacamole-server" "/opt/guacamole-server"
|
|
||||||
# fi
|
|
||||||
#
|
|
||||||
# Notes:
|
|
||||||
# - For repos that only publish tags, not GitHub Releases
|
|
||||||
# - Same interface as check_for_gh_release
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
check_for_gh_tag() {
|
|
||||||
local app="$1"
|
|
||||||
local repo="$2"
|
|
||||||
local prefix="${3:-}"
|
|
||||||
local app_lc=""
|
|
||||||
app_lc="$(echo "${app,,}" | tr -d ' ')"
|
|
||||||
local current_file="$HOME/.${app_lc}"
|
|
||||||
|
|
||||||
msg_info "Checking for update: ${app}"
|
|
||||||
|
|
||||||
|
# Extract tag names, filter by prefix, exclude pre-release patterns, sort by version
|
||||||
local latest=""
|
local latest=""
|
||||||
latest=$(get_latest_gh_tag "$repo" "$prefix") || return 1
|
latest=$(echo "$tags_json" | grep -oP '"name":\s*"\K[^"]+' |
|
||||||
|
{ [[ -n "$prefix" ]] && grep "^${prefix}" || cat; } |
|
||||||
|
grep -viE '(rc|alpha|beta|dev|test|preview|snapshot)' |
|
||||||
|
sort -V | tail -n1)
|
||||||
|
|
||||||
local current=""
|
if [[ -z "$latest" ]]; then
|
||||||
[[ -f "$current_file" ]] && current="$(<"$current_file")"
|
msg_warn "No matching tags found for ${repo}${prefix:+ (prefix: $prefix)}"
|
||||||
|
return 1
|
||||||
if [[ -z "$current" || "$current" != "$latest" ]]; then
|
|
||||||
CHECK_UPDATE_RELEASE="$latest"
|
|
||||||
msg_ok "Update available: ${app} ${current:-not installed} → ${latest}"
|
|
||||||
return 0
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
msg_ok "No update available: ${app} (${latest})"
|
if [[ "$strip_prefix" == "true" && -n "$prefix" ]]; then
|
||||||
return 1
|
latest="${latest#"$prefix"}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$latest"
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -2588,8 +2519,6 @@ check_for_codeberg_release() {
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
create_self_signed_cert() {
|
create_self_signed_cert() {
|
||||||
local APP_NAME="${1:-${APPLICATION}}"
|
local APP_NAME="${1:-${APPLICATION}}"
|
||||||
local HOSTNAME="$(hostname -f)"
|
|
||||||
local IP="$(hostname -I | awk '{print $1}')"
|
|
||||||
local APP_NAME_LC=$(echo "${APP_NAME,,}" | tr -d ' ')
|
local APP_NAME_LC=$(echo "${APP_NAME,,}" | tr -d ' ')
|
||||||
local CERT_DIR="/etc/ssl/${APP_NAME_LC}"
|
local CERT_DIR="/etc/ssl/${APP_NAME_LC}"
|
||||||
local CERT_KEY="${CERT_DIR}/${APP_NAME_LC}.key"
|
local CERT_KEY="${CERT_DIR}/${APP_NAME_LC}.key"
|
||||||
@@ -2607,8 +2536,8 @@ create_self_signed_cert() {
|
|||||||
|
|
||||||
mkdir -p "$CERT_DIR"
|
mkdir -p "$CERT_DIR"
|
||||||
$STD openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
|
$STD openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
|
||||||
-subj "/CN=${HOSTNAME}" \
|
-subj "/CN=${APP_NAME}" \
|
||||||
-addext "subjectAltName=DNS:${HOSTNAME},DNS:localhost,IP:${IP},IP:127.0.0.1" \
|
-addext "subjectAltName=DNS:${APP_NAME}" \
|
||||||
-keyout "$CERT_KEY" \
|
-keyout "$CERT_KEY" \
|
||||||
-out "$CERT_CRT" || {
|
-out "$CERT_CRT" || {
|
||||||
msg_error "Failed to create self-signed certificate"
|
msg_error "Failed to create self-signed certificate"
|
||||||
@@ -4256,8 +4185,6 @@ function setup_gs() {
|
|||||||
# - NVIDIA requires matching host driver version
|
# - NVIDIA requires matching host driver version
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
function setup_hwaccel() {
|
function setup_hwaccel() {
|
||||||
local service_user="${1:-}"
|
|
||||||
|
|
||||||
# Check if user explicitly disabled GPU in advanced settings
|
# Check if user explicitly disabled GPU in advanced settings
|
||||||
# ENABLE_GPU is exported from build.func
|
# ENABLE_GPU is exported from build.func
|
||||||
if [[ "${ENABLE_GPU:-no}" == "no" ]]; then
|
if [[ "${ENABLE_GPU:-no}" == "no" ]]; then
|
||||||
@@ -4509,7 +4436,7 @@ function setup_hwaccel() {
|
|||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
# Device Permissions
|
# Device Permissions
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
_setup_gpu_permissions "$in_ct" "$service_user"
|
_setup_gpu_permissions "$in_ct"
|
||||||
|
|
||||||
cache_installed_version "hwaccel" "1.0"
|
cache_installed_version "hwaccel" "1.0"
|
||||||
msg_ok "Setup Hardware Acceleration"
|
msg_ok "Setup Hardware Acceleration"
|
||||||
@@ -5143,7 +5070,6 @@ EOF
|
|||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
_setup_gpu_permissions() {
|
_setup_gpu_permissions() {
|
||||||
local in_ct="$1"
|
local in_ct="$1"
|
||||||
local service_user="${2:-}"
|
|
||||||
|
|
||||||
# /dev/dri permissions (Intel/AMD)
|
# /dev/dri permissions (Intel/AMD)
|
||||||
if [[ "$in_ct" == "0" && -d /dev/dri ]]; then
|
if [[ "$in_ct" == "0" && -d /dev/dri ]]; then
|
||||||
@@ -5210,12 +5136,6 @@ _setup_gpu_permissions() {
|
|||||||
chmod 666 /dev/kfd 2>/dev/null || true
|
chmod 666 /dev/kfd 2>/dev/null || true
|
||||||
msg_info "AMD ROCm compute device configured"
|
msg_info "AMD ROCm compute device configured"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Add service user to render and video groups for GPU hardware acceleration
|
|
||||||
if [[ -n "$service_user" ]]; then
|
|
||||||
usermod -aG render "$service_user" 2>/dev/null || true
|
|
||||||
usermod -aG video "$service_user" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@@ -6698,38 +6618,22 @@ EOF
|
|||||||
# - Optionally uses official PGDG repository for specific versions
|
# - Optionally uses official PGDG repository for specific versions
|
||||||
# - Detects existing PostgreSQL version
|
# - Detects existing PostgreSQL version
|
||||||
# - Dumps all databases before upgrade
|
# - Dumps all databases before upgrade
|
||||||
# - Installs optional PG_MODULES (e.g. postgis, contrib, cron)
|
# - Installs optional PG_MODULES (e.g. postgis, contrib)
|
||||||
# - Restores dumped data post-upgrade
|
# - Restores dumped data post-upgrade
|
||||||
#
|
#
|
||||||
# Variables:
|
# Variables:
|
||||||
# USE_PGDG_REPO - Use official PGDG repository (default: true)
|
# USE_PGDG_REPO - Use official PGDG repository (default: true)
|
||||||
# Set to "false" to use distro packages instead
|
# Set to "false" to use distro packages instead
|
||||||
# PG_VERSION - Major PostgreSQL version (e.g. 15, 16) (default: 16)
|
# PG_VERSION - Major PostgreSQL version (e.g. 15, 16) (default: 16)
|
||||||
# PG_MODULES - Comma-separated list of modules (e.g. "postgis,contrib,cron")
|
# PG_MODULES - Comma-separated list of modules (e.g. "postgis,contrib")
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# setup_postgresql # Uses PGDG repo, PG 16
|
# setup_postgresql # Uses PGDG repo, PG 16
|
||||||
# PG_VERSION="17" setup_postgresql # Specific version from PGDG
|
# PG_VERSION="17" setup_postgresql # Specific version from PGDG
|
||||||
# USE_PGDG_REPO=false setup_postgresql # Uses distro package instead
|
# USE_PGDG_REPO=false setup_postgresql # Uses distro package instead
|
||||||
# PG_VERSION="17" PG_MODULES="cron" setup_postgresql # With pg_cron module
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
# Internal helper: Configure shared_preload_libraries for pg_cron
|
function setup_postgresql() {
|
||||||
_configure_pg_cron_preload() {
|
|
||||||
local modules="${1:-}"
|
|
||||||
[[ -z "$modules" ]] && return 0
|
|
||||||
if [[ ",$modules," == *",cron,"* ]]; then
|
|
||||||
local current_libs
|
|
||||||
current_libs=$(sudo -u postgres psql -tAc "SHOW shared_preload_libraries;" 2>/dev/null || echo "")
|
|
||||||
if [[ "$current_libs" != *"pg_cron"* ]]; then
|
|
||||||
local new_libs="${current_libs:+${current_libs},}pg_cron"
|
|
||||||
$STD sudo -u postgres psql -c "ALTER SYSTEM SET shared_preload_libraries = '${new_libs}';"
|
|
||||||
$STD systemctl restart postgresql
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_postgresql() {
|
|
||||||
local PG_VERSION="${PG_VERSION:-16}"
|
local PG_VERSION="${PG_VERSION:-16}"
|
||||||
local PG_MODULES="${PG_MODULES:-}"
|
local PG_MODULES="${PG_MODULES:-}"
|
||||||
local USE_PGDG_REPO="${USE_PGDG_REPO:-true}"
|
local USE_PGDG_REPO="${USE_PGDG_REPO:-true}"
|
||||||
@@ -6767,7 +6671,6 @@ setup_postgresql() {
|
|||||||
$STD apt install -y "postgresql-${CURRENT_PG_VERSION}-${module}" 2>/dev/null || true
|
$STD apt install -y "postgresql-${CURRENT_PG_VERSION}-${module}" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
_configure_pg_cron_preload "$PG_MODULES"
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -6803,7 +6706,6 @@ setup_postgresql() {
|
|||||||
$STD apt install -y "postgresql-${INSTALLED_VERSION}-${module}" 2>/dev/null || true
|
$STD apt install -y "postgresql-${INSTALLED_VERSION}-${module}" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
_configure_pg_cron_preload "$PG_MODULES"
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -6825,7 +6727,6 @@ setup_postgresql() {
|
|||||||
$STD apt install -y "postgresql-${PG_VERSION}-${module}" 2>/dev/null || true
|
$STD apt install -y "postgresql-${PG_VERSION}-${module}" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
_configure_pg_cron_preload "$PG_MODULES"
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -6853,16 +6754,13 @@ setup_postgresql() {
|
|||||||
local SUITE
|
local SUITE
|
||||||
case "$DISTRO_CODENAME" in
|
case "$DISTRO_CODENAME" in
|
||||||
trixie | forky | sid)
|
trixie | forky | sid)
|
||||||
|
|
||||||
if verify_repo_available "https://apt.postgresql.org/pub/repos/apt" "trixie-pgdg"; then
|
if verify_repo_available "https://apt.postgresql.org/pub/repos/apt" "trixie-pgdg"; then
|
||||||
SUITE="trixie-pgdg"
|
SUITE="trixie-pgdg"
|
||||||
|
|
||||||
else
|
else
|
||||||
msg_warn "PGDG repo not available for ${DISTRO_CODENAME}, falling back to distro packages"
|
msg_warn "PGDG repo not available for ${DISTRO_CODENAME}, falling back to distro packages"
|
||||||
USE_PGDG_REPO=false setup_postgresql
|
USE_PGDG_REPO=false setup_postgresql
|
||||||
return $?
|
return $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
SUITE=$(get_fallback_suite "$DISTRO_ID" "$DISTRO_CODENAME" "https://apt.postgresql.org/pub/repos/apt")
|
SUITE=$(get_fallback_suite "$DISTRO_ID" "$DISTRO_CODENAME" "https://apt.postgresql.org/pub/repos/apt")
|
||||||
@@ -6946,7 +6844,6 @@ setup_postgresql() {
|
|||||||
}
|
}
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
_configure_pg_cron_preload "$PG_MODULES"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@@ -6965,7 +6862,6 @@ setup_postgresql() {
|
|||||||
# PG_DB_NAME="immich" PG_DB_USER="immich" PG_DB_EXTENSIONS="pgvector" setup_postgresql_db
|
# PG_DB_NAME="immich" PG_DB_USER="immich" PG_DB_EXTENSIONS="pgvector" setup_postgresql_db
|
||||||
# PG_DB_NAME="ghostfolio" PG_DB_USER="ghostfolio" PG_DB_GRANT_SUPERUSER="true" setup_postgresql_db
|
# PG_DB_NAME="ghostfolio" PG_DB_USER="ghostfolio" PG_DB_GRANT_SUPERUSER="true" setup_postgresql_db
|
||||||
# PG_DB_NAME="adventurelog" PG_DB_USER="adventurelog" PG_DB_EXTENSIONS="postgis" setup_postgresql_db
|
# PG_DB_NAME="adventurelog" PG_DB_USER="adventurelog" PG_DB_EXTENSIONS="postgis" setup_postgresql_db
|
||||||
# PG_DB_NAME="splitpro" PG_DB_USER="splitpro" PG_DB_EXTENSIONS="pg_cron" setup_postgresql_db
|
|
||||||
#
|
#
|
||||||
# Variables:
|
# Variables:
|
||||||
# PG_DB_NAME - Database name (required)
|
# PG_DB_NAME - Database name (required)
|
||||||
@@ -6997,13 +6893,6 @@ function setup_postgresql_db() {
|
|||||||
$STD sudo -u postgres psql -c "CREATE ROLE $PG_DB_USER WITH LOGIN PASSWORD '$PG_DB_PASS';"
|
$STD sudo -u postgres psql -c "CREATE ROLE $PG_DB_USER WITH LOGIN PASSWORD '$PG_DB_PASS';"
|
||||||
$STD sudo -u postgres psql -c "CREATE DATABASE $PG_DB_NAME WITH OWNER $PG_DB_USER ENCODING 'UTF8' TEMPLATE template0;"
|
$STD sudo -u postgres psql -c "CREATE DATABASE $PG_DB_NAME WITH OWNER $PG_DB_USER ENCODING 'UTF8' TEMPLATE template0;"
|
||||||
|
|
||||||
# Configure pg_cron database BEFORE creating the extension (must be set before pg_cron loads)
|
|
||||||
if [[ -n "${PG_DB_EXTENSIONS:-}" ]] && [[ ",${PG_DB_EXTENSIONS//[[:space:]]/}," == *",pg_cron,"* ]]; then
|
|
||||||
$STD sudo -u postgres psql -c "ALTER SYSTEM SET cron.database_name = '${PG_DB_NAME}';"
|
|
||||||
$STD sudo -u postgres psql -c "ALTER SYSTEM SET cron.timezone = 'UTC';"
|
|
||||||
$STD systemctl restart postgresql
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install extensions (comma-separated)
|
# Install extensions (comma-separated)
|
||||||
if [[ -n "${PG_DB_EXTENSIONS:-}" ]]; then
|
if [[ -n "${PG_DB_EXTENSIONS:-}" ]]; then
|
||||||
IFS=',' read -ra EXT_LIST <<<"${PG_DB_EXTENSIONS:-}"
|
IFS=',' read -ra EXT_LIST <<<"${PG_DB_EXTENSIONS:-}"
|
||||||
@@ -7013,12 +6902,6 @@ function setup_postgresql_db() {
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Grant pg_cron schema permissions to DB user
|
|
||||||
if [[ -n "${PG_DB_EXTENSIONS:-}" ]] && [[ ",${PG_DB_EXTENSIONS//[[:space:]]/}," == *",pg_cron,"* ]]; then
|
|
||||||
$STD sudo -u postgres psql -d "$PG_DB_NAME" -c "GRANT USAGE ON SCHEMA cron TO ${PG_DB_USER};"
|
|
||||||
$STD sudo -u postgres psql -d "$PG_DB_NAME" -c "GRANT ALL ON ALL TABLES IN SCHEMA cron TO ${PG_DB_USER};"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ALTER ROLE settings for Django/Rails compatibility (unless skipped)
|
# ALTER ROLE settings for Django/Rails compatibility (unless skipped)
|
||||||
if [[ "${PG_DB_SKIP_ALTER_ROLE:-}" != "true" ]]; then
|
if [[ "${PG_DB_SKIP_ALTER_ROLE:-}" != "true" ]]; then
|
||||||
$STD sudo -u postgres psql -c "ALTER ROLE $PG_DB_USER SET client_encoding TO 'utf8';"
|
$STD sudo -u postgres psql -c "ALTER ROLE $PG_DB_USER SET client_encoding TO 'utf8';"
|
||||||
@@ -7060,6 +6943,7 @@ function setup_postgresql_db() {
|
|||||||
export PG_DB_USER
|
export PG_DB_USER
|
||||||
export PG_DB_PASS
|
export PG_DB_PASS
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Installs rbenv and ruby-build, installs Ruby and optionally Rails.
|
# Installs rbenv and ruby-build, installs Ruby and optionally Rails.
|
||||||
#
|
#
|
||||||
|
|||||||
Reference in New Issue
Block a user