Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c57aff3c33 | ||
|
|
1ff8aaf2f1 | ||
|
|
2d57173e31 | ||
|
|
c65900638e |
+88
-7
@@ -224,7 +224,79 @@ Worth doing if the dialog starts 500-ing again after editing a macro in the UI.
|
||||
|
||||
---
|
||||
|
||||
## 4. Checking for plugin problems in future
|
||||
## 4. 🟠 The CodeGit panel looked like an empty black box
|
||||
|
||||
**Symptom:** the CodeGit dialog appeared as a mostly-empty black rectangle. Nothing was *broken* —
|
||||
it was unreadable, which reads as "broken".
|
||||
|
||||
**This was NOT a codegit bug.** Verified exhaustively first:
|
||||
|
||||
```
|
||||
all four panels return real content through the authenticated API:
|
||||
overview 200 940 bytes
|
||||
log 200 79596 bytes (full commit history)
|
||||
transfer 200 1460 bytes
|
||||
config 200 1174 bytes
|
||||
no JS errors in the browser console; all DOM helpers (replaceClass etc.) exist
|
||||
```
|
||||
|
||||
**Root cause — the generic `panel` rule hardcodes a height.** `theme/modules/common/_panel.scss`
|
||||
(after minification, in `theme/main.min.css`):
|
||||
|
||||
```css
|
||||
panel { padding: 5px; width: calc(100% - 200px); overflow: auto; overflow-y: scroll;
|
||||
height: 500px; position: relative; border-left: var(--borderLight); }
|
||||
```
|
||||
|
||||
Measured live, that produces:
|
||||
|
||||
| Element | Height | Problem |
|
||||
|---|---|---|
|
||||
| `#dialog` | 404px | clamped by `max-height: 70vh` + `overflow-y: auto` |
|
||||
| `#codegit panel` | 500px | **forced**, while its content is 119px |
|
||||
| `toolbar` ("Branch: …") | top **620** vs dialog bottom **490** | **pushed OUTSIDE the modal** |
|
||||
|
||||
So ~380px of the dialog was **empty dead space**, and anything after the panel — including the
|
||||
Branch line — rendered *below* the modal, invisible. A generic `panel { height: 500px }` is exactly
|
||||
the kind of rule that looks like a void.
|
||||
|
||||
**Fix applied:**
|
||||
|
||||
```css
|
||||
panel { ... height: auto; max-height: 58vh; overflow-y: auto; ... }
|
||||
```
|
||||
|
||||
Content-sized, capped so long content (the 79KB log) still scrolls. Verified live:
|
||||
|
||||
```
|
||||
before: panel 500px → toolbar top 620, dialog bottom 490 (outside)
|
||||
after: panel 136px → toolbar top 256, dialog bottom 279 (inside)
|
||||
```
|
||||
|
||||
`theme/modules/common/_panel.scss` is updated too, so the source does not contradict the shipped CSS
|
||||
(there is no Sass compiler on the guest, so `main.min.css` is edited directly).
|
||||
|
||||
---
|
||||
|
||||
## 5. 🟠 CSS/JS changes were invisible behind the browser cache
|
||||
|
||||
**Symptom:** a fix to the theme did not appear until a hard refresh — so the panel fix above looked
|
||||
like it had not worked at all.
|
||||
|
||||
**Root cause:** Atheos serves `theme/main.min.css` and the generated `public/*.min.js|css` bundles
|
||||
from **stable URLs with no cache headers**, so nginx adds only `Last-Modified`/`ETag` and browsers
|
||||
legitimately reuse their cached copy.
|
||||
|
||||
**Fix:** nginx now sends `Cache-Control: no-cache, must-revalidate` for `*.css`/`*.js`. Note this does
|
||||
**not** disable caching — it forces **revalidation**, so normal loads still get a cheap 304 while any
|
||||
change is guaranteed to be picked up on the next load.
|
||||
|
||||
**If you ever see a stale UI again:** force a reload (Ctrl/Cmd-Shift-R). That is the first thing to try
|
||||
before assuming a fix failed.
|
||||
|
||||
---
|
||||
|
||||
## 6. Checking for plugin problems in future
|
||||
|
||||
The plugin's JS is bundled into `public/plugins.min.js` (built by `classes/sourcemanager.php`), and
|
||||
its rightbar button is rendered by `components/sidebars/sb-right.php` from `plugin.json`. Both were
|
||||
@@ -258,17 +330,26 @@ verify them before trusting it.
|
||||
|
||||
---
|
||||
|
||||
## 5. Follow-ups not done (recorded so they are not forgotten)
|
||||
## 7. Follow-ups not done (recorded so they are not forgotten)
|
||||
|
||||
These came out of review. They are **operator decisions**, not agent ones — several change what the
|
||||
editor can do.
|
||||
|
||||
### 5.1 Remove the Terminal plugin — ✅ DONE
|
||||
### 7.0 Branch protection on `master` — ACCEPTED RISK
|
||||
|
||||
Deliberately **not** enabled while the editor is LAN-only (operator decision). Full reasoning and the
|
||||
"enable it before you expose this host" trigger are in
|
||||
[`docs/writer-branch-workflow.md` §2](writer-branch-workflow.md#2-protect-master-on-the-blog-repo--recommendation-deliberately-not-enabled).
|
||||
|
||||
The suite still probes for it on every run and reports `ACCEPTED-RISK` (not a failure) while it is
|
||||
off, so the recommendation self-cancels the moment it is enabled.
|
||||
|
||||
### 7.1 Remove the Terminal plugin — ✅ DONE
|
||||
|
||||
Removed through the market's own code path once §1 was fixed (see §2). A browser shell was the largest
|
||||
attack surface in the container and macros already cover command execution.
|
||||
|
||||
### 5.2 ❌ `disable_functions` cannot be "on by default" here — measured
|
||||
### 7.2 ❌ `disable_functions` cannot be "on by default" here — measured
|
||||
|
||||
Review asked for `php.ini` to disable `exec`, `shell_exec`, `passthru`, `system` by default.
|
||||
**This would break the editor's core function.** Evidence:
|
||||
@@ -285,18 +366,18 @@ turns the editor into a text editor that cannot commit. So it stays **a delibera
|
||||
you would also lose macros and git integration, which is why S-7 already says *"test, do not
|
||||
blind-apply"*.
|
||||
|
||||
### 5.3 Rotate the terminal password automatically
|
||||
### 7.3 Rotate the terminal password automatically
|
||||
|
||||
If the plugin is kept, `provision.sh` should generate a random password rather than leaving the
|
||||
upstream default. Not implemented: it would hardcode a secret into a repo file, which is the same
|
||||
class of problem this file already documents.
|
||||
|
||||
### 5.4 Deploy key could be read-only
|
||||
### 7.4 Deploy key could be read-only
|
||||
|
||||
Review suggested making the deploy key read-only if writes are unnecessary. **They are necessary** —
|
||||
that is the whole point of the writer-branch workflow (macro 2 pushes). Not applicable.
|
||||
|
||||
### 5.5 The real upstream fix for §3
|
||||
### 7.5 The real upstream fix for §3
|
||||
|
||||
`class.macro.php::save()` should write `[]` instead of `"N/A"` for folder macros. See §3 — the fix is
|
||||
currently applied to the data, so it survives updates, at the cost of being reversible from the UI.
|
||||
|
||||
@@ -17,7 +17,7 @@ merging into `master` is done by the operator in Gitea.
|
||||
│ operator opens a PR in Gitea
|
||||
│ operator reviews
|
||||
▼
|
||||
master (protected)
|
||||
master (protected: RECOMMENDED, currently OFF - see §2)
|
||||
│
|
||||
▼
|
||||
existing Pelican build pipeline
|
||||
@@ -43,12 +43,31 @@ This is the *same* global gitconfig php-fpm uses (`HOME=/home/atheos` — verifi
|
||||
as **you**. If it is not set, macro **2** refuses with an actionable error rather than
|
||||
committing under a bogus identity.
|
||||
|
||||
### 2. ⚠️ Protect `master` on the blog repo
|
||||
### 2. Protect `master` on the blog repo — RECOMMENDATION, deliberately not enabled
|
||||
|
||||
**Not yet enabled — the deploy key can currently force-push `master` directly**, which defeats the
|
||||
whole model. Set it in Gitea (repo owner only):
|
||||
**Status: accepted risk. Branch protection is OFF, and that is a decision, not an oversight.**
|
||||
|
||||
`armistace/blog` → **Settings** → **Branches** → add rule for `master`:
|
||||
> *"leave it as a recommendation — I won't do that unless I take this out to the wider internet"*
|
||||
> — operator
|
||||
|
||||
The deploy key **can** push `master` directly, which means the writer-branch model is **advisory, not
|
||||
enforced**. The operator has weighed this and chosen not to add the gate while the editor is LAN-only:
|
||||
|
||||
- **The compensating control is reachability, not immutability.** The editor is LAN-only (FR-10). The
|
||||
threat this setting defends against is a compromised editor publishing straight to `master`; while
|
||||
nothing off-LAN can reach the editor, and the only user on the LAN is the operator, that is largely
|
||||
theoretical.
|
||||
- **The cost is real now, the benefit is not.** Protection adds a merge gate for the sole user, who is
|
||||
also the repo owner — friction with no realistic risk reduction today.
|
||||
|
||||
#### ⚠️ Enable it BEFORE either of these happens
|
||||
|
||||
1. **The editor becomes reachable off-LAN** — an NPM proxy entry, a port-forward, or a
|
||||
Tailscale/WireGuard route to `blog-ide.aridgwayweb.com`.
|
||||
2. **Anyone other than the operator gets access to the editor.**
|
||||
|
||||
At that point a deploy key that can force-push `master` stops being theoretical. The setting
|
||||
(`armistace/blog` → **Settings** → **Branches** → rule for `master`):
|
||||
|
||||
| Setting | Value |
|
||||
|---|---|
|
||||
@@ -63,6 +82,14 @@ curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
https://git.aridgwayweb.com/api/v1/repos/armistace/blog/branch_protections
|
||||
```
|
||||
|
||||
#### It is still checked on every test run
|
||||
|
||||
`build-and-test.sh` attempts a direct push to `master` with the deploy key and requires it to be
|
||||
**rejected**. While protection is off that reports as `ACCEPTED-RISK`, not `FAIL` — so the suite stays
|
||||
green and CI-usable — but **the check runs every time**. It flips to
|
||||
`PASS master is protected` by itself the moment protection is enabled, and this recommendation
|
||||
becomes moot without anyone having to remember it.
|
||||
|
||||
### 3. The macros
|
||||
|
||||
Installed directly into Atheos's macro store (`data/macros.db.php`); a copy is kept in this repo at
|
||||
|
||||
+18
-9
@@ -7,13 +7,12 @@ server {
|
||||
root /var/www/atheos;
|
||||
index index.php index.html;
|
||||
|
||||
# --- logging ---------------------------------------------------------
|
||||
access_log /var/log/nginx/atheos.access.log;
|
||||
error_log /var/log/nginx/atheos.error.log;
|
||||
|
||||
# --- PHP -----------------------------------------------------------------
|
||||
# The socket path here MUST byte-match the pool's `listen =` in
|
||||
# /etc/php83/php-fpm.d/www.conf
|
||||
# (interface I-1 — a mismatch yields a bare 502 with no other diagnostic).
|
||||
# --- PHP: the socket path here MUST byte-match the pool's `listen =`
|
||||
# in /etc/php83/php-fpm.d/www.conf (interface I-1 -> else bare 502)
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
@@ -27,11 +26,12 @@ server {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# --- hardening -----------------------------------------------------------
|
||||
location ~ /\.git { deny all; }
|
||||
location ~ /\.ssh { deny all; }
|
||||
location ~ /\. { deny all; }
|
||||
location ~ ^/data/ { deny all; }
|
||||
# --- hardening (free, no downside) -----------------------------------
|
||||
# never serve the VCS directory, the app's private data, or dotfiles
|
||||
location ~ /\.git { deny all; }
|
||||
location ~ /\.ssh { deny all; }
|
||||
location ~ /\. { deny all; }
|
||||
location ~ ^/data/ { deny all; }
|
||||
location ~ ^/(config\.php|common\.php|controller\.php|traits/) { deny all; }
|
||||
location ~* \.(md|py|sh|log|conf|ini|sql|bak|orig)$ { deny all; }
|
||||
|
||||
@@ -40,4 +40,13 @@ server {
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
client_max_body_size 64m;
|
||||
|
||||
# Atheos serves theme/main.min.css and the generated *.min.js/css bundles from STABLE URLs
|
||||
# with no cache headers. Without this, a CSS/JS change stays invisible behind a cached copy
|
||||
# until the user hard-refreshes. "no-cache" permits caching but forces revalidation, so a
|
||||
# 304 keeps normal loads fast while guaranteeing updates are picked up on the next load.
|
||||
location ~* \.(css|js)$ {
|
||||
add_header Cache-Control "no-cache, must-revalidate";
|
||||
try_files $uri =404;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
# ==============================================================================
|
||||
W=/var/www/atheos
|
||||
B=$W/workspace/blog
|
||||
pass=0; fail=0
|
||||
pass=0; fail=0; note=0
|
||||
ok() { echo " PASS $1"; pass=$((pass+1)); }
|
||||
bad() { echo " FAIL $1"; fail=$((fail+1)); }
|
||||
note() { echo " ACCEPTED $1"; note=$((note+1)); }
|
||||
chk() { if [ "$2" = "$3" ]; then ok "$1 ($2)"; else bad "$1 (got '$2', want '$3')"; fi; }
|
||||
asuser() { su -s /bin/sh atheos -c "$1"; }
|
||||
|
||||
@@ -106,11 +107,31 @@ for d in mysqld mariadbd postgres redis-server; do
|
||||
if command -v "$d" >/dev/null 2>&1; then bad "$d present - unexpected"; else ok "$d absent (correct)"; fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "=============================================================="
|
||||
echo "REGRESSION: panel has no hardcoded height (empty-modal bug)"
|
||||
echo "=============================================================="
|
||||
# panel { height:500px } forced every modal panel to a fixed box: short content left a large
|
||||
# empty black region and anything after the panel (codegit's Branch toolbar) was pushed outside
|
||||
# the modal. docs/known-issues.md §4. Fails loudly if lost to an update.
|
||||
PNL=$(grep -oE '(^|[;}])panel\{[^}]*\}' /var/www/atheos/theme/main.min.css 2>/dev/null | head -1)
|
||||
if [ -z "$PNL" ]; then
|
||||
bad "could not read the panel rule from theme/main.min.css"
|
||||
elif echo "$PNL" | grep -q 'height:500px'; then
|
||||
bad "panel is fixed at 500px - re-run scripts/fix-panel-layout.sh"
|
||||
else
|
||||
ok "panel is content-sized ($(echo "$PNL" | grep -oE 'height:[^;]*' | head -1))"
|
||||
fi
|
||||
# and the CSS/JS cache header must be present, or fixes stay invisible
|
||||
if wget -qO /dev/null --server-response --header='Host: 127.0.0.1' http://127.0.0.1/theme/main.min.css 2>&1 | grep -qi 'Cache-Control.*no-cache'; then
|
||||
ok "theme CSS sends Cache-Control: no-cache (changes are picked up)"
|
||||
else
|
||||
bad "theme CSS has no Cache-Control - a CSS fix can stay hidden behind the browser cache"
|
||||
fi
|
||||
echo
|
||||
echo "=============================================================="
|
||||
echo "REGRESSION: rDelete works (GLOB_BRACE is glibc-only; musl lacks it)"
|
||||
echo "=============================================================="
|
||||
# traits/helpers.php::rDelete() used GLOB_BRACE, which is undefined under musl -> PHP 8 fatal.
|
||||
# That broke deleting ANY file/folder in the editor AND removing a market plugin (the UI
|
||||
# would spin forever because the fatal killed the response before Common::send()).
|
||||
# docs/known-issues.md §1. This test fails loudly if the patch is lost (e.g. by an update).
|
||||
@@ -142,9 +163,15 @@ else
|
||||
if echo "$PUSH_OUT" | grep -qiE 'protected|not allowed|denied|forbidden|remote rejected'; then
|
||||
ok "master is protected - the deploy key CANNOT push it"
|
||||
else
|
||||
bad "master is NOT protected - the deploy key could push master directly!"
|
||||
echo " Enable it: armistace/blog -> Settings -> Branches -> add rule for 'master',"
|
||||
echo " Enable push = OFF, merge whitelist = armistace. See docs/writer-branch-workflow.md"
|
||||
# ACCEPTED RISK, not a failure. The operator has decided NOT to enable branch protection
|
||||
# while this editor is LAN-only; the control is "it is not reachable off-LAN", not "master
|
||||
# is immutable". So this is reported, not failed - but it is checked on EVERY run, so the
|
||||
# moment protection does go on, the run says so and the recommendation below becomes moot.
|
||||
note "master is NOT protected - the deploy key CAN push master directly"
|
||||
echo " ACCEPTED because the editor is LAN-only (see docs/writer-branch-workflow.md)."
|
||||
echo " BECOMES A REAL FINDING THE MOMENT THIS HOST IS REACHABLE OFF-LAN. Before that:"
|
||||
echo " armistace/blog -> Settings -> Branches -> add rule for 'master'"
|
||||
echo " Enable push = OFF, merge whitelist = armistace"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -191,6 +218,6 @@ echo " listening ports: $(netstat -tln 2>/dev/null | awk '/LISTEN/{split($4,a,"
|
||||
|
||||
echo
|
||||
echo "=============================================================="
|
||||
printf 'RESULT: %d passed, %d failed\n' "$pass" "$fail"
|
||||
printf 'RESULT: %d passed, %d failed, %d accepted-risk\n' "$pass" "$fail" "$note"
|
||||
echo "=============================================================="
|
||||
[ "$fail" -eq 0 ] || exit 1
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/bin/sh
|
||||
# Re-apply the UI/layout fixes to a rebuilt or updated Atheos tree.
|
||||
#
|
||||
# Why this exists: Atheos has no releases and `scripts/update-atheos.sh` replaces the whole
|
||||
# application directory, so any local patch is lost on update. These are the two that were
|
||||
# needed to make the editor usable. Both are IDEMPOTENT - run it as often as you like.
|
||||
#
|
||||
# 1. panel { height: 500px } -> height:auto; max-height:58vh
|
||||
# Every modal panel was forced to a fixed 500px box, so dialogs with short content showed
|
||||
# a large empty black region and anything after the panel (codegit's "Branch:" toolbar)
|
||||
# was pushed OUTSIDE the modal. See docs/known-issues.md §4.
|
||||
#
|
||||
# 2. nginx: Cache-Control: no-cache for *.css/*.js
|
||||
# Theme/bundle changes were invisible behind the browser cache. §5.
|
||||
#
|
||||
# Usage: sh scripts/fix-panel-layout.sh (run ON the guest, as root)
|
||||
set -e
|
||||
|
||||
ATH=/var/www/atheos
|
||||
CSS=$ATH/theme/main.min.css
|
||||
SCSS=$ATH/theme/modules/common/_panel.scss
|
||||
NGX=/etc/nginx/http.d/atheos.conf
|
||||
|
||||
echo "=== 1. panel layout ==="
|
||||
OLD='height:500px;position:relative;border-left:var(--borderLight)'
|
||||
NEW='height:auto;max-height:58vh;position:relative;border-left:var(--borderLight)'
|
||||
if [ ! -f "$CSS" ]; then
|
||||
echo " !! $CSS not found - is Atheos installed?"
|
||||
elif grep -qF "$NEW" "$CSS"; then
|
||||
echo " already applied"
|
||||
elif grep -qF "$OLD" "$CSS"; then
|
||||
[ -f "$CSS.orig" ] || cp -a "$CSS" "$CSS.orig"
|
||||
php83 -r '$f=$argv[1];$o=$argv[2];$n=$argv[3];$s=file_get_contents($f);
|
||||
if(strpos($s,$o)===false){exit(1);} file_put_contents($f,str_replace($o,$n,$s));' "$CSS" "$OLD" "$NEW"
|
||||
echo " applied to main.min.css"
|
||||
else
|
||||
echo " !! expected panel rule not found - inspect manually"
|
||||
echo " (upstream may have changed it; diff against $CSS.orig)"
|
||||
fi
|
||||
|
||||
# keep the SCSS source consistent so the shipped CSS is not contradicted by the source
|
||||
if [ -f "$SCSS" ] && grep -q 'height: 500px;' "$SCSS"; then
|
||||
[ -f "$SCSS.orig" ] || cp -a "$SCSS" "$SCSS.orig"
|
||||
php83 -r '$f=$argv[1];$s=file_get_contents($f);
|
||||
$s=str_replace("height: 500px;","height: auto;\n\tmax-height: 58vh;",$s);
|
||||
file_put_contents($f,$s);' "$SCSS"
|
||||
echo " synchronised _panel.scss"
|
||||
fi
|
||||
|
||||
echo "=== 2. nginx cache headers ==="
|
||||
if [ ! -f "$NGX" ]; then
|
||||
echo " !! $NGX not found"
|
||||
elif grep -q 'must-revalidate' "$NGX"; then
|
||||
echo " already applied"
|
||||
else
|
||||
[ -f "$NGX.orig" ] || cp -a "$NGX" "$NGX.orig"
|
||||
cat > /tmp/.cacheblock <<'EOF'
|
||||
|
||||
# Atheos serves theme/main.min.css and the generated *.min.js/css bundles from STABLE URLs
|
||||
# with no cache headers, so a CSS/JS fix stays invisible behind the browser cache until a
|
||||
# hard refresh. "no-cache" permits caching but forces revalidation: normal loads still get a
|
||||
# cheap 304, and a change is guaranteed to be picked up on the next load.
|
||||
location ~* \.(css|js)$ {
|
||||
add_header Cache-Control "no-cache, must-revalidate";
|
||||
try_files $uri =404;
|
||||
}
|
||||
EOF
|
||||
php83 -r '$f=$argv[1];$b=$argv[2];$s=file_get_contents($f);
|
||||
$p=strrpos($s,"}"); if($p===false){exit(1);}
|
||||
file_put_contents($f,substr($s,0,$p).file_get_contents($b).substr($s,$p));' "$NGX" /tmp/.cacheblock
|
||||
rm -f /tmp/.cacheblock
|
||||
nginx -t >/dev/null 2>&1 && rc-service nginx reload >/dev/null 2>&1 && echo " applied + nginx reloaded" \
|
||||
|| echo " !! nginx -t failed - restore $NGX.orig"
|
||||
fi
|
||||
|
||||
echo "=== done ==="
|
||||
Reference in New Issue
Block a user