Add gopher documentation to README and execution notes

- Update README with gopher/phlog section, publishing commands, URLs,
  and first-time SDF setup instructions
- Add gopher workflow documentation to execution-notes.txt
- Include planning docs in docs/ directory
This commit is contained in:
mnw
2026-01-14 16:24:42 -06:00
parent b7ac21093a
commit 69d6b370b2
4 changed files with 1019 additions and 4 deletions
+697
View File
@@ -0,0 +1,697 @@
# Gopher Integration Plan
> **Status:** Planning
> **Created:** 2025-01-13
> **Goal:** Publish Hugo blog posts to SDF gopherspace with `phlog: true` frontmatter flag
---
## Overview
Enhance the marcus-web project to deploy selected blog posts to both the Hugo site (mnw.sdf.org) and the SDF gopherspace (gopher://sdf.org/1/users/mnw/). Posts are opted-in via frontmatter, converted from markdown to gopher-friendly plain text, and organized by series/category.
---
## Current State
### Hugo Site (mnw.sdf.org)
- **Series:** "Frank's Couch" (movies), "Fun Center" (tech), beercall content
- **Frontmatter:** title, date, series, summary, tags, draft
- **Deploy:** `remote_publish.sh` → SSH to SDF → git pull && hugo && mkhomepg -p
### Existing Gopherspace (/sdf/arpa/gm/m/mnw/gopher)
- Rich structure with ASCII art headers and footers
- Categories as directories: `journal/`, `older/`, `LISA19/`, `hope_2024/`, etc.
- Dynamic CGI: latest phlog, user info via `=echo` directives
- Template style established in `templates/phlogpost.txt`
---
## Design Decisions
| Decision | Choice | Rationale |
|----------|--------|-----------|
| Opt-in mechanism | `phlog: true` frontmatter | Explicit control per post |
| Category mapping | Hugo series → gopher directories | Natural organization |
| Beercall handling | One gopher file per year | Matches existing yearly logs |
| Movie poster | Link to web blog post | Gopher can't display images |
| Movie metadata | ASCII table | Old-school aesthetic |
| ASCII art headers | Category-specific art | Visual distinction, same size as current |
| Root gophermap | Script-managed with makeover | Cohesive, automated experience |
| Sync strategy | rsync with --delete | Git-tracked, reversible |
---
## Directory Structure
### On SDF (~/gopher aka /sdf/arpa/gm/m/mnw/gopher)
```
~/gopher/
├── gophermap # Root menu (UPDATED by script)
├── journal/ # Existing daily phlogs
├── older/ # Existing historical
├── LISA19/ # Existing conference notes
├── hope_2024/ # Existing conference notes
├── blog/ # NEW: Hugo-sourced content
│ ├── gophermap # Blog index
│ ├── franks-couch/ # Movie reviews
│ │ ├── gophermap # Category index with summaries
│ │ ├── megalopolis.txt
│ │ ├── joker-folie-a-deux.txt
│ │ └── ...
│ ├── fun-center/ # Tech posts
│ │ ├── gophermap
│ │ ├── is-it-me.txt
│ │ └── ...
│ └── beercalls/ # Yearly beer logs
│ ├── gophermap
│ ├── 2023.txt
│ └── 2024.txt
└── templates/ # Existing templates
```
### Local Build Directory
```
marcus-web/
├── gopher_build/ # Staging area (gitignored)
│ └── blog/
│ ├── gophermap
│ ├── franks-couch/
│ ├── fun-center/
│ └── beercalls/
```
---
## Frontmatter Extension
Add `phlog` field to any post:
```yaml
---
title: 'Megalopolis'
date: 2024-10-05T00:03:15Z
draft: false
series: "Frank's Couch"
summary: "A sprawling mess of ambition"
phlog: true # <-- NEW: publish to gopher
imdb: "tt10128846"
poster: "/images/posters/megalopolis.jpg"
year: 2024
runtime: 138
director: "Francis Ford Coppola"
genres:
- Drama
- Sci-Fi
tags:
- alamo-drafthouse
---
```
**Behavior:**
- `phlog: true` → Convert and deploy to gopher
- `phlog: false` or absent → Hugo only (web exclusive)
---
## Content Conversion
### Markdown → Gopher Text
| Markdown | Gopher |
|----------|--------|
| `# Heading` | `═══════════════════════════════════════`<br>` HEADING`<br>`═══════════════════════════════════════` |
| `## Subheading` | `--- Subheading ---` |
| `**bold**` | `*bold*` |
| `*italic*` | `_italic_` |
| `[text](url)` | `text [1]` + links section at bottom |
| `{{< imdbposter >}}` | ASCII metadata table (see below) |
| Code blocks | Preserve, indent 4 spaces |
| `---` (hr) | `────────────────────────────────────────` |
### Line Wrapping
- Wrap at 70 characters
- Preserve code block formatting
- Handle URLs gracefully (collect for footer)
### Movie Metadata Table
Convert the `{{< imdbposter >}}` shortcode to ASCII table:
```
┌─────────────────────────────────────────────────────────────────┐
│ MEGALOPOLIS (2024) │
├─────────────────────────────────────────────────────────────────┤
│ Director: Francis Ford Coppola │
│ Runtime: 138 minutes │
│ Genres: Drama, Sci-Fi │
│ │
│ View on web: https://mnw.sdf.org/posts/megalopolis/ │
└─────────────────────────────────────────────────────────────────┘
```
---
## ASCII Art Headers
Each category gets unique ASCII art. Size constraint: **No larger than current template (~12 lines max)**.
### Fun Center (Tech)
```
___ ___ _
| __| _ _ _ / __|___ _ _| |_ ___ _ _
| _| || | ' \ | (__/ -_) ' \ _/ -_) '_|
|_| \_,_|_||_| \___\___|_||_\__\___|_|
Tech posts from the Double Lunch Dispatch
```
### Frank's Couch (Movies)
```
___ _ _ ___ _
| __| _ __ _ _ _| |_( )___ / __|___ _ _ __| |_
| _| '_/ _` | ' \ / /|_-< | (__/ _ \ || (_-< ' \
|_||_| \__,_|_||_\_\ /__/ \___\___/\_,_/__/_||_|
Movie reviews from the couch
```
### Beercalls (Beer)
```
___ _ _
| _ ) ___ ___ _ _ __ __ _| | |___
| _ \/ -_) -_) '_/ _/ _` | | (_-<
|___/\___\___|_| \__\__,_|_|_/__/
Thursday night adventures in Austin
```
### Blog Index
```
___ _ ___ _ _
| _ )| |___ __ | __|_ _ | |_ _ _ (_)___ ___
| _ \| / _ \/ _|| _|| ' \| _| '_|| / -_|_-<
|___/|_\___/\__||___|_||_|\__|_| |_\___/__/
Posts from mnw.sdf.org
```
---
## Gophermap Formats
### Category Gophermap (e.g., `blog/franks-couch/gophermap`)
```
i ___ _ _ ___ _ fake (NULL) 0
i | __| _ __ _ _ _| |_( )___ / __|___ _ _ __| |_ fake (NULL) 0
i | _| '_/ _` | ' \ / /|_-< | (__/ _ \ || (_-< ' \ fake (NULL) 0
i |_||_| \__,_|_||_\_\ /__/ \___\___/\_,_/__/_||_| fake (NULL) 0
i fake (NULL) 0
i Movie reviews from the couch fake (NULL) 0
i fake (NULL) 0
i──────────────────────────────────────────────────── fake (NULL) 0
i fake (NULL) 0
0[2024-10-05] Megalopolis megalopolis.txt
i A sprawling mess of ambition fake (NULL) 0
i fake (NULL) 0
0[2024-10-07] Joker: Folie a Deux joker-folie-a-deux.txt
i Not what you'd expect fake (NULL) 0
i fake (NULL) 0
0[2024-10-12] Terrifier 3 terrifier-3.txt
i Art the Clown returns fake (NULL) 0
i fake (NULL) 0
i──────────────────────────────────────────────────── fake (NULL) 0
1Back to Blog Index ../
1Back to Main Menu /users/mnw/
```
### Blog Index Gophermap (`blog/gophermap`)
```
i ___ _ ___ _ _ fake (NULL) 0
i | _ )| |___ __ | __|_ _ | |_ _ _ (_)___ ___ fake (NULL) 0
i | _ \| / _ \/ _|| _|| ' \| _| '_|| / -_|_-< fake (NULL) 0
i |___/|_\___/\__||___|_||_|\__|_| |_\___/__/ fake (NULL) 0
i fake (NULL) 0
i Posts from mnw.sdf.org fake (NULL) 0
i The Double Lunch Dispatch fake (NULL) 0
i fake (NULL) 0
i──────────────────────────────────────────────── fake (NULL) 0
i fake (NULL) 0
1Frank's Couch - Movie Reviews franks-couch/
i X posts about films watched from the couch fake (NULL) 0
i fake (NULL) 0
1Fun Center - Tech Posts fun-center/
i X posts about technology and open source fake (NULL) 0
i fake (NULL) 0
1Beercalls - Thursday Night Adventures beercalls/
i Yearly logs of Austin beer adventures fake (NULL) 0
i fake (NULL) 0
i──────────────────────────────────────────────── fake (NULL) 0
hView on the web URL:https://mnw.sdf.org/posts/
1Back to Main Menu /users/mnw/
```
---
## Root Gophermap Makeover
Update the main gophermap to incorporate blog content while preserving existing sections.
### Proposed Structure
```
i fake (NULL) 0
i ''~`` fake (NULL) 0
i ( o o ) fake (NULL) 0
i+-------------------.oooO--(_)--Oooo.---------------+ fake (NULL) 0
i| | fake (NULL) 0
i| THE DOUBLE LUNCH DISPATCH | fake (NULL) 0
i| mnw @ sdf | fake (NULL) 0
i| | fake (NULL) 0
i+---------------------------------------------------+ fake (NULL) 0
i fake (NULL) 0
imnw(at)sdf.org | SDF VOIP Ext. 1908 fake (NULL) 0
i fake (NULL) 0
i═══════════════════════════════════════════════════ fake (NULL) 0
i DAILY PHLOGS fake (NULL) 0
i═══════════════════════════════════════════════════ fake (NULL) 0
i fake (NULL) 0
1Daily Journal journal/
=echo "0Latest Entry journal/`ls -t journal/ | grep -v gophermap | head -1`"
1Historical Phlogs (2018-2019) older/
i fake (NULL) 0
i═══════════════════════════════════════════════════ fake (NULL) 0
i FROM THE WEB fake (NULL) 0
i═══════════════════════════════════════════════════ fake (NULL) 0
i fake (NULL) 0
iPosts from mnw.sdf.org fake (NULL) 0
i fake (NULL) 0
1Blog Categories blog/
=echo "0Latest Blog Post `find ~/gopher/blog -name '*.txt' -type f -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | awk '{print $2}' | sed 's|.*/gopher/||'`"
i fake (NULL) 0
i═══════════════════════════════════════════════════ fake (NULL) 0
i CONFERENCE NOTES fake (NULL) 0
i═══════════════════════════════════════════════════ fake (NULL) 0
i fake (NULL) 0
iPhlogs from conferences (rough notes) fake (NULL) 0
i fake (NULL) 0
1HOPE 2024 hope_2024/
1FOSSY 2024 fossy_2024/
1LISA 2019 LISA19/
1SeaGL 2019 seagl_2019/
1Texas Linux Fest 2019 txlf_2019/
1HOPE 2018 hope_2018/
1SeaGL 2018 seagl_2018/
i fake (NULL) 0
i═══════════════════════════════════════════════════ fake (NULL) 0
i OTHER THINGS fake (NULL) 0
i═══════════════════════════════════════════════════ fake (NULL) 0
i fake (NULL) 0
1Comedy comedy/
1Short Stories stories/
i fake (NULL) 0
i .oooO fake (NULL) 0
i ( ) Oooo. fake (NULL) 0
i+-------------------\ (----( )-----------------+ fake (NULL) 0
i \_) ) / fake (NULL) 0
i (_/ fake (NULL) 0
i fake (NULL) 0
iMy SDF User Info fake (NULL) 0
=echo "`/usr/pkg/bin/uinfo mnw | tr -d '\-=><'`"
i fake (NULL) 0
i __^__ __^__ fake (NULL) 0
i( ___ )--------------------------------------------( ___ ) fake (NULL) 0
i | / | This gopher space proudly hosted by SDF | \ | fake (NULL) 0
i | / | mnw on pixelfed.social and tilde.zone | \ | fake (NULL) 0
i |___| Web: mnw.sdf.org |___| fake (NULL) 0
i(_____)--------------------------------------------(_____) fake (NULL) 0
i fake (NULL) 0
iThanks for reading! Enjoy an SDF oneliner: fake (NULL) 0
=echo "`/usr/pkg/bin/oneliner | tr -d '\-=><'`"
```
---
## Post Template
Each converted post follows this structure:
```
___ ___ ___
/\ \ /\ \ /\ \
|::\ \ \:\ \ _\:\ \
|:|:\ \ \:\ \ /\ \:\ \
__|:|\:\ \ _____\:\ \ _\:\ \:\ \
/::::|_\:\__\ /::::::::\__\ /\ \:\ \:\__\
\:\~~\ \/__/ \:\~~\~~\/__/ \:\ \:\/:/ /
\:\ \ \:\ \ \:\ \::/ /
\:\ \ \:\ \ \:\/:/ /
\:\__\ \:\__\ \::/ /
\/__/ \/__/ \/__/
mnw(at)sdf.org | SDF VOIP Ext. 1908
────────────────────────────────────────────────────────────────────
2024-11-19 | Fun Center
────────────────────────────────────────────────────────────────────
IS IT ME?
Marcus shares his blog writing process.
────────────────────────────────────────────────────────────────────
I've always loved talking to people. Whether it's at work, at
home, or out with friends, connecting with others and hearing
about their passions brings me so much joy. There's nothing
better than a conversation with someone excited to share what
they care about.
[... content continues, wrapped at 70 characters ...]
═══════════════════════════════════════════════════════════════════
LINKS
═══════════════════════════════════════════════════════════════════
[1] https://example.com/macwhisper
[2] https://openai.com/chatgpt
__^__ __^__
( ___ )------------------------------------------------------( ___ )
| / | | \ |
| / | This gopher space proudly hosted by SDF | \ |
| \ | mnw on pixelfed.social and tilde.zone | / |
| \ | | / |
(_____)------------------------------------------------------(_____)
Web version: https://mnw.sdf.org/posts/is-it-me/
```
---
## New Scripts
### 1. `scripts/gopher/convert_to_gopher.py`
**Purpose:** Convert Hugo markdown to gopher-formatted text
**Usage:**
```bash
python scripts/gopher/convert_to_gopher.py content/posts/blog-posting.md
python scripts/gopher/convert_to_gopher.py --all
python scripts/gopher/convert_to_gopher.py --all --output gopher_build/
```
**Responsibilities:**
- Parse YAML frontmatter
- Check `phlog: true` flag
- Determine output directory from series
- Convert markdown syntax
- Handle `{{< imdbposter >}}` → ASCII table
- Word wrap to 70 chars
- Apply header/footer templates
- Collect and format links section
### 2. `scripts/gopher/generate_gophermaps.py`
**Purpose:** Generate all gophermap files
**Usage:**
```bash
python scripts/gopher/generate_gophermaps.py
python scripts/gopher/generate_gophermaps.py --output gopher_build/
```
**Responsibilities:**
- Scan converted posts by category
- Generate category gophermaps with titles + summaries
- Generate blog index gophermap
- Count posts per category
- Sort by date (newest first)
### 3. `scripts/gopher/ascii_art.py`
**Purpose:** ASCII art assets and helper functions
**Contents:**
- Category header art (Fun Center, Frank's Couch, Beercalls)
- Post header template (MNW owl)
- Footer template (SDF box)
- Section dividers
- ASCII table generator for movie metadata
### 4. `scripts/gopher/update_root_gophermap.py`
**Purpose:** Update the main gophermap with blog section
**Usage:**
```bash
python scripts/gopher/update_root_gophermap.py
```
**Responsibilities:**
- Read existing root gophermap (preserve conference notes, etc.)
- Insert/update "FROM THE WEB" section
- Update dynamic CGI for latest blog post
- Optionally apply full makeover template
---
## Modified Scripts
### `scripts/remote_publish.sh`
Add gopher deployment support:
```bash
#!/bin/bash
# Remote publish script for marcus-web
GOPHER=false
GOPHER_ONLY=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--gopher)
GOPHER=true
shift
;;
--gopher-only)
GOPHER_ONLY=true
GOPHER=true
shift
;;
*)
shift
;;
esac
done
# ... existing git/commit logic ...
if [[ "$GOPHER_ONLY" != "true" ]]; then
# Existing Hugo deployment
ssh "$SDF_HOST" "cd $REMOTE_DIR && git pull && hugo && mkhomepg -p"
echo "Site is live at: https://mnw.sdf.org/"
fi
if [[ "$GOPHER" == "true" ]]; then
echo -e "${GREEN}=== Deploying to Gopher ===${NC}"
# Build gopher content locally
python scripts/gopher/convert_to_gopher.py --all --output gopher_build/blog/
python scripts/gopher/generate_gophermaps.py --output gopher_build/blog/
# Sync to SDF (--delete removes posts where phlog was set to false)
rsync -avz --delete gopher_build/blog/ "$SDF_HOST:~/gopher/blog/"
# Fix permissions on SDF
ssh "$SDF_HOST" "find ~/gopher/blog -type f -exec chmod 644 {} \; && find ~/gopher/blog -type d -exec chmod 755 {} \;"
echo "Gopher updated at: gopher://sdf.org/1/users/mnw/blog/"
fi
```
### `scripts/new_techpost.py`
Add phlog prompt:
```python
# After getting summary...
phlog_input = input("\nPublish to gopher phlog? (y/N): ").strip().lower()
phlog = phlog_input == 'y'
# In frontmatter:
content = f'''---
title: '{title}'
date: {now}
draft: true
series: "Fun Center"
summary: "{summary}"
phlog: {str(phlog).lower()}
tags:
{tags_yaml}
---
'''
```
### `scripts/new_movie.py` / `scripts/import_letterboxd.py`
Same pattern - add phlog prompt after other inputs.
---
## Deployment Workflow
### Standard Deploy (Hugo only)
```bash
./scripts/remote_publish.sh
```
### Deploy Hugo + Gopher
```bash
./scripts/remote_publish.sh --gopher
```
### Deploy Gopher Only
```bash
./scripts/remote_publish.sh --gopher-only
```
### One-time Root Gophermap Update
```bash
# After first blog/ deployment
python scripts/gopher/update_root_gophermap.py
scp gopher_build/gophermap mnw@sdf.org:~/gopher/gophermap
```
---
## Implementation Order
### Phase 1: Core Infrastructure
1. [ ] Create `scripts/gopher/` directory
2. [ ] Create `scripts/gopher/ascii_art.py` - All ASCII art assets
3. [ ] Create `scripts/gopher/convert_to_gopher.py` - Markdown conversion
4. [ ] Add `gopher_build/` to `.gitignore`
### Phase 2: Gophermap Generation
5. [ ] Create `scripts/gopher/generate_gophermaps.py` - Category + index maps
6. [ ] Create `scripts/gopher/update_root_gophermap.py` - Root gophermap
### Phase 3: Deployment Integration
7. [ ] Modify `scripts/remote_publish.sh` - Add --gopher flags
8. [ ] Test with single post locally
### Phase 4: Post Creation Updates
9. [ ] Modify `scripts/new_techpost.py` - Add phlog prompt
10. [ ] Modify `scripts/import_letterboxd.py` - Add phlog prompt
11. [ ] Modify `scripts/new_movie.py` - Add phlog prompt (if separate)
### Phase 5: Content & Testing
12. [ ] Add `phlog: true` to existing posts to backfill
13. [ ] Run full deployment to SDF
14. [ ] Verify gopher content via `gopher gopher://sdf.org/1/users/mnw/blog/`
15. [ ] Update root gophermap on SDF
### Phase 6: Polish
16. [ ] Fine-tune ASCII art sizing
17. [ ] Adjust line wrapping edge cases
18. [ ] Document in README or execution-notes.txt
---
## File Checklist
### New Files
- [ ] `scripts/gopher/ascii_art.py`
- [ ] `scripts/gopher/convert_to_gopher.py`
- [ ] `scripts/gopher/generate_gophermaps.py`
- [ ] `scripts/gopher/update_root_gophermap.py`
- [ ] `gopher_build/` directory (gitignored)
### Modified Files
- [ ] `scripts/remote_publish.sh`
- [ ] `scripts/new_techpost.py`
- [ ] `scripts/import_letterboxd.py`
- [ ] `.gitignore` (add gopher_build/)
### Content Files (add phlog: true)
- [ ] `content/posts/blog-posting.md`
- [ ] `content/posts/recovering-failed-ubuntu-upgrade.md`
- [ ] Movie posts as desired
- [ ] Beercall yearly files as desired
---
## URLs
After deployment:
| Type | URL |
|------|-----|
| Gopher root | `gopher://sdf.org/1/users/mnw/` |
| Blog index | `gopher://sdf.org/1/users/mnw/blog/` |
| Fun Center | `gopher://sdf.org/1/users/mnw/blog/fun-center/` |
| Frank's Couch | `gopher://sdf.org/1/users/mnw/blog/franks-couch/` |
| Beercalls | `gopher://sdf.org/1/users/mnw/blog/beercalls/` |
| Web proxy | `https://gopher.floodgap.com/gopher/gw?a=gopher://sdf.org/1/users/mnw/` |
---
## References
- SDF Gopher Setup: https://wiki.sdf.org/doku.php?id=gopher_site_setup_and_hosting_features
- SDF Web Spaces: https://wiki.sdf.org/doku.php?id=accessing_web_spaces#gopher
- Tilde.town Gopher Guide: https://tilde.town/wiki/editing-your-homepage/gopher.html
- Floodgap Gopher Proxy: https://gopher.floodgap.com/gopher/gw
---
## Gophermap Quick Reference
From tilde.town wiki - item types we'll use:
| Code | Type | Usage |
|------|------|-------|
| `0` | Text file | Blog posts (.txt) |
| `1` | Directory | Category folders, navigation |
| `i` | Inline text | ASCII art, descriptions, spacing |
| `h` | HTML link | Link to web version (prefix with `URL:`) |
| `7` | Search | Future: search posts (CGI) |
**Syntax:** `{type}{description}\t{path}\t{host}\t{port}`
- Tab character separates columns
- Host/port optional for local resources
- For inline text (`i`): use `fake` as path, `(NULL)` as host, `0` as port
- For HTML links: path should be `URL:https://...`
**Example entries:**
```
i fake (NULL) 0
0[2024-10-05] Megalopolis megalopolis.txt
1Back to Blog Index ../
hView on web URL:https://mnw.sdf.org/posts/
```
---
## Notes
- Gophermaps must NOT have execute permission (causes errors)
- Files need 644, directories need 755
- CGI scripts (=echo) must use full paths on SDF
- ASCII art should stay under 12 lines for headers
- The `--delete` flag on rsync will remove posts where `phlog` was changed to `false`
- All work is git-tracked, so deletions are recoverable