krz/orgo

Lightning fast org-mode static site generator. fast go org-mode static-site-generator

Commit f018f6e1da

f018f6e1dab362139ea30ca74d426f5e51bea24a

parent: 9fdb0c2c02

Unsigned

cmc <hello@cleberg.net> · 2026-08-12 00:22 UTC

Publish .well-known

Deploying a real site with orgo deleted its security.txt. The dot-entry rule
that keeps `.git` and `.env` out of a published site also excluded
`.well-known`, and rsync --delete finished the job on the server.

`.well-known` is the one dot-directory the web defines (RFC 8615). It holds
security.txt, ACME challenges, and other files whose entire purpose is to be
served. It is now the single exception, in both places the rule is applied: the
source tree and asset roots.

Also documents excluding .orgo-cache.json from a deploy, which the same publish
put on the live site. Nothing in it is secret, but it is not part of anyone's
site — and a deploy that *deletes* it remotely is worse than one that copies it,
since the next build then re-renders everything.

Layout: unified · split

docs/guide/10-deploying.org +15
@@ -113,6 +113,21 @@ Both zeros matter. Unresolved links are internal links pointing at nothing; diag
113113are malformed org that degraded rather than failing. With =--strict= neither can reach
114114this line, because either would have failed the build.
115115
116* Do not publish the cache
117
118=<output>/.orgo-cache.json= is a build artefact that happens to live in the output
119directory, because it describes exactly that directory. Nothing breaks if it is served —
120it holds hashes and paths, not secrets — but it is not part of your site, so leave it
121behind:
122
123#+BEGIN_SRC sh
124rsync -r --delete-before --exclude '.orgo-cache.json' _site/ server:/var/www/example.com/
125#+END_SRC
126
127Anything that uploads a directory wholesale needs the same exclusion. A deploy that
128*deletes* it on the far side is worse than one that copies it: the next build then has no
129cache to reuse and re-renders everything.
130
116131* Telling a search engine where things are
117132
118133A build with =site.base_url= set writes =sitemap.xml= at the site root, listing every
src/site.rs +19 −5
@@ -1400,7 +1400,7 @@ fn collect_assets(
14001400 .strip_prefix(&base)
14011401 .map(|p| p.to_owned())
14021402 .unwrap_or_else(|_| abs.clone());
1403 if rel.components().any(|c| c.as_str().starts_with('.')) {
1403 if rel.components().any(|c| is_hidden(c.as_str())) {
14041404 continue;
14051405 }
14061406 assets.push(Asset { from: abs, rel });
@@ -1462,14 +1462,28 @@ fn excluded_dirs(src: &Utf8Path, config: &Config, out: Option<&Utf8Path>) -> Vec
14621462/// Is this source-relative path excluded from discovery?
14631463///
14641464/// Dot-entries are skipped wholesale. That is the conventional rule for site generators,
1465/// Is this path component a dot-entry that must not be published?
1466///
1467/// Dot-directories are excluded because a source directory is very often a git repository,
1468/// and publishing `.git` — or `.env` — leaks a project's entire history alongside its
1469/// homepage. `.well-known` is the exception the web actually defines (RFC 8615): it holds
1470/// `security.txt`, ACME challenges, and other files whose entire purpose is to be served.
1471/// Excluding it is how a deploy quietly deletes a site's security contact.
1472fn is_hidden(component: &str) -> bool {
1473 component.starts_with('.')
1474 && component != "."
1475 && component != ".."
1476 && component != WELL_KNOWN
1477}
1478
1479/// The one dot-directory the web expects to be published.
1480const WELL_KNOWN: &str = ".well-known";
1481
14651482/// and the reason is safety rather than tidiness: a source directory is very often a git
14661483/// repository, and publishing `.git` — or `.env` — is a way to leak a project's entire
14671484/// history alongside its homepage.
14681485fn is_excluded(rel: &Utf8Path, skip_dirs: &[Utf8PathBuf]) -> bool {
1469 if rel
1470 .components()
1471 .any(|c| c.as_str().starts_with('.') && c.as_str() != "." && c.as_str() != "..")
1472 {
1486 if rel.components().any(|c| is_hidden(c.as_str())) {
14731487 return true;
14741488 }
14751489 skip_dirs
tests/config.rs +36
@@ -2463,3 +2463,39 @@ fn the_sitemap_can_be_disabled() {
24632463
24642464 assert!(!out.join("sitemap.xml").exists(), "disabled means absent");
24652465}
2466
2467/// `.well-known` is the one dot-directory the web defines (RFC 8615): `security.txt`,
2468/// ACME challenges, and other files whose whole purpose is to be served. Excluding it
2469/// with the rest is how a deploy silently deletes a site's security contact — which is
2470/// exactly what happened the first time this ran against a real server.
2471#[test]
2472fn well_known_is_published_but_other_dot_entries_are_not() {
2473 let root = tmpdir("wellknown");
2474 let src = root.join("src");
2475 std::fs::create_dir_all(src.join(".well-known")).unwrap();
2476 std::fs::create_dir_all(src.join(".git")).unwrap();
2477 std::fs::create_dir_all(root.join("static/.well-known")).unwrap();
2478 write_site(&src);
2479 std::fs::write(src.join(".well-known/security.txt"), "Contact: mailto:a@b.c\n").unwrap();
2480 std::fs::write(src.join(".git/config"), "[core]\n").unwrap();
2481 std::fs::write(src.join(".env"), "SECRET=1\n").unwrap();
2482 std::fs::write(root.join("static/.well-known/assetlinks.json"), "[]\n").unwrap();
2483 std::fs::write(
2484 src.join("orgo.toml"),
2485 "[build]\nassets = [\"../static\"]\n",
2486 )
2487 .unwrap();
2488 let out = root.join("out");
2489 build(&src, &out);
2490
2491 assert!(
2492 out.join(".well-known/security.txt").exists(),
2493 "from the source directory"
2494 );
2495 assert!(
2496 out.join(".well-known/assetlinks.json").exists(),
2497 "and from an asset root"
2498 );
2499 assert!(!out.join(".git/config").exists(), ".git stays out");
2500 assert!(!out.join(".env").exists(), ".env stays out");
2501}