đŸ’Ÿ Archived View for flexibeast.space â€ș gemlog â€ș 2023-05-15.gmi captured on 2024-05-12 at 15:10:09. Gemini links have been rewritten to link to archived content

View Raw

More Information

âŹ…ïž Previous capture (2024-05-10)

-=-=-=-=-=-=-

Setting up the soju IRC bouncer on OpenBSD, and using it with Emacs' ERC

Setting up the soju[a] IRC bouncer[b] on my OpenBSD server was fairly straightforward. It's not currently in ports, so i downloaded a release from sourcehut:

soju on sourcehut

soju is written in Go, but soju releases contain a Makefile that allows one to do `make && make install`. After doing that, i created a `_soju` group and user via groupadd(8) and useradd(8).

/etc/group:

_soju:*:1010:

/etc/passwd:

_soju:*:1010:1010:Soju Account:/nonexistent:/sbin/nologin

i then created an rc script for soju.

/etc/rc.d/soju:

#!/bin/ksh

daemon="/usr/local/bin/soju"
daemon_user="_soju"
daemon_logger="daemon.info"

. /etc/rc.d/rc.subr

rc_reload=NO

rc_cmd $1

i then created a `/run/soju` directory, chown(8)'d it to `_soju:_soju`, and after editing `/etc/soju/config` appropriately, ran the following commands:

 # sojudb create-user admin -admin
 # chown -R _soju:_soju /var/lib/soju
 # sojuctl user create -username [username] -password [password]
 # rcctl start soju
 # sojuctl user run [username] network create -addr ircs://irc.oftc.net -name oftc -nick [nick] -connect-command "PRIVMSG NickServ :IDENTIFY [password]"

Et voilĂĄ: this was enough to get soju working with the IRC app on my phone (Revolution IRC[c]).

However, getting Emacs' ERC to work with it was not so straightforward. The main problem is that ERC, as provided by Emacs 28.3:

The latter is a problem when ERC tries to automatically reconnect after a disconnect: it will fail, due to trying to use the nick associated with a given network, rather than the soju username.

Working around these issues involved:

GitHub: erc-sasl.el

  (defvar erc-soju-server "[host]"
    "Hostname running soju instance.")
  (defvar erc-soju-port [port]
    "Port on which soju is running.")
  (defvar erc-soju-username "[soju username]"
    "Username on soju instance.")
  (defvar erc-soju-networks '([list of networks])
    "Networks available for `erc-soju-username' on soju instance.")
  (defvar erc-soju-networks-channels
  '(("[network name as reported by network]" . ([list of channels]))
    [...]))
    "Association list of channels on each network to join by default.")
  (defvar erc-soju t
    "Whether ERC is being used with soju.")

  ;; Modify erc-login to support SASL.
  ;; https://libera.chat/guides/emacs-erc

  (load-file "~/.config/emacs/erc-sasl.el")
  (add-to-list 'erc-sasl-server-regexp-list erc-soju-server)

  (defun erc-login ()
    "Perform user authentication at the IRC server. (PATCHED)"
    (erc-log (format "login: nick: %s, user: %s %s %s :%s"
                     (erc-current-nick)
                     (user-login-name)
                     (or erc-system-name (system-name))
                     erc-session-server
                     erc-session-user-full-name))
    (if erc-session-password
        (erc-server-send (format "PASS %s" erc-session-password))
      (message "Logging in without password"))
    (when (and (featurep 'erc-sasl) (erc-sasl-use-sasl-p))
      (erc-server-send "CAP REQ :sasl"))
    (erc-server-send (format "NICK %s" (erc-current-nick)))
    (erc-server-send
     (format "USER %s %s %s :%s"
             ;; hacked - S.B.
             (if erc-anonymous-login erc-email-userid (user-login-name))
             "0" "*"
             erc-session-user-full-name))
    (erc-update-mode-line))

  ;; Modify erc-server-reconnect to behave differently with soju.

  (defun erc-server-reconnect ()
    "Reestablish the current IRC connection.
  Make sure you are in an ERC buffer when running this."
    (let ((buffer (erc-server-buffer)))
      (unless (buffer-live-p buffer)
        (if (eq major-mode 'erc-mode)
            (setq buffer (current-buffer))
          (error "Reconnect must be run from an ERC buffer")))
      (with-current-buffer buffer
        (erc-update-mode-line)
        (erc-set-active-buffer (current-buffer))
        (setq erc-server-last-sent-time 0)
        (setq erc-server-lines-sent 0)
        (let ((erc-server-connect-function (or erc-session-connector
                                               #'erc-open-network-stream)))
          (erc-open erc-session-server erc-session-port
                    (if erc-soju
                        erc-soju-username
                      erc-server-current-nick)
                    erc-session-user-full-name t erc-session-password
                    nil nil nil erc-session-client-certificate
                    (if erc-soju
                        erc-soju-username
                      erc-session-username)
                    (erc-networks--id-given erc-networks--id))
          (unless (with-suppressed-warnings ((obsolete erc-reuse-buffers))
                    erc-reuse-buffers)
            (cl-assert (not (eq buffer (current-buffer)))))))))

  (defun erc-soju-start (arg)
    "Start an ERC session with soju.
  With prefix arg, close channel buffers and terminate processes."
    (interactive "P")
    (let ((choice
           (completing-read
            "[Soju] Network? "
            erc-soju-networks)))
      (setq erc-soju t)
      ;;(setq erc-autojoin-mode nil)
      (if arg
          (dolist (bfr (buffer-list))
            (if (and (string=
                      "erc-mode"
                      (with-current-buffer bfr major-mode))
                     (string-match
                      (concat
                       "^#"
                       "\\|"
                       "^"
                       erc-soju-server)
                      (buffer-name bfr)))
                (let ((kill-buffer-query-functions nil))
                  (kill-buffer bfr)))))
      (setq erc-email-userid
            (concat
             erc-soju-username
             "/"
             choice
             "@emacs"))
      (erc-tls :server erc-soju-server
               :port erc-soju-port
               :nick erc-soju-username)))

  (defun erc-soju-after-connect (_server _nick)
    "Join channels specified by `erc-soju-networks-channels'."
    (let ((network (assoc
                    erc-network
                    erc-soju-networks-channels)))
      (if network
          (dolist (chan (cdr network))
            (erc-join-channel chan)))))
  (add-hook 'erc-after-connect #'erc-soju-after-connect)

Whew! But having done all that, ERC seems to now be working with soju.

☙

đŸ· ict

Glossary

Gemlog Home

☙

[a] i chose soju after initially trying to use ZNC, which i chose after doing some research into (a) recommended IRC bouncers that were (b) also available as OpenBSD ports. Once i actually started trying to use it, i encountered an issue which led me to this page:

“ZNC Design Flaws”

Thus educated, i felt it best to consider other options. The main option i found was the combination of pounce(1) and calico(1):

Man page: pounce(1)

Man page: calico(1)

This _felt_ like it was probably the best option overall, but it also felt like the setup was too complicated for someone as-yet unfamiliar with IRC bouncers. i thus decided to go with manually setting up soju, as the bouncer used by sourcehut.

[b] An “IRC bouncer” is a particular type of Bounced Network Connection (BNC):

Many BNCs remain connected to an IRC server in the event the client should disconnect from the Internet. Often state changes are tracked so that they may be relayed to the client upon reconnection. Some implementations opt to store all messages sent across the network that the client would have normally received and send them upon the client's reconnection; this is often considered to be much too resource dependent for commercial hosting services to provide.

Wikipedia: ‘BNC (software)’ / ‘IRC’

[c] Revolution IRC is available from F-Droid:

F-Droid: ‘Revolution IRC’