Venusia is a fast and flexible Haskell library and daemon for building servers for the Internet Gopher Protocol. It's designed for ease of use, powerful configuration, and extensibility.
It works well with other tools in its ecosystem:
- Build: Use Bore for static site generation (phlogs, templates) for your gopherspace.
- Rank: Use RYVM to rank files for search results.
Venusia is a complete framework for creating modern Gopherholes.
- Declarative Routing: Easily define routes with support for wildcards.
- Built-in Handlers: Ready-to-use handlers for directories and files.
.gophermapSupport: Automatically serves.gophermapfiles for custom menus.- Menu Builder: An intuitive DSL for creating Gopher menus.
- TOML Configuration: Use a simple
routes.tomlto configure file servers and gateways. - Hot Reloading: Watches your directory and
routes.toml, reloading automatically on changes with no downtime. - Process Gateways: Execute external commands (e.g.,
cowsay,figlet,curl) and serve their output.
The quickest way to start is with the venusia-exe daemon.
-
Create a
routes.tomlfile:This file configures your Gopher server's behavior.
# routes.toml [[files]] selector = "/files/" path = "/home/user/gopherhole/" # Gateway to the 'cowsay' command. [[gateway]] selector = "/gateway/cowsay" command = "cowsay" arguments = ["$search"] # Takes a search query menu = true search = true wildcard = false
TODO: You can also use wildcards--a neat feature, I need to show an example!
I sugguest trying out using
.lhs(Literate Haskell) scripts with something like:[[gateway]] selector = "/gateway/something" search = false menu = false wildcard = false command = "runghc" arguments = ["/var/gopher/output/example.lhs"]
Note that for the above TOML config nothing is mapped to /, so to test try something like
gopher://localhost/1/files/. Make sure to change thepathin the example (for[[files]]) to a directory you wanna serve.You may also want to know in Gopher the root selector is actually blank
""and not"/". -
Run the
watchcommand:Point the watcher to your directory, host, and port.
venusia watch /path/to/your/gopherhole gopher.example.com 7070
Your Gopher server is now live. Changes to your files or
routes.tomlwill be reflected instantly.
You can use my ryvm software as a gateway like this, in your routes.toml:
[[gateway]]
selector = "/search"
search = true
wildcard = false
menu = false
command = "/var/gopher/source/search.sh"
arguments = ["$search"]
And here's the /var/gopher/source/search.sh (don't forget to chmod +X!):
#!/usr/bin/env bash
# search.sh <search> [HOST] [PORT]
s="$1"; h="${2:-gopher.someodd.zip}"; p="${3:-70}"
cd /var/gopher/output || exit 1
ryvm --ext-whitelist txt --make-relative . "$s" \
| awk -F'\t' -v h="$h" -v p="$p" '
function is_gophermap(path, l,ok){
ok=0
if ((getline l < path) > 0) {
sub(/\r$/,"",l)
if (l ~ /^.{2,}\t[^\t]+\t[^\t]+\t[^\t]+$/) ok=1
}
close(path)
return ok
}
{
file=$1
sel = ($2 && $2 != "") ? $2 : file
score = $3
snip = $4
t = is_gophermap(file) ? "1" : "0" # 1 = menu (gophermap), 0 = text
printf "%s%s — %s [score %s]\t%s\t%s\t%s\r\n", t, sel, snip, score, file, h, p
}'
To build a custom server, use Venusia in your own Haskell project.
-
Add the dependency in
package.yaml:dependencies: - Venusia
-
Create your server:
-- app/Main.hs module Main (main) where import Venusia.Server import Venusia.MenuBuilder import Venusia.FileHandler import Venusia.SearchHandler import qualified Data.Text as T import Control.Concurrent.MVar import Data.Maybe (fromMaybe) host :: T.Text host = "localhost" port :: Int port = 7070 -- Define server routes routes :: [Route] routes = [ on "/hello" $ \_ -> return $ TextResponse "Hello, gopher!\r\n" , onWildcard "/echo/*" $ \req -> pure $ TextResponse $ fromMaybe "Nothing." (req.reqWildcard) , onWildcard "/files/*" $ \req -> case req.reqWildcard of Just path -> serveDirectory host port "/var/gopher" "/files/" path Nothing Nothing -> pure $ TextResponse "No path provided." ] -- Main entry point main :: IO () main = do -- Wrap routes in MVar because serveHotReload requires mutable route list routesVar <- newMVar routes -- Start the new hot-reloadable, streaming-safe server serveHotReload (show port) noMatchHandler routesVar
For production, Venusia offers Debian packages with systemd support to run as a managed daemon.
After installing a package, you can edit the service configuration at /lib/systemd/system/venusia.service. For example, to integrate with bore, you could change the ExecStart line to:
ExecStart=/usr/bin/venusia watch /var/gopher/source gopher.someodd.zip 7071 "/usr/bin/bore build --source /var/gopher/source --output /var/gopher/output" 10000000
Then, reload the systemd daemon and restart the service:
sudo systemctl daemon-reload
sudo systemctl restart venusia.service
Configure the daemon by defining routes in routes.toml.
[[files]]: Serves static files.selector: Gopher path prefix (e.g.,/files/).path: Local directory to serve.
[[gateway]]: Executes a shell command.selector: Gopher path, can include a wildcard (*).command: The command to run.arguments: Command arguments.$searchand$wildcardare replaced with user input.wildcard: Set totrueif the selector uses a wildcard.menu: Set totrueto format the command's output as a Gopher menu.