Commit Graph
136 Commits
Author SHA1 Message Date
hoelee 541b00c958 post(tutorials): does re-encoding to HEVC actually shrink your files? I measured it (EN+ZH) + og/banner
Deploy / build (push) Successful in 19s
Measured on real files with quality held fixed (SSIM vs the source, harness self-tested at 1.0000):
hevc_nvenc cq27 produced 115% and 119% of the source video bitrate on two of three files while cq30/31
landed 51-84%; libx265 crf26 landed 33.7/56.7/67.0% at min SSIM 0.988-0.993, i.e. 10-22% smaller than
NVENC at matched quality. Two sources with identical bpp (0.0437) shrank 49% vs 17%, so bpp classifies
files but does not predict the ratio. Notes the audio floor (a 33.7% video stream still yields a 53%
file because AAC dominates what is left) and why VP9/AV1 sources have no headroom (copy the video).
2026-10-08 05:19:27 +08:00
hoelee 0b523f40d6 post(devops): it reported success, nothing had changed (EN+ZH) + og/banner
Three silent failures on a network-mounted share: a 403 MB upload that wrote 12.5 MB and returned OK
(the timeout toast arrived minutes later, asynchronously), an os.replace() that never landed while
getsize()/mtime served cached metadata (the destination was still the original H.264 file), and a
byte-count source check that agreed with a length-preserving bad copy. Adds read-back hashing at all
three boundaries (source copy, upload, install) and records the metric traps that hid them: a clean
decode test on wrong pictures, SSIM on near-flat frames, input-seek decoding of open-GOP sources,
stream-copy slices dropping frames, and fixed-name scratch files colliding across concurrent runs.
Audit after the fix: reported=30 landed=29 not-landed=0.
2026-10-08 05:04:54 +08:00
hoelee e6476e1d15 post(devops): every player stuttered on the same video — the file was blameless (EN+ZH) + og/banner
Deploy / build (push) Successful in 24s
The converted HEVC stuttered in VLC, Chrome and on an iPad (offline) while the source played fine.
Container timing was perfect (5818/5818 frame intervals at exactly 0.040000 s, no dup/backward PTS,
ctts not flat) and a full decode printed nothing. Cross-decoder SSIM found the real defect: NVDEC's
legacy av1_cuvid wrapper returns frames from the wrong timestamps - 106 of 5819 frames, SSIM down to
0.330, deterministically, roughly every 30 frames - and the HEVC output was a faithful encode of those
wrong pictures (min 0.991 against the NVDEC decode, min 0.330 against a software decode). Fix is one
line (-hwaccel cuda -hwaccel_output_format cuda -c:v av1, verified 1.000000 over 1500 frames; vp9_cuvid
unaffected). Adds the content gate (structure AND pictures, defect-shaped thresholds) to the pipeline.
2026-10-08 04:52:04 +08:00
hoelee c2f8452372 docs(project-state): B2k — the ef3f520 follow-up and the private Gitea mirror of the rewrite
Deploy / build (push) Successful in 15s
2026-10-06 17:27:25 +08:00
hoelee ef3f520f21 post(upstream-rewrite): the wipe is verifiable in git — master is one 2026-06-22 commit ('deleted'), no common ancestor with main
Deploy / build (push) Successful in 17s
Re-checked against the actual repo rather than the API summary: the old branch
survives as a single commit whose README is the word 'deleted', and it shares no
common ancestor with main's 233 commits. Sharper and fully verifiable, EN+ZH.
2026-10-06 17:26:08 +08:00
hoelee dca84a8ca5 docs(project-state): Step B2k — the two reader/gateway posts, the corrected upstream facts, and the backdating constraint
Deploy / build (push) Successful in 18s
2026-10-06 17:24:40 +08:00
hoelee 378d55c3c8 post: two reader/gateway posts — the upstream rewrite, and the front-door pattern (EN+ZH) + og/banner images
Deploy / build (push) Successful in 22s
1) the-upstream-was-deleted-then-came-back-rewritten (devops, pubDate 2026-10-06)
   docker pull hectorqin/reader -> 404; the repo was re-initialised 2026-09-16 and now
   carries 233 commits, a TypeScript/Node rewrite, port 5888, SQLite /data, config moved
   into the admin UI, and an image on cnb.cool with no tags or releases. Covers telling
   dead from rewriting, the migration boundary, and keeping the front door outside the
   app image. Facts re-verified live 2026-10-06.

2) putting-a-front-door-on-an-app-you-cant-modify (devops, pubDate 2025-06-24 backdated
   into the empty 2025-06 window, updatedDate 2026-10-06 so lastmod stays honest)
   The reader-gateway pattern: a second nginx container owning the public port, the
   request-time resolver, why a proxy body rewrite cannot touch a client-rendered SPA,
   the gate-bounce injection with its pass token, the app's own CSS hook for branding,
   and the silent-failure check to run after every app upgrade.

Both: EN + ZH twins, TERMINALS/BANNERS entries appended via append_generator_entries.py
(additive, deletions 0, node --check OK), og/banner generated and measured PASS
(og rows=1 overflow=0 missingHash=0; banner 8 rows, delta 2, overflow 0).
2026-10-06 17:19:46 +08:00
hoelee 67b844e3a0 post(case-studies): running production infrastructure solo — 162 containers, three hosts (EN+ZH) + og/banner
Deploy / build (push) Successful in 18s
The Phase C1 keystone asset (Story B). Publishes the infra case study:
- architecture: 3 hosts / 162 containers / 78 compose stacks with the per-host split
- incident record: the monitoring panels that lied, and the Passbolt cascade
- how the numbers are re-derived (verify_infra.py) rather than remembered
- no uptime percentage: availability is not measured, so the post says 'monitored'
- hire CTA (case-study convention)

EN + ZH twins, og + banner generated and measured (OG 5 tags/1 row, banner 8 lines delta 2).
2026-10-06 08:04:02 +08:00
hoelee fbec241d0d draft(case-studies): running production infrastructure solo — 162 containers, three hosts
Deploy / build (push) Successful in 20s
Phase C1 keystone (Story B). Draft, not published: draft: true.
- architecture: 3 hosts / 162 containers / 78 stacks, per-host split (86/44, 37/21, 39/13)
- ingress (Traefik + ACME), identity (authentik), monitoring (single Grafana), CI/CD, backups
- incident record: the monitoring panels that lied, and the Passbolt cascade (one missing
  config value, three failure modes) — both link to their deep-dive posts
- numbers are re-derivable via verify_infra.py rather than remembered
- NO uptime percentage: availability is not measured anywhere (no probe-based monitor
  deployed), so the post says "monitored", not a figure
- DO_NOT_PUBLISH services excluded entirely
EN only + no og/banner yet: not publishable until the ZH twin and images exist and the
owner approves. Local commit only — not pushed.
2026-10-06 07:36:36 +08:00
hoelee 1ec97706bb post: stop writing agent prompts for deterministic work (EN+ZH) + og/banner images
Deploy / build (push) Successful in 14s
2026-10-01 01:38:58 +08:00
hoelee f2d9c20207 post: loop engineering without a coding agent — the 11 cron jobs that run my business (EN+ZH) + og/banner images
Deploy / build (push) Successful in 18s
2026-10-01 01:36:32 +08:00
hoelee f744cecd67 docs(project-state): Step B2j — the one-hostname post, and the tracker now wired into three sites
Deploy / build (push) Successful in 13s
2026-09-30 04:56:16 +08:00
hoelee 54d8ccef7e post: one hostname for a public tracker and an SSO-gated dashboard (EN+ZH) + og/banner images
Deploy / build (push) Successful in 18s
2026-09-30 04:52:43 +08:00
hoelee 10500a667b docs(project-state): Umami tracker wired into the blog + digikedai, verified with a real browser; record the headless-UA pitfall
Deploy / build (push) Successful in 29s
2026-09-29 06:44:17 +08:00
hoelee de17bea603 analytics: add the self-hosted Umami tracker to the base layout (blog.hoelee.com)
Deploy / build (push) Successful in 29s
2026-09-29 06:35:40 +08:00
hoelee e4a2da29db docs(project-state): open ^/mcp on the stats proxy provider (public MCP with Bearer key) + record the verification
Deploy / build (push) Successful in 33s
2026-09-29 06:23:42 +08:00
hoelee b7ecd27076 docs(project-state): correct the SSO rollback note (gate container is stopped, not running)
Deploy / build (push) Successful in 19s
2026-09-29 05:54:35 +08:00
hoelee 88560a8afe docs(project-state): E1 — SSO gate in front of stats.hoelee.com (authentik proxy provider pk 64, per-app flow auth-stats, skip-path tracker); record the forward_single-vs-proxy pitfall, the outpost config lag, and the post-cut-over tracker verification
Deploy / build (push) Successful in 37s
2026-09-29 05:51:41 +08:00
hoelee c5e10a0e70 docs: correct the dashboard-access note — one hostname is enough (app login + 2FA, or cookie SSO with skip-paths); a second hostname is the SaaS-scale ingest-vs-app split
Deploy / build (push) Successful in 18s
2026-09-29 05:21:56 +08:00
hoelee 8f3befaf72 docs: stats.hoelee.com hardening (A+B) — rotate password/APP_SECRET, add the umami-gateway, dashboard LAN-only, tracker verified end-to-end
Deploy / build (push) Successful in 21s
2026-09-29 05:12:31 +08:00
hoelee ca02aaa624 docs: stats.hoelee.com is live via DNS+DSM reverse proxy (not CF) — record the exposure path, the client-IP header fix, and the open default-credential item
Deploy / build (push) Successful in 34s
2026-09-29 04:35:41 +08:00
hoelee 2beea7b219 docs: record the self-hosted Umami deployment (E1) and what is still blocked on Cloudflare
Deploy / build (push) Successful in 23s
2026-09-29 04:16:57 +08:00
hoelee b0ada17fa7 docs: live re-audit (2026-09-29) + Phase E distribution backlog
Deploy / build (push) Successful in 29s
Re-checked the Sept research against the live site and the repo:
- 53 posts x 2 languages, 127 sitemap URLs, 133/133 internal links 200
- Mark C1 (per-post OG), D1 (search), B2b (draft bank) as done — they were stale as open
- New Phase E (top of the queue): E1 analytics (CF Web Analytics + GSC), E2 main-site ->
  blog link (deferred to the www.hoelee.com overhaul), E3 dev.to syndication, E4 ZH nav +
  /zh/posts/, E5 og:locale, E6 Cloudflare HTML caching, E7 volume guardrail, E8 hygiene
- New section 4: measured evidence table; conventions section renumbered
2026-09-29 03:07:51 +08:00
hoelee 95b21e3456 Add 5 posts (EN + ZH): fully on-chain SVG NFTs, the CRLF art trap, a Chainlink VRF v2.5 lottery, page-by-page PDF verification, JPA @Version
Deploy / build (push) Successful in 20s
Five posts with custom OG + banner and hire CTAs. Three are web3, giving
that category its first posts — /categories/web3/ previously 404'd while the
index advertised it as '0 posts - coming soon'.

Backdated where the work genuinely is older: the foundry-nft and
hardhat-smartcontract-lottery commits date to 2024-08-15..19, so those two
posts fill the empty 2024-10 and 2024-12 archive months with updatedDate
holding the real date. The two 2026-08-19 posts use their real work date.

TERMINALS/BANNERS entries for all five slugs are committed this time (both
generators were clean; diffs verified purely additive).
2026-09-29 02:41:58 +08:00
hoelee 9889ef58f8 Backdate 2 of the 6 posts dated 2026-09-29 into empty archive months (EN + ZH)
Deploy / build (push) Successful in 18s
2026-09-29 01:54:39 +08:00
hoelee 6963c4a22d Link the container-docs post to the Synology spreadsheet API flagship (EN + ZH)
Deploy / build (push) Successful in 17s
2026-09-29 01:45:25 +08:00
hoelee 48472e0daa Add 3 posts (EN + ZH): the Synology Spreadsheet API container, the 401-with-correct-password gotcha, and reading a container's own API docs
Deploy / build (push) Successful in 24s
2026-09-29 01:43:00 +08:00
Hermes Agent d88a5f6f2c docs: record the two agent-safety/CDP posts in the project state (Step B2f)
Deploy / build (push) Successful in 29s
2026-09-29 01:40:57 +08:00
Hermes Agent 170f5ccb3f Add 2 posts (EN + ZH): agent-managed Shopee vouchers (propose mode) + why CDP clicks silently fail
Backdated into the empty 2025 stretch of the archive (pubDate 2025-07-08 / 2025-03-19)
with the real date kept in updatedDate 2026-09-29 so sitemap lastmod stays honest.
Custom OG + banner for both; no date- or version-pinned prose in either post.
2026-09-29 01:40:19 +08:00
hoelee e8c5124db1 Add 4 monitoring posts (EN + ZH): smartctl exit 32, Grafana no-data variable, percentage alert thresholds, one Prometheus for 3 hosts
Deploy / build (push) Successful in 28s
Backdated into the 2026-03-25 -> 2026-09-04 archive gap (pubDate 2026-04-14/05-17/06-24/07-29)
with updatedDate 2026-09-29 holding the real date, so sitemap lastmod stays honest.
Custom OG + banner per post, hire CTA, language switch verified.
2026-09-29 01:37:23 +08:00
hoelee d588e9691b docs: record the two SSO posts in the project state (Step B2e)
Deploy / build (push) Successful in 34s
2026-09-29 01:15:48 +08:00
hoelee 2d0de72f51 Add post: NocoDB SSO Is a Licensed Feature (EN + ZH)
Deploy / build (push) Successful in 25s
2026-09-29 01:14:17 +08:00
hoelee e7e4ee58ef Add post: The Forward-Auth Gate That Verified Perfectly — and Wasn't Live (EN + ZH) 2026-09-29 01:14:17 +08:00
hoelee 8dbf124437 Fix pubDate to the actual publish date (2026-09-29) for the Chrome tabs post
Deploy / build (push) Successful in 17s
2026-09-29 01:12:42 +08:00
hoelee 893675e6a3 Add post: Why Chrome Forgets Its Tabs in a Container (EN + ZH)
Deploy / build (push) Successful in 17s
- docker/Chrome/CDP gotcha: the container kills the browser, so it never exits
  cleanly and no restore mechanism fires (flag, Preferences, managed policy all
  verified failing) -> 60s snapshot + replay keeper
- also covers the stale Singleton* lock and custom-cont-init.d permission traps
- custom OG + banner art, EN + ZH, backlog B2 updated
2026-09-29 01:10:39 +08:00
hoelee 1951b02df8 docs: record the dependency-vetting post in the project state (Step B2d)
Deploy / build (push) Successful in 16s
2026-09-29 01:09:39 +08:00
hoelee 87111360ce Add post: How I Vet an Open-Source Dependency Before Betting On It (EN + ZH)
Deploy / build (push) Successful in 15s
New `devops` post on vetting an upstream dependency before building on it:
the six checks that corrected six assumptions from a 1,306-line architecture
spec — repo vital signs via the GitHub API, grepping for the feature instead
of reading for it (OIDC/SSO/SAML → 0 hits), reading a feature doc's target
branch (white-label lives on `multi-tenant`, not `main`), reading the data
model rather than the feature list (percent/fixed vs five assumed rule types),
the Community-vs-Enterprise tier gate (API tokens free, SSO paid), and whether
the money rail works in-country (selfhost + manual payouts, not Stripe Connect).
Closes with the fallback question and the authentik proxy-provider answer.

- EN: src/content/posts/vetting-an-open-source-dependency-before-you-bet-on-it.md
- ZH: src/content/posts/zh/<same slug>.md (same filename → auto language switch)
- Custom OG (1200x630) + banner (1600x900) art via TERMINALS/BANNERS entries
- pubDate 2026-09-29; build verified 93 pages, 17/17 content checks, listing
  order monotonic on /posts/, / and /zh/

Note: the generator maps also carry two entries belonging to parallel
in-flight posts (adding-english-mode…, and the sessions' other slugs);
their post files were left uncommitted.
2026-09-29 01:07:43 +08:00
hoelee a77fb0b5d3 docs: record the English-mode post in the project state
Deploy / build (push) Successful in 28s
2026-09-29 01:06:14 +08:00
hoelee 8a9ba6af1b Add post: No API for Browser Translation (EN + ZH)
Deploy / build (push) Successful in 23s
2026-09-29 01:02:42 +08:00
Hermes Agent 416246cc68 Publish the two CodeIgniter draft posts (EN+ZH) with OG + banner art
Deploy / build (push) Successful in 24s
- migrating-codeigniter-iis-to-openlitespeed (engineering): the two fatal errors
  IIS + SSO had been hiding (env() called from Constants.php; parent::__construct
  in a controller) — both only surfaced on the first unauthenticated request
- upgrading-codeigniter-46-to-47 (notes): the two undefined config properties the
  official upgrade guide cannot warn about (Config\App::$permittedURIChars,
  Config\Format::$jsonEncodeDepth)
- both flipped draft:true -> false, pubDate 2026-09-20 -> 2026-09-27 (real publish date)
- generated 1200x630 OG cards + 1600x900 banners for both slugs
- verified before commit: EN+ZH pages build, language switch links both ways,
  listing order stays monotonic on /posts, /zh and the homepage, banner terminal
  panel centred (gapAbove 102 / gapBelow 104) with no overflow
2026-09-29 01:01:42 +08:00
hoelee d1c040d648 Add post: A Read-Only NocoDB Dashboard for a Database on Another Machine
Deploy / build (push) Successful in 28s
EN + ZH twins. The recipe for pointing NocoDB at a MySQL/MariaDB database on
another machine: the Docker SNAT source-IP trap (the DB sees the host IP, not
the container IP), a SELECT-only grant restricted to that one host, the async
source-creation API with no job-status route, the auto-sync that makes a manual
table step unnecessary (and the create-table route that makes junk tables), and
what a read-only source costs (no metadata edits, UTC-labelled DATETIMEs).

Also adds the og-gen TERMINALS and banner-gen BANNERS entries for the slug
(keeping the sibling session's entries untouched) and the generated PNGs.
2026-09-29 00:57:57 +08:00
hoelee d634385318 Restore 2026 dates on two version-pinned posts
Deploy / build (push) Successful in 4m22s
how-i-host-this-blog and automating-cyberpanel-without-the-ui backdated
cleanly by prose but describe 2026-era software (Gitea 1.27 /
act_runner 0.2.13; CyberPanel 2.4.4.1), so an older byline contradicted
the body. Put both back on their real 2026-09 dates and drop the
updatedDate that only existed to hold that date.
2026-09-21 22:02:15 +08:00
hoelee 2df67b6c50 Fix listing sort order: order by pubDate, matching the date displayed
Deploy / build (push) Successful in 36s
Listings render `pubDate` but sorted by `updatedDate ?? pubDate`, so any
post carrying both dates sorted by a date it never displayed. After the
backdate commit the two diverged by years and the list read out of order
("January 7, 2026" above "September 13, 2026", "March 11, 2026" below
"December 10, 2025").

Replace `sortByUpdated` with `sortForListing`, which orders by `pubDate`
and uses `updatedDate` only as a tiebreak. This matches the RSS feed,
which already sorted by `pubDate`.

Verified all three listings (EN, EN homepage, ZH) are monotonically
non-increasing across the full 2024-09 -> 2026-09 span.
2026-09-21 21:57:10 +08:00
hoelee ae2ccedaad Backdate evergreen posts to build a real archive span (EN+ZH)
Deploy / build (push) Successful in 1m2s
11 evergreen posts had no date- or version-sensitive prose, but all
carried September 2026 publish dates, making the archive look like it
started two weeks ago. Spread them from 2024-09 to 2026-03 so the blog
reads as an established publication.

Per post-guideline.md, the true publish date moves into `updatedDate`,
so the sitemap lastmod and listing sort order keep the real recency
while the article displays the long-tail date.

Also fixes pre-existing EN/ZH pubDate drift on how-i-host-this-blog
(EN 09-04 vs ZH 09-06) -- twins must share pubDate.

Posts left untouched pin themselves in prose (e.g. "In September 2026
a Seagate IronWolf 110...", prompt-expiry dates, model release dates).
2026-09-21 21:36:41 +08:00
hoelee 473a20edf7 Add post: Why I Still Bought a GPU to Run AI When Claude and GPT Are Stronger (EN+ZH)
Deploy / build (push) Successful in 5m13s
2026-09-21 21:17:55 +08:00
hoelee 01672cf6ee Draft: the > character that broke authentik brand CSS (EN+ZH)
Deploy / build (push) Successful in 23s
Held unpublished (draft: true). Records the u003e escaping bug:
authentik renders > in branding_custom_css as the literal text
u003e, so any child combinator produces an invalid selector that
silently matches nothing. Includes the cssRules-based debugging
order and the character safety probe.

Docs: not yet recorded in project-state.md
2026-09-20 09:53:01 +08:00
hoelee 378aff24d0 docs: check off Workbench/MariaDB gotcha post (B2)
Deploy / build (push) Successful in 16s
2026-09-20 04:20:45 +08:00
hoelee a9a91a25d4 Draft bank: 2 CodeIgniter posts (EN+ZH), held unpublished
Deploy / build (push) Successful in 18s
Two finished posts written from the numerology-report migration work, kept as
draft: true so they build no pages and appear in no listing until published.
Content bank for weeks when there is nothing fresh to write.

- migrating-codeigniter-iis-to-openlitespeed (engineering)
  IIS -> OpenLiteSpeed/CyberPanel. The two fatals that only appeared once the
  authentik SSO gate was gone, the docroot public/ separation, and the
  loopback self-call that becomes a real outbound HTTPS request on LiteSpeed.
- upgrading-codeigniter-46-to-47 (notes)
  The two fatal config properties NOT in the official upgrade guide
  (permittedURIChars, jsonEncodeDepth), why Composer never merges app/Config,
  and the property-diff script that finds the whole class of problem at once.

Both fill the starved engineering (1 post) and notes categories. project-state.md
records them as Step B2b with the publish checklist.
2026-09-20 04:19:37 +08:00
hoelee a1488da2d8 Add post: When Your Database Client Lies to You (Workbench 26 / MariaDB)
Deploy / build (push) Successful in 22s
EN + ZH devops gotcha post on debugging MySQL Workbench 26.7.0's failure
to connect to MariaDB. Three patches to Oracle's bundled code, all the
same root cause: `major >= 8` is not a valid MySQL-vs-MariaDB test.

Also adds per-post OG + banner (TERMINALS/BANNERS entries).
2026-09-20 04:18:11 +08:00
hoelee 060b0f1733 docs: record robots.txt encoding rule and sitemap derivation in seo-reference
Deploy / build (push) Successful in 19s
The crawler/sitemap mechanics now live in the repo doc, not just in the
commit log: robots.txt is a build-time endpoint that must stay pure ASCII
(no charset on a text/plain response means non-ASCII renders as mojibake),
the policy is allow-all with enforcement deliberately left to Cloudflare,
why writing a real robots.txt demotes Cloudflare's placeholder from
replacement to prepend, and that lastmod/hreflang are derived so they must
never be hand-authored.
2026-09-19 22:14:40 +08:00