8 Commits

Author SHA1 Message Date
github-actions[bot]
7a63e4d80f Delete teleport (ct) after migration to ProxmoxVE 2026-03-20 08:06:48 +00:00
tremor021
4b3d9366d2 Teleport: increase sleep to 10s 2026-03-19 14:11:05 +01:00
CanbiZ (MickLesk)
0527db122b feat(bot): add version to ALLOWED_FIELDS 2026-03-19 08:47:04 +01:00
CanbiZ (MickLesk)
b689917eee fix(bot): handle PocketBase returning JSON fields as pre-parsed objects 2026-03-18 19:39:44 +01:00
CanbiZ (MickLesk)
8bb2f28b48 debug(bot): show raw field values in method list when count=0 2026-03-18 19:36:51 +01:00
CanbiZ (MickLesk)
71c02e052b fix(bot): fallback to expanded install_methods relation when install_methods_json is empty 2026-03-18 19:05:32 +01:00
CanbiZ (MickLesk)
105dceb42e refactor(bot): use notes_json and install_methods_json directly, remove extra table dependencies 2026-03-18 18:59:47 +01:00
CanbiZ (MickLesk)
8ba7b55cd5 Merge pull request #1591 from community-scripts/feature/pocketbase-bot
Pocketbase bot
2026-03-18 18:44:58 +01:00
4 changed files with 207 additions and 416 deletions

502
.github/workflows/pocketbase-bot.yml generated vendored
View File

@@ -234,36 +234,204 @@ jobs:
const setMatch = rest.match(/^set\s+(\S+)/i);
if (noteMatch) {
// ── NOTE SUBCOMMAND ──────────────────────────────────────────────
} else if (methodMatch) {
// ── METHOD SUBCOMMAND ────────────────────────────────────────────
const methodArgs = rest.replace(/^method\s*/i, '').trim();
const methodListMode = !methodArgs || methodArgs.toLowerCase() === 'list';
// ── NOTE SUBCOMMAND (reads/writes notes_json on script record) ────
const noteAction = noteMatch[1].toLowerCase();
const noteArgsStr = rest.substring(noteMatch[0].length).trim();
// Helper: format bytes/numbers nicely
function fmtMethod(im) {
const r = im.resources || {};
const typeLabel = im.expand && im.expand.type ? (im.expand.type.type || im.expand.type.name || im.expand.type.value || im.type) : im.type;
return '**`' + (typeLabel || '(unknown type)') + '`** — CPU: `' + (r.cpu != null ? r.cpu : im.resources_cpu || 0) + '` · RAM: `' + (r.ram != null ? r.ram : im.resources_ram || 0) + ' MB` · HDD: `' + (r.hdd != null ? r.hdd : im.resources_hdd || 0) + ' GB`';
// 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;
}
// Fetch expanded install_methods
const expRes = await request(recordsUrl + '/' + record.id + '?expand=install_methods,install_methods.type', { headers: { 'Authorization': token } });
const expRec = JSON.parse(expRes.body);
const installMethods = (expRec.expand && expRec.expand.install_methods) || [];
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');
if (installMethods.length === 0) {
await postComment(' **PocketBase Bot**: No install methods found for **`' + slug + '`**.');
} else {
const lines = installMethods.map(function (im, i) {
const r = im.resources || {};
const typeLabel = im.expand && im.expand.type ? (im.expand.type.type || im.expand.type.name || im.expand.type.value || '') : '';
return (i + 1) + '. **`' + (typeLabel || im.type || im.id) + '`** — CPU: `' + (im.resources_cpu || 0) + '` · RAM: `' + (im.resources_ram || 0) + ' MB` · HDD: `' + (im.resources_hdd || 0) + ' GB`';
}).join('\n');
await postComment(' **PocketBase Bot**: Install methods for **`' + slug + '`** (' + installMethods.length + ' total)\n\n' + lines);
}
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+(.+)$/);
@@ -275,7 +443,7 @@ jobs:
);
process.exit(0);
}
const targetType = methodParts[1].toLowerCase();
const targetType = methodParts[1].toLowerCase();
const resourcesStr = methodParts[2];
// Parse resource fields (only cpu/ram/hdd allowed)
@@ -293,50 +461,34 @@ jobs:
process.exit(0);
}
// Find matching install method by type name/value
const matchedMethod = installMethods.find(function (im) {
const typeLabel = im.expand && im.expand.type ?
(im.expand.type.type || im.expand.type.name || im.expand.type.value || '') : '';
return typeLabel.toLowerCase() === targetType || (im.type && im.type.toLowerCase && im.type.toLowerCase() === targetType);
// Find matching method by type name (case-insensitive)
const idx = methodsArr.findIndex(function (im) {
return (im.type || '').toLowerCase() === targetType;
});
if (!matchedMethod) {
if (idx === -1) {
await addReaction('-1');
const availableTypes = installMethods.map(function (im) {
return im.expand && im.expand.type ? (im.expand.type.type || im.expand.type.name || im.expand.type.value || im.type || im.id) : (im.type || im.id);
});
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.join('`, `') + '`\n\n' +
'**Available types:** `' + (availableTypes.length ? availableTypes.join('`, `') : '(none)') + '`\n\n' +
'Use `/pocketbase ' + slug + ' method list` to see all methods.'
);
process.exit(0);
}
// Build patch payload for script_install_methods record
const imPatch = {};
if (resourceChanges.cpu != null) imPatch.resources_cpu = resourceChanges.cpu;
if (resourceChanges.ram != null) imPatch.resources_ram = resourceChanges.ram;
if (resourceChanges.hdd != null) imPatch.resources_hdd = resourceChanges.hdd;
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 imPatchRes = await request(apiBase + '/collections/script_install_methods/records/' + matchedMethod.id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify(imPatch)
});
if (!imPatchRes.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: PATCH failed for install method:\n```\n' + imPatchRes.body + '\n```');
process.exit(1);
}
const typeLabel = matchedMethod.expand && matchedMethod.expand.type ?
(matchedMethod.expand.type.type || matchedMethod.expand.type.name || matchedMethod.expand.type.value || targetType) : targetType;
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 **`' + typeLabel + '`** for **`' + slug + '`**\n\n' +
'✅ **PocketBase Bot**: Updated install method **`' + methodsArr[idx].type + '`** for **`' + slug + '`**\n\n' +
'**Changes applied:**\n' + changesLines + '\n\n' +
'*Executed by @' + actor + '*'
);
@@ -386,247 +538,6 @@ jobs:
'**Value set:**\n```\n' + preview + '\n```\n\n' +
'*Executed by @' + actor + '*'
);
const noteArgsStr = rest.substring(noteMatch[0].length).trim();
// Load note types from PocketBase
const noteTypeToId = {};
const noteTypeToName = {};
try {
const ntRes = await request(apiBase + '/collections/z_ref_note_types/records?perPage=500', { headers: { 'Authorization': token } });
if (ntRes.ok) {
JSON.parse(ntRes.body).items.forEach(function (item) {
if (item.type != null) {
noteTypeToId[item.type.toLowerCase()] = item.id;
noteTypeToName[item.id] = item.type;
}
});
}
} catch (e) { console.warn('z_ref_note_types:', e.message); }
const VALID_NOTE_TYPES = Object.keys(noteTypeToId);
// 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;
}
// Helper: fetch record with expanded notes relation
async function fetchExpandedNotes() {
const r = await request(recordsUrl + '/' + record.id + '?expand=notes', { headers: { 'Authorization': token } });
const rec = JSON.parse(r.body);
return { rec, notes: (rec.expand && rec.expand.notes) || [] };
}
// Helper: format notes list for display
function formatNotesList(notes) {
if (notes.length === 0) return '*None*';
return notes.map(function (n, i) {
return (i + 1) + '. **`' + (noteTypeToName[n.type] || n.type) + '`**: ' + n.text;
}).join('\n');
}
if (noteAction === 'list') {
// ── note list ────────────────────────────────────────────────
const { notes } = await fetchExpandedNotes();
await addReaction('+1');
await postComment(
' **PocketBase Bot**: Notes for **`' + slug + '`** (' + notes.length + ' total)\n\n' +
formatNotesList(notes)
);
} else if (noteAction === 'add') {
// ── note add <type> "<text>" ──────────────────────────────────
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>"`\n' +
'**Valid types:** `' + VALID_NOTE_TYPES.join('`, `') + '`'
);
process.exit(0);
}
const noteType = tokens[0].toLowerCase();
const noteText = tokens.slice(1).join(' ');
const typeId = noteTypeToId[noteType];
if (!typeId) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: Unknown note type `' + noteType + '`.\n' +
'**Valid types:** `' + VALID_NOTE_TYPES.join('`, `') + '`'
);
process.exit(0);
}
// POST new note to script_notes
const postNoteRes = await request(apiBase + '/collections/script_notes/records', {
method: 'POST',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ text: noteText, type: typeId, script: record.id })
});
if (!postNoteRes.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Failed to create note:\n```\n' + postNoteRes.body + '\n```');
process.exit(1);
}
const newNoteId = JSON.parse(postNoteRes.body).id;
// PATCH script to include new note in relation
const existingNoteIds = Array.isArray(record.notes) ? record.notes : [];
const patchLinkRes = await request(recordsUrl + '/' + record.id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ notes: [...existingNoteIds, newNoteId] })
});
if (!patchLinkRes.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Note created but failed to link to script:\n```\n' + patchLinkRes.body + '\n```');
process.exit(1);
}
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') {
// ── note edit <type> "<old text>" "<new text>" ────────────────
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' +
'**Valid types:** `' + VALID_NOTE_TYPES.join('`, `') + '`\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 typeId = noteTypeToId[noteType];
if (!typeId) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: Unknown note type `' + noteType + '`.\n' +
'**Valid types:** `' + VALID_NOTE_TYPES.join('`, `') + '`'
);
process.exit(0);
}
const { notes } = await fetchExpandedNotes();
const matchingNote = notes.find(function (n) { return n.type === typeId && n.text === oldText; });
if (!matchingNote) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: No `' + noteType + '` note found with that exact text.\n\n' +
'**Current notes for `' + slug + '`:**\n' + formatNotesList(notes)
);
process.exit(0);
}
// PATCH the note record directly
const patchNoteRes = await request(apiBase + '/collections/script_notes/records/' + matchingNote.id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ text: newText })
});
if (!patchNoteRes.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Failed to update note:\n```\n' + patchNoteRes.body + '\n```');
process.exit(1);
}
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') {
// ── note remove <type> "<text>" ───────────────────────────────
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' +
'**Valid types:** `' + VALID_NOTE_TYPES.join('`, `') + '`\n\n' +
'Use `/pocketbase ' + slug + ' note list` to see current notes.'
);
process.exit(0);
}
const noteType = tokens[0].toLowerCase();
const noteText = tokens[1];
const typeId = noteTypeToId[noteType];
if (!typeId) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: Unknown note type `' + noteType + '`.\n' +
'**Valid types:** `' + VALID_NOTE_TYPES.join('`, `') + '`'
);
process.exit(0);
}
const { rec: expandedRecord, notes } = await fetchExpandedNotes();
const matchingNote = notes.find(function (n) { return n.type === typeId && n.text === noteText; });
if (!matchingNote) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: No `' + noteType + '` note found with that exact text.\n\n' +
'**Current notes for `' + slug + '`:**\n' + formatNotesList(notes)
);
process.exit(0);
}
// PATCH script to remove note ID from relation, then DELETE the note record
const existingNoteIds = Array.isArray(expandedRecord.notes) ? expandedRecord.notes : [];
const patchRes = await request(recordsUrl + '/' + record.id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ notes: existingNoteIds.filter(function (id) { return id !== matchingNote.id; }) })
});
if (!patchRes.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Failed to unlink note from script:\n```\n' + patchRes.body + '\n```');
process.exit(1);
}
const delRes = await request(apiBase + '/collections/script_notes/records/' + matchingNote.id, {
method: 'DELETE',
headers: { 'Authorization': token }
});
if (!delRes.ok && delRes.statusCode !== 204) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Note unlinked but could not be deleted:\n```\n' + delRes.body + '\n```');
process.exit(1);
}
await addReaction('+1');
await postComment(
'✅ **PocketBase Bot**: Removed note from **`' + slug + '`**\n\n' +
'- **Type:** `' + noteType + '`\n' +
'- **Text:** ' + noteText + '\n\n' +
'*Executed by @' + actor + '*'
);
}
} else {
// ── FIELD=VALUE PATH ─────────────────────────────────────────────
@@ -655,6 +566,7 @@ jobs:
disable_message: 'string',
is_deleted: 'boolean',
deleted_message: 'string',
version: 'string',
};
// Field=value parser (handles quoted values and empty=null)

View File

@@ -1,46 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Slaviša Arežina (tremor021)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://goteleport.com/
APP="Teleport"
var_tags="${var_tags:-zero-trust}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -f /etc/teleport.yaml ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating Teleport"
$STD apt update
$STD apt upgrade -y
msg_ok "Updated successfully!"
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}https://${IP}:3080${CL}"

View File

@@ -1,34 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Slaviša Arežina (tremor021)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://goteleport.com/
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
setup_deb822_repo \
"teleport" \
"https://deb.releases.teleport.dev/teleport-pubkey.asc" \
"https://apt.releases.teleport.dev/debian" \
"trixie" \
"stable/v18"
msg_info "Configuring Teleport"
$STD apt install -y teleport
$STD teleport configure -o /etc/teleport.yaml
systemctl enable -q --now teleport
sleep 5
tctl users add teleport-admin --roles=editor,access --logins=root >~/teleportadmin.creds
sed -i "s|https://[^:]*:3080|https://${LOCAL_IP}:3080|g" ~/teleportadmin.creds
msg_ok "Configured Teleport"
motd_ssh
customize
cleanup_lxc

View File

@@ -1,41 +0,0 @@
{
"name": "Teleport",
"slug": "teleport",
"categories": [
6
],
"date_created": "2026-03-18",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 3080,
"documentation": "https://goteleport.com/docs/",
"config_path": "/etc/teleport.yaml",
"website": "https://goteleport.com/",
"github": "https://github.com/gravitational/teleport",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/teleport.webp",
"description": "Teleport unifies identities — humans, machines, and AI — with strong identity implementation to speed up engineering, improve resiliency against identity-based attacks, and control AI in production infrastructure.",
"install_methods": [
{
"type": "default",
"script": "ct/teleport.sh",
"resources": {
"cpu": 1,
"ram": 1024,
"hdd": 4,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "After installation finishes, do `cat ~/teleportadmin.creds` inside LXC to get admin creation URL.",
"type": "info"
}
]
}