Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision e27b8db4e316874f7498fbe36e20270bc39b1d7f)
+++ src/routes/posts.js	(revision ccaa5301c212451f2e6b7c28c49282055278e961)
@@ -3,5 +3,4 @@
 import path from 'path';
 import fs from 'fs';
-import { fileURLToPath } from 'url';
 import multer from 'multer';
 import ejs from 'ejs';
@@ -25,10 +24,7 @@
 import { verifyBlob } from '../services/CryptoBox.js';
 import MusicMeta from '../services/MusicMeta.js';
-
-const __dirname = path.dirname(fileURLToPath(import.meta.url));
-const POST_IMAGES_DIR = path.resolve(
-  process.env.POST_IMAGES_PATH ||
-  path.join(__dirname, '..', '..', 'storage', 'media', 'post-images')
-);
+import { mediaDir } from '../config/paths.js';
+
+const POST_IMAGES_DIR = mediaDir('POST_IMAGES_PATH', 'post-images');
 fs.mkdirSync(POST_IMAGES_DIR, { recursive: true });
 
@@ -38,8 +34,5 @@
 // Rich replies: media dropped/pasted into the reply editor. Images, audio and
 // video, stored as-is (no transcode; a reply attachment is not a track).
-const REPLY_MEDIA_DIR = path.resolve(
-  process.env.REPLY_MEDIA_PATH ||
-  path.join(__dirname, '..', '..', 'storage', 'media', 'reply-media')
-);
+const REPLY_MEDIA_DIR = mediaDir('REPLY_MEDIA_PATH', 'reply-media');
 fs.mkdirSync(REPLY_MEDIA_DIR, { recursive: true });
 const ALLOWED_REPLY_MEDIA_EXT = new Set([
