gopher://gopher.someodd.zip:70/1/catalog/applets/ Gopher Applets Little programs that run in gopherspace. 2026-06-09T00:00:00Z gopher.someodd.zip gopher://gopher.someodd.zip:70/1/applets/interlog Interlog: Gopher-style forum. 2026-06-09T00:00:00Z 2026-06-09T00:00:00Z Text BBS/4ch-style forum. Text BBS/4ch-style forum. gopher://gopher.someodd.zip:70/1/applets/README.gophermap README.gophermap 2026-05-23T00:00:00Z 2026-05-23T00:00:00Z gopher://gopher.someodd.zip:70/9/applets/girc.lhs.bak-20260615 girc.lhs.bak-20260615 2026-05-23T00:00:00Z 2026-05-23T00:00:00Z gopher://gopher.someodd.zip:70/1/applets/webhole.lhs webhole: browse any website as a gopherhole 2026-05-23T00:00:00Z 2026-05-23T00:00:00Z paste a web-page URL and walk it as a gopher menu -- prose becomes ... gopher://gopher.someodd.zip:70/0/applets/curl/README.txt README.txt 2026-05-23T00:00:00Z 2026-05-23T00:00:00Z The idea of these applets is to evangalize Gopher to people who don... gopher://gopher.someodd.zip:70/1/applets/curl/ask.lhs ask: question a local LLM over gopher 2026-05-14T00:00:00Z 2026-05-23T00:00:00Z ask a short question, get a short plain-text answer from a local LL... gopher://gopher.someodd.zip:70/1/applets/whoami.lhs whoami.lhs 2026-05-22T00:00:00Z 2026-05-22T00:00:00Z gopher://gopher.someodd.zip:70/1/applets/bujo/bujo.lhs bujo: a bullet journal over gopher 2026-05-22T00:00:00Z 2026-05-22T00:00:00Z a dead-simple bullet journal you keep entirely in gopherspace --- o... gopher://gopher.someodd.zip:70/1/applets/curl/atomize.lhs atomize: any web page into an Atom feed 2026-05-22T00:00:00Z 2026-05-22T00:00:00Z paste a web-page URL, get back an Atom feed -- deterministically di... gopher://gopher.someodd.zip:70/1/applets/curl/txtify.lhs txtify: any web page into plaintext 2026-05-22T00:00:00Z 2026-05-22T00:00:00Z paste a web-page URL, get back the most likely main textual content... gopher://gopher.someodd.zip:70/1/applets/girc.lhs girc: chat on IRC over gopher 2026-05-20T00:00:00Z 2026-05-21T00:00:00Z Read #main's scrollback over gopher (the shared oddbot channel log)... gopher://gopher.someodd.zip:70/1/applets/4_ch.lhs 4_ch.lhs 2026-05-18T00:00:00Z 2026-05-18T00:00:00Z gopher://gopher.someodd.zip:70/1/applets/grep.lhs Search this gopherhole with grep 2026-05-18T00:00:00Z 2026-05-18T00:00:00Z gopher://gopher.someodd.zip:70/1/applets/search.lhs search.lhs 2026-05-18T00:00:00Z 2026-05-18T00:00:00Z gopher://gopher.someodd.zip:70/1/applets/diggings diggings: prospect gopherspace 2026-05-14T00:00:00Z 2026-05-14T00:00:00Z surf gopherspace through a proxy, strike gold for finding selectors... surf gopherspace through a proxy, strike gold for finding selectors nobody has visited, and leave verified tripcode posts on any page --- the whole protocol as one text board gopher://gopher.someodd.zip:70/1/applets/qdb qdb: a Gopher <-> IRC quote database 2026-05-14T00:00:00Z 2026-05-14T00:00:00Z IRC quotes captured in #main with .qdb, kept forever as plain text,... IRC quotes captured in #main with .qdb, kept forever as plain text, browsable in gopherspace --- read as text or as a Microsoft Comic Chat strip gopher://gopher.someodd.zip:70/1/applets/icecast internet radio over gopher 2026-05-10T00:00:00Z 2026-05-12T00:00:00Z stream any http/https internet radio over gopher as audio passthrou... stream any http/https internet radio over gopher as audio passthrough or 144p showcqt visualization gopher://gopher.someodd.zip:70/1/applets/fewbytes/yt.lhs YouTube at modem speeds 2026-05-11T00:00:00Z 2026-05-11T00:00:00Z stream youtube over gopher as 6 kbps audio, 144p video, or plain-te... gopher://gopher.someodd.zip:70/1/applets/figlet.lhs Figlet ASCII font art generator 2026-05-10T00:00:00Z 2026-05-10T00:00:00Z create ASCII art text in gopherspace gopher://gopher.someodd.zip:70/0/applets/digest.hs digest.hs 2026-05-06T00:00:00Z 2026-05-06T00:00:00Z #!/usr/bin/env runghc {-# LANGUAGE OverloadedStrings #-} {-# LANGUA... IO () dbg s = lookupEnv "DEBUG" >>= \d -> case d of Just "1" -> hPutStrLn stderr ("[dbg] " ++ s); _ -> pure () trim :: String -> String trim = dropWhile isSpace . dropWhileEnd isSpace lower :: String -> String lower = map toLower short :: Int -> String -> String short n s = let t = unwords (words s) in if length t > n then take (n-1) t ++ "…" else t section :: String -> IO () section t = putStrLn $ "\n===== " ++ t ++ " =====" -- curl / fetch / jq curlBody :: String -> IO (Maybe String) curlBody u = do ik <- lookupEnv "INSECURE" let k = if ik == Just "1" && ("someodd.zip" `isInfixOf` u) && ("http" `isPrefixOf` u) then ["-k"] else [] a = ["-fsSL","--location","--connect-timeout","3","--max-time","8","-A","someodd-digest/hs","--"] ++ k ++ [u] dbg $ "curl " ++ unwords a readProcessWithExitCode "curl" a "" >>= \case (ExitSuccess, out, _) -> pure (Just out) _ -> pure Nothing fetch :: String -> IO (Maybe String) fetch u | "gopher://" `isPrefixOf` u = curlBody u | "https://" `isPrefixOf` u = curlBody u >>= \case { Just x -> pure (Just x); _ -> curlBody ("http://" ++ drop 8 u) } | otherwise = curlBody u jq :: String -> String -> IO (Maybe String) jq e s = readProcessWithExitCode "jq" ["-r", e] s >>= \case (ExitSuccess, o, _) -> let t = trim o in pure $ if t == "" || t == "null" then Nothing else Just t _ -> pure Nothing -- 1) latest toot toot :: IO () toot = do section "Toot" fetch "https://fosstodon.org/api/v1/accounts/lookup?acct=someodd" >>= \case Nothing -> putStrLn "(couldn't resolve account)" Just js -> jq ".id" js >>= \case Nothing -> putStrLn "(couldn't resolve account)" Just aid -> fetch ("https://fosstodon.org/api/v1/accounts/" ++ aid ++ "/statuses?limit=1&exclude_replies=true&exclude_reblogs=true") >>= \case Nothing -> putStrLn "(unreachable)" Just s -> jq ".[0] | select(.!=null) | [.created_at, (.content//\"\")] | @tsv" s >>= \case Nothing -> putStrLn "(none found)" Just tsv -> let (ts, rest) = span (/= '\t') tsv body = stripHTML (drop 1 rest) in putStrLn (ts ++ " — " ++ short 280 body) where stripHTML = decode . kill kill [] = [] kill ('<':xs) = kill (drop 1 (dropWhile (/= '>') xs)) kill (x:xs) = x : kill xs decode = rep " " " " . rep "&" "&" . rep "<" "<" . rep ">" ">" . rep """ "\"" . rep "'" "'" rep a b = go where go s | a `isPrefixOf` s = b ++ go (drop (length a) s) | otherwise = case s of [] -> []; (c:cs) -> c : go cs -- 2) latest bartleby library accession (via gopher atom feed) bartleby :: IO () bartleby = do section "Bartleby Library" fetch "gopher://gopher.someodd.zip:70/0/library/catalog/feed.xml" >>= \case Nothing -> putStrLn "(unreachable)" Just feed -> do mt <- xpath feed "//a:entry[1]/a:title" ms <- xpath feed "//a:entry[1]/a:summary" let title = stripMd (fromMaybe "" mt) summary = fromMaybe "" ms if null title then putStrLn "(no accessions)" else do putStrLn $ "Latest: " ++ title case summary of "" -> pure () s | s == title -> pure () | otherwise -> putStrLn $ "Summary: " ++ short 280 s where stripMd s = if ".md" `isSuffixOf` s then take (length s - 3) s else s xpath input expr = let args = ["sel", "-N", "a=http://www.w3.org/2005/Atom", "-t", "-v", expr] in readProcessWithExitCode "xmlstarlet" args input >>= \case (ExitSuccess, o, _) ]]> gopher://gopher.someodd.zip:70/0/applets/server.sh server.sh 2025-11-13T00:00:00Z 2025-11-13T00:00:00Z #!/usr/bin/env bash /dev/null && echo 'UP' || echo 'DOWN'" # --- 2. Systemd Services --- # Loops through a list of services and checks if they are active. echo echo "--- Services ---" for service in nginx venusia phorum smbd apcupsd; do print_check "$service" "systemctl is-active --quiet $service && echo 'UP' || echo 'DOWN'" done # special case for mpd because it runs as user print_check mpd "pgrep -u baudrillard mpd >/dev/null && echo 'UP' || echo 'DOWN'" # --- 3. Counter-Strike 1.6 --- # Runs the dedicated stats script and pretty-prints the JSON output. echo echo "--- Counter-Strike 1.6 ---" # Pipe the JSON output to 'jq' to format it and 'sed' to indent it. /usr/local/bin/cs_stats.sh | jq . | sed 's/^/ /' # --- 4. Disk Usage --- echo echo "--- Storage ---" print_check "Disk Usage (Root)" "df -h / | awk 'NR==2 {print \$5}'" print_check "Disk Usage (BackupRAID)" "df -h /media/root/BackupRAID | awk 'NR==2 {print \$5}'" # --- 5. Restic Backups --- # Checks the logs for any sign of errors or warnings. echo echo "--- Restic Backups ---" # Check the backup log for errors. If found, print the whole log. if grep -q -E 'Warning|error|failed' /var/log/restic-backup.log; then printf "%-25s: %s\n" "Backup Status" "ERRORS FOUND (see log below)" echo "------------------------- Restic Backup Log -------------------------" sed 's/^/ /' /var/log/restic-backup.log echo "-------------------------------------------------------------------" else print_check "Backup Status" "echo 'OK'" fi # This command will print any error lines from the maintenance log, or 'No errors found' if none exist. print_check "Maintenance Status" "grep 'errors' /var/log/restic-maintain.log || echo 'No errors found'" echo echo "LAST BACKUP LOG, 3 LINES" echo tail -n 3 /var/log/restic-backup.log echo echo "MAINTENANCE LOG, 3 LINES" echo tail -n 3 /var/log/restic-maintain.log # --- 6. Hardware & System --- echo echo "--- Hardware & System ---" print_check "System Uptime" "uptime -p" print_check "Last UPS Event" "tail -n 1 /var/log/apcupsd.events" echo echo "UPS/BATTERY" apcaccess status echo inxi -SCGIDm -t cm5 echo echo "--- Report Complete ---" ]]>