Skip to content

Commit f0a7e1f

Browse files
Merge pull request #29 from niteeshkanna-sh/claude/stoic-rubin-fnglq7
Ship the admin panel with the build, where the host actually serves from
2 parents 9addbbb + 7abe3a1 commit f0a7e1f

3 files changed

Lines changed: 107 additions & 3 deletions

File tree

‎my-app/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "module",
66
"scripts": {
77
"dev": "node scripts/photo-manifest.mjs && node scripts/fetch-content.mjs && vite",
8-
"build": "node scripts/photo-manifest.mjs && node scripts/fetch-content.mjs && tsc -b && vite build && node scripts/prerender-seo.mjs",
8+
"build": "node scripts/photo-manifest.mjs && node scripts/fetch-content.mjs && tsc -b && vite build && node scripts/copy-admin-panel.mjs && node scripts/prerender-seo.mjs",
99
"lint": "oxlint",
1010
"preview": "vite preview"
1111
},
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copies the PHP admin panel into the build output, so it ships with the site.
2+
//
3+
// Hostinger deploys this repository by running `npm run build` and serving
4+
// my-app/dist. That means dist is the document root: anything not in it does
5+
// not exist as far as the web is concerned, which is why niteshacars.in/admin
6+
// returned the site's own "page not found" while the panel sat perfectly well
7+
// in the repository.
8+
//
9+
// Vite copies public/ verbatim into dist/, so the panel could simply live
10+
// there -- but public/ is for the website's own assets, and burying a PHP
11+
// application inside it would be a surprise to anyone reading the tree. This
12+
// copies it explicitly instead, after the build, where the intent is visible.
13+
//
14+
// config.php is never copied. It holds the database password, it is gitignored,
15+
// and dist is rebuilt from scratch on every deploy -- so anything written into
16+
// it is temporary by construction. The panel finds its configuration outside
17+
// the build output; see config() in the panel's src/db.php.
18+
19+
import { cp, mkdir, rm, stat } from 'node:fs/promises';
20+
import { dirname, join } from 'node:path';
21+
import { fileURLToPath } from 'node:url';
22+
23+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
24+
const source = join(dirname(root), 'public_html', 'admin.niteshacars.in', 'admin');
25+
const target = join(root, 'dist', 'admin');
26+
27+
// Never ship these. The test tooling is not a security boundary, but it is
28+
// noise on a public server and some of it takes arguments.
29+
const SKIP = new Set(['config.php', '.ftp-deploy-sync-state.json', '.ftp-deploy-admin-state.json']);
30+
31+
try {
32+
await stat(source);
33+
} catch {
34+
console.error(`copy-admin-panel: no panel at ${source}`);
35+
process.exit(1);
36+
}
37+
38+
await rm(target, { recursive: true, force: true });
39+
await mkdir(dirname(target), { recursive: true });
40+
41+
let copied = 0;
42+
await cp(source, target, {
43+
recursive: true,
44+
filter: (src) => {
45+
const name = src.slice(src.lastIndexOf('/') + 1);
46+
if (SKIP.has(name)) return false;
47+
copied += 1;
48+
return true;
49+
},
50+
});
51+
52+
console.log(`copy-admin-panel: ${copied} entries into dist/admin`);

‎public_html/admin.niteshacars.in/admin/src/db.php‎

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,58 @@ function config_set(array $values): void
1515
$GLOBALS['__config_override'] = $values;
1616
}
1717

18+
/**
19+
* Finds config.php.
20+
*
21+
* It used to be one fixed path, next to the panel. That stopped working when
22+
* the host began deploying this repository by building it: the build output is
23+
* the document root and it is recreated from scratch every time, so anything
24+
* written beside the panel is erased on the next push. Keeping the file there
25+
* would mean re-uploading the database password after every deploy.
26+
*
27+
* So the panel looks outward instead. A directory named nitesha-config,
28+
* anywhere above the panel, holds the file; being outside the document root it
29+
* survives deploys and cannot be fetched over HTTP even if a rule is
30+
* misconfigured, which is where a database password belongs anyway.
31+
*
32+
* The old location is still checked first, so an install that predates this
33+
* keeps working untouched.
34+
*
35+
* Returns null when there is nothing to load; the caller decides what to say.
36+
*/
37+
function config_path(): ?string
38+
{
39+
$explicit = getenv('NITESHA_CONFIG');
40+
if (is_string($explicit) && $explicit !== '' && is_file($explicit)) {
41+
return $explicit;
42+
}
43+
44+
// Beside the panel: how this has always worked, and still right for an
45+
// install that is uploaded rather than built.
46+
$beside = __DIR__ . '/../config.php';
47+
if (is_file($beside)) {
48+
return $beside;
49+
}
50+
51+
// Then upwards. Six levels is past the account root on every layout this
52+
// has run on, and stopping at the filesystem root keeps it terminating on
53+
// any layout it has not.
54+
$dir = dirname(__DIR__);
55+
for ($i = 0; $i < 6; $i++) {
56+
$candidate = $dir . '/nitesha-config/config.php';
57+
if (is_file($candidate)) {
58+
return $candidate;
59+
}
60+
$parent = dirname($dir);
61+
if ($parent === $dir) {
62+
break;
63+
}
64+
$dir = $parent;
65+
}
66+
67+
return null;
68+
}
69+
1870
function config(?string $key = null): mixed
1971
{
2072
static $config = null;
@@ -24,8 +76,8 @@ function config(?string $key = null): mixed
2476
}
2577

2678
if ($config === null) {
27-
$path = __DIR__ . '/../config.php';
28-
if (!is_file($path)) {
79+
$path = config_path();
80+
if ($path === null) {
2981
http_response_code(500);
3082
exit('Not set up yet. Open install.php in your browser to get started.');
3183
}

0 commit comments

Comments
 (0)