Files
marcus-web/docs/gopher-integration-plan.md
Marcus 69d6b370b2 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
2026-01-14 16:24:42 -06:00

25 KiB

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:

---
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:

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:

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:

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:

#!/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:

# 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)

./scripts/remote_publish.sh

Deploy Hugo + Gopher

./scripts/remote_publish.sh --gopher

Deploy Gopher Only

./scripts/remote_publish.sh --gopher-only

One-time Root Gophermap Update

# 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

  1. Create scripts/gopher/generate_gophermaps.py - Category + index maps
  2. Create scripts/gopher/update_root_gophermap.py - Root gophermap

Phase 3: Deployment Integration

  1. Modify scripts/remote_publish.sh - Add --gopher flags
  2. Test with single post locally

Phase 4: Post Creation Updates

  1. Modify scripts/new_techpost.py - Add phlog prompt
  2. Modify scripts/import_letterboxd.py - Add phlog prompt
  3. Modify scripts/new_movie.py - Add phlog prompt (if separate)

Phase 5: Content & Testing

  1. Add phlog: true to existing posts to backfill
  2. Run full deployment to SDF
  3. Verify gopher content via gopher gopher://sdf.org/1/users/mnw/blog/
  4. Update root gophermap on SDF

Phase 6: Polish

  1. Fine-tune ASCII art sizing
  2. Adjust line wrapping edge cases
  3. 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


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