SPB Git

spb/drive Public

SPB Drive — self-hosted personal cloud drive (files, previews, sharing) on the MacLustr cluster.

JavaScript 82.7% CSS 10.6% Nunjucks 3.6% Shell 1.8% SQL 1.3%

fix: accept 7zz binary (brew sevenzip), install LibreOffice with --no-quarantine, ngrok authtoken fallback in pm2 config

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed yesterday (Aug 10, 2026) parent fd15941

Showing 3 changed files with +34 and −5

modified deploy/ecosystem.config.cjs +16 −0
@@ -14,6 +14,21 @@ const APP_DIR = '/srv/drive/app';
14 14 const DATA_DIR = '/srv/drive';
15 15 const LOG_DIR = '/srv/drive/logs';
16 16
17 +// deploy/ngrok.yml expands ${NGROK_AUTHTOKEN}; when the env var isn't set,
18 +// fall back to the token already stored in the agent's default config.
19 +function ngrokAuthtoken() {
20 + if (process.env.NGROK_AUTHTOKEN) return process.env.NGROK_AUTHTOKEN;
21 + try {
22 + const fs = require('node:fs');
23 + const os = require('node:os');
24 + const agentCfg = `${os.homedir()}/Library/Application Support/ngrok/ngrok.yml`;
25 + const match = fs.readFileSync(agentCfg, 'utf8').match(/authtoken:\s*(\S+)/);
26 + return match ? match[1] : '';
27 + } catch {
28 + return '';
29 + }
30 +}
31 +
17 32 module.exports = {
18 33 apps: [
19 34 {
@@ -43,6 +58,7 @@ module.exports = {
43 58 script: 'ngrok',
44 59 args: ['start', 'spbdrive', '--config', `${APP_DIR}/deploy/ngrok.yml`],
45 60 interpreter: 'none',
61 + env: { NGROK_AUTHTOKEN: ngrokAuthtoken() },
46 62 autorestart: true,
47 63 restart_delay: 5000,
48 64 out_file: `${LOG_DIR}/tunnel.out.log`,
modified deploy/setup-m3u96b.sh +5 −1
@@ -28,7 +28,11 @@ log "Installing system packages (ffmpeg, libreoffice, poppler, 7z, ngrok, pm2)"
28 28 "$BREW" list ffmpeg >/dev/null 2>&1 || "$BREW" install ffmpeg
29 29 "$BREW" list poppler >/dev/null 2>&1 || "$BREW" install poppler # pdftotext + pdftoppm
30 30 "$BREW" list sevenzip >/dev/null 2>&1 || "$BREW" install sevenzip # 7z
31 "$BREW" list --cask libreoffice >/dev/null 2>&1 || "$BREW" install --cask libreoffice
31 +if ! "$BREW" list --cask libreoffice >/dev/null 2>&1 && [ ! -d /Applications/LibreOffice.app ]; then
32 + # --no-quarantine avoids the hdiutil Gatekeeper prompt on headless installs.
33 + "$BREW" install --cask --no-quarantine libreoffice || \
34 + echo "⚠ LibreOffice install failed — office previews will fall back to download cards" >&2
35 +fi
32 36 command -v ngrok >/dev/null 2>&1 || "$BREW" install ngrok
33 37 command -v node >/dev/null 2>&1 || "$BREW" install node@20
34 38 command -v pm2 >/dev/null 2>&1 || npm install -g pm2
modified src/preview/archive.mjs +13 −4
@@ -22,6 +22,13 @@ import { hasBin } from './thumbs.mjs';
22 22
23 23 const execFileP = promisify(execFile);
24 24 const MAX_ENTRIES = 5000;
25 +
26 +/** Homebrew's sevenzip ships `7zz`; p7zip ships `7z`. Accept either. */
27 +async function sevenZipBin() {
28 + if (await hasBin('7z')) return '7z';
29 + if (await hasBin('7zz')) return '7zz';
30 + return null;
31 +}
25 32 const MAX_MEMBER_BYTES = 200 * 1024 * 1024;
26 33
27 34 /** Archive kind from filename ('zip' | 'tar' | 'tgz' | '7z' | 'rar' | null). */
@@ -95,8 +102,9 @@ async function listTar(file, kind) {
95 102 }
96 103
97 104 async function listWith7z(file) {
98 if (!(await hasBin('7z'))) return null;
99 const { stdout } = await execFileP('7z', ['l', '-slt', '-ba', file], {
105 + const bin = await sevenZipBin();
106 + if (!bin) return null;
107 + const { stdout } = await execFileP(bin, ['l', '-slt', '-ba', file], {
100 108 timeout: 60_000, maxBuffer: 32 * 1024 * 1024,
101 109 });
102 110 const entries = [];
@@ -177,8 +185,9 @@ export async function extractMember(sha, name, memberPath) {
177 185 }
178 186
179 187 if (kind === '7z' || kind === 'rar') {
180 if (!(await hasBin('7z'))) return null;
181 const child = execFile('7z', ['x', '-so', file, memberPath], {
188 + const bin = await sevenZipBin();
189 + if (!bin) return null;
190 + const child = execFile(bin, ['x', '-so', file, memberPath], {
182 191 timeout: 5 * 60_000, maxBuffer: MAX_MEMBER_BYTES,
183 192 });
184 193 const out = new PassThrough();
185 194