mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-11-13 17:56:21 +01:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd5bd55b8b | ||
|
|
9b6c24f8ff | ||
|
|
cbb347cc61 | ||
|
|
0ad290c81f | ||
|
|
0b25f9c6a5 | ||
|
|
1ac6100001 | ||
|
|
46f54a9071 | ||
|
|
d67f53176e |
No files matched your search
Generated
+99
-43
@@ -4,16 +4,20 @@ name: PR script test
|
||||
# reports the outcome on the PR, as a check run and as one comment kept up to
|
||||
# date.
|
||||
#
|
||||
# A self-hosted runner that executes pull request code as root is only
|
||||
# acceptable if a human decides what runs on it. So it starts on a push only
|
||||
# for trusted PRs: those the push-app-to-main app opens on a branch of this
|
||||
# repository when a script moves over from DevScripts (already reviewed and
|
||||
# tested there; only writers can push to such a branch). Those are built
|
||||
# automatically and approved when every script passes. Everything else waits
|
||||
# for someone with write access to comment `/test` (optionally followed by a
|
||||
# full SHA to pin, and the ct/ paths to build). Only a commit that existed when
|
||||
# the comment was written gets built: a push after it is never covered, not
|
||||
# even one that lands in the seconds before this job runs.
|
||||
# Every push to a non-draft PR is built automatically, forks included: the
|
||||
# maintainers decided (2026-11-12) that the lab hosts run contributors' code
|
||||
# unattended. They are isolated, hold no secret, and are treated as
|
||||
# compromised anyway; the sweeper and fresh guests per build keep them usable.
|
||||
# Someone with write access can also comment `/test` (optionally followed by
|
||||
# a full SHA to pin, the scripts to build, and the platforms to build on) to
|
||||
# build again or pick what to build. Only a commit that existed when the
|
||||
# comment was written gets built: a push after it is never covered, not even
|
||||
# one that lands in the seconds before this job runs.
|
||||
#
|
||||
# Approval stays narrow: only PRs the push-app-to-main app opens on a branch
|
||||
# of this repository (a script moving over from DevScripts, reviewed and
|
||||
# tested there) are approved when every script passes. Every other PR gets
|
||||
# the report, the check, and "changes requested" on a real failure.
|
||||
#
|
||||
# gate ubuntu-latest who asked, which scripts, which commit
|
||||
# test proxmox-test / build + checks, one matrix job per script, platform
|
||||
@@ -30,13 +34,14 @@ name: PR script test
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
# Trusted PRs only, see above. pull_request_target runs this file from main,
|
||||
# never the PR's copy of it.
|
||||
# Every PR, see above. pull_request_target runs this file from main, never
|
||||
# the PR's copy of it, and does not wait for "approve and run" on forks.
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
- "ct/**"
|
||||
- "install/**"
|
||||
- "vm/**"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr:
|
||||
@@ -48,20 +53,30 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
scripts:
|
||||
description: "Space-separated ct/ or vm/ paths (empty = everything the PR touches)"
|
||||
description: "Space-separated app names, ct/ or vm/ paths, and platforms (pve, incus, arm); empty = everything the PR touches, everywhere"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions: {}
|
||||
|
||||
# One queue per PR. A newer push replaces a run that is still waiting (its
|
||||
# gate would skip anyway: the head moved), but never kills one that is
|
||||
# building: a cancelled job leaves its guest behind until the sweeper. Any
|
||||
# comment starts this workflow; one that is not a /test gets a group of its
|
||||
# own, so it cannot push a waiting test out of the queue.
|
||||
concurrency:
|
||||
group: >-
|
||||
pr-script-test-${{ github.event_name == 'issue_comment' && !startsWith(github.event.comment.body, '/test')
|
||||
&& format('noop-{0}', github.run_id)
|
||||
|| github.event.pull_request.number || github.event.issue.number || inputs.pr }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
gate:
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '/test')) ||
|
||||
(github.event_name == 'pull_request_target' && !github.event.pull_request.draft &&
|
||||
github.event.pull_request.user.login == 'push-app-to-main[bot]' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository)
|
||||
(github.event_name == 'pull_request_target' && !github.event.pull_request.draft)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read # when the run was dispatched, for a /test without SHA
|
||||
@@ -79,6 +94,7 @@ jobs:
|
||||
not_built: ${{ steps.plan.outputs.not_built }}
|
||||
trusted: ${{ steps.plan.outputs.trusted }}
|
||||
kind: ${{ steps.plan.outputs.kind }}
|
||||
platforms: ${{ steps.plan.outputs.platforms }}
|
||||
steps:
|
||||
- id: plan
|
||||
uses: actions/github-script@v8
|
||||
@@ -97,7 +113,6 @@ jobs:
|
||||
let prNumber, requested = '';
|
||||
if (context.eventName === 'pull_request_target') {
|
||||
const p = context.payload.pull_request;
|
||||
if (!isTrusted(p)) return;
|
||||
prNumber = p.number;
|
||||
// Exactly the pushed commit; a later push starts its own run.
|
||||
requested = p.head.sha;
|
||||
@@ -164,19 +179,33 @@ jobs:
|
||||
// and read after the PR, so a push in between shows up.
|
||||
// Branches of this repository belong to people with
|
||||
// write access, who could build anything anyway.
|
||||
const tokens = requested.split(/\s+/).filter(Boolean);
|
||||
const words = requested.split(/\s+/).filter(Boolean);
|
||||
// Platform words pick where to build: `/test arm`, `/test pve incus
|
||||
// bark-server`. None means every platform. A run on only some of
|
||||
// them is reported under its own check name and never approves.
|
||||
const PLATFORM = { pve: 'pve', proxmox: 'pve', incus: 'incus', arm: 'arm', arm64: 'arm', aarch64: 'arm' };
|
||||
const platformOf = t => PLATFORM[t.toLowerCase()];
|
||||
const only = new Set(words.map(platformOf).filter(Boolean));
|
||||
const onlyList = ['pve', 'incus', 'arm'].filter(p => only.has(p));
|
||||
const tokens = words.filter(t => !platformOf(t));
|
||||
const isSha = t => /^[0-9a-f]{7,40}$/.test(t);
|
||||
// App names (bark-server) become ct/ paths; such a name without a
|
||||
// ct/ script falls back to vm/ below.
|
||||
const bare = new Set();
|
||||
const toPath = t => {
|
||||
if (/^(ct|vm)\/[A-Za-z0-9._-]+\.sh$/.test(t) || !/^[A-Za-z0-9._-]+$/.test(t)) return t;
|
||||
const p = `ct/${t.replace(/\.sh$/, '')}.sh`;
|
||||
bare.add(p);
|
||||
return p;
|
||||
};
|
||||
const approved = isIssue ? [] : tokens.filter(isSha);
|
||||
let entries = isIssue ? [] : tokens.filter(t => !isSha(t));
|
||||
let entries = (isIssue ? tokens : tokens.filter(t => !isSha(t))).map(toPath);
|
||||
const refuse = async (why, retry) => {
|
||||
await sticky([MARK, '### Proxmox script test', '', `⚠️ ${why} Nothing was built.`, '', retry].join('\n'));
|
||||
core.notice(why);
|
||||
};
|
||||
if (isIssue) {
|
||||
// App names (bark-server) or ct/ and vm/ paths; the default branch
|
||||
// is trusted. A name without a ct/ script falls back to vm/ below.
|
||||
entries = tokens.map(t => /^(ct|vm)\/[A-Za-z0-9._-]+\.sh$/.test(t) ? t
|
||||
: /^[A-Za-z0-9._-]+$/.test(t) ? `ct/${t.replace(/\.sh$/, '')}.sh` : t);
|
||||
// The default branch is trusted; only which app is needed.
|
||||
if (entries.length === 0) {
|
||||
await refuse('In an issue, `/test` needs to know which app.', 'Comment `/test <app>`, for example `/test bark-server` (the name of its `ct/` script).');
|
||||
return;
|
||||
@@ -207,8 +236,8 @@ jobs:
|
||||
}
|
||||
if (entries.some(e => !/^(ct|vm)\/[A-Za-z0-9._-]+\.sh$/.test(e))) {
|
||||
await refuse(
|
||||
isIssue ? 'Only app names (as in `ct/<app>.sh` or `vm/<name>.sh`) can be tested.' : 'Only `ct/<name>.sh` and `vm/<name>.sh` paths can be requested.',
|
||||
isIssue ? 'For example: `/test bark-server` or `/test debian-vm`' : 'For example: `/test ct/bark-server.sh`, or just `/test`.');
|
||||
isIssue ? 'Only app names (as in `ct/<app>.sh` or `vm/<name>.sh`) and platforms (`pve`, `incus`, `arm`) can be requested.' : 'Only app names, `ct/<name>.sh` and `vm/<name>.sh` paths, platforms (`pve`, `incus`, `arm`) and the commit can be requested.',
|
||||
isIssue ? 'For example: `/test bark-server`, `/test arm bark-server` or `/test debian-vm`' : 'For example: `/test`, `/test arm`, or `/test pve incus bark-server`.');
|
||||
return;
|
||||
}
|
||||
// Everything the PR changes that this test cannot build is named in
|
||||
@@ -238,13 +267,15 @@ jobs:
|
||||
for (let script of entries.slice(0, MAX)) {
|
||||
let text = await fetchScript(script);
|
||||
// /test debian-vm in an issue: there is no ct/debian-vm.sh, but a vm/ one.
|
||||
if (text === null && isIssue && script.startsWith('ct/')) {
|
||||
const wasBare = isIssue || bare.has(script);
|
||||
if (text === null && wasBare && script.startsWith('ct/')) {
|
||||
const vm = `vm/${script.slice(3)}`;
|
||||
if ((text = await fetchScript(vm)) !== null) script = vm;
|
||||
}
|
||||
if (text === null) {
|
||||
notBuilt.push(isIssue
|
||||
? (script.startsWith('ct/') ? `${code(script.slice(3, -3))}: neither \`ct/\` nor \`vm/\` has it on the default branch` : `${code(script)} does not exist on the default branch`)
|
||||
const where = isIssue ? 'on the default branch' : 'in this PR';
|
||||
notBuilt.push(wasBare
|
||||
? (script.startsWith('ct/') ? `${code(script.slice(3, -3))}: neither \`ct/\` nor \`vm/\` has it ${where}` : `${code(script)} does not exist ${where}`)
|
||||
: `\`${script}\` does not exist in this PR (an \`install/\` script is built through the \`ct/\` script of the same name)`);
|
||||
continue;
|
||||
}
|
||||
@@ -264,8 +295,12 @@ jobs:
|
||||
// says var_arm64=no is not built there. Unset is built with
|
||||
// var_arm64=yes, to see whether it would work.
|
||||
const arm = (text.match(/^[ \t]*var_arm64="\$\{var_arm64:-(yes|no)\}"/m) || [])[1] || 'unset';
|
||||
const platforms = isVm ? ['pve'] : arm === 'no' ? ['pve', 'incus'] : ['pve', 'incus', 'arm'];
|
||||
if (!isVm && arm === 'no') notBuilt.push(`${code(script)} on ARM64: the script sets \`var_arm64=no\``);
|
||||
let platforms = isVm ? ['pve'] : arm === 'no' ? ['pve', 'incus'] : ['pve', 'incus', 'arm'];
|
||||
if (!isVm && arm === 'no' && (!only.size || only.has('arm'))) notBuilt.push(`${code(script)} on ARM64: the script sets \`var_arm64=no\``);
|
||||
if (only.size) {
|
||||
if (isVm && !only.has('pve')) notBuilt.push(`${code(script)}: VM scripts are built on \`pve\` only`);
|
||||
platforms = platforms.filter(p => only.has(p));
|
||||
}
|
||||
for (const platform of platforms) {
|
||||
for (const os of oses) include.push({ script, os, platform, arm64: platform === 'arm' ? arm : '', name: `${os ? `${name}-${os}` : name}-${platform}` });
|
||||
}
|
||||
@@ -274,8 +309,8 @@ jobs:
|
||||
if (include.length === 0) {
|
||||
await sticky([
|
||||
MARK, '### Proxmox script test', '',
|
||||
isIssue
|
||||
? 'ℹ️ Nothing to build:'
|
||||
isIssue || onlyList.length
|
||||
? `ℹ️ Nothing to build${onlyList.length ? ` on ${onlyList.join(', ')}` : ''}:`
|
||||
: 'ℹ️ Nothing to build: this test builds `ct/` and `vm/` scripts (and changed `install/` scripts through their `ct/` script).', '',
|
||||
...(notBuilt.length ? notBuilt.map(n => `- ${n}`) : ['- this PR changes no `ct/`, `vm/` or `install/` script']),
|
||||
].join('\n'));
|
||||
@@ -288,7 +323,9 @@ jobs:
|
||||
let checkId = '';
|
||||
if (!isIssue) {
|
||||
const { data: check } = await github.rest.checks.create({
|
||||
owner, repo, name: 'Proxmox script test', head_sha: headSha, status: 'in_progress',
|
||||
// A partial run gets its own name: it must not stand in for the
|
||||
// full test, e.g. as a required check.
|
||||
owner, repo, name: `Proxmox script test${onlyList.length ? ` (${onlyList.join(', ')} only)` : ''}`, head_sha: headSha, status: 'in_progress',
|
||||
details_url: runUrl,
|
||||
output: { title: `Building ${include.length}`, summary: include.map(i => `- \`${i.script}\` ${[i.platform, i.os].filter(Boolean).join(' · ')}`).join('\n') },
|
||||
});
|
||||
@@ -301,12 +338,13 @@ jobs:
|
||||
core.setOutput('sha', headSha);
|
||||
core.setOutput('check_id', checkId);
|
||||
core.setOutput('not_built', JSON.stringify(notBuilt));
|
||||
core.setOutput('platforms', onlyList.join(' '));
|
||||
// Who asked: the /test commenter, or whoever dispatched the run.
|
||||
core.setOutput('actor', context.actor);
|
||||
|
||||
await sticky([
|
||||
MARK, '### Proxmox script test', '',
|
||||
`⏳ Building ${include.length} at ${headSha.slice(0, 7)}${isIssue ? ' (default branch)' : ''} — [watch it live](${runUrl}) (open the \`test\` job)`, '',
|
||||
`⏳ Building ${include.length} at ${headSha.slice(0, 7)}${isIssue ? ' (default branch)' : ''}${onlyList.length ? `, only on ${onlyList.join(', ')}` : ''} — [watch it live](${runUrl}) (open the \`test\` job)`, '',
|
||||
...include.map(i => `- \`${i.script}\` (${[i.platform, i.os].filter(Boolean).join(' · ')})`),
|
||||
...(notBuilt.length ? ['', '**Not built:**', ...notBuilt.map(n => `- ${n}`)] : []),
|
||||
].join('\n'));
|
||||
@@ -405,6 +443,7 @@ jobs:
|
||||
NOT_BUILT: ${{ needs.gate.outputs.not_built }}
|
||||
TRUSTED: ${{ needs.gate.outputs.trusted }}
|
||||
KIND: ${{ needs.gate.outputs.kind }}
|
||||
PLATFORMS: ${{ needs.gate.outputs.platforms }}
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
@@ -534,13 +573,22 @@ jobs:
|
||||
const broken = count('fail');
|
||||
const setupTrouble = count('error') + count('cancelled');
|
||||
const allPass = passed === coreRows.length;
|
||||
const cancelled = coreRows.every(r => r.status === 'cancelled');
|
||||
// `/test arm`: nothing that counts was built, so the result is
|
||||
// information only as a whole.
|
||||
const armOnly = coreRows.length === 0;
|
||||
// Built on only some platforms (`/test pve`): a pass there says
|
||||
// nothing about the others, so it neither approves nor lifts an
|
||||
// earlier "changes requested". A failure is still a failure.
|
||||
const partial = !!process.env.PLATFORMS;
|
||||
const cancelled = (armOnly ? rows : coreRows).every(r => r.status === 'cancelled');
|
||||
const of = `**${passed}/${coreRows.length} passed**`;
|
||||
const armPassed = armRows.filter(r => r.status === 'pass').length;
|
||||
const armLine = armRows.length
|
||||
const armLine = armRows.length && !armOnly
|
||||
? `ARM64 (information only, not counted): ${armPassed}/${armRows.length} passed${armPassed < armRows.length ? `; failures there do not block this PR` : ''}.`
|
||||
: '';
|
||||
const headline = broken
|
||||
const headline = armOnly
|
||||
? `ℹ️ ARM64 only, information only: **${armPassed}/${armRows.length} passed**`
|
||||
: broken
|
||||
? `❌ ${of}`
|
||||
: setupTrouble
|
||||
? `⚠️ ${of}, ${setupTrouble} could not be tested because of a problem with the test setup (not the PR)`
|
||||
@@ -613,10 +661,11 @@ jobs:
|
||||
`${headline} at \`${process.env.SHA.slice(0, 7)}\``, '',
|
||||
table, '',
|
||||
...(armLine ? [`<sub>${armLine}</sub>`, ''] : []),
|
||||
...(partial ? [`<sub>Built only on ${process.env.PLATFORMS.split(' ').join(', ')}; comment \`/test\` for every platform.</sub>`, ''] : []),
|
||||
...shots, '',
|
||||
...(() => { try { const n = JSON.parse(process.env.NOT_BUILT || '[]'); return n.length ? ['**Not built:**', ...n.map(x => `- ${x}`), ''] : []; } catch (e) { return []; } })(),
|
||||
...logs, '',
|
||||
`Full logs: [workflow run](${runUrl}) (artifacts \`result-*\`). Comment \`/test${process.env.KIND === 'issue' ? ' ' + [...new Set(rows.map(r => r.script.slice(3, -3)))].join(' ') : ''}\` to build again.`,
|
||||
`Full logs: [workflow run](${runUrl}) (artifacts \`result-*\`). Comment \`/test${partial ? ` ${process.env.PLATFORMS}` : ''}${process.env.KIND === 'issue' ? ' ' + [...new Set(rows.map(r => r.script.slice(3, -3)))].join(' ') : ''}\` to build again.`,
|
||||
].join('\n');
|
||||
|
||||
// Issues have no check run (there is no PR commit to attach it to).
|
||||
@@ -625,8 +674,13 @@ jobs:
|
||||
owner, repo, check_run_id: Number(process.env.CHECK_ID), status: 'completed',
|
||||
// Untestable here is "test it by hand", not a pass: action_required,
|
||||
// because a required check would count neutral as passed.
|
||||
conclusion: cancelled ? 'cancelled' : allPass ? 'success' : broken ? 'failure' : 'action_required',
|
||||
output: { title: `${passed}/${coreRows.length} passed${armRows.length ? ` (+ ARM ${armPassed}/${armRows.length}, not counted)` : ''}`, summary: summary.slice(0, 65000) },
|
||||
conclusion: cancelled ? 'cancelled' : armOnly ? 'neutral' : allPass ? 'success' : broken ? 'failure' : 'action_required',
|
||||
output: {
|
||||
title: armOnly
|
||||
? `ARM64 ${armPassed}/${armRows.length} passed (information only)`
|
||||
: `${passed}/${coreRows.length} passed${armRows.length ? ` (+ ARM ${armPassed}/${armRows.length}, not counted)` : ''}`,
|
||||
summary: summary.slice(0, 65000),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -670,7 +724,9 @@ jobs:
|
||||
// what a migration contains: ct/ and install/ scripts. A PR that
|
||||
// also changes anything else (workflows, misc/, ...) is tested but
|
||||
// never approved.
|
||||
let approve = process.env.TRUSTED === 'true' && allPass;
|
||||
let approve = process.env.TRUSTED === 'true' && allPass && !partial;
|
||||
// A partial run that passed leaves every review as it is.
|
||||
const leaveReviews = partial && !failedRows.length;
|
||||
let noApproveWhy = '';
|
||||
if (approve) {
|
||||
const changed = await github.paginate(github.rest.pulls.listFiles, { owner, repo, pull_number: pr });
|
||||
@@ -686,13 +742,13 @@ jobs:
|
||||
try {
|
||||
const reviews = await github.paginate(github.rest.pulls.listReviews, { owner, repo, pull_number: pr });
|
||||
const ours = state => reviews.filter(v => v.user && v.user.type === 'Bot' && v.state === state && (v.body || '').includes(REVIEW_MARK));
|
||||
for (const v of ours('CHANGES_REQUESTED')) {
|
||||
for (const v of leaveReviews ? [] : ours('CHANGES_REQUESTED')) {
|
||||
await github.rest.pulls.dismissReview({
|
||||
owner, repo, pull_number: pr, review_id: v.id,
|
||||
message: failedRows.length ? 'Replaced by a newer Proxmox script test.' : `Proxmox script test passed at ${sha7}.`,
|
||||
});
|
||||
}
|
||||
if (!approve && !cancelled) {
|
||||
if (!approve && !cancelled && !leaveReviews) {
|
||||
for (const v of ours('APPROVED')) {
|
||||
await github.rest.pulls.dismissReview({
|
||||
owner, repo, pull_number: pr, review_id: v.id,
|
||||
|
||||
@@ -581,6 +581,18 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
</details>
|
||||
|
||||
## 2026-11-12
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- Pixelfed ([#18523](https://github.com/community-scripts/ProxmoxVE/pull/18523))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- Whisparr-eros: set the first login and save it [@MickLesk](https://github.com/MickLesk) ([#18617](https://github.com/community-scripts/ProxmoxVE/pull/18617))
|
||||
|
||||
## 2026-11-11
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
@@ -11,8 +11,8 @@ var_tags="${var_tags:-web}"
|
||||
var_cpu="${var_cpu:-1}"
|
||||
var_ram="${var_ram:-1024}"
|
||||
var_disk="${var_disk:-8}"
|
||||
var_os="${var_os:-ubuntu}"
|
||||
var_version="${var_version:-24.04}"
|
||||
var_os="${var_os:-Debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_arm64="${var_arm64:-yes}"
|
||||
var_unprivileged="${var_unprivileged:-0}"
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env bash
|
||||
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
|
||||
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://pixelfed.org/
|
||||
|
||||
APP="Pixelfed"
|
||||
var_tags="${var_tags:-fediverse;social}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-2048}"
|
||||
var_disk="${var_disk:-10}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
# var_arm64="${var_arm64:-no}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
|
||||
if [[ ! -d /opt/pixelfed ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "pixelfed" "pixelfed/pixelfed"; then
|
||||
msg_info "Stopping Services"
|
||||
systemctl stop pixelfed-horizon pixelfed-scheduler.timer
|
||||
msg_ok "Services stopped"
|
||||
|
||||
create_backup /opt/pixelfed/.env \
|
||||
/opt/pixelfed/storage
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "pixelfed" "pixelfed/pixelfed" "tarball" "latest" "/opt/pixelfed"
|
||||
|
||||
restore_backup
|
||||
|
||||
msg_info "Updating Pixelfed"
|
||||
cd /opt/pixelfed
|
||||
chmod -R 755 /opt/pixelfed
|
||||
chmod -R 775 /opt/pixelfed/storage /opt/pixelfed/bootstrap/cache
|
||||
export COMPOSER_ALLOW_SUPERUSER=1
|
||||
$STD composer install --no-dev --no-ansi --no-interaction --optimize-autoloader
|
||||
chown -R pixelfed:pixelfed /opt/pixelfed
|
||||
$STD sudo -u pixelfed php artisan storage:link
|
||||
$STD sudo -u pixelfed php artisan migrate --force
|
||||
$STD sudo -u pixelfed php artisan route:cache
|
||||
$STD sudo -u pixelfed php artisan view:cache
|
||||
$STD sudo -u pixelfed php artisan config:cache
|
||||
$STD sudo -u pixelfed php artisan horizon:publish
|
||||
msg_ok "Updated Pixelfed"
|
||||
|
||||
msg_info "Starting Services"
|
||||
systemctl start pixelfed-horizon pixelfed-scheduler.timer
|
||||
msg_ok "Services started"
|
||||
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 "${GATEWAY}${BGN}http://${IP}${CL}"
|
||||
echo -e "${INFO}${YW} Create an admin account with:${CL}"
|
||||
echo -e "${TAB}cd /opt/pixelfed && sudo -u pixelfed php artisan user:create"
|
||||
@@ -56,3 +56,4 @@ 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}:6969${CL}"
|
||||
echo -e "${INFO}${YW} Login: whisparr, password in ~/whisparr-eros.creds inside the container${CL}"
|
||||
@@ -0,0 +1,232 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://pixelfed.org/
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
nginx \
|
||||
redis-server \
|
||||
ffmpeg \
|
||||
jpegoptim \
|
||||
optipng \
|
||||
pngquant \
|
||||
gifsicle \
|
||||
libvips42
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
msg_info "Creating Pixelfed User"
|
||||
useradd -rU -s /bin/bash pixelfed
|
||||
usermod -aG redis pixelfed
|
||||
msg_ok "Created Pixelfed User"
|
||||
|
||||
PG_VERSION="17" setup_postgresql
|
||||
PG_DB_NAME="pixelfed" PG_DB_USER="pixelfed" setup_postgresql_db
|
||||
PHP_VERSION="8.4" PHP_FPM="YES" PHP_MODULE="bcmath,ctype,curl,exif,gd,imagick,intl,mbstring,pgsql,redis,xml,zip" PHP_UPLOAD_MAX_FILESIZE="500M" PHP_POST_MAX_SIZE="500M" PHP_MAX_EXECUTION_TIME="600" setup_php
|
||||
setup_composer
|
||||
|
||||
msg_info "Configuring Redis"
|
||||
REDIS_PASS=$(random_password)
|
||||
sed -i "s/^# requirepass foobared/requirepass $REDIS_PASS/" /etc/redis/redis.conf
|
||||
sed -i "s/^requirepass .*/requirepass $REDIS_PASS/" /etc/redis/redis.conf
|
||||
systemctl restart redis-server
|
||||
msg_ok "Configured Redis"
|
||||
|
||||
msg_info "Configuring PHP-FPM Pool"
|
||||
cp /etc/php/8.4/fpm/pool.d/www.conf /etc/php/8.4/fpm/pool.d/pixelfed.conf
|
||||
sed -i 's/\[www\]/[pixelfed]/' /etc/php/8.4/fpm/pool.d/pixelfed.conf
|
||||
sed -i 's/^user = www-data/user = pixelfed/' /etc/php/8.4/fpm/pool.d/pixelfed.conf
|
||||
sed -i 's/^group = www-data/group = pixelfed/' /etc/php/8.4/fpm/pool.d/pixelfed.conf
|
||||
sed -i 's|^listen = .*|listen = /run/php/php8.4-fpm-pixelfed.sock|' /etc/php/8.4/fpm/pool.d/pixelfed.conf
|
||||
sed -i 's/^listen.owner = .*/listen.owner = www-data/' /etc/php/8.4/fpm/pool.d/pixelfed.conf
|
||||
sed -i 's/^listen.group = .*/listen.group = www-data/' /etc/php/8.4/fpm/pool.d/pixelfed.conf
|
||||
sed -i -E 's/^\s*;?\s*ffi\.enable\s*=.*/ffi.enable=true/' /etc/php/8.4/fpm/php.ini
|
||||
systemctl restart php8.4-fpm
|
||||
msg_ok "Configured PHP-FPM Pool"
|
||||
|
||||
fetch_and_deploy_gh_release "pixelfed" "pixelfed/pixelfed" "tarball" "latest" "/opt/pixelfed"
|
||||
|
||||
msg_info "Installing Pixelfed (Patience)"
|
||||
cd /opt/pixelfed
|
||||
cat <<EOF >/opt/pixelfed/.env
|
||||
APP_NAME="Pixelfed"
|
||||
APP_ENV="production"
|
||||
APP_KEY=
|
||||
APP_DEBUG="false"
|
||||
APP_URL=http://${LOCAL_IP}
|
||||
APP_DOMAIN=${LOCAL_IP}
|
||||
ADMIN_DOMAIN=${LOCAL_IP}
|
||||
SESSION_DOMAIN=null
|
||||
TRUST_PROXIES="*"
|
||||
FORCE_HTTPS_URLS="false"
|
||||
|
||||
OPEN_REGISTRATION="false"
|
||||
ENFORCE_EMAIL_VERIFICATION="false"
|
||||
PF_MAX_USERS="1000"
|
||||
OAUTH_ENABLED="true"
|
||||
ENABLE_CONFIG_CACHE="true"
|
||||
INSTANCE_DISCOVER_PUBLIC="true"
|
||||
|
||||
PF_OPTIMIZE_IMAGES="true"
|
||||
IMAGE_QUALITY="80"
|
||||
MAX_PHOTO_SIZE="15000"
|
||||
MAX_CAPTION_LENGTH="500"
|
||||
MAX_ALBUM_LENGTH="4"
|
||||
|
||||
DB_CONNECTION="pgsql"
|
||||
DB_HOST="127.0.0.1"
|
||||
DB_PORT="5432"
|
||||
DB_DATABASE="${PG_DB_NAME}"
|
||||
DB_USERNAME="${PG_DB_USER}"
|
||||
DB_PASSWORD="${PG_DB_PASS}"
|
||||
|
||||
REDIS_CLIENT="predis"
|
||||
REDIS_SCHEME="tcp"
|
||||
REDIS_HOST="127.0.0.1"
|
||||
REDIS_PASSWORD="${REDIS_PASS}"
|
||||
REDIS_PORT="6379"
|
||||
|
||||
SESSION_DRIVER="database"
|
||||
CACHE_STORE="redis"
|
||||
QUEUE_DRIVER="redis"
|
||||
BROADCAST_DRIVER="log"
|
||||
LOG_CHANNEL="stack"
|
||||
HORIZON_PREFIX="horizon-"
|
||||
|
||||
ACTIVITY_PUB="true"
|
||||
AP_REMOTE_FOLLOW="true"
|
||||
AP_INBOX="true"
|
||||
AP_OUTBOX="true"
|
||||
AP_SHAREDINBOX="true"
|
||||
|
||||
EXP_EMC="true"
|
||||
|
||||
MAIL_DRIVER="log"
|
||||
MAIL_HOST="smtp.mailtrap.io"
|
||||
MAIL_PORT="2525"
|
||||
MAIL_USERNAME="null"
|
||||
MAIL_PASSWORD="null"
|
||||
MAIL_ENCRYPTION="null"
|
||||
MAIL_FROM_ADDRESS="pixelfed@example.com"
|
||||
MAIL_FROM_NAME="Pixelfed"
|
||||
|
||||
PF_ENABLE_CLOUD="false"
|
||||
FILESYSTEM_CLOUD="s3"
|
||||
SESSION_SECURE_COOKIE="false"
|
||||
HTTPS="false"
|
||||
EOF
|
||||
chmod -R 755 /opt/pixelfed
|
||||
chmod -R 775 /opt/pixelfed/storage /opt/pixelfed/bootstrap/cache
|
||||
export COMPOSER_ALLOW_SUPERUSER=1
|
||||
$STD composer install --no-dev --no-ansi --no-interaction --optimize-autoloader
|
||||
$STD php artisan key:generate --force
|
||||
$STD php artisan storage:link
|
||||
$STD php artisan migrate --force
|
||||
$STD php artisan import:cities
|
||||
$STD php artisan passport:keys
|
||||
$STD php artisan route:cache
|
||||
$STD php artisan view:cache
|
||||
$STD php artisan config:cache
|
||||
$STD php artisan instance:actor
|
||||
$STD php artisan horizon:install
|
||||
chown -R pixelfed:pixelfed /opt/pixelfed
|
||||
msg_ok "Installed Pixelfed"
|
||||
|
||||
msg_info "Configuring Nginx"
|
||||
cat <<'EOF' >/etc/nginx/sites-available/pixelfed
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /opt/pixelfed/public;
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
client_max_body_size 100M;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
|
||||
error_page 404 /index.php;
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/run/php/php8.4-fpm-pixelfed.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
}
|
||||
|
||||
location ~ /\.(?!well-known).* {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
nginx_enable_site pixelfed
|
||||
msg_ok "Configured Nginx"
|
||||
|
||||
msg_info "Creating Services"
|
||||
cat <<'EOF' >/etc/systemd/system/pixelfed-horizon.service
|
||||
[Unit]
|
||||
Description=Pixelfed Horizon Queue Worker
|
||||
After=network.target redis-server.service postgresql.service
|
||||
Requires=redis-server.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=pixelfed
|
||||
WorkingDirectory=/opt/pixelfed
|
||||
ExecStart=/usr/bin/php /opt/pixelfed/artisan horizon
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
cat <<'EOF' >/etc/systemd/system/pixelfed-scheduler.service
|
||||
[Unit]
|
||||
Description=Pixelfed Task Scheduler
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=pixelfed
|
||||
WorkingDirectory=/opt/pixelfed
|
||||
ExecStart=/usr/bin/php /opt/pixelfed/artisan schedule:run
|
||||
EOF
|
||||
|
||||
cat <<'EOF' >/etc/systemd/system/pixelfed-scheduler.timer
|
||||
[Unit]
|
||||
Description=Run Pixelfed Scheduler every minute
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* *:*:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOF
|
||||
systemctl enable -q --now pixelfed-horizon pixelfed-scheduler.timer
|
||||
msg_ok "Created Services"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -27,6 +27,7 @@ chmod 775 /var/lib/whisparr-eros/ /opt/Whisparr-Eros/
|
||||
msg_ok "Configured Whisparr-Eros"
|
||||
|
||||
msg_info "Creating Service"
|
||||
WHISPARR_PASS=$(random_password 20)
|
||||
cat <<EOF >/etc/systemd/system/whisparr-eros.service
|
||||
[Unit]
|
||||
Description=Whisparr-Eros Daemon
|
||||
@@ -35,6 +36,7 @@ After=syslog.target network.target
|
||||
[Service]
|
||||
UMask=0002
|
||||
Type=simple
|
||||
Environment=WHISPARR__AUTH__INITIALPASSWORD=${WHISPARR_PASS}
|
||||
ExecStart=/opt/Whisparr-Eros/Whisparr -nobrowser -data=/var/lib/whisparr-eros/
|
||||
TimeoutStopSec=20
|
||||
KillMode=process
|
||||
@@ -44,6 +46,18 @@ Restart=on-failure
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now whisparr-eros
|
||||
for _ in {1..60}; do
|
||||
grep -qs "created 'whisparr' with the configured password" /var/lib/whisparr-eros/logs/whisparr.txt && break
|
||||
sleep 2
|
||||
done
|
||||
sed -i '/WHISPARR__AUTH__INITIALPASSWORD/d' /etc/systemd/system/whisparr-eros.service
|
||||
systemctl daemon-reload
|
||||
cat <<EOF >~/whisparr-eros.creds
|
||||
Whisparr-Eros Credentials
|
||||
Username: whisparr
|
||||
Password: ${WHISPARR_PASS}
|
||||
EOF
|
||||
chmod 600 ~/whisparr-eros.creds
|
||||
msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
|
||||
Reference in new issue
Block a user