Skip to content

Commit 5898799

Browse files
committed
Put the site at the top level, where the deploy actually lands
Hostinger's Git deployment clones the whole repository into the document root. "Root directory: public_html" is the destination on the server, not a folder inside the repository. I read it the other way and spent several rounds putting the site into a public_html/ folder here, which the deploy then cloned to public_html/public_html/ -- one level too deep, no index.html at the top, and a 403 every time. Both readings produce a 403, which is why nothing distinguished them until a listing of the document root showed .git, my-app, package.json and a nested public_html sitting in it. So the built site now sits at the top level of the repository, beside the source, and public_html/ is gone. It is not a pretty tree. It is what this host does, and the alternative was asking someone to change a control panel setting every time the arrangement moved. admin/ is both the panel's source and part of the build output -- the build copies it verbatim, and the two are byte-identical -- so it stays one directory rather than becoming two. Because the source now shares the web root with the site, .htaccess has to keep it unreachable. It already denied dotfiles, package.json and the config files; it now also denies .github (which the .git rule misses, needing a slash straight after "git" where this has "hub"), the my-app, scripts and assets-original trees, every .md, and LICENSE. The .git rule stops being a precaution here: the deploy puts .git in the document root on every run by design, and it cannot be removed without giving up automatic deploys, so those rules are the only thing between it and the internet. The database password in that history still needs rotating. publish-site.mjs replaces publish-public-html.mjs. It cannot empty the target first -- the source lives there -- so it records what it wrote in .site-manifest.json and removes those entries on the next run, which is what stops a deleted page living at the top level for good. Verified by serving the repository root the way Apache will: / 200, correct title /cars/ 200 /fleet-check.html 200 /assets/index-*.js 200, 352540 bytes /admin/install.php 200 /admin/ 500, and the body is "The panel cannot find its configuration" -- correct, config.php is not in the repository Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0128YzbhrfGdegUSc9RrARRf
1 parent 072545f commit 5898799

85 files changed

Lines changed: 241 additions & 11640 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/public-html-is-current.yml‎

Lines changed: 0 additions & 71 deletions
This file was deleted.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Fails when the site committed at the top level is not the current build.
2+
#
3+
# Hostinger's Git deployment clones this repository into the document root and
4+
# runs no build, so whatever sits at the top level IS the website. A change to
5+
# my-app/ or admin/ that does not also update those files never reaches the
6+
# server -- and nothing reports a problem. The deploy succeeds, the site keeps
7+
# serving the previous build, and the only symptom is that the thing you
8+
# changed is not there.
9+
#
10+
# This makes that failure loud. It builds, republishes, and fails if that
11+
# changed anything -- which means the committed site was stale.
12+
#
13+
# It writes nothing back and pushes nothing. The fix is a person running
14+
# `npm run publish:site` and committing, because a workflow that could commit
15+
# the fix would also be a workflow that can push to main unattended, which is a
16+
# far larger permission than catching a stale folder is worth.
17+
name: Site is current
18+
19+
on:
20+
push:
21+
branches: [main]
22+
pull_request:
23+
branches: [main]
24+
25+
permissions:
26+
contents: read
27+
28+
jobs:
29+
check:
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v4
33+
34+
- uses: actions/setup-node@v4
35+
with:
36+
node-version: 22.x
37+
cache: npm
38+
cache-dependency-path: my-app/package-lock.json
39+
40+
- name: Install
41+
working-directory: my-app
42+
run: npm ci
43+
44+
- name: Build
45+
working-directory: my-app
46+
run: npm run build
47+
48+
- name: Republish and see whether anything moved
49+
run: |
50+
node scripts/publish-site.mjs
51+
52+
if git diff --quiet; then
53+
echo "The committed site matches the build."
54+
exit 0
55+
fi
56+
57+
echo "::error::The site committed at the top level is not the current build, so these changes will not reach the website."
58+
echo ""
59+
echo "Hostinger clones this repository into the document root and runs no"
60+
echo "build, so the site keeps serving whatever is committed here."
61+
echo ""
62+
echo "Fix it with:"
63+
echo ""
64+
echo " npm run publish:site"
65+
echo " git add -A && git commit"
66+
echo ""
67+
echo "Out of date:"
68+
git diff --name-only | sed 's/^/ /'
69+
exit 1
Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,27 @@ ErrorDocument 404 /404.html
8585
# a request for /.git/config has a filename of "config" and sails straight
8686
# past it. This blocks the paths instead.
8787
#
88-
# It is here because a .git directory was found in the web root of this very
89-
# server. A served .git is not a tidiness problem -- standard tooling
90-
# reconstructs the entire repository from it, and this repository's history
91-
# contains a database password. The directory should not be on a web server at
92-
# all; this is the second line of defence for the day it comes back.
88+
# A served .git is not a tidiness problem -- standard tooling reconstructs the
89+
# entire repository from it, and this repository's history contains a database
90+
# password from before it was moved out.
91+
#
92+
# This is no longer a precaution. Hostinger's Git deployment clones the whole
93+
# repository into the document root, so .git is there on every deploy by
94+
# design, and cannot be removed without giving up automatic deploys. These
95+
# rules are what stands between it and the internet, which is also why the
96+
# password still needs rotating in hPanel rather than being considered handled.
9397
<IfModule mod_rewrite.c>
9498
RewriteEngine On
9599
RewriteRule (^|/)\.(git|svn|hg|env|ssh)(/|$) - [F,L]
100+
101+
# .github is not caught above: the pattern needs a slash or the end of the
102+
# path straight after "git", and this has "hub" there instead.
103+
RewriteRule (^|/)\.github(/|$) - [F,L]
104+
105+
# The rest of the repository, which now sits in the web root beside the
106+
# site. FilesMatch cannot express these because it matches one filename at a
107+
# time, and what needs blocking here is whole trees.
108+
RewriteRule ^(my-app|scripts|assets-original)(/|$) - [F,L]
96109
</IfModule>
97110

98111
# Source and build files are not content either. None of these should ever
@@ -102,6 +115,16 @@ ErrorDocument 404 /404.html
102115
Require all denied
103116
</FilesMatch>
104117

118+
# Documentation, which the deploy now puts in the web root: README.md,
119+
# CODE_OF_CONDUCT.md, LICENSE, and the note in photos/ describing how to name a
120+
# car photograph. None of it is secret and none of it is for visitors.
121+
<FilesMatch "\.md$">
122+
Require all denied
123+
</FilesMatch>
124+
<Files "LICENSE">
125+
Require all denied
126+
</Files>
127+
105128
# The directory listing is off by default on most hosts, but not all, and an
106129
# open listing of the web root is a map of everything on the server.
107130
Options -Indexes

‎.site-manifest.json‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[
2+
".htaccess",
3+
"404.html",
4+
"CNAME",
5+
"about",
6+
"assets",
7+
"bikes",
8+
"blog",
9+
"car-brezza.avif",
10+
"cars",
11+
"contact",
12+
"favicon.svg",
13+
"fleet-check.html",
14+
"hero-car.webp",
15+
"index.html",
16+
"monthly",
17+
"nri",
18+
"open-road.webp",
19+
"photos",
20+
"robots.txt",
21+
"sitemap.xml",
22+
"tariff",
23+
"tourist-vehicles",
24+
"wedding-cars"
25+
]
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)