Quellcode durchsuchen

Merge branch 'chore/monoexp-domain' of bajdev/posts into main

bajdev vor 2 Stunden
Ursprung
Commit
355803289e

+ 5 - 5
README.md

@@ -1,6 +1,6 @@
 # posts
 
-A small Go application that serves a Markdown blog at <https://posts.monoaf.com>.
+A small Go application that serves a Markdown blog at <https://posts.monoexp.com>.
 
 Each Markdown file in `content/` is one blog post. The app reads the files from
 disk, converts them to HTML, and serves them with simple navigation. Adding or
@@ -64,7 +64,7 @@ and take effect without a restart.
 {
   "homeText": "Home",
   "defaultAuthor": "Claude",
-  "repoURL": "https://git.monoaf.com/bajdev/posts"
+  "repoURL": "https://git.monoexp.com/bajdev/posts"
 }
 ```
 
@@ -72,7 +72,7 @@ and take effect without a restart.
 |---|---|---|
 | `homeText` | `Home` | Text of the header link to the index page |
 | `defaultAuthor` | `Claude` | Author shown for posts without an `author` line |
-| `repoURL` | `https://git.monoaf.com/bajdev/posts` | Source repo; the About page links the running commit to `<repoURL>/commit/<hash>` |
+| `repoURL` | `https://git.monoexp.com/bajdev/posts` | Source repo; the About page links the running commit to `<repoURL>/commit/<hash>` |
 
 A missing file, a missing setting or a blank value uses the default. If the
 file is invalid JSON or has an unknown setting name, the error is logged and
@@ -86,7 +86,7 @@ Environment variables (on the server, in `/etc/goapps/posts.env`):
 |---|---|---|
 | `PORT` | (required) | Port to listen on, bound to `127.0.0.1` |
 | `CONTENT_DIR` | `content` | Directory holding the `.md` posts and `site.json` |
-| `SITE_URL` | `https://posts.monoaf.com` | Absolute base URL used in the RSS feed |
+| `SITE_URL` | `https://posts.monoexp.com` | Absolute base URL used in the RSS feed |
 
 ## Development
 
@@ -104,7 +104,7 @@ a user-visible change. The git commit is added automatically at build time.
 Runs as systemd unit `goapp@posts` on port 8002 behind nginx.
 
 Merging a pull request into `main` deploys it automatically. The pipeline in
-[bajdev/devops](https://git.monoaf.com/bajdev/devops) builds the merge commit,
+[bajdev/devops](https://git.monoexp.com/bajdev/devops) builds the merge commit,
 syncs `content/` to `/opt/apps/posts/content/`, restarts the app, and tags
 the release `deploy/posts/<UTC time>`. There's no manual deploy step.
 

+ 1 - 1
about.md

@@ -65,7 +65,7 @@ after merging, set `draft: true`.
 ## What Markdown works
 
 - **Bold**, *italic*, ~~strikethrough~~ and `inline code`
-- Links like https://posts.monoaf.com are linked automatically
+- Links like https://posts.monoexp.com are linked automatically
 - Headings, lists, quotes, images, tables, task lists and fenced code blocks
 
 | Feature | Supported |

+ 1 - 1
config.go

@@ -24,7 +24,7 @@ func (c siteConfig) withDefaults() siteConfig {
 		c.HomeText = "Home"
 	}
 	if strings.TrimSpace(c.RepoURL) == "" {
-		c.RepoURL = "https://git.monoaf.com/bajdev/posts"
+		c.RepoURL = "https://git.monoexp.com/bajdev/posts"
 	}
 	if strings.TrimSpace(c.DefaultAuthor) == "" {
 		c.DefaultAuthor = "Claude"

+ 1 - 1
content/2026-10-24-hello-world.md

@@ -71,7 +71,7 @@ after merging, set `draft: true`.
 ## What Markdown works
 
 - **Bold**, *italic*, ~~strikethrough~~ and `inline code`
-- Links like https://posts.monoaf.com are linked automatically
+- Links like https://posts.monoexp.com are linked automatically
 - Headings, lists, quotes, images, tables, task lists and fenced code blocks
 
 | Feature | Supported |

+ 7 - 7
content/2026-10-24-vps-part-1-foundation.md

@@ -27,7 +27,7 @@ what on it, how web traffic reaches an app, and how apps run.
 | OS | openSUSE Leap 16.0 (kernel 6.12, systemd 257) |
 | Web server | nginx 1.27 |
 | TLS | Let's Encrypt via certbot 5.1 |
-| DNS | `monoaf.com` at Hover |
+| DNS | `monoexp.com` at Hover |
 
 Provisioning, SSH hardening and the firewall: **manual setup by admin**.
 DNS records are also added by hand at Hover, one per host, before anything
@@ -60,9 +60,9 @@ and permissions, and nobody needs sudo just to copy a file into place.
 │   └── proxy.conf         headers for reverse proxying
 └── vhosts.d/              one file per host (root-owned)
     ├── 00-default.conf
-    ├── git.monoaf.com.conf
-    ├── gotest.monoaf.com.conf
-    └── posts.monoaf.com.conf
+    ├── git.monoexp.com.conf
+    ├── gotest.monoexp.com.conf
+    └── posts.monoexp.com.conf
 
 /etc/letsencrypt/live/<host>/   certificate and key per host
 /etc/goapps/<name>.env          per-app settings (PORT=...)
@@ -109,7 +109,7 @@ server { listen 80 default_server; listen [::]:80 default_server; return 444; }
 server { listen 443 ssl default_server; listen [::]:443 ssl default_server; ssl_reject_handshake on; }
 server {
     listen 80; listen [::]:80;
-    server_name .monoaf.com;
+    server_name .monoexp.com;
     location /.well-known/acme-challenge/ { root /srv/www/acme; }
     location / { return 301 https://$host$request_uri; }
 }
@@ -119,7 +119,7 @@ server {
   the connection without a response.
 - On port 443, an unknown host has its TLS handshake refused, so the server
   never shows a certificate for a name it doesn't serve.
-- Any `*.monoaf.com` host on port 80 serves ACME challenges from one shared
+- Any `*.monoexp.com` host on port 80 serves ACME challenges from one shared
   folder and redirects everything else to HTTPS. A new host can get its
   certificate before it has its own nginx file.
 
@@ -149,7 +149,7 @@ proxy_set_header Connection "upgrade";
 ## Certificates
 
 Once the DNS record exists, a certificate is one command. The agent runs a
-small root-owned wrapper that accepts a single `*.monoaf.com` hostname:
+small root-owned wrapper that accepts a single `*.monoexp.com` hostname:
 
 ```sh
 sudo /usr/local/sbin/issue-cert <host>

+ 11 - 11
content/2026-10-24-vps-part-2-deploy-tooling.md

@@ -73,7 +73,7 @@ rsync -a --delete "$src/" "/srv/www/vhosts/$host/"
 ```
 
 `${host%%.*}` takes everything before the first dot, so
-`deploy-static gotest.monoaf.com` deploys `~/src/gotest/public/`. No
+`deploy-static gotest.monoexp.com` deploys `~/src/gotest/public/`. No
 restart is needed: nginx serves the files directly.
 
 ## The rules, written down
@@ -99,22 +99,22 @@ The port table is a plain Markdown file:
 
 | Port | Service | Notes |
 |---|---|---|
-| 3000 | Gogs | git.monoaf.com |
-| 8001 | gotest | gotest.monoaf.com/api/ |
-| 8002 | posts | posts.monoaf.com |
+| 3000 | Gogs | git.monoexp.com |
+| 8001 | gotest | gotest.monoexp.com/api/ |
+| 8002 | posts | posts.monoexp.com |
 | 9000 | deploy receiver | reserved |
 
 Writing the rules down turned out to matter as much as the scripts. Most of
 the later process (part 5) is just more lines in this file.
 
-## A scratch host: gotest.monoaf.com
+## A scratch host: gotest.monoexp.com
 
 Before deploying anything real, one host was set aside for trying deploys.
 It runs both kinds of site at once:
 
 ```
-gotest.monoaf.com
-├── /        static page   ← deploy-static gotest.monoaf.com
+gotest.monoexp.com
+├── /        static page   ← deploy-static gotest.monoexp.com
 └── /api/    Go app :8001  ← deploy-go gotest
 ```
 
@@ -125,13 +125,13 @@ itself for the Go build.
 ```nginx
 server {
     listen 443 ssl; listen [::]:443 ssl;
-    server_name gotest.monoaf.com;
+    server_name gotest.monoexp.com;
 
-    ssl_certificate     /etc/letsencrypt/live/gotest.monoaf.com/fullchain.pem;
-    ssl_certificate_key /etc/letsencrypt/live/gotest.monoaf.com/privkey.pem;
+    ssl_certificate     /etc/letsencrypt/live/gotest.monoexp.com/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/gotest.monoexp.com/privkey.pem;
     include snippets/tls-common.conf;
 
-    root /srv/www/vhosts/gotest.monoaf.com;
+    root /srv/www/vhosts/gotest.monoexp.com;
     index index.html;
 
     location / { try_files $uri $uri/ =404; }

+ 8 - 8
content/2026-10-24-vps-part-3-gogs.md

@@ -11,7 +11,7 @@ scripts.*
 
 Code needs somewhere to live and be reviewed. Instead of a hosted service,
 the server runs its own git host: [Gogs](https://gogs.io), at
-`git.monoaf.com`. It's a single Go binary with a web UI for repositories,
+`git.monoexp.com`. It's a single Go binary with a web UI for repositories,
 issues and pull requests, and it's light enough to share a 4 GB machine with
 everything else.
 
@@ -31,12 +31,12 @@ the server:
 
 - **Web UI over HTTPS.** nginx terminates TLS and proxies to Gogs on
   `127.0.0.1:3000`. Gogs isn't reachable from outside except through nginx.
-- **git over SSH on port 22.** Clones and pushes go to `git@git.monoaf.com`
+- **git over SSH on port 22.** Clones and pushes go to `git@git.monoexp.com`
   through the system's SSH server. Gogs matches the key to an account and
   checks repository permissions. Logging in for a shell isn't possible:
 
   ```
-  $ ssh -T git@git.monoaf.com
+  $ ssh -T git@git.monoexp.com
   Hi there, You've successfully authenticated, but Gogs does not provide shell access.
   ```
 
@@ -65,9 +65,9 @@ The nginx file is the same shape as every other site, with one addition:
 ```nginx
 server {
     listen 443 ssl; listen [::]:443 ssl;
-    server_name git.monoaf.com;
-    ssl_certificate     /etc/letsencrypt/live/git.monoaf.com/fullchain.pem;
-    ssl_certificate_key /etc/letsencrypt/live/git.monoaf.com/privkey.pem;
+    server_name git.monoexp.com;
+    ssl_certificate     /etc/letsencrypt/live/git.monoexp.com/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/git.monoexp.com/privkey.pem;
     include snippets/tls-common.conf;
     client_max_body_size 512m;
     location / {
@@ -84,7 +84,7 @@ UI.
 ## How the agent gets access
 
 The agent has a Gogs account with write access to the blog's repository,
-[`bajdev/posts`](https://git.monoaf.com/bajdev/posts), and an SSH key on
+[`bajdev/posts`](https://git.monoexp.com/bajdev/posts), and an SSH key on
 the server. That's all it has: no password for the web UI and no API token.
 SSH is enough to clone, push branches, delete branches and push tags, which
 covers everything the workflow needs.
@@ -102,7 +102,7 @@ branches.
  agent                               admin
  ─────                               ─────
  push feature/x ───────────────────▶ open compare link:
-                                      git.monoaf.com/bajdev/posts/
+                                      git.monoexp.com/bajdev/posts/
                                         compare/main...feature/x
                                      click "New Pull Request"
                                      review, then merge

+ 4 - 4
content/2026-10-24-vps-part-4-building-posts.md

@@ -1,5 +1,5 @@
 ---
-title: "VPS part 4: Building posts.monoaf.com"
+title: "VPS part 4: Building posts.monoexp.com"
 date: 2026-10-24T21:03:00-04:00
 summary: The blog you're reading — a Go app that turns a folder of Markdown files into a site, with no database and no JavaScript.
 author: Claude
@@ -11,7 +11,7 @@ an AI agent. [Part 3](/posts/vps-part-3-gogs) covers the git server.*
 The first real app on the server is this blog. The brief was short: a Go
 app that serves a folder of Markdown files, one file per post, with simple
 navigation. The source is at
-[git.monoaf.com/bajdev/posts](https://git.monoaf.com/bajdev/posts) under the
+[git.monoexp.com/bajdev/posts](https://git.monoexp.com/bajdev/posts) under the
 MIT license.
 
 ## The stack
@@ -80,7 +80,7 @@ Each post is `content/YYYY-MM-DD-slug.md`, with an optional header block:
 
 ```markdown
 ---
-title: VPS part 4: Building posts.monoaf.com
+title: VPS part 4: Building posts.monoexp.com
 date: 2026-10-24T21:03:00-04:00
 summary: One line for the index page and the feed.
 author: Claude
@@ -198,7 +198,7 @@ Two kinds of settings, kept apart:
 The blog uses everything from parts 1 and 2 without any special cases:
 
 - runs as `goapp@posts` on port 8002, with its env file
-- an nginx file proxying `posts.monoaf.com` to `127.0.0.1:8002`
+- an nginx file proxying `posts.monoexp.com` to `127.0.0.1:8002`
 - deployed with `deploy-go posts --content`, which builds the binary and
   syncs `content/`
 

+ 7 - 7
content/2026-10-25-sudo-hardening.md

@@ -96,20 +96,20 @@ every option and accepts one hostname:
 ```bash
 #!/bin/bash
 # issue-cert <host> — request a Let's Encrypt cert using the shared
-# ACME webroot. Accepts a single *.monoaf.com hostname.
+# ACME webroot. Accepts a single *.monoexp.com hostname.
 set -euo pipefail
 PATH=/usr/sbin:/usr/bin:/sbin:/bin
 [[ $# -eq 1 ]] || { echo "usage: issue-cert <host>" >&2; exit 2; }
 host=$1
-[[ $host =~ ^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+monoaf\.com$ ]] \
-  || { echo "issue-cert: '$host' is not a monoaf.com hostname" >&2; exit 2; }
+[[ $host =~ ^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+monoexp\.com$ ]] \
+  || { echo "issue-cert: '$host' is not a monoexp.com hostname" >&2; exit 2; }
 exec certbot certonly --non-interactive --webroot -w /srv/www/acme \
   -d "$host" --deploy-hook 'systemctl reload nginx'
 ```
 
 There are two layers here. sudo's pattern refuses anything with a space or
 an unusual character, so no extra options get through. The script then
-refuses any hostname outside `monoaf.com`. Renewals don't need the agent
+refuses any hostname outside `monoexp.com`. Renewals don't need the agent
 at all: certbot's own timer runs them as root.
 
 ### Logs without sudo
@@ -154,8 +154,8 @@ The test was the one that matters: try each route and see it refused.
 | `systemctl restart 'goapp@posts;id'` | refused |
 | `systemctl reload gogs`, `daemon-reload` | refused |
 | `certbot --version`, `certbot renew --pre-hook id` | refused |
-| `issue-cert 'x.monoaf.com --pre-hook id'` | refused by sudo's pattern |
-| `issue-cert example.com`, `issue-cert -d.monoaf.com` | rejected by the script |
+| `issue-cert 'x.monoexp.com --pre-hook id'` | refused by sudo's pattern |
+| `issue-cert example.com`, `issue-cert -d.monoexp.com` | rejected by the script |
 | `journalctl` and `nginx -T` through sudo | refused |
 | creating a file in `vhosts.d/` | permission denied |
 
@@ -163,7 +163,7 @@ And the work still gets done:
 
 | Task | Result |
 |---|---|
-| `issue-cert gotest.monoaf.com` | "not yet due for renewal; no action taken" |
+| `issue-cert gotest.monoexp.com` | "not yet due for renewal; no action taken" |
 | `certbot certificates` | lists all three certificates |
 | `nginx -t`, `systemctl reload nginx` | both succeed |
 | a real `deploy-go posts --content` | `active` |

+ 1 - 1
content/2026-10-26-anatomy-of-a-change.md

@@ -184,7 +184,7 @@ build	vcs.modified=false
 and loaded the About page from it:
 
 ```html
-<dd>0.5.3 <span class="muted">(<a href="https://git.monoaf.com/bajdev/posts/commit/a756b16dc9…">a756b16</a>)</span></dd>
+<dd>0.5.3 <span class="muted">(<a href="https://git.monoexp.com/bajdev/posts/commit/a756b16dc9…">a756b16</a>)</span></dd>
 ```
 
 Then a screenshot from headless Chromium, which I looked at before going

+ 1 - 1
content/2026-10-26-deploy-pipeline.md

@@ -17,7 +17,7 @@ reviewed and merged it, and then I ran `deploy-go posts --content` by hand
 shipped; a separate step, run by me, decided *when*.
 
 Now the merge is the release. The source is public in
-[bajdev/devops](https://git.monoaf.com/bajdev/devops).
+[bajdev/devops](https://git.monoexp.com/bajdev/devops).
 
 ```
  PR merged in Gogs

+ 1 - 1
go.mod

@@ -1,4 +1,4 @@
-module git.monoaf.com/bajdev/posts
+module git.monoexp.com/bajdev/posts
 
 go 1.27.3
 

+ 1 - 1
main.go

@@ -188,7 +188,7 @@ func main() {
 	if port == "" {
 		log.Fatal("PORT not set")
 	}
-	a, err := newApp(getenv("CONTENT_DIR", "content"), getenv("SITE_URL", "https://posts.monoaf.com"))
+	a, err := newApp(getenv("CONTENT_DIR", "content"), getenv("SITE_URL", "https://posts.monoexp.com"))
 	if err != nil {
 		log.Fatal(err)
 	}

+ 1 - 1
main_test.go

@@ -325,7 +325,7 @@ func TestAboutCommitLink(t *testing.T) {
 
 	t.Run("clean build links the full hash", func(t *testing.T) {
 		body := about(t, commitInfo{Full: full, Short: full[:7]}, "")
-		want := `<span class="muted">(<a href="https://git.monoaf.com/bajdev/posts/commit/` + full + `">d2a387e</a>)</span>`
+		want := `<span class="muted">(<a href="https://git.monoexp.com/bajdev/posts/commit/` + full + `">d2a387e</a>)</span>`
 		if !strings.Contains(body, want) {
 			t.Errorf("missing %s in:\n%s", want, body)
 		}

+ 2 - 2
pull-releases/010-commit-link/README.md

@@ -4,8 +4,8 @@ Branch `feature/commit-link` · version 0.5.3
 
 ## What changed
 
-- **Linked hash:** the About page's build hash, e.g. `0.5.3 (d2a387e)`, now links to that commit in Gogs. The link uses the full hash: `https://git.monoaf.com/bajdev/posts/commit/<full hash>`.
-- **New `repoURL` setting:** optional, in `content/site.json`, defaulting to `https://git.monoaf.com/bajdev/posts`. Anyone running a fork of the code can point the link at their own repo.
+- **Linked hash:** the About page's build hash, e.g. `0.5.3 (d2a387e)`, now links to that commit in Gogs. The link uses the full hash: `https://git.monoexp.com/bajdev/posts/commit/<full hash>`.
+- **New `repoURL` setting:** optional, in `content/site.json`, defaulting to `https://git.monoexp.com/bajdev/posts`. Anyone running a fork of the code can point the link at their own repo.
 - **Dirty builds aren't linked.** A binary built from uncommitted changes matches no commit, so it still shows `(abc1234-dirty)` as plain text. Pipeline builds always come from a clean checkout, so the live site always has the link.
 - **README:** documents `repoURL`.
 - **Version:** 0.5.3.