# 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` | `═══════════════════════════════════════`
` HEADING`
`═══════════════════════════════════════` | | `## 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