summaryrefslogtreecommitdiff
path: root/sites/pmikkelsen.com/plan9
diff options
context:
space:
mode:
Diffstat (limited to 'sites/pmikkelsen.com/plan9')
-rw-r--r--sites/pmikkelsen.com/plan9/basic_9p_server.md58
-rw-r--r--sites/pmikkelsen.com/plan9/discord.md83
-rw-r--r--sites/pmikkelsen.com/plan9/dns.md41
-rw-r--r--sites/pmikkelsen.com/plan9/fonts.md29
-rw-r--r--sites/pmikkelsen.com/plan9/lets_encrypt.md59
-rw-r--r--sites/pmikkelsen.com/plan9/mounting-9p-over-drawterm.md58
-rw-r--r--sites/pmikkelsen.com/plan9/network_booting.md30
-rw-r--r--sites/pmikkelsen.com/plan9/trellofs.md347
-rw-r--r--sites/pmikkelsen.com/plan9/using_irc.md48
9 files changed, 753 insertions, 0 deletions
diff --git a/sites/pmikkelsen.com/plan9/basic_9p_server.md b/sites/pmikkelsen.com/plan9/basic_9p_server.md
new file mode 100644
index 0000000..7c1c514
--- /dev/null
+++ b/sites/pmikkelsen.com/plan9/basic_9p_server.md
@@ -0,0 +1,58 @@
+## Writing a basic 9P server
+
+On plan 9 the entire system is build around the idea of namespaces
+and that _"everything is a file"_. For this reason it is very easy to write
+a new 9P fileserver in C since all the boring tasks are implemented in
+libraries. This note describes a minimal program which serves a folder to
+`/mnt/hello9p` containing a single synthetic file with the contents "Hello from 9P!".
+
+## The code
+
+ #include <u.h>
+ #include <libc.h>
+ #include <fcall.h>
+ #include <thread.h>
+ #include <9p.h>
+
+ void
+ fsread(Req *r)
+ {
+ readstr(r, "Hello from 9P!\n");
+ respond(r, nil);
+ }
+
+ Srv fs = {
+ .read = fsread,
+ };
+
+ void
+ main(void)
+ {
+ Tree *tree;
+
+ tree = alloctree(nil, nil, DMDIR|0555, nil);
+ fs.tree = tree;
+ createfile(tree->root, "hello", nil, 0555, nil);
+
+ postmountsrv(&fs, nil, "/mnt/hello9p", MREPL | MCREATE);
+ }
+
+## Explanation
+
+The global variable `fs` is a structure which contains function pointers
+to all the 9P handlers, but since I only plan on reading from the file,
+only the `read` field is set. The fsread function calls two helper functions
+from the 9p(2) library which will create a response with the given string as
+the file contents.
+
+In `main` I start by allocating a new file tree, since this 9P server deals with
+a fileserver that has a tree structure, and therefore I don't have to worry
+about how directories are handled for example. A file is added with `createfile`
+to the root of the tree.
+
+The call to `postmountsrv` will mount the 9P server under `/mnt/hello9p`.
+
+## Thats it
+
+This is not very complicated, but see the manpages at 9p(2) and 9pfile(2)
+and intro(5) for more information about the libraries and 9P itself.
diff --git a/sites/pmikkelsen.com/plan9/discord.md b/sites/pmikkelsen.com/plan9/discord.md
new file mode 100644
index 0000000..8a9815f
--- /dev/null
+++ b/sites/pmikkelsen.com/plan9/discord.md
@@ -0,0 +1,83 @@
+# Chatting on discord from 9front
+
+Sometimes you work with people who doesn't want to use the same communication
+tools as you do. A sad example is when your team members wants to use discord,
+and not something simpler like IRC. Oh well, gotta deal with it.
+
+This is the reason I wrote a "discord bot" (ugh) which can run on your 9front
+system and allow you to chat on discord anyways.
+
+## Example gif
+
+The example below just opens two chat windows with different channels in each.
+While it works both in acme and in the terminal, I like having everything
+together inside acme.
+
+[![An animated gif showing discord chats from 9front][1]][1]
+
+## The code
+
+The project is made of a few scripts and a go program. The only thing that
+actually communicates with discord is the go program, and the scripts just
+makes it easier to send and recieve on the correct channels. The code is
+available [here](https://git.sr.ht/~pmikkelsen/discordfront), and there is
+a precompiled version of the go program included (compiled for 9front amd64).
+
+## Usage
+
+To use the service, you must first create a bot on discord and get the
+access token. I personally found this step much more confusing then the
+actual coding itself, which either tells you something about my google
+skills, or about discord.. Oh, and then you must invite the bot to your
+discord server..
+
+Next, the server part of the bot must be started:
+
+ `discordsrv YOURTOKEN`
+
+This will post a pipe in /srv/discordfront, where messages can be send
+and recieved from, but you should not read from it directly. The
+`discordsrv` script does this for you as well, and it takes each line
+and dumps it in a nice readable format into
+`$home/lib/discord/logs/$serverName/$channelName`
+where the server name is the name of a discord server (or guild?), and
+the channel name is, well, the name of the channel.
+
+Now that the server is started, you can either run `discordacme` which
+opens an acme with the file `$home/lib/discord/channels` open. This file
+is not handled by any of the scripts, so it is just a handy text file to
+keep shortcuts to various channels (see the gif for an example).
+Inside `discordacme`, the `openChat` command will open a new chat window,
+using the simpler `discord` script.
+
+The `discord` script simply runs `tail -f` on the given channel's logfile
+while it reads messages from standard input and sends them to
+/srv/discordfront at the same time.
+
+## Using remotely
+
+Since this program does not fetch previous messages, it might be a good
+idea to run the server part `discordsrv` on a machine that is always online,
+so that all messages gets logged. Since this is plan9, it is almost trivial
+to still use the client on your local machine, just by using `rimport` to
+import the relevant file trees from the server.
+
+Here is what I do (in a seperate script which is run at startup):
+
+ #!/bin/rc
+
+ rfork
+ rimport -ac -p $serverhost /srv
+ rimport -c -p $serverhost /usr/glenda/lib/discord
+ discordacme
+
+I don't even notice it is not running locally.
+
+## Notes
+
+* Yes, the names of the scripts suck.
+* Yes, there is a lot of missing functionality.
+* No, this has not been tested very much, it just seems to do the job
+well enough for now.
+
+[1]: /images/discordgif.gif
diff --git a/sites/pmikkelsen.com/plan9/dns.md b/sites/pmikkelsen.com/plan9/dns.md
new file mode 100644
index 0000000..04bd7b5
--- /dev/null
+++ b/sites/pmikkelsen.com/plan9/dns.md
@@ -0,0 +1,41 @@
+# Using 9front as an authoritative DNS server
+
+This note describes the steps I took to make the 9front server
+at pmikkelsen.com the authoritative dns server for itself.
+
+First, I logged into my domain name registrar and changed the dns servers for
+my domain to `ns1.pmikkelsen.com` and `ns2.pmikkelsen.com` (It would not let me have
+just one, but I choose to live dangerously and point both of those domains at
+the same server).
+
+To let the world know _where_ `ns1` and `ns2` can be found,
+I added their ip on the registrar's website (since my own dns server cannot serve
+them for good reasons). Anyways, this might not be the same for you since you
+may not use the same provider.
+
+## Starting the dns server in "serve mode"
+
+First I added 1 line to the `/cfg/$sysname/cpurc` file to enable the dns server.
+
+ ndb/dns -s
+
+After that I added a few lines in `/lib/ndb/local` to setup all the dns records I needed:
+
+ dom=pmikkelsen.com soa=
+ ip=80.240.16.196
+ mx=vps1.pmikkelsen.com pref=1
+ txtrr="v=spf1 a mx ip:80.240.16.196 ~all"
+
+ dom=p9auth.pmikkelsen.com soa=
+ ip=80.240.16.196
+
+ dom=vps1.pmikkelsen.com soa=
+ ip=80.240.16.196
+
+ dom=_dmarc.pmikkelsen.com soa=
+ txtrr="v=DMARC1; p=none"
+
+With this done, I now have A records, mx records and txt records in place, so my website,
+mail and rcpu works as expected.
+
+Bye. \ No newline at end of file
diff --git a/sites/pmikkelsen.com/plan9/fonts.md b/sites/pmikkelsen.com/plan9/fonts.md
new file mode 100644
index 0000000..6cd6780
--- /dev/null
+++ b/sites/pmikkelsen.com/plan9/fonts.md
@@ -0,0 +1,29 @@
+# Fonts on 9front
+
+By default 9front uses a font called VGA but its too small for my eyes.
+
+The fonts I use (dejavu) look like this[![an image showing the fonts][1]][1] and are attached below:
+
+[proportional font](/_files/djv.tar)
+
+[monospace font](/_files/djvmono.tar)
+
+They can be recreated by having them installed on linux and doing the following from
+linux with plan9port installed.
+
+ fontsrv -m /tmp/fonts
+ cp -r /tmp/fonts/DejaVuSomeThing/14a/ /tmp/djv
+
+You can try a different font or font size if you want.
+
+Then connect to 9front via drawterm and run:
+
+ mkdir /lib/font/bit/djv
+ dircp /mnt/term/tmp/djv /lib/font/bit/djv
+
+Finally change your `lib/profile` to use `/lib/font/bit/djv/font` :)
+
+Note that 9front includes truetypefs which allows you to use `.ttf` files
+directly, but I find the results are better looking this way.
+
+[1]: /images/djvfonts.png \ No newline at end of file
diff --git a/sites/pmikkelsen.com/plan9/lets_encrypt.md b/sites/pmikkelsen.com/plan9/lets_encrypt.md
new file mode 100644
index 0000000..f0b1c07
--- /dev/null
+++ b/sites/pmikkelsen.com/plan9/lets_encrypt.md
@@ -0,0 +1,59 @@
+## How I get tls certificates for 9front
+
+First of all, I use linux and drawterm for this for now, but
+I would like to be able to do it all from 9front at some point.
+
+## Generate the certificate
+
+Install certbot on linux and run the following command
+
+ certbot certonly --manual -d pmikkelsen.com -d vps1.pmikkelsen.com
+
+and do the challenges, they should be easy.
+
+## Importing the cert and private key
+
+Start drawterm and login as the hostowner. After this, the filesystem of the linux
+system is available at `/mnt/term`. Run the following:
+
+ cd /sys/lib/tls/
+ cp /mnt/term/etc/letsencrypt/live/pmikkelsen.com/privkey.pem ./
+ cp /mnt/term/etc/letsencrypt/live/pmikkelsen.com/fullchain.pem ./cert
+
+Now the private key must be converted to one that can be loaded into factotum
+
+ auth/pemdecode 'PRIVATE KEY' privkey.pem | auth/asn12rsa -t 'service=tls role=client' > key
+ rm privkey.pem
+ chmod 400 key
+
+Add the following to `/cfg/$sysname/cpurc` to load the private key on boot.
+
+ cat /sys/lib/tls/key >> /mnt/factotum/ctl
+
+Done.
+
+## SMTP over TLS
+
+I have the following in `/bin/service.auth/tcp25`
+
+ #!/bin/rc
+
+ user=`{cat /dev/user}
+ exec upas/smtpd -c /sys/lib/tls/cert -n $3
+
+Notice I had to put it in the `/bin/service.auth` folder so that it could find the private key.
+
+## Https with rc-httpd
+
+I have the following in `/bin/service.auth/tcp443`
+
+ #!/bin/rc
+
+ exec tlssrv -c /sys/lib/tls/cert -l /sys/log/https /bin/service/tcp80 $*
+
+Again, in the `/bin/service.auth` folder. It simply wraps the plain http service
+in a tls wrapper which looks like this for me
+
+ #!/bin/rc
+ PLAN9=/
+ exec /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
diff --git a/sites/pmikkelsen.com/plan9/mounting-9p-over-drawterm.md b/sites/pmikkelsen.com/plan9/mounting-9p-over-drawterm.md
new file mode 100644
index 0000000..414b9f2
--- /dev/null
+++ b/sites/pmikkelsen.com/plan9/mounting-9p-over-drawterm.md
@@ -0,0 +1,58 @@
+# Mounting a 9P connection over drawterm
+
+I sometimes use drawterm on linux to connect to my 9front server.
+While it is possible to access the host system's files under `/mnt/term`, there is no builtin way to access the remote system's file under linux.
+Now, why would anybody want to do this?
+In my case, I often want to write some code under 9front, but for languages which aren't supported such as prolog in this case, so there are three options as I see it:
+
+* Store the files on the host machine, and access them under `/mnt/term`.
+* Store the files on the server and somehow mount the server's filesystem on the host.
+* Store the files on a third machine that both the host and server can access.
+
+Option number two seems best for me, so I asked around and it seems like the best tool to mount a 9P connection on linux is [9pfs](https://github.com/bunny351/9pfs).
+
+## How it works
+
+Before I could mount the connection, I had to serve it somehow. Normally I already serve 9P directly from my server's filesystem, but that requires some
+authentication that 9pfs does not support, so I had to serve it without authentication.
+Serving directly to the internet without authentication is of course pretty dumb since everyone can then access my files, so thanks to a hint from hiro, I figured
+out that it is actually possible to use the host's network stack on the server by binding `/mnt/term/net` over `/net`.
+
+I'll just show the final script below and explain it afterwards:
+
+ #!/bin/rc
+
+ rfork n
+ bind /mnt/term/net /net
+ aux/listen1 -t tcp!*!12335 /bin/exportfs -r / &
+ os mkdir -p /tmp/drawterm
+ os 9pfs localhost -p 12335 /tmp/drawterm
+
+So the first thing that happens is that I bind the host's network in, and from that point on, every network connection in this namespace actually goes out
+from the host instead of from the server!
+
+Then `exportfs` is started and it is serving the `/` directory over 9P at port 12335.
+The `os` command runs a command on the host, so it just creates the folder that the system will be mounted to, and then uses `9pfs` to actually mount it.
+The nice thing here is that `9pfs` just connects to localhost.
+
+## Using it
+
+As I said in the beginning, I did this to be able to edit files on the 9front server, and run compilers/interpreters on linux.
+The `os` command goes a long way, but the following script makes it even easier (I have this installed as `linux`):
+
+ #!/bin/rc
+
+ dir=/tmp/drawterm/`{pwd}
+ os -d $dir $*
+
+This means I can just go into any directory on the server, type `linux ghci` and I get a haskell repl running in the correct directory, as seen in the gif below.
+
+[![An animated gif showing ghci loading a file on the 9front server][1]][1]
+
+
+## Final notes
+
+The bind net trick still blows my mind a bit, since it is so trivial, yet so powerful. It is also fun to think about how one would do this
+on other systems than plan9, which I can't even imagine.
+
+[1]: /images/linuxreverse.gif
diff --git a/sites/pmikkelsen.com/plan9/network_booting.md b/sites/pmikkelsen.com/plan9/network_booting.md
new file mode 100644
index 0000000..3bb51b9
--- /dev/null
+++ b/sites/pmikkelsen.com/plan9/network_booting.md
@@ -0,0 +1,30 @@
+## Network booting into the server at pmikkelsen.com
+
+Sometimes it is nice to be able to connect directly to a remote server
+from my laptop and have the same root filesystem available.
+This is possible by typing `tls` at the boot prompt and then
+typing `pmikkelsen.com` for the fs and auth server.
+
+## Speed
+
+On my internet connection some things can be very slow
+(such as compiling the entire system), because of fs access now happens
+over the network. The easy fix is to just start a cpu connection to the server
+where the fs access can happen fast since it is on the same machine. This is
+done by typing
+
+ rcpu -h pmikkelsen.com
+
+Of course the server has a pretty slow cpu..
+
+## Server settings
+
+Some steps are needed to make this work:
+
+1. The user must be added to the fileserver and auth server.
+2. The fileserver must be listening for remote connections.
+3. The correct ndb entries must be set (in the beginning I forgot `fs` and I could
+only connect via tcp, not tls)
+
+More information about all those steps can be found in section 7 of [the 9front FQA](http://fqa.9front.org/fqa7.html).
+
diff --git a/sites/pmikkelsen.com/plan9/trellofs.md b/sites/pmikkelsen.com/plan9/trellofs.md
new file mode 100644
index 0000000..42e2cfd
--- /dev/null
+++ b/sites/pmikkelsen.com/plan9/trellofs.md
@@ -0,0 +1,347 @@
+## A filesystem for viewing trello
+
+Since my last note [here](http://pmikkelsen.com/plan9/basic_9p_server)
+was very simple, here comes
+a small program (around 250 lines of C), which makes it
+possible to view trello as a read-only filesystem.
+
+## Usage example
+
+First set the `trellokey` and `trellotoken` environment
+variables to the API key and token from the trello website.
+
+ term% trellokey=1298791723987937123.....
+ term% trellotoken=923748237497ab798798999999.....
+
+Then `webfs` must be started so the program can make
+requests to the trello REST api.
+
+ term% webfs
+
+Then `trellofs` itself must be started. It mounts the filesystem under `/mnt/trello`.
+
+ term% trellofs
+
+If I now go and see which boards I have, I get the following
+
+ term% cd /mnt/trello
+ term% ls
+ Beaglebone_Black
+ Fly
+ Fysik_Rapport
+ Guld_kobber
+ Ideer_til_2._produkt
+ Lektier
+ P0
+ P1
+ P2
+ Projekt_Materialer
+ Spil
+ Welcome_Board
+
+The names of the folders are almost like the original board names, except
+that that some characters have been replaced by `_`. Lets look at `P1`
+
+ term% cd P1
+ term% ls
+ Backlog
+ Done
+ Estimat
+ In-progress
+ Info
+ Master.C_Top_Down_Ting
+ Sprint_1
+ Test
+ Test_sprint_1
+ Todo
+
+Todo must have something worth looking at
+
+ term% ls
+ Færdig_rapport_aflevering
+ Korrekturlæsning
+
+Those two items are the actual trello "cards", which I think of as tasks. The first
+one means "Final report hand in", so let's have a look at that.
+
+ term% cd Færdig_rapport_aflevering
+ term% ls
+ description
+ duedate
+ url
+
+There are always three files inside each card folder, but since the information in them
+is not mandatory on trello, some of them will be empty.
+
+ term% cat description
+
+ term% cat duedate
+ 2019-12-18T13:00:00.000Z
+ term% cat url
+ https://trello.com/c/u23ZcNAm/9-f%C3%A6rdig-rapport-aflevering
+
+## Limitations
+
+The key and token are right now in environment variables, which is somewhat
+awkward. Also I would like to be able to actually create and move cards directly
+from the filesystem just by creating files, but that is not something I will implement
+right now.
+
+## The code
+
+The code for the `trellofs` program is listed below.
+
+ #include <u.h>
+ #include <libc.h>
+ #include <fcall.h>
+ #include <thread.h>
+ #include <9p.h>
+ #include <json.h>
+
+ #define MAX_RESPONSE_SIZE (1024 * 1024 * 8)
+
+ char *key;
+ char *token;
+
+ void
+ fsread(Req *r)
+ {
+ char *str;
+
+ if (r->fid->file->aux == nil) {
+ readstr(r, "");
+ } else {
+ str = malloc(strlen(r->fid->file->aux) + 2);
+ sprint(str, "%s\n", r->fid->file->aux);
+ readstr(r, str);
+ }
+ respond(r, nil);
+ }
+
+ Srv fs = {
+ .read = fsread,
+ };
+
+ JSON *
+ trelloget(char *endpoint, char *params)
+ {
+ int ctlfd, bodyfd;
+ int n;
+ int err;
+ char *buf;
+ JSON *res;
+
+ res = nil;
+ bodyfd = -1;
+
+ buf = malloc(MAX_RESPONSE_SIZE + 1);
+ if(buf == nil) {
+ perror("malloc");
+ return nil;
+ }
+
+ ctlfd = open("/mnt/web/clone", ORDWR);
+ if(ctlfd < 0) {
+ perror("open");
+ goto fail;
+ }
+
+ if(read(ctlfd, buf, 32) < 0) {
+ perror("read");
+ goto fail;
+ }
+
+ n = atoi(buf);
+
+ sprint(buf, "url https://api.trello.com/%s?key=%s&token=%s", endpoint, key, token);
+ if(params)
+ sprint(buf + strlen(buf), "\&%s\n", params);
+ else
+ sprint(buf + strlen(buf), "\n");
+
+ err = write(ctlfd, buf, strlen(buf));
+ if(err < 0){
+ perror("write");
+ goto fail;
+ }
+
+ sprint(buf, "/mnt/web/%d/body", n);
+ bodyfd = open(buf, OREAD);
+ if(bodyfd < 0){
+ perror("open");
+ goto fail;
+ }
+
+ err = readn(bodyfd, buf, MAX_RESPONSE_SIZE);
+ if (err < 0) {
+ perror("read");
+ goto fail;
+ }
+
+ res = jsonparse(buf);
+
+ fail:
+ close(ctlfd);
+ close(bodyfd);
+ free(buf);
+
+ return res;
+ }
+
+ char *
+ escapename(char *str)
+ {
+ char *new;
+ int i, len;
+
+ len = strlen(str);
+
+ new = malloc(len + 1);
+ for(i = 0; i <= len; i++){
+ switch(str[i]){
+ case '/':
+ case ' ':
+ case ',':
+ case ':':
+ case '"':
+ case '\'':
+ new[i] = '_';
+ break;
+ default:
+ new[i] = str[i];
+ }
+ }
+ return new;
+ }
+
+ void
+ addcard(File *dir, JSON *card)
+ {
+ JSON *element;
+ char *filename;
+ File *carddir;
+ char *description;
+ char *url;
+ char *duedate;
+
+ element = jsonbyname(card, "name");
+ filename = escapename(element->s);
+ carddir = createfile(dir, filename, nil, DMDIR|0555, nil);
+ if(carddir == nil){
+ perror("createfile");
+ return;
+ } else {
+ element = jsonbyname(card, "desc");
+ description = strdup(element->s);
+ element = jsonbyname(card, "url");
+ url = strdup(element->s);
+ element = jsonbyname(card, "due");
+ if (element->t == JSONString)
+ duedate = strdup(element->s);
+ else
+ duedate = nil;
+ createfile(carddir, "description", nil, 0444, description);
+ createfile(carddir, "url", nil, 0444, url);
+ createfile(carddir, "duedate", nil, 0444, duedate);
+ }
+ free(filename);
+ }
+
+ void
+ addlist(File *dir, JSON *list)
+ {
+ JSON *element;
+ char *filename;
+ JSON *cards;
+ JSONEl *card;
+ char cardsEndpoint[128];
+ File *listdir;
+
+ element = jsonbyname(list, "name");
+
+ filename = escapename(element->s);
+ listdir = createfile(dir, filename, nil, DMDIR|0555, nil);
+ if(listdir == nil){
+ perror("createfile");
+ return;
+ } else {
+ element = jsonbyname(list, "id");
+ sprint(cardsEndpoint, "1/lists/%s/cards", element->s);
+ cards = trelloget(cardsEndpoint, "fields=desc,name,url,due");
+ if(cards == nil)
+ return;
+
+ for(card = cards->first; card != nil; card = card->next){
+ addcard(listdir, card->val);
+ }
+ }
+ jsonfree(cards);
+ free(filename);
+ }
+
+ void
+ addboard(File *root, JSON *board)
+ {
+ JSON *element;
+ JSONEl *list;
+ char *filename;
+ File *boarddir;
+
+ element = jsonbyname(board, "name");
+ filename = escapename(element->s);
+ boarddir = createfile(root, filename, nil, DMDIR|0555, nil);
+ if(boarddir == nil) {
+ perror("createfile");
+ return;
+ }
+
+ element = jsonbyname(board, "lists");
+ for(list = element->first; list != nil; list = list->next){
+ addlist(boarddir, list->val);
+ }
+
+ free(filename);
+ }
+
+ void
+ trelloinit(File *root)
+ {
+ JSON *result;
+ JSONEl *board;
+
+ result = trelloget("1/members/me/boards", "fields=name,lists&lists=open");
+
+ for(board = result->first; board != nil; board = board->next){
+ addboard(root, board->val);
+ }
+ jsonfree(result);
+ }
+
+ void
+ main(void)
+ {
+ JSONfmtinstall();
+
+ key = getenv("trellokey");
+ token = getenv("trellotoken");
+
+ Tree *tree;
+
+ tree = alloctree(nil, nil, DMDIR|0555, nil);
+
+ fs.tree = tree;
+ trelloinit(tree->root);
+
+ postmountsrv(&fs, nil, "/mnt/trello", MREPL | MCREATE);
+ }
+
+The program can be compiled by hand or by using the following `mkfile`.
+
+ BIN=/usr/glenda/bin/amd64
+
+ TARG=trellofs
+
+ OFILES=\
+ main.$O\
+
+ </sys/src/cmd/mkone
diff --git a/sites/pmikkelsen.com/plan9/using_irc.md b/sites/pmikkelsen.com/plan9/using_irc.md
new file mode 100644
index 0000000..0c3622c
--- /dev/null
+++ b/sites/pmikkelsen.com/plan9/using_irc.md
@@ -0,0 +1,48 @@
+_Last updated: 2020-07-30_
+
+# Using IRC on 9front
+
+While 9front comes with an irc client called `ircrc`, it can be quite annoying that it does not
+keep a persistent connection.
+
+A different program `irc7` provides a server part which keeps a connection open to an irc server,
+and a client part which allows for connecting to the server. This works great since it makes it possible to
+get all the messages on a channel without being online all the time.
+
+## Setup
+
+Download the irc7 code via `hg`:
+
+ hg clone https://code.9front.org/hg/irc7/
+
+Build and install
+
+ cd irc7
+ mk install
+
+## Usage
+
+First the server program `ircsrv` must be started, so I would run
+
+ ircsrv -e -p VerySecretPassword pmikkelsen net!irc.freenode.net!7000
+
+to connect as `pmikkelsen` to freenode over tls and with password for my nickname.
+After this, it is possible to connect to different channels by opening new rio windows
+and running
+
+ irc -t '#cat-v' -b
+
+to connect to the cat-v channel. The `-b` option without any extra arguments
+prints the entire conversation since the ircsrv was started,
+so it might not always be desirable.
+
+To see all the messages sent directly to me, I would run
+
+ irc -t MSGS
+
+and maybe start a conversation with user 'mousehater' by running
+
+ irc -t mousehater
+
+I run this on a remote cpu server that is rarely powered off, and then I just connect via
+drawterm from linux to chat, or via rcpu from other 9front machines.