Compare commits

...
Author SHA1 Message Date
github-actions[bot] cd5bd55b8b chore(ct): sync dotnetaspwebapi defaults from PocketBase 2026-11-12 10:06:12 +00:00
community-scripts-pr-app[bot]andgithub-actions[bot] 9b6c24f8ff Update CHANGELOG.md (#18627)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-11-12 08:54:34 +00:00
push-app-to-main[bot]andCanbiZ cbb347cc61 Pixelfed (#18523)
* Add pixelfed (ct)

* arm64 unknown

* pixelfed: use nginx_enable_site, enable FFI for vips, align .env with upstream

---------

Co-authored-by: push-app-to-main[bot] <203845782+push-app-to-main[bot]@users.noreply.github.com>
Co-authored-by: CanbiZ (MickLesk) <47820557+MickLesk@users.noreply.github.com>
2026-11-12 09:54:10 +01:00
community-scripts-pr-app[bot]andgithub-actions[bot] 0ad290c81f Update CHANGELOG.md (#18626)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-11-12 08:53:15 +00:00
CanbiZ (MickLesk) 0b25f9c6a5 Whisparr-eros: set the first login and save it (#18617)
* fix(whisparr-eros): set the first login and save it

Eros 3.6 turns on forms login and creates the user whisparr with a password that only appears in its log, so a fresh install showed a login nobody knew the password for (#18616). The install now sets a random password through WHISPARR__AUTH__INITIALPASSWORD for the first start only, removes it again, and saves the login in ~/whisparr-eros.creds; the password stays out of the logs.

* whisparr-eros: set the first password in the unit, write creds with cat
2026-11-12 09:52:47 +01:00
Rögl-Brunner Michel 1ac6100001 pr-script-test: build every PR automatically, forks included 2026-11-12 09:22:03 +01:00
community-scripts-pr-app[bot]andgithub-actions[bot] 46f54a9071 Update CHANGELOG.md (#18625)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-11-12 08:13:14 +00:00
Rögl-Brunner Michel d67f53176e pr-script-test: /test can pick the platforms, and takes app names in PRs 2026-11-12 09:12:45 +01:00
7 changed files with 439 additions and 45 deletions

No files matched your search

+99 -43
View File
@@ -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,
+12
View File
@@ -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
+2 -2
View File
@@ -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}"
+79
View File
@@ -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"
+1
View File
@@ -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}"
+232
View File
@@ -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
+14
View File
@@ -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