phoebe - a Gemini-first wiki server
phoebe [--host=hostname ...] [--port=port] [--cert_file=filename] [--key_file=filename] [--no_cert] [--log_level=error|warn|info|debug] [--log_file=filename] [--wiki_dir=directory] [--wiki_token=token ...] [--wiki_page=pagename ...] [--wiki_main_page=pagename] [--wiki_mime_type=mimetype ...] [--wiki_page_size_limit=n] [--wiki_space=space ...]
Phoebe does two and a half things:
It's a program that you run on a computer and other people connect to it using their Gemini client in order to read the pages on it.
It's a wiki, which means that people can edit the pages without needing an account. All they need is a client that speaks both Gemini and Titan, and the password. The default password is "hello". 😃
Optionally, people can also access it using a regular web browser.
Gemini itself is very simple network protocol, like Gopher or Finger, but with TLS. Gemtext is a very simple markup language, a bit like Markdown, but line oriented.
Pages are written in gemtext, a lightweight hypertext format. You can use your favourite text editor to write them.
A text line is a paragraph of text.
This is a paragraph. This is another paragraph.
A link line starts with "=>", a space, a URL, optionally followed by whitespace and some text; the URL can be absolute or relative.
=> http://transjovian.org/ The Transjovian Council on the web => Welcome Welcome to The Transjovian Council
A line starting with "```" toggles preformatting on and off.
Here is an example: ``` The tapping calms me: Constant mindless murmuring Rain drops against glass ```
A line starting with "#", "##", or "###", followed by a space and some text is a heading.
## License The GNU Affero General Public License.
A line starting with "*", followed by a space and some text is a list item.
* one item * another item
A line starting with ">", followed by a space and some text is a quote.
The monologue at the end is fantastic, with the city lights and the rain. > I have seen things you people would not believe.
It might be best if you had a separate user for Phoebe:
sudo adduser --disabled-login --disabled-password phoebe sudo su phoebe --shell=/bin/bash cd
Now you're in the new home directory, /home/phoebe. If you start phoebe here, your wiki directory will be /home/phoebe/wiki. If you haven't installed App::Phoebe for all your users, you will have to install it again.
phoebe
cpan App::Phoebe
The Perl files are stored in /home/phoebe/perl5.
Start Phoebe.
When you run it for the first time, Phoebe is going to prompt you for a hostname and create certificates for you. If in doubt, answer localhost. The certificate and a private key are stored in the cert.pem and key.pem files, using elliptic curves, valid for five years, without password protection.
localhost
Do you want to create them right now? The certificate uses eliptic curves and is valid for five years. If so, please provide your hostname (e.g. localhost). If not, just press Enter. localhost openssl req -new -x509 -newkey ec -subj "/CN=localhost" -pkeyopt ec_paramgen_curve:prime256v1 -days 1825 -nodes -out cert.pem -keyout key.pem Generating an EC private key writing new private key to 'key.pem' -----
If it aborts, see the "Troubleshooting" section below. If it runs, open a second terminal and test it:
gemini gemini://localhost/
You should see a Gemini page starting with the following:
20 text/gemini; charset=UTF-8 Welcome to Phoebe!
Success!! 😀 🚀🚀
Let's create a new page using the Titan protocol, from the command line:
echo "Welcome to the wiki!" > test.txt echo "Please be kind." >> test.txt titan --url=titan://localhost/raw/Welcome --token=hello test.txt
You should get a nice redirect message.
30 gemini://localhost:1965/page/Welcome
You can check the page:
gemini gemini://localhost:1965/page/Welcome
You should get back a page that starts as follows:
20 text/gemini; charset=UTF-8 Welcome to the wiki! Please be kind.
Yay! 😁🎉 🚀🚀
If you have a bunch of Gemtext files in a directory, you can upload them all in one go:
titan --url=titan://localhost/ --token=hello *.gmi
If you want to generate your own certificates, here's how you would generate a certificate for two domains (you can add as many as you need), and a common name of "Phoebe" (use whatever you want).
openssl req -new -x509 -newkey ec \ -pkeyopt ec_paramgen_curve:prime256v1 \ -subj "/CN=Phoebe" \ -addext "subjectAltName=DNS:localhost,DNS:phoebe.local" \ -days 1825 -nodes -out cert.pem -keyout key.pem
OK, how do image uploads work? First, we need to specify which MIME types Phoebe accepts. The files are going to be served back with that MIME type, so even if somebody uploads an executable and claim it's an image, other people's clients will treat it as an image instead of executing it (one hopes!) – so let's start with a list of common MIME types.
image/jpeg is for photos (usually with the jpg extension)
image/jpeg
jpg
image/png is for graphics (usually with the png extension)
image/png
png
audio/mpeg is for sound (usually with the mp3 extension)
audio/mpeg
mp3
Let's continue using the setup we used for the "QUICKSTART" section. Restart the server and allow photos:
phoebe --wiki_mime_type=image/jpeg
Upload the image using the titan script:
titan
titan --url=titan://localhost:1965/jupiter.jpg \ --token=hello Pictures/Planets/Juno.jpg
You should get back a redirect to the uploaded image:
30 gemini://localhost:1965/file/jupiter.jpg
How did the titan script know the MIME-type to use for the upload? If you don't specify a MIME-type using --mime, the file utility is called to guess the MIME type of the file.
--mime
file
Test it:
file --mime-type --brief Pictures/Planets/Juno.jpg
The result is the MIME-type we enabled for our wiki:
Here's what happens when you're trying to upload an unsupported MIME-type:
titan --url=titan://localhost:1965/earth.png \ --token=hello Pictures/Planets/Earth.png
What you get back explains the problem:
59 This wiki does not allow image/png
In order to allow such graphics as well, you need to restart Phoebe:
phoebe --wiki_mime_type=image/jpeg --wiki_mime_type=image/png
Except that in my case, the image is too big:
59 This wiki does not allow more than 100000 bytes per page
I could scale it down before I upload the image, using convert (which is part of ImageMagick):
convert
convert -scale 20% Pictures/Planets/Earth.png earth-small.png
Try again:
titan --url=titan://localhost:1965/earth.png \ --token=hello earth-small.png
Alternatively, you can increase the size limit using the --wiki_page_size_limit option, but you need to restart Phoebe:
--wiki_page_size_limit
phoebe --wiki_page_size_limit=10000000 \ --wiki_mime_type=image/jpeg --wiki_mime_type=image/png
Now you can upload about 10MB…
Systemd is going to handle daemonisation for us. There's more documentation available online. https://www.freedesktop.org/software/systemd/man/systemd.service.html.
Basically, this is the template for our service, assuming that you created a separate user for Phoebe:
[Unit] Description=Phoebe After=network.target [Service] Type=simple WorkingDirectory=/home/phoebe ExecStart=/home/phoebe/phoebe Restart=always User=phoebe Group=phoebe MemoryMax=100M MemoryHigh=90M [Install] WantedBy=multi-user.target
Save this as phoebe.service, and then link it:
sudo ln -s /home/phoebe/phoebe.service /etc/systemd/system/
Reload systemd:
sudo systemctl daemon-reload
Start Phoebe:
sudo systemctl start phoebe
Check the log output:
sudo journalctl --unit phoebe
🔥 Unknown command phoebe 🔥 If you installed Phoebe using cpan or cpanm and you still get this error, then something about your Perl installation isn't working. Phoebe probably got installed in some directory, you just need to make sure it's in your PATH. If you use zsh and Perlbrew, for example, you need to add the following line to your ~/.zshenv file:
source ${HOME}/perl5/perlbrew/etc/bashrc
🔥 1408A0C1:SSL routines:ssl3_get_client_hello:no shared cipher 🔥 If you created a new certificate and key using elliptic curves using an older OpenSSL, you might run into this. Try to create a RSA key instead. It is larger, but at least it'll work.
openssl req -new -x509 -newkey rsa \ -days 1825 -nodes -out cert.pem -keyout key.pem
Your home directory should now also contain a wiki directory called wiki, your wiki directory. In it, you'll find a few more files:
page is the directory with all the page files in it; each file has the gmi extension and should be written in Gemtext format
gmi
index is a file containing all the files in your page directory for quick access; if you create new files in the page directory, you should delete the index file – it will get regenerated when needed; the format is one page name (without the .gmi extension) per line, with lines separated from each other by a single \n
.gmi
\n
keep is the directory with all the old revisions of pages in it – if you've only made one change, then it won't exist; if you don't care about the older revisions, you can delete them; assuming you have a page called Welcome and edit it once, you have the current revision as page/Welcome.gmi, and the old revision in keep/Welcome/1.gmi (the page name turns into a subdirectory and each revision gets an apropriate number)
Welcome
file is the directory with all the uploaded files in it – if you haven't uploaded any files, then it won't exist; you must explicitly allow MIME types for upload using the --wiki_mime_type option (see Options below)
--wiki_mime_type
meta is the directory with all the meta data for uploaded files in it – there should be a file here for every file in the file directory; if you create new files in the file directory, you should create a matching file here; if you have a file file/alex.jpg you want to create a file meta/alex.jpg containing the line content-type: image/jpeg
content-type: image/jpeg
changes.log is a file listing all the pages made to the wiki; if you make changes to the files in the page or file directory, they aren't going to be listed in this file and thus people will be confused by the changes you made – your call (but in all fairness, if you're collaborating with others you probably shouldn't do this); the format is one change per line, with lines separated from each other by a single \n, and each line consisting of time stamp, pagename or filename, revision number if a page or 0 if a file, and the numeric code of the user making the edit (see "Privacy" below), all separated from each other with a \x1f
\x1f
config probably doesn't exist, yet; it is an optional file containing Perl code where you can add new features and change how Phoebe works (see "Configuration" below)
conf.d probably doesn't exist, either; it is an optional directory containing even more Perl files where you can add new features and change how Phoebe works (see "Configuration" below); the idea is that people can share stand-alone configurations that you can copy into this directory without having to edit your own config file.
--wiki_token is for the token that users editing pages have to provide; the default is "hello"; you can use this option multiple times and give different users different passwords, if you want
--wiki_token
--wiki_page is an extra page to show in the main menu; you can use this option multiple times; this is ideal for general items like About or Contact
--wiki_page
--wiki_main_page is the page containing your header for the main page; that's were you would put your ASCII art header, your welcome message, and so on, see "Main Page and Title" below
--wiki_main_page
--wiki_mime_type is a MIME type to allow for uploads; text/plain is always allowed and doesn't need to be listed; you can also just list the type without a subtype, eg. image will allow all sorts of images (make sure random people can't use your server to exchange images – set a password using --wiki_token)
image
--wiki_page_size_limit is the number of bytes to allow for uploads, both for pages and for files; the default is 10000 (10kB)
--host is the hostname to serve; the default is localhost – you probably want to pick the name of your machine, if it is reachable from the Internet; if you use it multiple times, each host gets its own wiki space (see --wiki_space below)
--host
--wiki_space
--port is the port to use; the default is 1965; if you use it multiple times on the same host, they all share the same wiki space
--port
--wiki_dir is the wiki data directory to use; the default is either the value of the PHOEBE_DATA_DIR environment variable, or the "./wiki" subdirectory
--wiki_dir
PHOEBE_DATA_DIR
--wiki_space adds an extra space that acts as its own wiki; a subdirectory with the same name gets created in your wiki data directory and thus you shouldn't name spaces like any of the files and directories already there (see "FILES"); not that settings such as --wiki_page and --wiki_main_page apply to all spaces, but the page content will be different for every wiki space
--cert_file is the certificate PEM file to use; the default is cert.pem
--cert_file
--key_file is the private key PEM file to use; the default is key.pem
--key_file
--no_cert indicates that the server should not be using TLS; use this if you have a reverse proxy handling requests (so that front end and back end don't need to use TLS to communicate with each other)
--no_cert
--log_level is the log level to use (fatal, error, warn, info, debug); the default is warn
--log_level
fatal
error
warn
info
debug
--log_file is the log file to use; the default is undefined, which means that STDERR is used
--log_file
When looking at the command line, Phoebe accumulates hostnames (--host)and ports (--port) until both a certificate (--cert_file) and a private key (--key_file) have been provided, or you have indicated that no TLS is required (--no_cert), at which point the process starts again.
Here is an example that is problematic:
phoebe --host transjovian.org \ --port 1965 --port 443 --cert_file cert.pem --key_file key.pem
This serves the host on both ports, using the same certificate. This is probably not what you want if your certificates are signed by Let's Encrypt or some other service that make you renew the certificate every now and then. This breaks the TOFU model as Gemini clients will warn users everytime the certificate has changed, asking them to confirm the change. This is very annoying. Most likely you want different certificates!
Something like the following probably better suited. The web certificate and web private key is what you get from Let's Encrypt (and if you do, restart Phoebe), and the regular certificate and key file is what you generated yourself for Gemini. Just make sure you replace those before they expire!
phoebe --host transjovian.org \ --port 1965 --cert_file cert.pem --key_file key.pem \ --port 443 --cert_file web_cert.pem --key_file web_key.pem
If you allow uploads of binary files, these are stored separately from the regular pages; the wiki doesn't keep old revisions of files around. If somebody overwrites a file, the old revision is gone.
You definitely don't want random people uploading all sorts of images, videos and binaries to your server. Make sure you set up those tokens using --wiki_token!
The server uses "access tokens" to check whether people are allowed to edit files. You could also call them "passwords", if you want. They aren't associated with a username. You set them using the --wiki_token option. By default, the only password is "hello". That's why the Titan command above contained "token=hello". 😊
If you're going to check up on your wiki often (daily!), you could just tell people about the token on a page of your wiki. Spammers would at least have to read the instructions and in my experience the hardly ever do.
You could also create a separate password for every contributor and when they leave the project, you just remove the token from the options and restart Phoebe. They will no longer be able to edit the site.
The server only actively logs changes to pages. It calculates a "code" for every contribution: it is a four digit octal code. The idea is that you could colour every digit using one of the eight standard terminal colours and thus get little four-coloured flags.
This allows you to make a pretty good guess about edits made by the same person, without telling you their IP numbers.
The code is computed as follows: the IP numbers is turned into a 32bit number using a hash function, converted to octal, and the first four digits are the code. Thus all possible IP numbers are mapped into 8⁴=4096 codes.
If you increase the log level, the server will produce more output, including information about the connections happening, like 2020/06/29-15:35:59 CONNECT SSL Peer: "[::1]:52730" Local: "[::1]:1965" and the like (in this case ::1 is my local address so that isn't too useful but it could also be your visitor's IP numbers, in which case you will need to tell them about it using in order to comply with the GDPR.
2020/06/29-15:35:59 CONNECT SSL Peer: "[::1]:52730" Local: "[::1]:1965"
::1
Here's an example for how to start Phoebe. It listens on localhost port 1965, adds the "Welcome" and the "About" page to the main menu, and allows editing using one of two tokens.
phoebe \ --wiki_token=Elrond \ --wiki_token=Thranduil \ --wiki_page=Welcome \ --wiki_page=About
Here's what my phoebe.service file actually looks like:
[Unit] Description=Phoebe After=network.target [Install] WantedBy=multi-user.target [Service] Type=simple WorkingDirectory=/home/alex/farm Restart=always User=alex Group=ssl-cert MemoryMax=100M MemoryHigh=90M ExecStart=/home/alex/src/phoebe/script/phoebe \ --wiki_dir=/home/alex/phoebe \ --log_level=debug \ --host=transjovian.org \ --port=443 \ --cert_file=/var/lib/dehydrated/certs/transjovian.org/fullchain.pem \ --key_file=/var/lib/dehydrated/certs/transjovian.org/privkey.pem \ --port=1965 \ --host=toki.transjovian.org \ --host=vault.transjovian.org \ --host=communitywiki.org \ --host=alexschroeder.ch \ --host=next.oddmuse.org \ --host=emacswiki.org \ --cert_file=/home/alex/phoebe/cert.pem \ --key_file=/home/alex/phoebe/key.pem \ --wiki_main_page=Welcome \ --wiki_page=About \ --wiki_mime_type=image/png \ --wiki_mime_type=image/jpeg \ --wiki_mime_type=audio/mpeg \ --wiki_space=transjovian.org/test \ --wiki_space=transjovian.org/phoebe \ --wiki_space=transjovian.org/anthe \ --wiki_space=transjovian.org/gemini \ --wiki_space=transjovian.org/titan
In the example above, I'm using certificates I get from Let's Encrypt. Thus, the regular website served on port 443 and the Phoebe website on port 1965 use the same certificates. My problem is that for the regular website, Apache can read the certificates, but in the setup above Phoebe runs as the user alex and cannot access the certificates. My solution is to use the group ssl-cert. This is the group that already has read access to /etc/ssl/private on my system. I granted the following permissions:
alex
ssl-cert
drwxr-x--- root ssl-cert /var/lib/dehydrated/certs drwxr-s--- root ssl-cert /var/lib/dehydrated/certs/* drwxr----- root ssl-cert /var/lib/dehydrated/certs/*/*.pem
The main page will include ("transclude") a page of your choosing if you use the --wiki_main_page option. This also sets the title of your wiki in various places like the RSS and Atom feeds.
In order to be more flexible, the name of the main page does not get printed. If you want it, you need to add it yourself using a header. This allows you to keep the main page in a page called "Welcome" containing some ASCII art such that the word "Welcome" does not show on the main page. This assumes you're using --wiki_main_page=Welcome, of course.
--wiki_main_page=Welcome
If you have pages with names that start with an ISO date like 2020-06-30, then I'm assuming you want some sort of blog. In this case, up to ten of them will be shown on your front page.
There are search machines out there that will index your site. Ideally, these wouldn't index the history pages and all that: they would only get the list of all pages, and all the pages. I'm not even sure that we need them to look at all the files. The Robots Exclusion Standard lets you control what the bots ought to index and what they ought to skip. It doesn't always work. https://en.wikipedia.org/wiki/Robots_exclusion_standard
Here's my suggestion:
User-agent: * Disallow: /raw Disallow: /html Disallow: /diff Disallow: /history Disallow: /do/comment Disallow: /do/changes Disallow: /do/all/changes Disallow: /do/all/latest/changes Disallow: /do/rss Disallow: /do/atom Disallow: /do/all/atom Disallow: /do/new Disallow: /do/more Disallow: /do/match Disallow: /do/search # allowing do/index! Crawl-delay: 10
In fact, as long as you don't create a page called robots then this is what gets served. I think it's a good enough way to start. If you're using spaces, the robots pages of all the spaces are concatenated.
robots
If you want to be more paranoid, create a page called robots and put this on it:
User-agent: * Disallow: /
Note that if you've created your own robots page, and you haven't decided to disallow them all, then you also have to do the right thing for all your spaces, if you use them at all.
See App::Phoebe for more information.
Wiki spaces are separate wikis managed by the same Phoebe server, on the same machine, but with data stored in a different directory. If you used --wiki_space=alex and --wiki_space=berta, for example, then you'd have three wikis in total:
--wiki_space=alex
--wiki_space=berta
gemini://localhost/ is the main space that continues to be available
gemini://localhost/
gemini://localhost/alex/ is the wiki space for Alex
gemini://localhost/alex/
gemini://localhost/berta/ is the wiki space for Berta
gemini://localhost/berta/
Note that all three spaces are still editable by anybody who knows any of the tokens.
Per default, there is simply one set of tokens which allows the editing of the wiki, and all the wiki spaces you defined. If you want to give users a token just for their space, you can do that, too. Doing this is starting to strain the command line interface, however, and therefore the following illustrates how to do more advanced configuration using the config file:
package App::Phoebe; use Modern::Perl; our ($server); $server->{wiki_space_token}->{alex} = ["*secret*"];
The code above sets up the wiki_space_token property. It's a hash reference where keys are existing wiki spaces and values are array references listing the valid tokens for that space (in addition to the global tokens that you can set up using --wiki_token which defaults to the token "hello"). Thus, the above code sets up the token *secret* for the alex wiki space.
wiki_space_token
*secret*
You can use the config file to change the values of other properties as well, even if these properties are set via the command line.
package App::Phoebe; use Modern::Perl; our ($server); $server->{wiki_token} = [];
This code simply deactivates the token list. No more tokens!
Sometimes you want have a machine reachable under different domain names and you want each domain name to have their own wiki space, automatically. You can do this by using multiple --host options.
Here's a simple, stand-alone setup that will work on your local machine. These are usually reachable using the IPv4 127.0.0.1 or the name localhost. The following command tells Phoebe to serve both 127.0.0.1 and localhost (the default is to just serve localhost).
127.0.0.1
phoebe --host=127.0.0.1 --host=localhost
Visit both at gemini://localhost/ and gemini://127.0.0.1/, and create a new page in each one, then examine the data directory wiki. You'll see both wiki/localhost and wiki/127.0.0.1.
If you're using more wiki spaces, you need to prefix them with the respective hostname if you use more than one:
phoebe --host=127.0.0.1 --host=localhost \ --wiki_space=127.0.0.1/alex --wiki_space=localhost/berta
In this situation, you can visit gemini://127.0.0.1/, gemini://127.0.0.1/alex/, gemini://localhost/, and gemini://localhost/berta/, and they will all be different.
If this is confusing, remember that not using virtual hosting and not using spaces is fine, too. 😀
If you're using virtual hosting as discussed above, you have two options: you can use one certificate for all your hostnames, or you can use different certificates for the hosts. If you want to use just one certificate for all your hosts, you don't need to do anything else. If you want to use different certificates for different hosts, you have to specify them all on the command line. Generally speaking, use --host to specifiy one or more hosts, followed by both --cert_file and --key_file to specifiy the certificate and key to use for the hosts.
For example:
phoebe --host=transjovian.org \ --cert_file=/home/alex/phoebe/transjovian-cert.pem \ --key_file=/home/alex/phoebe/transjovian-key.pem \ --host=alexschroeder.ch \ --cert_file=/home/alex/phoebe/alexschroeder-cert.pem \ --key_file=/home/alex/phoebe/alexschroeder-key.pem
The Transjovian Council has a wiki space dedicated to Phoebe, and it includes a section with more configuration examples. See gemini://transjovian.org/phoebe or https://transjovian.org:1965/phoebe.
GNU Affero General Public License
To install App::Phoebe, copy and paste the appropriate command in to your terminal.
cpanm
cpanm App::Phoebe
CPAN shell
perl -MCPAN -e shell install App::Phoebe
For more information on module installation, please visit the detailed CPAN module installation guide.