Refactor configuration handling: implement dynamic loading of config and cache paths for improved security and flexibility in Docker environments
This commit is contained in:
1 parent
c7b836bcf2
commit
bb65ecd43c
7 files changed
+53
-12
No files matched your search
+17
-4
@@ -5,10 +5,13 @@
|
|||||||
# docker-compose.yaml (this file)
|
# docker-compose.yaml (this file)
|
||||||
# .env (optional, see PROXY_NETWORK below)
|
# .env (optional, see PROXY_NETWORK below)
|
||||||
# www/ (contents of `npm run dist`)
|
# www/ (contents of `npm run dist`)
|
||||||
# data/ (fitz.sqlite — never inside www/)
|
# config/config.php (live secrets — never inside www/, never in dist/)
|
||||||
|
# data/ (fitz.sqlite + cache/ — never inside www/)
|
||||||
#
|
#
|
||||||
# First run: create data/ and `chown 33:33 data` (www-data in the image),
|
# First run: create data/ and `chown 33:33 data` (www-data in the image).
|
||||||
# then create the first admin with bin/create-user.php (see its header).
|
# Copy config.example.php to config/config.php, fill it in, and make it
|
||||||
|
# readable by www-data (`chown root:33` + `chmod 640`). Then create the
|
||||||
|
# first admin with bin/create-user.php (see its header).
|
||||||
#
|
#
|
||||||
# The CasaOS dev host keeps its own copy of this file in the CasaOS app
|
# The CasaOS dev host keeps its own copy of this file in the CasaOS app
|
||||||
# settings, with CasaOS paths and its x-casaos block.
|
# settings, with CasaOS paths and its x-casaos block.
|
||||||
@@ -43,13 +46,23 @@ services:
|
|||||||
- type: bind
|
- type: bind
|
||||||
source: ./www
|
source: ./www
|
||||||
target: /var/www/html
|
target: /var/www/html
|
||||||
# Database lives outside the web root so Apache can't serve it.
|
# Database and cache live outside the web root so Apache can't serve
|
||||||
|
# them and deploys to www/ never touch them.
|
||||||
- type: bind
|
- type: bind
|
||||||
source: ./data
|
source: ./data
|
||||||
target: /var/www/data
|
target: /var/www/data
|
||||||
|
# Config is mounted as a directory, not a single file: a single-file
|
||||||
|
# bind mount pins the inode, so an editor that saves by replacing the
|
||||||
|
# file leaves the container reading the old copy until a restart.
|
||||||
|
- type: bind
|
||||||
|
source: ./config
|
||||||
|
target: /var/www/config
|
||||||
|
read_only: true
|
||||||
environment:
|
environment:
|
||||||
- TZ=America/Indiana/Indianapolis
|
- TZ=America/Indiana/Indianapolis
|
||||||
- FITZ_DB_PATH=/var/www/data/fitz.sqlite
|
- FITZ_DB_PATH=/var/www/data/fitz.sqlite
|
||||||
|
- FITZ_CACHE_DIR=/var/www/data/cache
|
||||||
|
- FITZ_CONFIG_PATH=/var/www/config/config.php
|
||||||
# Cap container logs so PHP's error log can't slowly fill the disk.
|
# Cap container logs so PHP's error log can't slowly fill the disk.
|
||||||
logging:
|
logging:
|
||||||
driver: json-file
|
driver: json-file
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ function fritz_google_reviews(): array {
|
|||||||
return $fallback;
|
return $fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
$cacheFile = __DIR__ . '/../cache/google-reviews.json';
|
// In Docker, FITZ_CACHE_DIR points into the writable data/ mount, outside
|
||||||
|
// the web root, so deploys don't wipe it and www/ can stay read-only to
|
||||||
|
// www-data. Local dev falls back to cache/ in the project root.
|
||||||
|
$cacheDir = getenv('FITZ_CACHE_DIR') ?: __DIR__ . '/../cache';
|
||||||
|
$cacheFile = $cacheDir . '/google-reviews.json';
|
||||||
|
|
||||||
if (is_file($cacheFile)) {
|
if (is_file($cacheFile)) {
|
||||||
$cached = json_decode((string) file_get_contents($cacheFile), true);
|
$cached = json_decode((string) file_get_contents($cacheFile), true);
|
||||||
@@ -33,7 +37,6 @@ function fritz_google_reviews(): array {
|
|||||||
$fresh = fritz_fetch_google_reviews($fallback);
|
$fresh = fritz_fetch_google_reviews($fallback);
|
||||||
$fresh['fetched_at'] = time();
|
$fresh['fetched_at'] = time();
|
||||||
|
|
||||||
$cacheDir = dirname($cacheFile);
|
|
||||||
if (!is_dir($cacheDir)) {
|
if (!is_dir($cacheDir)) {
|
||||||
@mkdir($cacheDir, 0775, true);
|
@mkdir($cacheDir, 0775, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the site config. In Docker, FITZ_CONFIG_PATH points outside the
|
||||||
|
* web root (see docker-compose.yaml), so a deploy that replaces www/ can
|
||||||
|
* never overwrite or ship it. Local dev falls back to config.php in the
|
||||||
|
* project root.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require getenv('FITZ_CONFIG_PATH') ?: __DIR__ . '/../config.php';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
require __DIR__ . '/config.php';
|
require __DIR__ . '/inc/load-config.php';
|
||||||
require __DIR__ . '/inc/assets.php';
|
require __DIR__ . '/inc/assets.php';
|
||||||
require __DIR__ . '/inc/security-headers.php';
|
require __DIR__ . '/inc/security-headers.php';
|
||||||
send_security_headers();
|
send_security_headers();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
require __DIR__ . '/config.php';
|
require __DIR__ . '/inc/load-config.php';
|
||||||
require __DIR__ . '/inc/assets.php';
|
require __DIR__ . '/inc/assets.php';
|
||||||
require __DIR__ . '/inc/security-headers.php';
|
require __DIR__ . '/inc/security-headers.php';
|
||||||
send_security_headers();
|
send_security_headers();
|
||||||
|
|||||||
@@ -6,9 +6,14 @@
|
|||||||
*
|
*
|
||||||
* This is an allowlist by design: only the files and folders listed below
|
* This is an allowlist by design: only the files and folders listed below
|
||||||
* are copied. Dev-only stuff (node_modules/, src/, package*.json, .git/,
|
* are copied. Dev-only stuff (node_modules/, src/, package*.json, .git/,
|
||||||
* .claude/, docker-compose.yaml, dev.sh, .hintrc, config.example.php, the
|
* .claude/, docker-compose.yaml, dev.sh, .hintrc, config.php,
|
||||||
* live db/fitz.sqlite, ...) never ends up in dist/ — including anything
|
* config.example.php, the live db/fitz.sqlite, ...) never ends up in
|
||||||
* added later, since it has to be added here explicitly first.
|
* dist/ — including anything added later, since it has to be added here
|
||||||
|
* explicitly first.
|
||||||
|
*
|
||||||
|
* config.php is deliberately excluded: it holds live secrets and lives
|
||||||
|
* outside the web root on the server (see FITZ_CONFIG_PATH in
|
||||||
|
* docker-compose.yaml), so a deploy can never overwrite it.
|
||||||
*
|
*
|
||||||
* Run with: npm run dist
|
* Run with: npm run dist
|
||||||
*/
|
*/
|
||||||
@@ -25,7 +30,6 @@ const FILES = [
|
|||||||
'main.js',
|
'main.js',
|
||||||
'styles.css',
|
'styles.css',
|
||||||
'robots.txt',
|
'robots.txt',
|
||||||
'config.php', // holds the live FB_PAGE_ACCESS_TOKEN — see README before sharing dist/ anywhere but the server
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const DIRS = ['admin', 'assets', 'bin', 'inc'];
|
const DIRS = ['admin', 'assets', 'bin', 'inc'];
|
||||||
|
|||||||
@@ -24,4 +24,14 @@ if (preg_match($blocked, $path)) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apache (mod_dir) redirects /admin to /admin/; the built-in server just
|
||||||
|
// serves admin/index.php at /admin. Every relative link on that page then
|
||||||
|
// resolves against / instead of /admin/ — "menu.php" lands on the public
|
||||||
|
// menu, "users.php" 404s. Match Apache so dev behaves like production.
|
||||||
|
if ($path !== '/' && !str_ends_with($path, '/') && is_dir(__DIR__ . '/..' . $path)) {
|
||||||
|
$query = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_QUERY);
|
||||||
|
header('Location: ' . $path . '/' . ($query ? '?' . $query : ''), true, 301);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
Reference in new issue
Block a user