diff --git a/.config/ci.yml b/.config/ci.yml index d20ede8d35..8730ccab3a 100644 --- a/.config/ci.yml +++ b/.config/ci.yml @@ -229,3 +229,8 @@ checkActivityPubGetSignature: false # Upload or download file size limits (bytes) #maxFileSize: 262144000 + +# CHMod-style permission bits to apply to uploaded files. +# Permission bits are specified as a base-8 string representing User/Group/Other permissions. +# This setting is only useful for custom deployments, such as using a reverse proxy to serve media. +#filePermissionBits: '644' diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml index d8013a1c95..342b0f43da 100644 --- a/.config/cypress-devcontainer.yml +++ b/.config/cypress-devcontainer.yml @@ -222,3 +222,8 @@ allowedPrivateNetworks: [ # Upload or download file size limits (bytes) #maxFileSize: 262144000 + +# CHMod-style permission bits to apply to uploaded files. +# Permission bits are specified as a base-8 string representing User/Group/Other permissions. +# This setting is only useful for custom deployments, such as using a reverse proxy to serve media. +#filePermissionBits: '644' diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 5fac3dc41e..ce2daf3aec 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -312,3 +312,8 @@ checkActivityPubGetSignature: false # Upload or download file size limits (bytes) #maxFileSize: 262144000 + +# CHMod-style permission bits to apply to uploaded files. +# Permission bits are specified as a base-8 string representing User/Group/Other permissions. +# This setting is only useful for custom deployments, such as using a reverse proxy to serve media. +#filePermissionBits: '644' diff --git a/.config/example.yml b/.config/example.yml index cf7b972de5..9debb3bf70 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -334,3 +334,8 @@ checkActivityPubGetSignature: false # PID File of master process #pidFile: /tmp/misskey.pid + +# CHMod-style permission bits to apply to uploaded files. +# Permission bits are specified as a base-8 string representing User/Group/Other permissions. +# This setting is only useful for custom deployments, such as using a reverse proxy to serve media. +#filePermissionBits: '644' diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 3dc49c7eb6..9cac058d96 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -115,6 +115,7 @@ type Source = { }; pidFile: string; + filePermissionBits?: string; }; export type Config = { @@ -212,6 +213,7 @@ export type Config = { } | undefined; pidFile: string; + filePermissionBits?: string; }; const _filename = fileURLToPath(import.meta.url); @@ -347,6 +349,7 @@ export function loadConfig(): Config { deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7), import: config.import, pidFile: config.pidFile, + filePermissionBits: config.filePermissionBits, }; } @@ -452,7 +455,10 @@ function applyEnvOverrides(config: Source) { } } - const alwaysStrings = { 'chmodSocket': true } as { [key: string]: boolean }; + const alwaysStrings: { [key in string]?: boolean } = { + 'chmodSocket': true, + 'filePermissionBits': true, + }; function _assign(path: (string | number)[], lastStep: string | number, value: string) { let thisConfig = config as any; @@ -490,7 +496,7 @@ function applyEnvOverrides(config: Source) { _apply_top(['sentryForBackend', 'enableNodeProfiling']); _apply_top([['clusterLimit', 'deliverJobConcurrency', 'inboxJobConcurrency', 'relashionshipJobConcurrency', 'deliverJobPerSec', 'inboxJobPerSec', 'relashionshipJobPerSec', 'deliverJobMaxAttempts', 'inboxJobMaxAttempts']]); _apply_top([['outgoingAddress', 'outgoingAddressFamily', 'proxy', 'proxySmtp', 'mediaProxy', 'proxyRemoteFiles', 'videoThumbnailGenerator']]); - _apply_top([['maxFileSize', 'maxNoteLength', 'maxRemoteNoteLength', 'maxAltTextLength', 'maxRemoteAltTextLength', 'pidFile']]); + _apply_top([['maxFileSize', 'maxNoteLength', 'maxRemoteNoteLength', 'maxAltTextLength', 'maxRemoteAltTextLength', 'pidFile', 'filePermissionBits']]); _apply_top(['import', ['downloadTimeout', 'maxFileSize']]); _apply_top([['signToActivityPubGet', 'checkActivityPubGetSignature']]); } diff --git a/packages/backend/src/core/InternalStorageService.ts b/packages/backend/src/core/InternalStorageService.ts index 5b179baf41..b00c5796d2 100644 --- a/packages/backend/src/core/InternalStorageService.ts +++ b/packages/backend/src/core/InternalStorageService.ts @@ -4,7 +4,7 @@ */ import * as fs from 'node:fs'; -import { copyFile, unlink, writeFile } from 'node:fs/promises'; +import { copyFile, unlink, writeFile, chmod } from 'node:fs/promises'; import * as Path from 'node:path'; import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; @@ -41,12 +41,20 @@ export class InternalStorageService { @bindThis public async saveFromPath(key: string, srcPath: string): Promise { await copyFile(srcPath, this.resolvePath(key)); - return `${this.config.url}/files/${key}`; + return await this.finalizeSavedFile(key); } @bindThis public async saveFromBuffer(key: string, data: Buffer): Promise { await writeFile(this.resolvePath(key), data); + return await this.finalizeSavedFile(key); + } + + private async finalizeSavedFile(key: string): Promise { + if (this.config.filePermissionBits) { + const path = this.resolvePath(key); + await chmod(path, this.config.filePermissionBits); + } return `${this.config.url}/files/${key}`; }