Fix Bug: WeKan v8.50 WRITABLE_PATH.

Thanks to snowsky and xet7 !

Fixes #6242
This commit is contained in:
Lauri Ojansivu committed 2026-04-03 20:16:32 +03:00
1 parent 60657c6e94
commit edc6b18b5f
6 files changed
+124 -2

No files matched your search

+19
View File
@@ -1,14 +1,33 @@
import { Meteor } from 'meteor/meteor';
import { FilesCollection } from 'meteor/ostrio:files';
import { generateUniversalAttachmentUrl } from '/models/lib/universalUrlGenerator';
import path from 'path';
// XXX Enforce a schema for the Attachments FilesCollection
// see: https://github.com/VeliovGroup/Meteor-Files/wiki/Schema
// Compute storage path:
// - Docker (WRITABLE_PATH=/data): /data/files/attachments
// - Snap (WRITABLE_PATH=$SNAP_COMMON/files): $SNAP_COMMON/files/attachments
const computeAttachmentStoragePath = () => {
const basePath = process.env.WRITABLE_PATH || process.cwd();
const endsWithFiles = basePath.endsWith('/files') || basePath.endsWith('\\files');
if (endsWithFiles) {
// Snap: WRITABLE_PATH already includes /files
return basePath + '/attachments';
} else {
// Docker & Dev: append /files/attachments
return basePath + '/files/attachments';
}
};
const storagePath = Meteor.isServer ? computeAttachmentStoragePath() : 'assets/app/uploads/attachments';
const Attachments = new FilesCollection({
debug: false, // Change to `true` for debugging
collectionName: 'attachments',
allowClientCode: true,
storagePath: storagePath,
namingFunction(opts) {
let filenameWithoutExtension = ""
let fileId = "";
+9 -1
View File
@@ -22,7 +22,15 @@ let attachmentUploadMimeTypes = [];
let attachmentUploadSize = 0;
const attachmentBucket = createBucket('attachments');
const storagePath = path.join(process.env.WRITABLE_PATH || process.cwd(), 'files', 'attachments');
// Compute storage path:
// - Docker (WRITABLE_PATH=/data): /data/files/attachments
// - Snap (WRITABLE_PATH=$SNAP_COMMON/files): $SNAP_COMMON/files/attachments
const basePath = process.env.WRITABLE_PATH || process.cwd();
const endsWithFiles = basePath.endsWith('/files') || basePath.endsWith('\\files');
const storagePath = endsWithFiles
? path.join(basePath, 'attachments')
: path.join(basePath, 'files', 'attachments');
if (process.env.ATTACHMENTS_UPLOAD_MIME_TYPES) {
attachmentUploadMimeTypes = process.env.ATTACHMENTS_UPLOAD_MIME_TYPES.split(',');
+19
View File
@@ -1,16 +1,35 @@
import { Meteor } from 'meteor/meteor';
import { FilesCollection } from 'meteor/ostrio:files';
import { generateUniversalAvatarUrl } from '/models/lib/universalUrlGenerator';
import path from 'path';
const { filesize } = require('filesize');
const getTAPi18n = () => require('/imports/i18n').TAPi18n;
let avatarsUploadSize = 72000;
// Compute storage path:
// - Docker (WRITABLE_PATH=/data): /data/files/avatars
// - Snap (WRITABLE_PATH=$SNAP_COMMON/files): $SNAP_COMMON/files/avatars
const computeAvatarStoragePath = () => {
const basePath = process.env.WRITABLE_PATH || process.cwd();
const endsWithFiles = basePath.endsWith('/files') || basePath.endsWith('\\files');
if (endsWithFiles) {
// Snap: WRITABLE_PATH already includes /files
return basePath + '/avatars';
} else {
// Docker & Dev: append /files/avatars
return basePath + '/files/avatars';
}
};
const storagePath = Meteor.isServer ? computeAvatarStoragePath() : 'assets/app/uploads/avatars';
const Avatars = new FilesCollection({
debug: false, // Change to `true` for debugging
collectionName: 'avatars',
allowClientCode: true,
storagePath: storagePath,
namingFunction(opts) {
let filenameWithoutExtension = ""
let fileId = "";
+9 -1
View File
@@ -40,7 +40,15 @@ if (process.env.AVATARS_UPLOAD_EXTERNAL_PROGRAM) {
}
const avatarsBucket = createBucket('avatars');
const storagePath = path.join(process.env.WRITABLE_PATH || process.cwd(), 'files', 'avatars');
// Compute storage path:
// - Docker (WRITABLE_PATH=/data): /data/files/avatars
// - Snap (WRITABLE_PATH=$SNAP_COMMON/files): $SNAP_COMMON/files/avatars
const basePath = process.env.WRITABLE_PATH || process.cwd();
const endsWithFiles = basePath.endsWith('/files') || basePath.endsWith('\\files');
const storagePath = endsWithFiles
? path.join(basePath, 'avatars')
: path.join(basePath, 'files', 'avatars');
// ---------------------------------------------------------------------------
// File store strategy factory
+4
View File
@@ -3,6 +3,10 @@
// Loaded by server/main.js after bootstrap (collectionHelpers) completes.
// ============================================================================
// ****IMPORTANT**** Initialize upload directories BEFORE models are loaded
// This ensures ostrio:files can create necessary directories without permission errors
import '/server/initializeDirs';
// ----------------------------------------------------------------------------
// 0. API middleware & auth routes (must register before model routes)
// ----------------------------------------------------------------------------
+64
View File
@@ -0,0 +1,64 @@
// ============================================================================
// Initialize Upload Directories
// Runs at server startup before models are loaded to ensure ostrio:files
// can create necessary upload directories without permission errors.
// ============================================================================
import fs from 'fs';
import path from 'path';
// Compute upload subdirectory path:
// - On Docker (WRITABLE_PATH=/data): /data/files/attachments, /data/files/avatars
// - On Snap (WRITABLE_PATH=$SNAP_COMMON/files): $SNAP_COMMON/files/attachments, $SNAP_COMMON/files/avatars
// - On dev (WRITABLE_PATH=..): ../files/attachments, ../files/avatars
//
// Snap already appends '/files' to WRITABLE_PATH, so we detect and handle that case.
function getUploadPaths() {
const basePath = process.env.WRITABLE_PATH || process.cwd();
// Check if basePath already ends with '/files' (Snap case)
const endsWithFiles = basePath.endsWith('/files') || basePath.endsWith('\\files');
if (endsWithFiles) {
// Snap: WRITABLE_PATH is already $SNAP_COMMON/files, so append subdirectories directly
return {
attachments: path.join(basePath, 'attachments'),
avatars: path.join(basePath, 'avatars'),
};
} else {
// Docker & Dev: WRITABLE_PATH is /data or .., so append /files/{subdir}
return {
attachments: path.join(basePath, 'files', 'attachments'),
avatars: path.join(basePath, 'files', 'avatars'),
};
}
}
const uploadPaths = getUploadPaths();
const dirsToCreate = [uploadPaths.attachments, uploadPaths.avatars];
dirsToCreate.forEach(dirPath => {
try {
// Create directory recursively with proper permissions (0o755)
// Ignore error if it already exists
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true, mode: 0o755 });
if (process.env.DEBUG === 'true') {
console.info(`[initializeDirs] Created directory: ${dirPath}`);
}
} else {
// Verify the directory is writable
try {
fs.accessSync(dirPath, fs.constants.W_OK);
if (process.env.DEBUG === 'true') {
console.info(`[initializeDirs] Directory exists and is writable: ${dirPath}`);
}
} catch (accessError) {
console.warn(`[initializeDirs] WARNING: Directory exists but may not be writable: ${dirPath}`, accessError.message);
}
}
} catch (error) {
console.error(`[initializeDirs] FAILED to initialize directory: ${dirPath}`, error.message);
throw new Error(`Cannot create or access upload directory: ${dirPath}\n${error.message}`);
}
});