Преглед на файлове

Add static home page, nginx vhost and setup README

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude преди 12 часа
родител
ревизия
4d461fa58f
променени са 5 файла, в които са добавени 378 реда и са изтрити 1 реда
  1. 96 1
      README.md
  2. 37 0
      nginx/monoexp.com.conf
  3. 12 0
      public/favicon.svg
  4. 83 0
      public/index.html
  5. 150 0
      public/style.css

+ 96 - 1
README.md

@@ -1,3 +1,98 @@
 # home
 
-Home page for monoexp.com. Showcase linking to different projects.
+Home page for monoexp.com. Showcase linking to different projects.
+
+Plain HTML and CSS, no build step and no JavaScript.
+
+## Layout
+
+```
+public/            what gets deployed (the site root)
+  index.html       the page: about section, then project cards
+  style.css        all styles, light and dark via prefers-color-scheme
+  favicon.svg
+nginx/
+  monoexp.com.conf reference copy of the server's vhost file
+```
+
+## Adding a project
+
+Copy one `<li class="project">` block in `public/index.html` and change:
+
+- the title in `<h3>`
+- the tag (language or kind), e.g. `Go`
+- the one- or two-sentence description
+- the **Visit** link to `https://<name>.monoexp.com/` (leave it out for source-only projects)
+- the **Source** link to `https://git.monoexp.com/bajdev/<repo>`
+
+Open a PR; merging it deploys the page once the site is on the pipeline.
+
+## Hosting
+
+| | |
+|---|---|
+| Canonical URL | `https://monoexp.com` |
+| `www.monoexp.com` | 301 redirect to the apex |
+| Site root | `/srv/www/vhosts/monoexp.com` |
+| nginx file | `/etc/nginx/vhosts.d/monoexp.com.conf` |
+| Certificate | `monoexp-home` (covers `monoexp.com` and `www.monoexp.com`) |
+
+DNS needs no change: the `@` and `*` A records at Hover already point here.
+
+## First-time setup (as `bajdev` over SSH)
+
+1. **Certificate.** The `issue-cert` wrapper only accepts subdomains, so the
+   apex goes through certbot directly:
+
+   ```bash
+   sudo certbot certonly --webroot -w /srv/www/acme --cert-name monoexp-home \
+     -d monoexp.com -d www.monoexp.com --deploy-hook 'systemctl reload nginx'
+   ```
+
+   Port 80 already serves `/.well-known/acme-challenge/` for every
+   `.monoexp.com` name, the apex included.
+
+2. **Site folder and first deploy.**
+
+   ```bash
+   mkdir /srv/www/vhosts/monoexp.com
+   git clone git@git.monoexp.com:bajdev/home.git ~/src/home
+   rsync -a --delete ~/src/home/public/ /srv/www/vhosts/monoexp.com/
+   ```
+
+   (From Claude's account the same step is
+   `deploy-static monoexp.com ~/src/home/public`; pass the source folder
+   explicitly, since the script's default would look for `~/src/monoexp/public`.)
+
+3. **nginx.**
+
+   ```bash
+   sudo install -o root -g root -m 644 ~/src/home/nginx/monoexp.com.conf /etc/nginx/vhosts.d/
+   sudo nginx -t && sudo systemctl reload nginx
+   ```
+
+4. **Check.**
+
+   ```bash
+   curl -sI https://monoexp.com | head -1          # HTTP/2 200
+   curl -sI https://www.monoexp.com | grep -i location   # https://monoexp.com/
+   curl -sI http://monoexp.com | grep -i location        # https://monoexp.com/
+   ```
+
+## Merge-to-deploy
+
+Same steps as any other site on the pipeline (see the VPS setup plan, Phase 7):
+
+1. In `bajdev/devops`, add `bajdev/home` to `sites.json` through a PR, modelled
+   on the existing static entry for gotest, with host `monoexp.com` and
+   content folder `public`. Merge, then reinstall the file:
+   `sudo install -o root -g root -m 644 sites.json /etc/deployd/sites.json`
+2. Hand the deployed files to the deploy user:
+   `sudo chown -R deploy /srv/www/vhosts/monoexp.com`
+3. In Gogs, on `bajdev/home`: add `deploy` (and `claude`, if Claude will work
+   on it) as Write collaborators, turn on branch protection for `main`, and
+   add a webhook: payload URL `http://127.0.0.1:9000/deploy`, content type
+   `application/json`, the secret from `/etc/deployd/secret.env`, and only the
+   **Pull Request** event.
+4. Merge a small test PR and check for a `deploy/…` tag and a line in
+   `/var/log/deploys.log`.

+ 37 - 0
nginx/monoexp.com.conf

@@ -0,0 +1,37 @@
+# /etc/nginx/vhosts.d/monoexp.com.conf
+#
+# monoexp.com is canonical; www.monoexp.com redirects to it.
+# Port 80 (ACME challenges and the HTTP->HTTPS redirect) is already handled
+# by the `.monoexp.com` block in 00-default.conf, which matches the apex too.
+#
+# Certificate: one cert named "monoexp-home" covering both names. The
+# issue-cert wrapper only accepts subdomains, so issue it directly as bajdev:
+#   sudo certbot certonly --webroot -w /srv/www/acme --cert-name monoexp-home \
+#     -d monoexp.com -d www.monoexp.com --deploy-hook 'systemctl reload nginx'
+
+# www -> apex
+server {
+    listen 443 ssl; listen [::]:443 ssl;
+    server_name www.monoexp.com;
+    ssl_certificate     /etc/letsencrypt/live/monoexp-home/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/monoexp-home/privkey.pem;
+    include snippets/tls-common.conf;
+    return 301 https://monoexp.com$request_uri;
+}
+
+# apex: the home page
+server {
+    listen 443 ssl; listen [::]:443 ssl;
+    server_name monoexp.com;
+    ssl_certificate     /etc/letsencrypt/live/monoexp-home/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/monoexp-home/privkey.pem;
+    include snippets/tls-common.conf;
+
+    root /srv/www/vhosts/monoexp.com;
+    index index.html;
+
+    location / { try_files $uri $uri/ =404; }
+
+    # Short cache for the stylesheet and icon; HTML keeps nginx's defaults.
+    location ~* \.(css|svg)$ { expires 1h; }
+}

+ 12 - 0
public/favicon.svg

@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+  <style>
+    rect { fill: #1c1c1a; }
+    path { stroke: #fafaf9; }
+    @media (prefers-color-scheme: dark) {
+      rect { fill: #ececea; }
+      path { stroke: #121211; }
+    }
+  </style>
+  <rect width="32" height="32" rx="7"/>
+  <path d="M8 23V10l8 8 8-8v13" fill="none" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 83 - 0
public/index.html

@@ -0,0 +1,83 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Mono Experiments</title>
+  <meta name="description" content="Mono Experiments: a small self-hosted workshop for side projects. Live sites and their source.">
+  <meta name="color-scheme" content="light dark">
+  <link rel="icon" href="/favicon.svg" type="image/svg+xml">
+  <link rel="stylesheet" href="/style.css">
+</head>
+<body>
+  <header class="site-header">
+    <div class="wrap header-inner">
+      <a class="wordmark" href="/">mono<span>exp</span></a>
+      <nav aria-label="Main">
+        <a href="#projects">Projects</a>
+        <a href="https://git.monoexp.com/">Git</a>
+      </nav>
+    </div>
+  </header>
+
+  <main class="wrap">
+    <section id="about" class="about" aria-labelledby="about-title">
+      <h1 id="about-title">Mono Experiments</h1>
+      <p class="lede">
+        A small, self-hosted workshop for side projects.
+      </p>
+      <p>
+        Everything here runs on a single server. Code lives in a self-hosted Git
+        service, and merging a pull request builds and deploys it automatically.
+        Some projects are finished; most are works in progress. Each one below
+        links to the live site and to its source.
+      </p>
+    </section>
+
+    <section id="projects" aria-labelledby="projects-title">
+      <h2 id="projects-title">Projects</h2>
+
+      <ul class="projects">
+        <!--
+          To add a project, copy one <li class="project"> block.
+          Leave out the "Visit" link for source-only projects.
+        -->
+        <li class="project">
+          <div class="project-head">
+            <h3>Posts</h3>
+            <span class="tag">Go</span>
+          </div>
+          <p>
+            A simple blog for write-ups. Posts are written in lightly formatted
+            Markdown and served as plain HTML with basic navigation.
+          </p>
+          <div class="links">
+            <a href="https://posts.monoexp.com/">Visit <span aria-hidden="true">&rarr;</span><span class="sr-only"> Posts</span></a>
+            <a href="https://git.monoexp.com/bajdev/posts">Source<span class="sr-only"> for Posts</span></a>
+          </div>
+        </li>
+
+        <li class="project">
+          <div class="project-head">
+            <h3>DevOps</h3>
+            <span class="tag">Go &middot; Bash</span>
+          </div>
+          <p>
+            The tooling behind this server's merge-to-deploy pipeline: a webhook
+            receiver, a release script and a certificate helper.
+          </p>
+          <div class="links">
+            <a href="https://git.monoexp.com/bajdev/devops">Source<span class="sr-only"> for DevOps</span></a>
+          </div>
+        </li>
+      </ul>
+    </section>
+  </main>
+
+  <footer class="site-footer">
+    <div class="wrap">
+      <p>&copy; 2026 Mono Experiments &middot; <a href="https://git.monoexp.com/bajdev/home">Source for this page</a></p>
+    </div>
+  </footer>
+</body>
+</html>

+ 150 - 0
public/style.css

@@ -0,0 +1,150 @@
+/* monoexp.com — one stylesheet, no framework */
+
+:root {
+  --bg: #fafaf9;
+  --surface: #ffffff;
+  --text: #1c1c1a;
+  --muted: #5f5f5a;
+  --border: #e4e4e0;
+  --accent: #1c1c1a;
+  --tag-bg: #f0f0ed;
+  --focus: #2f6fdb;
+
+  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+  --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
+  --radius: 10px;
+  --max: 760px;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --bg: #121211;
+    --surface: #1a1a19;
+    --text: #ececea;
+    --muted: #a3a39e;
+    --border: #2c2c2a;
+    --accent: #ececea;
+    --tag-bg: #252524;
+    --focus: #7aa7f5;
+  }
+}
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
+
+body {
+  margin: 0;
+  background: var(--bg);
+  color: var(--text);
+  font: 17px/1.6 var(--sans);
+  -webkit-font-smoothing: antialiased;
+}
+
+a { color: inherit; text-underline-offset: 3px; }
+a:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; border-radius: 4px; }
+
+.wrap {
+  max-width: var(--max);
+  margin: 0 auto;
+  padding: 0 20px;
+}
+
+.sr-only {
+  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
+  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
+}
+
+/* Header */
+.site-header { border-bottom: 1px solid var(--border); }
+.header-inner {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  height: 64px;
+}
+.wordmark {
+  font: 600 18px/1 var(--mono);
+  letter-spacing: -0.02em;
+  text-decoration: none;
+}
+.wordmark span { color: var(--muted); }
+nav { display: flex; gap: 20px; }
+nav a {
+  font-size: 15px;
+  color: var(--muted);
+  text-decoration: none;
+}
+nav a:hover { color: var(--text); }
+
+/* About */
+.about { padding: 56px 0 40px; }
+.about h1 {
+  margin: 0 0 12px;
+  font-size: clamp(30px, 6vw, 42px);
+  line-height: 1.15;
+  letter-spacing: -0.03em;
+}
+.about p { margin: 0 0 14px; color: var(--muted); max-width: 62ch; }
+.about .lede { font-size: 20px; color: var(--text); }
+
+/* Projects */
+#projects { padding-bottom: 64px; scroll-margin-top: 16px; }
+#projects h2 {
+  margin: 0 0 16px;
+  font: 600 13px/1 var(--mono);
+  text-transform: uppercase;
+  letter-spacing: 0.08em;
+  color: var(--muted);
+}
+.projects {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+  display: grid;
+  grid-template-columns: 1fr;
+  gap: 16px;
+}
+@media (min-width: 640px) {
+  .projects { grid-template-columns: 1fr 1fr; }
+}
+.project {
+  display: flex;
+  flex-direction: column;
+  background: var(--surface);
+  border: 1px solid var(--border);
+  border-radius: var(--radius);
+  padding: 20px;
+}
+.project-head {
+  display: flex;
+  align-items: baseline;
+  justify-content: space-between;
+  gap: 12px;
+  margin-bottom: 8px;
+}
+.project h3 { margin: 0; font-size: 19px; letter-spacing: -0.01em; }
+.tag {
+  flex-shrink: 0;
+  font: 12px/1 var(--mono);
+  color: var(--muted);
+  background: var(--tag-bg);
+  padding: 5px 8px;
+  border-radius: 6px;
+}
+.project p { margin: 0 0 18px; color: var(--muted); font-size: 15.5px; }
+.links {
+  margin-top: auto;
+  display: flex;
+  gap: 18px;
+  font-size: 15px;
+  font-weight: 500;
+}
+.links a { text-decoration: none; border-bottom: 1px solid var(--border); padding-bottom: 1px; }
+.links a:hover { border-color: var(--accent); }
+
+/* Footer */
+.site-footer { border-top: 1px solid var(--border); }
+.site-footer p { margin: 0; padding: 24px 0 32px; font-size: 14px; color: var(--muted); }