aboutsummaryrefslogblamecommitdiffstats
path: root/articles/ejabberd.md
blob: b832f704aad6cc4cf63add87ebb8967cc7a4febf (plain) (tree)





















                                                                                                                                                                                                                                                                                                                                          
                              
                   

                       













                                                                                      
                                     























                                                                                                                                                
                                                                            



                                                                    


                                                
 


                                                

   
                                                                                                                                                                                                                                                                                                                                   




















                                                                                                                    
                       





















































































































                                                                                                                                                                                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                         


































































                                                                                                                                                   
                                 




                           
                                 































































                                                                                                                                                                                                                                
                                              
 






                                                                                                                                                                                                          






                



                                                                                                                                                                                                                 



































































































                                                                                                                                                                                                                                                                                                                                                                      















                                                                                                                                                                     
+++
title = "cozy ejabberd xmpp chat server"
created_at = "2023-09-16T21:07:40+0100" 
tags = ["tech", "sysadmin"]
+++

today we are going to do the **ultimate** *cozy* ejabberd server guide. the only prerequisites for this guide are, one, having already set up some form of server, whether it be a vps, or an old laptop behind your fridge, it doesn't matter, because this isn't the email network, and two, ownership of a domain name, e.g. `blos.sm`.

we shall begin with a single virtualhost[^1]. for example purposes, we will use `example.slay` (let's imagine this is a real tld because why not) as the domain. just replace all instances of `example.slay` with your own domain. 

[^1]: virtualhosts allow more than one xmpp service to be run on one server. for example, you could have one xmpp service with the domain `example.slay` and another xmpp service with the domain `example.flop`, both somewhat separated from each other as if they were run on different servers.

<div>
<div class="panel checklist">

checklist!:
- [ ] add dns records
- [ ] open firewall ports
- [ ] set up web server
- [ ] get ssl certificates
- [ ] set up postgres database
- [ ] install ejabberd
- [ ] write/edit configuration
- [ ] start service
- [ ] create admin user
- [ ] change loglevel

</div>

## step 1: set up your dns records

you will need dns A/AAAA records and srv records for each service on your xmpp server.

you will need a records for:

- `example.slay`
- `muc.example.slay` (for group chats)
- `upload.example.slay` (for http file upload)
- `pubsub.example.slay` (for the pubsub node)
- `proxy.example.slay` (for file transfer proxy)
- `turn.example.slay` (for stun/turn)

each pointing to the ip address of your server that is going to run ejabberd. the last two are technically optional, but i would recommend them.

as well as srv records for each, pointing to a domain that resolves to your server, as so:

(all records are in the form
`_service._proto.name IN SRV priority weight port target`)

```
_xmpp-client._tcp   IN SRV  5 0 5222 example.slay.
_xmpps-client._tcp  IN SRV  5 0 5223 example.slay.
_xmpp-server._tcp   IN SRV  5 0 5269 example.slay.
_xmpps-server._tcp  IN SRV  5 0 5270 example.slay.
```

and 

```
_xmpp-client._tcp.muc   IN SRV  5 0 5222 example.slay.
_xmpps-client._tcp.muc  IN SRV  5 0 5223 example.slay.
_xmpp-server._tcp.muc   IN SRV  5 0 5269 example.slay.
_xmpps-server._tcp.muc  IN SRV  5 0 5270 example.slay.
```

for each of the subdomains (starting with muc). exclude `turn.example.slay`.

you will then have to add one last set of srv records for stun/turn.

```
_stun._udp   IN SRV  5 0 3478 turn.example.slay.
_stun._tcp   IN SRV  5 0 3478 turn.example.slay.
_stuns._tcp  IN SRV  5 0 5349 turn.example.slay.

_turn._udp   IN SRV  5 0 3478 turn.example.slay.
_turn._tcp   IN SRV  5 0 3478 turn.example.slay.
_turns._tcp  IN SRV  5 0 5349 turn.example.slay.
```

extra info: as a result of these records, you could technically play around with hosting xmpp on a server other than the one at `example.slay` (i.e. if you were splitting services across servers on one domain) by using the srv delegation. further info can be found at [XEP-0368](https://xmpp.org/extensions/xep-0368.html). 

## step 2: open your firewall ports

open ports:

tcp: `80` `443` `5222` `5223` `5269` `5270` `3478` `5349` `49152-65535`

udp: `3478` `49152-65535`

`80` & `443` are for the web server, `5222`, `5223`, `5269` and `5270` are for xmpp, and the rest are for stun/turn.

## step 3: set up your web server and get your ssl certificates

you need to:

- get (an) ssl certificate(s) for your domain, as well as several subdomains, all in all:
  - `example.slay`
  - `muc.example.slay`
  - `upload.example.slay`
  - `pubsub.example.slay`
  - `proxy.example.slay`
  - `turn.example.slay`
- proxypass http://127.0.0.1:5443 through to:
  - https://example.slay/xmpp
  - https://example.slay/.well-known/host-meta
  - https://example.slay/.well-known/host-meta.json

  make sure that for /xmpp you have what is necessary to proxy websockets too. if you're using nginx, increase `client_max_body_size` for http uploads.
- make sure the certificate files are readable and/or in a place that is readable by the `ejabberd` user

you can technically avoid using something like nginx + certbot, and use the built-in [ejabberd acme module](https://docs.ejabberd.im/admin/configuration/basic/#acme), thereby skipping this, but i am assuming you may also want to host other web services on your system, in which case you would want to reverse proxy each http service to a single https web service.

## step 4: set up postgres database

we will be creating a separate database for each virtualhost, as i feel this makes things clearer, and also it easier to migrate individual virtualhosts in the future. however, there is also now the ability to only have one, as described [here](https://docs.ejabberd.im/admin/configuration/database/#default-and-new-schemas). 

you should follow standard postgresql installation instructions for your OS. at the time of writing, the database driver for ejabberd does [not support](https://github.com/processone/p1_pgsql/issues/6) `scram-sha-256`, so you will need to [set](https://www.postgresql.org/docs/16/runtime-config-connection.html#GUC-PASSWORD-ENCRYPTION) `password_encryption = 'md5'` in your `postgresql.conf` file.

once this has been done, you should connect to your database as an admin and:

1. create an ejabberd database user with `CREATE USER ejabberd WITH PASSWORD 'your_password';`. don't forget to change the password.
2. create a database for your virtualhost with `CREATE DATABASE ejabberd_example OWNER ejabberd;`. replace `example` with something corresponding to your virtualhost.
3. import the database schema from github with the command `su -c "curl -s https://raw.githubusercontent.com/processone/ejabberd/master/sql/pg.sql | psql ejabberd_example" ejabberd` (once again replace `example`).

note down the postgres ejabberd user password.

## step 5: install ejabberd

now you should finally install the [system package](https://docs.ejabberd.im/admin/installation/#operating-system-packages). make sure that your build has postgresql support.

make sure that the file `/etc/ejabberd/ejabberd.yml` exists, and of course, is readable by the user that runs ejabberd (almost definitely `ejabberd`), by, if necessary, copying over the example `ejabberd.yml` or `wget`/`curl`ing it from the [github repo](https://github.com/processone/ejabberd/blob/master/ejabberd.yml.example). if you are getting it from the repo, make sure the version corresponds to the version of ejabberd packaged by your os. 

## step 6: ejabberd configuration

- loglevel last
- create docroot for uploads

begin by replacing `localhost` under `hosts` with your virtualhost (e.g. `example.slay`), then list the certfiles you have obtained under `certfiles`.

now set `default_db: sql` at the root level of the yaml file. this should be followed by `host_config` and the database configuration for your virtualhost, as shown below. customise each value to your setup.

```
host_config:
  example.slay:
    sql_type: pgsql
    sql_server: "localhost"
    sql_port: 5432
    sql_username: "ejabberd"
    sql_password: "postgres_password"
    sql_database: "ejabberd_example"
    auth_method: sql
    auth_password_format: scram
```

under `listen`, ensure all the correct services are enabled on each port, including tls s2s on port `5270` (not default):

```
listen:
  -
    port: 5222
    module: ejabberd_c2s
    max_stanza_size: 262144
    shaper: c2s_shaper
    access: c2s
    starttls_required: true
  -
    port: 5223
    tls: true
    module: ejabberd_c2s
    max_stanza_size: 262144
    shaper: c2s_shaper
    access: c2s
    starttls_required: true
  -
    port: 5269
    module: ejabberd_s2s_in
    max_stanza_size: 524288
  -
    port: 5270
    tls: true
    module: ejabberd_s2s_in
    max_stanza_size: 524288
```

we will also be enabling the http server and the stun/turn server modules. make sure you have set `turn_ipv4_address` and `ip` to your server's ipv4 address.

```
  -
    port: 5443
    module: ejabberd_http
    request_handlers:
      /xmpp/admin: ejabberd_web_admin
      /xmpp/bosh: mod_bosh
      /xmpp/upload: mod_http_upload
      /xmpp/ws: ejabberd_http_ws
      /.well-known/host-meta: mod_host_meta
      /.well-known/host-meta.json: mod_host_meta
  -
    port: 3478
    transport: udp
    module: ejabberd_stun
    use_turn: true
    turn_min_port: 49152
    turn_max_port: 65535
    # The server's public IPv4 address:
    turn_ipv4_address: 0.0.0.0
  -
    port: 5349
    transport: tcp
    module: ejabberd_stun
    use_turn: true
    tls: true
    turn_min_port: 49152
    turn_max_port: 65535
    ip: 0.0.0.0
    turn_ipv4_address: 0.0.0.0
```

now set `s2s_use_starttls: required` at the root.

at this point you can set up some ACLs. `acls` are just the access control lists, you can also set up `access_rules` corresponding to your needs, which will be what are passed to module settings. you should at the minimum add an admin user. example:

```
acl:
  admin:
    user: admin@example.slay
  flop:
    - user: flop@example.slay
  clown:
    - user: clown@example.slay

access_rules:
  queens: 
    allow: flop
    allow: clown
```

next, we will be adding some modules under `modules` and changing some settings.

add abuse addresses under `mod_disco`:

```
modules:
# ...
  mod_disco:
    server_info:
    -
      modules: all
      name: "abuse-addresses"
      urls: ["mailto:abuse@example.slay"]
```

add `mod_host_meta`:

```
  mod_host_meta:
    bosh_service_url: "https://@HOST@/xmpp/bosh"
    websocket_url: "wss://@HOST@/xmpp/ws"
```

edit `mod_mam`, and change `assume_mam_usage` to `false` and `default` to `never` if you wish not to archive messages on the server:

```
  mod_mam:
    db_type: sql
    assume_mam_usage: never
    default: never
```

add `mod_stun_disco` to advertise the stun service to clients, changing `0.0.0.0` and `example.slay` to your server's ip and hostname respectively:

```
  mod_stun_disco: 
    credentials_lifetime: 12h
    services:
        -
          host: 0.0.0.0
          port: 3478
          type: stun
          transport: udp
          restricted: false
        -
          host: 0.0.0.0
          port: 3478
          type: turn
          transport: udp
          restricted: true
        -
          host: turn.example.slay
          port: 5349
          type: stuns
          transport: tcp
          restricted: false
        -
          host: turn.example.slay
          port: 5349
          type: turns
          transport: tcp
          restricted: true
```

now it is time to enable MUCs (multi-user chatrooms), file proxy, http upload, and pubsub.

### mucs:

make sure that you set the host to your muc subdomain, otherwise it will attempt to use `conference.example.slay`. you can also set `mam: false` in `default_room_options` to disable server-side message archiving by default. 

```
  mod_muc:
    host: muc.example.slay
    access:
      - allow
    access_admin:
      - allow: admin
    access_create: muc_create
    access_persistent: muc_create
    access_mam:
      - allow
    default_room_options:
      mam: false
```

### file proxy:

```
  mod_proxy65:
    access: local
    max_connections: 5
```

### http file upload:

```
  mod_http_upload:
    put_url: https://@HOST@/xmpp/upload
    docroot: /var/www/ejabberdupload
    max_size: 1073741824
    custom_headers:
      "Access-Control-Allow-Origin": "https://@HOST@"
      "Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS"
      "Access-Control-Allow-Headers": "Content-Type"
```

create the folder for the `docroot`, and make sure it is owned by the `ejabberd` user.

### pubsub:

```
  mod_pubsub:
    access_createnode: pubsub_createnode
    plugins:
      - flat
      - pep
    force_node_config:
      ## Avoid buggy clients to make their bookmarks public
      storage:bookmarks:
        access_model: whitelist
```

## step 7: start server and create admin user 

start the ejabberd server! 

use `su -c "ejabberdctl register admin example.slay password" ejabberd` to register `admin@example.slay` with the password `password`.

once you are done and believe everything has been set up correctly, you can optionally change the [`loglevel`](https://docs.ejabberd.im/admin/configuration/toplevel/#loglevel) at the root of the config.

there will be an admin page accessible at [https://example.slay/xmpp/admin](https://example.slay/xmpp/admin).

</div>

<hr>

# extra goodies!

## web client

you can set up conversejs using [`mod_conversejs`](https://docs.ejabberd.im/admin/configuration/modules/#mod-conversejs). you will also need to possibly update your web server config to proxy the new endpoint.

## further virtualhosts?

for further virtualhosts you should create a new database for each, and add them to the database part of the config. e.g.:

```
host_config:
  example.slay:
    sql_type: pgsql
    sql_server: "localhost"
    sql_port: 5432
    sql_username: "ejabberd"
    sql_password: "postgres_password"
    sql_database: "ejabberd_slay"
    auth_method: sql
    auth_password_format: scram
  example.flop:
    sql_type: pgsql
    sql_server: "localhost"
    sql_port: 5432
    sql_username: "ejabberd"
    sql_password: "postgres_password"
    sql_database: "ejabberd_flop"
    auth_method: sql
    auth_password_format: scram
```

there cannot be conflicts between declarations in the config file, so if you have `mod_muc`, `mod_proxy65`, `mod_http_upload` and `mod_pubsub` declared under `modules` at the root, they (as well as other configuration differences between virtualhosts) must be deleted and replicated for each virtualhost under `append_host_config` at the root. example as so:

```
append_host_config:
  example.flop:
    modules:
      mod_muc:
        host: muc.example.flop
        access_create: flops
        access_persistent: flops
        access:
          - allow
        access_admin:
          - allow: admin
        default_room_options:
          mam: false
      mod_proxy65:
        access: flops
        max_connections: 5
      mod_http_upload:
        access: flops
        put_url: https://@HOST@/xmpp/upload
        docroot: /var/www/ejabberdupload
        max_size: 1073741824
        custom_headers:
          "Access-Control-Allow-Origin": "https://@HOST@"
          "Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS"
          "Access-Control-Allow-Headers": "Content-Type"
      mod_pubsub:
        access_createnode: flops
        plugins:
          - flat
          - pep
        force_node_config:
          ## Avoid buggy clients to make their bookmarks public
          storage:bookmarks:
            access_model: whitelist
  example.flop:
    modules:
      mod_muc:
        hosts: 
          - muc.example.flop
        access_create: local
        access_persistent: local
        access:
          - allow
        access_admin:
          - allow: admin
        default_room_options:
          mam: false
      mod_proxy65:
        access: local
        max_connections: 5
      mod_http_upload:
        put_url: https://@HOST@/xmpp/upload
        docroot: /var/www/ejabberdupload
        max_size: 1073741824
        custom_headers:
          "Access-Control-Allow-Origin": "https://@HOST@"
          "Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS"
          "Access-Control-Allow-Headers": "Content-Type"
      mod_pubsub:
        access_createnode: pubsub_createnode
        plugins:
          - flat
          - pep
        force_node_config:
          ## Avoid buggy clients to make their bookmarks public
          storage:bookmarks:
            access_model: whitelist
```

as you can see above, you may also want to disable access to certain services per virtualhost using ACLs, in order to e.g. prevent users on `example.slay` from creating MUCs on `muc.example.flop`.

## separate turn server (coturn)

in this case, change `mod_stun_disco` to this, and don't enable the `listen` opts for stun/turn. generate an auth secret and share it with your turn server instance.

``` 
  mod_stun_disco:
    secret: "auth_secret"
    services:
      -
        host: turn.example.slay
        type: stun
      -
        host: turn.example.slay
        type: turn
```