Pleroma 2.5.0 with Elixir 1.11 on OpenBSD 7.1

nabbisen - Dec 27 '22 - - Dev Community

Summary

Pleroma is one of the fediverse implementations for microblogging like Twitter and Mastodon. It uses Elixir which "runs on Erlang VM known for creating low-latency, distributed, and fault-tolerant systems".

Here is the default landing view:

pleroma front page

The new major minor version, 2.5.0, was released just last week 🎉🎉🎉

This post shows how to install it on OpenBSD, the solid unix system.

Why was OpenBSD 7.1 used instead of 7.2, the latest ?

I found it was easier to use OpenBSD 7.1 in order to install Pleroma, for Elixir in ports was available.

On 7.2, the mix task below failed with Elixir in Ports:

$ env LC_ALL=en_US.UTF-8 MIX_ENV=prod \
    mix phx.server
Enter fullscreen mode Exit fullscreen mode

with the error:

xx:xx:xx.xxx [notice] Application runtime_tools exited: :runtime_tools.start(:normal, []) returned an error: shutdown: failed to start child: :ttb_autostart
    ** (EXIT) an exception was raised:
        ** (UndefinedFunctionError) function :observer_backend.ttb_resume_trace/0 is undefined (module :observer_backend is not available)
            (runtime_tools 1.19) :observer_backend.ttb_resume_trace()
            (runtime_tools 1.19) ttb_autostart.erl:47: :ttb_autostart.init/1
            (stdlib 4.0.1) gen_server.erl:848: :gen_server.init_it/2
            (stdlib 4.0.1) gen_server.erl:811: :gen_server.init_it/6
            (stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
{"Kernel pid terminated",application_controller,"{application_start_failure,runtime_tools,{{shutdown,{failed_to_start_child,ttb_autostart,{undef,[{observer_backend,ttb_resume_trace,[],[]},{ttb_autostart,init,1,[{file,\"ttb_autostart.erl\"},{line,47}]},{gen_server,init_it,2,[{file,\"gen_server.erl\"},{line,848}]},{gen_server,init_it,6,[{file,\"gen_server.erl\"},{line,811}]},{proc_lib,init_p_do_apply,3,[{file,\"proc_lib.erl\"},{line,240}]}]}}},{runtime_tools,start,[normal,[]]}}}"}
Kernel pid terminated (application_controller) ({application_start_failure,runtime_tools,{{shutdown,{failed_to_start_child,ttb_autostart,{undef,[{observer_backend,ttb_resume_trace,[],[]},{ttb_autostart,init,1,[{file,"ttb_autostart.erl"},{line,47}]},{gen_server,init_it,2,[{file,"gen_server.erl"},{line,848}]},{gen_server,init_it,6,[{file,"gen_server.erl"},{line,811}]},{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,240}]}]}}},{runtime_tools,start,[normal,[]]}}})

Crash dump is being written to: erl_crash.dump...done
Enter fullscreen mode Exit fullscreen mode

It's possibly because of the version of Elixir offered by 7.2's ports, 1.14.

Environment

  • OpenBSD 7.1 (released on Apr 21, 2022)
  • PostgreSQL 14.5 (released on Aug 11, 2022)
  • Erlang/OTP 21.2.5 (released on Feb 4, 2019)
  • Elixir 1.11.4 (released on May 16, 2021)
  • Pleroma (released on Dec 23, 2022)

Tutorial

Prepare database / application engine / user

You have to set up the database, the application engine and the service (daemon) user.

Install PostgreSQL server (Optional)

Skip this section, if the database server already runs * with postgresql-contrib, its extensions * in your environment.

Install PostgreSQL and the extensions:

$ doas pkg_add postgresql-server \
    postgresql-contrib
Enter fullscreen mode Exit fullscreen mode

The output was:

quirks-5.5 signed on 2022-10-18T12:24:43Z
postgresql-server-14.5:libxml-2.9.13p2: ok
postgresql-server-14.5:postgresql-client-14.5: ok
useradd: Warning: home directory `/var/postgresql' doesn't exist, and -m was not specified
postgresql-server-14.5: ok
postgresql-contrib-14.5: ok
Running tags: ok
The following new rcscripts were installed: /etc/rc.d/postgresql
See rcctl(8) for details.
New and changed readme(s):
    /usr/local/share/doc/pkg-readmes/postgresql-server
Enter fullscreen mode Exit fullscreen mode

Then, let's initialize the database system.
Act as _postgresql:

$ doas su _postgresql -
Enter fullscreen mode Exit fullscreen mode

to run:

$ initdb -D /var/postgresql/data -U postgres
Enter fullscreen mode Exit fullscreen mode

The output was:

The files belonging to this database system will be owned by user "_postgresql".
This user must also own the server process.

The database cluster will be initialized with locale "C".
The default database encoding has accordingly been set to "SQL_ASCII".
The default text search configuration will be set to "english".

Data page checksums are disabled.

creating directory /var/postgresql/data ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 20
selecting default shared_buffers ... 128MB
selecting default time zone ... Asia/Tokyo
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

initdb: warning: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.

Success. You can now start the database server using:

    pg_ctl -D /var/postgresql/data -l logfile start

Enter fullscreen mode Exit fullscreen mode

Done.

$ exit
Enter fullscreen mode Exit fullscreen mode

Finally, activate the daemon and start it:

$ doas rcctl enable postgresql
$ doas rcctl start postgresql
Enter fullscreen mode Exit fullscreen mode

Install Elixir

Thankfully, the ports system offers the application environment required:

$ doas pkg_add elixir
Enter fullscreen mode Exit fullscreen mode

The output was:

quirks-5.5 signed on 2022-10-18T12:24:43Z
elixir-1.11.4p0:erlang-21.2p5v0: ok
elixir-1.11.4p0: ok
--- +erlang-21.2p5v0 -------------------
You may wish to add /usr/local/lib/erlang21/man to /etc/man.conf
Enter fullscreen mode Exit fullscreen mode

Install the packages for Pleroma

Also, the ports system helps us.

Install the essential packages:

$ doas pkg_add gmake \
    git cmake libmagic
Enter fullscreen mode Exit fullscreen mode

Additionally, there are optional ones:

$ doas pkg_add ImageMagick \
    ffmpeg p5-Image-ExifTool
Enter fullscreen mode Exit fullscreen mode

The output of the former was:

quirks-5.5 signed on 2022-10-18T12:24:43Z
gmake-4.3: ok
git-2.35.1p0:cvsps-2.1p2: ok
(...)
git-2.35.1p0: ok
cmake-3.20.3p6v0:libuv-1.44.1: ok
(...)
cmake-3.20.3p6v0: ok
libmagic-5.43: ok
The following new rcscripts were installed: /etc/rc.d/gitdaemon
See rcctl(8) for details.
New and changed readme(s):
    /usr/local/share/doc/pkg-readmes/git
Enter fullscreen mode Exit fullscreen mode

That of the latter was:

quirks-5.5 signed on 2022-10-18T12:24:43Z
ImageMagick-6.9.12.38:x265-3.5p0: ok
(...)
ImageMagick-6.9.12.38: ok
ffmpeg-4.4.1p3v1:fribidi-1.0.11: ok
(...)
ffmpeg-4.4.1p3v1: ok
p5-Image-ExifTool-12.40: ok
Running tags: ok
New and changed readme(s):
    /usr/local/share/doc/pkg-readmes/ffmpeg
    /usr/local/share/doc/pkg-readmes/sdl2
Enter fullscreen mode Exit fullscreen mode

Create user and project directory

Create the app user used in a part of rc.d, the daemon script:

$ doas useradd \
    -d /var/www/_pleroma -m _pleroma
Enter fullscreen mode Exit fullscreen mode

Here, We define /var/www/_pleroma as the home directory which is automatically created.

Create the project directory next to it and set the permissions:

$ doas mkdir /var/www/pleroma

$ doas chown -R \
    _pleroma:_pleroma /var/www/pleroma
Enter fullscreen mode Exit fullscreen mode

Finally, extend the login class of the user, because the default settings may be too small to run Elixir Phoenix apps:

$ doas nvim /etc/login.conf
Enter fullscreen mode Exit fullscreen mode

like below:

+ _pleroma:\
+         :datasize-max=1536M:\
+         :datasize-cur=1536M:\
+         :openfiles-max=4096
Enter fullscreen mode Exit fullscreen mode

Then apply it:

$ doas cap_mkdb /etc/login.conf
Enter fullscreen mode Exit fullscreen mode

As a reference, the results were:

$ ls -l /etc/login*
-rw-r--r--  1 root  wheel   2785 Dec 26 21:02 /etc/login.conf
-rw-r--r--  1 root  wheel  73728 Dec 26 21:02 /etc/login.conf.db
(...)
Enter fullscreen mode Exit fullscreen mode

Install Pleroma

Here is the main part at last.

Assume the current directory and get the source

Act as:

$ doas su _pleroma -
Enter fullscreen mode Exit fullscreen mode

ksh will be perhaps used as login shell.

Well, go to the project directory:

$ cd /var/www/pleroma
Enter fullscreen mode Exit fullscreen mode

Get the latest stable source in the current directory:

$ git clone -b stable \
    https://git.pleroma.social/pleroma/pleroma.git \
    .
Enter fullscreen mode Exit fullscreen mode

The output was:

Cloning into '.'...
remote: Enumerating objects: 155563, done.
remote: Counting objects: 100% (1606/1606), done.
remote: Compressing objects: 100% (562/562), done.
remote: Total 155563 (delta 1131), reused 1458 (delta 1040), pack-reused 153957
Receiving objects: 100% (155563/155563), 198.25 MiB | 6.12 MiB/s, done.
Resolving deltas: 100% (118994/118994), done.
Enter fullscreen mode Exit fullscreen mode

Mix task: deps.get

Get the dependencies:

$ mix deps.get
Enter fullscreen mode Exit fullscreen mode

You will be asked to install Hex. Go on with "y" entered:

warning: the VM is running with native name encoding of latin1 which may cause Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 (which can be verified by running "locale" in your shell)
!!! RUNNING IN LOCALHOST DEV MODE! !!!
FEDERATION WON'T WORK UNTIL YOU CONFIGURE A dev.secret.exs
Could not find Hex, which is needed to build dependency :phoenix
Shall I install Hex? (if running non-interactively, use "mix local.hex --force") [Yn] y
Enter fullscreen mode Exit fullscreen mode

The output was:

* creating /var/www/_pleroma/.mix/archives/hex-2.0.0
* Getting gettext (https://github.com/tusooa/gettext.git - 72fb2496b6c5280ed911bdc3756890e7f38a4808)
remote: Enumerating objects: 4046, done.        
remote: Counting objects: 100% (254/254), done.        
remote: Compressing objects: 100% (130/130), done.        
remote: Total 4046 (delta 121), reused 213 (delta 100), pack-reused 3792        
(...)
Resolving Hex dependencies...
Resolution completed in 1.875s
Unchanged:
  accept 0.3.5
(...)
  websockex 0.4.3
* Getting phoenix (Hex package)
(...)
* Getting phoenix_pubsub (Hex package)
You have added/upgraded packages you could sponsor, run `mix hex.sponsor` to learn more
Enter fullscreen mode Exit fullscreen mode

Fix elixir-captcha which is incompatible with OpenBSD (Optional)

elixir-captcha 0.1.0 is brought in the previous section, which incompatible with OpenBSD, for it uses make in any envs. If you want to use the native captcha of Pleroma, you have to tell it to use gmake instead. Edit:

$ nvim deps/captcha/mix.exs
Enter fullscreen mode Exit fullscreen mode

to:

  defmodule Mix.Tasks.Compile.Make do
    def run(_) do
-     {result, _error_code} = System.cmd("make", [], stderr_to_stdout: true)
+     {result, _error_code} = System.cmd("gmake", [], stderr_to_stdout: true)
      Mix.shell().info(result)
  (...)
  defmodule Mix.Tasks.Clean.Make do
    def run(_) do
-     {result, _error_code} = System.cmd("make", ['clean'], stderr_to_stdout: true)
+     {result, _error_code} = System.cmd("gmake", ['clean'], stderr_to_stdout: true)
      Mix.shell().info(result)
Enter fullscreen mode Exit fullscreen mode

Otherwise, you will get the error below, which is not fatal, in running mix pleroma.instance gen:

==> captcha
rm -f priv/captcha src/captcha.o 
cc -g  -c src/captcha.c
mkdir -p priv
cc -I src -o priv/captcha  src/captcha.o 
cc: error: no such file or directory: 'src/captcha.o'
cc: error: no input files
*** Error 1 in /var/www/pleroma/deps/captcha (Makefile:10 'priv/captcha')
Enter fullscreen mode Exit fullscreen mode

Mix task: pleroma.instance gen

Run to generate Pleroma instance:

$ env MIX_ENV=prod \
    mix pleroma.instance gen
Enter fullscreen mode Exit fullscreen mode

You will be asked to install rebar3. Go on with "y" entered as well:

warning: the VM is running with native name encoding of latin1 which may cause Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 (which can be verified by running "locale" in your shell)
warning: `config/prod.secret.exs` not found. You may want to create one by running `mix pleroma.instance gen`

Could not find "rebar3", which is needed to build dependency :parse_trans
I can install a local copy which is just used by Mix
Shall I install rebar3? (if running non-interactively, use "mix local.rebar --force") [Yn] y
Enter fullscreen mode Exit fullscreen mode

The output was:

* creating /var/www/_pleroma/.mix/rebar
* creating /var/www/_pleroma/.mix/rebar3
===> Analyzing applications...
===> Compiling parse_trans
(...)
==> pleroma
Compiling 592 files (.ex)
(...)
Generated pleroma app
Enter fullscreen mode Exit fullscreen mode

Configure app

Then you will be asked as below. It's actually up to you.

What domain will your instance use? (e.g pleroma.soykaf.com) []  pleroma-on-openbsd.com
What is the name of your instance? (e.g. The Corndog Emporium) [pleroma-on-openbsd.com]  
What is your admin email address? []  ciao@pleroma-on-openbsd.com
What email address do you want to use for sending email notifications? [ciao@pleroma-on-openbsd.com]  
Do you want search engines to index your site? (y/n) [n]  
Do you want to store the configuration in the database (allows controlling it from admin-fe)? (y/n) [n]  
What is the hostname of your database? [localhost]  127.0.0.1
What is the name of your database? [pleroma]  
What is the user used to connect to your database? [pleroma]  
What is the password used to connect to your database? [autogenerated]  
Would you like to use RUM indices? [n]  
What port will the app listen to (leave it if you are using the default setup with nginx)? [4000]  
What ip will the app listen to (leave it if you are using the default setup with nginx)? [127.0.0.1]  
What directory should media uploads go in (when using the local uploader)? [uploads]  
What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)? [instance/static/]  
Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n) [y]  
Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as installed. (y/n) [y]  
Do you want to anonymize the filenames of uploads? (y/n) [n]  y
Do you want to deduplicate uploaded files? (y/n) [n]  
Writing config to config/generated_config.exs.
Writing the postgres script to config/setup_db.psql.
Writing /var/www/pleroma/instance/static/robots.txt.

 All files successfully written! Refer to the installation instructions for your platform for next steps.
Enter fullscreen mode Exit fullscreen mode

Generating instance is completed !! Prepare your own custom config file:

$ cp config/generated_config.exs \
    config/prod.secret.exs
Enter fullscreen mode Exit fullscreen mode

Customize app (Optional)

In case, you might need do edit it:

$ nvim config/prod.secret.exs
Enter fullscreen mode Exit fullscreen mode

For example, when your PostgreSQL server is in an external host and moreover requires TLS/SSL connections:

  config :pleroma, Pleroma.Repo,
    (...)
-   hostname: "(your-hostname)"
+   hostname: "(your-hostname)",
+   port: xxx,
+   ssl: true
  (...)
+ config :pleroma, Pleroma.Captcha,
+   enabled: true
Enter fullscreen mode Exit fullscreen mode

Also, when you want use kocaptcha instead of native captcha:

+ config :pleroma, Pleroma.Captcha,
+   enabled: true,
+   method: Pleroma.Captcha.Kocaptcha
Enter fullscreen mode Exit fullscreen mode

Migrate database

Let's set up the database of Pleroma.
Run:

$ psql -U postgres -f config/setup_db.psql
Enter fullscreen mode Exit fullscreen mode

The output was:

CREATE ROLE
CREATE DATABASE
You are now connected to database "pleroma" as user "postgres".
CREATE EXTENSION
CREATE EXTENSION
CREATE EXTENSION
Enter fullscreen mode Exit fullscreen mode

Then run to migrate:

$ env MIX_ENV=prod \
    mix ecto.migrate
Enter fullscreen mode Exit fullscreen mode

The output was:

warning: the VM is running with native name encoding of latin1 which may cause Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 (which can be verified by running "locale" in your shell)
Compiling 592 files (.ex)
(...)
xx:xx:xx.xxx [info]  == Migrated 20221103014728 in 0.0s

xx:xx:xx.xxx [info]  == Running 20221111164213 Pleroma.Repo.Migrations.DeprecateQuack.up/0 forward

xx:xx:xx.xxx [info]  == Migrated 20221111164213 in 0.0s
Enter fullscreen mode Exit fullscreen mode

Mix task: Start Phoenix server

We are almost ready. Start Phoenix:

$ env LC_ALL=en_US.UTF-8 MIX_ENV=prod \
    mix phx.server
Enter fullscreen mode Exit fullscreen mode

The output was:

xx:xx:xx.xxx [warning] Description: 'Authenticity is not established by certificate path validation'
     Reason: 'Option {verify, verify_peer} and cacertfile/cacerts is missing'


xx:xx:xx.xxx [info] tzdata release in place is from a file last modified Wed, 21 Oct 2020 18:40:20 GMT. Release file on server was last modified Sat, 29 Oct 2022 01:50:44 GMT.

xx:xx:xx.xxx [warning] The on_load function for module crypt returned:
{:error, {:load, 'Library load-call unsuccessful (1).'}}


xx:xx:xx.xxx [info] Tzdata has updated the release from 2020d to 2022f

xx:xx:xx.xxx [warning] The on_load function for module crypt returned:
{:error, {:load, 'Library load-call unsuccessful (1).'}}


xx:xx:xx.xxx [info] Running Pleroma.Web.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http)

xx:xx:xx.xxx [info] Access Pleroma.Web.Endpoint at https://(your-domain)

xx:xx:xx.xxx [info] Gopher server disabled

xx:xx:xx.xxx [info] Transferring embedded hashtags to `hashtags` (from oid: 0)...



xx:xx:xx.xxx [warn] The on_load function for module crypt returned:
{:error, {:load, 'Library load-call unsuccessful (1).'}}

(...)

xx:xx:xx.xxx [info] Running Pleroma.Web.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http)

xx:xx:xx.xxx [info] Access Pleroma.Web.Endpoint at https://pleroma-on-openbsd.com

xx:xx:xx.xxx [info] Gopher server disabled

xx:xx:xx.xxx [info] Transferring embedded hashtags to `hashtags` (from oid: 0)...

xx:xx:xx.xxx [info] Deleting context objects from `objects` (from oid: 0)...
Enter fullscreen mode Exit fullscreen mode

Testing the server

Keep the Phoenix process above (actually acting as Pleroma) running.

Check if the response returns correctly:

$ curl -I http://127.0.0.1:4000/
Enter fullscreen mode Exit fullscreen mode

In my case, the response header was:

HTTP/1.1 200 OK
access-control-allow-credentials: true
access-control-allow-origin: *
access-control-expose-headers: Link,X-RateLimit-Reset,X-RateLimit-Limit,X-RateLimit-Remaining,X-Request-Id,Idempotency-Key
cache-control: max-age=0, private, must-revalidate
content-length: 7880
content-security-policy: upgrade-insecure-requests;script-src 'self' 'wasm-unsafe-eval';connect-src 'self' blob: https://pleroma-on-openbsd.com wss://pleroma-on-openbsd.com;media-src 'self' https:;img-src 'self' data: blob: https:;default-src 'none';base-uri 'self';frame-ancestors 'none';style-src 'self' 'unsafe-inline';font-src 'self';manifest-src 'self';
content-type: text/html; charset=utf-8
date: Mon, 26 Dec 2022 12:35:06 GMT
permissions-policy: interest-cohort=()
referrer-policy: same-origin
server: Cowboy
x-content-type-options: nosniff
x-download-options: noopen
x-frame-options: DENY
x-permitted-cross-domain-policies: none
x-request-id: FzRX_VnDh-YizPQAABaR
x-xss-protection: 1; mode=block
Enter fullscreen mode Exit fullscreen mode

Seems sweet :)

When "HTTP/... 200 OK" is printed, the health of your Pleroma is good.

Create a Pleroma admin

Note you are in /var/www/pleroma as _pleroma.
Run to create a super user:

$ env LC_ALL=en_US.UTF-8 MIX_ENV=prod \
    mix pleroma.user new \
    (admin-name) (your@e.mail) --admin
Enter fullscreen mode Exit fullscreen mode

You will be asked as below:

A user will be created with the following information:
  - nickname: pleroma_admin
  - email: pleroma_admin@example.com
  - password: [generated; a reset link will be created]
  - name: (admin-name)
  - bio: 
  - moderator: false
  - admin: true

Continue? [n]  y
Enter fullscreen mode Exit fullscreen mode

When I proceeded, the output was:

User (admin-name) created
Admin status of (admin-name): true
Generated password reset token for (admin-name)
URL: https://(your-domain))/api/v1/pleroma/password_reset/xxx...
Enter fullscreen mode Exit fullscreen mode

Start relayd

By default, Pleroma listens to only 127.0.0.1, the localhost.

In order to publish it as service, you have to combine it with some web server. In Pleroma, the cases using nginx seem more than Apache or Caddy.

OpenBSD develops and supports relayd, their native relay daemon which "runs as a load-balancer, application layer gateway, or transparent proxy".
In order for relayd to relay the requests/responses between the outside and Pleroma inside, edit:

$ doas nvim /etc/relayd.conf
Enter fullscreen mode Exit fullscreen mode

The simplest configuration is below:

table <pleroma_server> { 127.0.0.1 }

relay pleroma {
    listen on egress port 4000

    forward to <pleroma_server> port 4000 check http "/" code 200
}
Enter fullscreen mode Exit fullscreen mode

Activate and start it:

$ doas rcctl enable relayd
$ doas rcctl start relayd
Enter fullscreen mode Exit fullscreen mode

rc.d script

There is another issue left to publish Pleroma as service.
It is not registered as daemon.

To solve it, create the rc.d script:

$ doas nvim /etc/rc.d/pleroma
Enter fullscreen mode Exit fullscreen mode

Write the below in it:

#!/bin/ksh

daemon="cd /var/www/pleroma; env LC_ALL=en_US.UTF-8 MIX_ENV=prod mix"
daemon_user="_pleroma"
daemon_flags="phx.server --no-compile"

. /etc/rc.d/rc.subr

rc_cmd $1
Enter fullscreen mode Exit fullscreen mode

Besides, I tried daemon_execdir="/var/www/pleroma" instead of cd ...; but it didn't work.

Set the permissions:

$ doas chmod a+x /etc/rc.d/pleroma
Enter fullscreen mode Exit fullscreen mode

Then activate and run it:

$ doas rcctl enable pleroma
$ doas rcctl start pleroma
Enter fullscreen mode Exit fullscreen mode

Well, there is an alternative way. If you don't want to enable it now, you can start it temporarily with -f option: The whole command line is doas rcctl -f start pleroma.

Conclusion

Access with your browser to http://127.0.0.1:4000/. You will see the front page !!

After signing in, the dashboard must be there 🐡

pleroma user

Enjoy swimming across the fediverse :)

Reference

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .