Add Grav CMS source files.

This commit is contained in:
Jordan Bancino 2024-01-11 20:03:16 -05:00
commit 7dbbd4edb1
42 changed files with 1971 additions and 0 deletions

8
README.md Normal file
View file

@ -0,0 +1,8 @@
# Telodendria Website
This repository hosts the data and theme files for the official [Telodendria website](https://telodendria.io).
The website is a [Grav](https://getgrav.org/) instance, so this repository is structured in the way that
Grav expects it to be.
If you are looking for the Telodendria project itself, go to [Telodendria/Telodendria](/Telodendria/Telodendria).

14
user/.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
# Recommended by Grav
accounts/*
!accounts/.*
data/*
!data/.*
languages/*
!languages/.*
plugins/*
!plugins/.*
themes/*
!themes/.*
!themes/bancino/
**/config/security.yaml

1
user/accounts/.gitkeep Normal file
View file

@ -0,0 +1 @@
/* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved. */

0
user/config/media.yaml Normal file
View file

View file

@ -0,0 +1,7 @@
enabled: true
limit: 10
title: 'Telodendria Newsletter'
description: 'The official Telodendria newsletter.'
length: 0
enable_json_feed: true
show_last_modified: false

7
user/config/site.yaml Normal file
View file

@ -0,0 +1,7 @@
title: Grav
author:
name: Joe Bloggs
email: 'joe@example.com'
metadata:
description: 'Grav is an easy to use, yet powerful, open source flat-file CMS'

230
user/config/system.yaml Normal file
View file

@ -0,0 +1,230 @@
absolute_urls: false
timezone: ''
default_locale: null
param_sep: ':'
wrapped_site: false
reverse_proxy_setup: false
force_ssl: false
force_lowercase_urls: true
custom_base_url: ''
username_regex: '^[a-z0-9_-]{3,16}$'
pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
intl_enabled: true
http_x_forwarded:
protocol: true
host: false
port: true
ip: true
languages:
supported: { }
default_lang: null
include_default_lang: true
include_default_lang_file_extension: true
translations: true
translations_fallback: true
session_store_active: false
http_accept_language: false
override_locale: false
content_fallback: { }
pages_fallback_only: false
debug: false
home:
alias: /home
hide_in_urls: false
pages:
type: regular
dirs:
- 'page://'
theme: quark
order:
by: default
dir: asc
list:
count: 20
dateformat:
default: null
short: 'jS M Y'
long: 'F jS \a\t g:ia'
publish_dates: true
process:
markdown: true
twig: false
twig_first: false
never_cache_twig: false
events:
page: true
twig: true
markdown:
extra: false
auto_line_breaks: false
auto_url_links: false
escape_markup: false
special_chars:
'>': gt
'<': lt
valid_link_attributes:
- rel
- target
- id
- class
- classes
types:
- html
- htm
- xml
- txt
- json
- rss
- atom
append_url_extension: ''
expires: 604800
cache_control: null
last_modified: false
etag: true
vary_accept_encoding: false
redirect_default_code: 302
redirect_trailing_slash: 1
redirect_default_route: 0
ignore_files:
- .DS_Store
ignore_folders:
- .git
- .idea
ignore_hidden: true
hide_empty_folders: false
url_taxonomy_filters: true
frontmatter:
process_twig: false
ignore_fields:
- form
- forms
cache:
enabled: true
check:
method: file
driver: auto
prefix: g
purge_at: '0 4 * * *'
clear_at: '0 3 * * *'
clear_job_type: standard
clear_images_by_default: false
cli_compatibility: false
lifetime: 604800
gzip: false
allow_webserver_gzip: false
redis:
socket: false
password: null
database: null
twig:
cache: true
debug: true
auto_reload: true
autoescape: true
undefined_functions: true
undefined_filters: true
safe_functions: { }
safe_filters: { }
umask_fix: false
assets:
css_pipeline: false
css_pipeline_include_externals: true
css_pipeline_before_excludes: true
css_minify: true
css_minify_windows: false
css_rewrite: true
js_pipeline: false
js_pipeline_include_externals: true
js_pipeline_before_excludes: true
js_module_pipeline: false
js_module_pipeline_include_externals: true
js_module_pipeline_before_excludes: true
js_minify: true
enable_asset_timestamp: false
enable_asset_sri: false
collections:
jquery: 'system://assets/jquery/jquery-3.x.min.js'
errors:
display: true
log: true
log:
handler: file
syslog:
facility: local6
tag: grav
debugger:
enabled: false
provider: clockwork
censored: false
shutdown:
close_connection: true
twig: true
images:
default_image_quality: 85
cache_all: false
cache_perms: '0755'
debug: false
auto_fix_orientation: true
seofriendly: false
cls:
auto_sizes: false
aspect_ratio: false
retina_scale: 1
defaults:
loading: auto
watermark:
image: 'system://images/watermark.png'
position_y: center
position_x: center
scale: 33
watermark_all: false
media:
enable_media_timestamp: false
unsupported_inline_types: { }
allowed_fallback_types: { }
auto_metadata_exif: false
upload_limit: 2097152
session:
enabled: true
initialize: true
timeout: 1800
name: grav-site
uniqueness: path
secure: false
secure_https: true
httponly: true
samesite: Lax
split: true
domain: null
path: null
gpm:
releases: stable
official_gpm_only: true
verify_peer: true
http:
method: auto
enable_proxy: true
proxy_url: null
proxy_cert_path: null
concurrent_connections: 5
verify_peer: true
verify_host: true
accounts:
type: regular
storage: file
avatar: gravatar
flex:
cache:
index:
enabled: true
lifetime: 60
object:
enabled: true
lifetime: 600
render:
enabled: true
lifetime: 600
strict_mode:
yaml_compat: false
twig_compat: false
blueprint_compat: false

View file

@ -0,0 +1,20 @@
enabled: true
production-mode: true
grid-size: grid-lg
header-fixed: true
header-animated: true
header-dark: false
header-transparent: false
sticky-footer: true
blog-page: /blog
spectre:
exp: false
icons: false
custom_logo:
user/themes/quark/images/logo/Telodendria-196x196.png:
name: Telodendria-196x196.png
full_path: Telodendria-196x196.png
type: image/png
size: 34653
path: user/themes/quark/images/logo/Telodendria-196x196.png
custom_logo_mobile: { }

View file

@ -0,0 +1,26 @@
enabled: true
production-mode: true
grid-size: grid-lg
header-fixed: true
header-animated: true
header-dark: false
header-transparent: false
sticky-footer: true
blog-page: /blog
spectre:
exp: false
icons: false
streams:
schemes:
theme:
type: ReadOnlyStream
prefixes:
'': [user/themes/bancino, user/themes/quark]
custom_logo:
user/themes/quark/images/logo/Telodendria-196x196.png:
name: Telodendria-196x196.png
full_path: Telodendria-196x196.png
type: image/png
size: 34653
path: user/themes/quark/images/logo/Telodendria-196x196.png
custom_logo_mobile: { }

View file

@ -0,0 +1,6 @@
core:
grav:
version: 1.7.44
schema: 1.7.0_2020-11-20_1
history:
- { version: 1.7.44, date: '2024-01-12 00:54:40' }

1
user/data/.gitkeep Normal file
View file

@ -0,0 +1 @@
/* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved. */

View file

@ -0,0 +1,139 @@
---
title: Home
---
<h1 style="text-align: center;">Lightweight, Decentralized Chat.</h1>
**Telodendria** is an extremely powerful, yet lightweight and portable
chat server designed to be easy to install and configure. Powered by
the [Matrix](https://matrix.org) protocol, Telodendria empowers
everyone to run their own chat server on ordinary hardware, including
old and embedded devices. Whether you want a simple chat server just
for you and your friends and family, or want to talk to users on other
Matrix homeservers but don't want to go through all the hastle of
hosting a complicated, high-maintenance homeserver or joining an
existing homeserver for privacy or other reasons, then Telodendria
might be for you.
!!!! **Note:** Telodendria still in development. See **Status** below.
## What is Matrix?
Matrix is an **open standard** for *interoperable*, *decentralized*,
*secure*, and *real-time* communication over the internet.
Matrix can be thought of as the successor to email, but it works
very similar to iMessage, Discord, or direct messaging on most
social media networks. The primary difference between Matrix and these
other services, however, is that Matrix doesn't depend on one central
authority, and is designed in such a way to respect your privacy.
Matrix has proven itself over the last few
years to be a reliable communication tool, and has only gotten more
user-friendly over the course of its development. Matrix is capable
enough that it can&mdash;and should&mdash;totally replace any other
means of digital communication, and it offers a much higher degree
of security, simplicity, and functionality.
Strictly speaking, Matrix itself is just the *protocol* by which
clients and servers communicate. In order to use Matrix, we need
implementations of both clients and servers. Telodendria is a server
implementation of the Matrix protocol.
## Why Telodendria?
- **Lightweight:** Written in the C programming language, Telodendria
is automatically lighter and faster than other self-hosted chat servers.
It has very few external dependencies and is as self-contained as
possible.
- **Fully-Featured:** Most lightweight chat solutions compromise on
features. Telodendria is built on the fully-featured Matrix protocol,
which provides a chat experience that most normal users are familiar
with.
- **Portable:** You can run Telodendria on just about everything,
including more traditional options like a personal home server or VPS,
but also more obscure platforms like Raspberry Pis or retro computers.
Telodendria can run on a broad number of operating systems, which means
that no matter which platform and OS you prefer, there is a good chance
you can add Telodendria without much difficulty. It is also extremely
easy to migrate a Telodendria instance between platforms; just copy the
data directory to a new device.
- **Simple:** Telodendria is designed to be a simple, no-frills
chat server. It is easy to install, easy to configure, and easy to
maintain.
- **Stable:** Other Matrix homeservers develop at the pace of the
Matrix specification itself, which is to say quite rapidly. Changes are
always being made, and a version shipped 6 months ago is already
incredibly outdated. Telodendria, on the other hand, aims to be stable.
It should *just work* for long periods of time between upgrades, and
you should never feel like Telodendria is going to change significantly
between upgrades.
- **Well-Documented:** Telodendria places as much emphasis on documentation as on code, which means you can be sure that the documentation will always remain up-to-date, accurate, and most importantly, reasonably exhaustive.
[Read Technical Rationale &rightarrow;](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/dev/rationale.md)
## Get Started
Check out the [Documentation](https://git.telodendria.io/Telodendria/telodendria/src/branch/master/docs/README.md) to get started with
Telodendria.
## Status
Telodendria is in the very early stages of development. As such, it may
not yet deliver on all of its promises. Currently, Telodendria is not
ready for end-users yet. While it features very basic user
authentication, it does not actually work as a chat server yet.
We are hoping to ship Telodendria `v1.7.0-alpha4` by January of 2025. This
release should be usable for communication between **local users**
only. Additional features, including federation with other Matrix
homeservers will be added in future releases.
You can help speed up development by **sponsoring**
Telodendria or [getting involved](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/CONTRIBUTING.md).
## Sponsorship
Telodendria is maintained by a loosely-knit band of volunteers. The
project currently has no sponsors and thus no source of income to
pay for infrastructure costs and developer time. To ensure
Telodendria's long-term success, please consider sponsoring the
project.
You can make a recurring donation to Telodendria using
[LiberaPay](https://liberapay.com/Telodendria/donate). You can also make
one-time donations using
[Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU). If you would
like to make a recurring donation larger than that allowed by
LiberaPay, please contact Jordan Bancino over Matrix at
`@jordan:bancino.net` or email at `jordan@bancino.net`.
### Benefits
While there are no set sponsorship tiers at this time, sponsoring
Telodendria is a mutually beneficial relationship. Depending on the
amount you donate, you can get your name, logo, and website links
on the [Sponsors](../sponsors) page, the project `README`, or the
main website.
## License
All of the code and documentation for Telodendria is licensed under a
modified MIT license. The MIT license is an extremely permissive
license that has very few restrictions. Please consult the
[`LICENSE.txt`](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/LICENSE.txt) file for the actual license text. It is
important to note that the Telodendria license text differs from the
original MIT license in the following ways:
- Where the MIT license states that the copyright notice and permission
notice shall be included in all copies or *substantial* portions of the
software, the Telodendria requires the copyright notice and
permission notice be included with *all* portions, regardless of the
size, by omitting the word *substantial*.
The Telodendria logo in all its forms, including the ASCII
representation, belongs solely to the Telodendria project. It must be
used only to represent the official Telodendria project. You are free
to use the logo in any way as long as it represents or links to the
official project. If Telodendria is forked, the logo must be removed
completely from the project, and optionally replaced by a different
one.

View file

@ -0,0 +1,63 @@
---
title: 'April Status Update'
date: '21-04-2023 21:18'
feed:
limit: 10
---
Since I last wrote a newsletter, a lot of neat stuff has happened. I apologize for the delay and the periodic absences, but I'm almost to a point where I will hopefully have a bit more free time to work on Telodendria.
## Architecture
- **Source/Sink-Agnostic IO:** I abstracted all of the I/O code in Telodendria out into the `Io` and `Stream` APIs. These APIs are designed to unify the way we read and write streams, regardless of where they are coming from and where they are going. `Io` is heavily inspired by the BSDs' [`funopen()`](https://man.openbsd.org/funopen), or GNU's [`fopencookie()`](https://linux.die.net/man/3/fopencookie). Essentially, it abstracts out the POSIX system calls `read()`, `write()`, `seek()`, and `close()`, and allows a program to specify alternate implementations, basically creating entirely custom IO streams. `Stream` builds on `Io` to emulate the behavior of the standard C library, essentially replacing `FILE` pointers.
Okay cool, what does this have to do with implementing a Matrix homeserver, you ask? Well, it made it _so_ much easier to implement a crucial feature called...
- **Transport Layer Security:** Telodendria now fully supports TLS! Currently, Telodendria supports OpenSSL and LibreSSL, so it should work out of the box on most Linux distributions and all of the BSDs. Thanks to the new `Io` API, supporting other TLS libraries should also be incredibly easy for those trying to port Telodendria to more obscure systems. In fact, I have even added a template in `contrib/` that anyone can use as a starter to add support for additional TLS libraries. My goal is to make Telodendria support the TLS libraries that operating systems ship with, so that building Telodendria doesn't require installing anything additional.
- **HTTP Request Router:** I finally got around to implementing a real HTTP request router, complete with POSIX regular expression support. Previously, we were routing requests using a bunch of nested `if`-statements, which got very messy very quickly. While extremely fast and memory efficient, it simply isn't sustainable, so I'm willing to sacrifice a few bytes of memory and the runtime overhead of building a routing tree so that we can keep the code maintainable.
- **Administrator API:** Work has begun on the `/_telodendria/admin/*` endpoints. Currently implemented are these:
- `/_telodendria/admin/privileges`
- `/_telodnedria/admin/config` (more on that below)
- `/_telodendria/admin/shutdown`
- `/_telodendria/admin/restart`
- `/_telodendria/admin/stats`
Consult the `telodendria-admin(7)` page for all the documentation on these endpoints. Prototype endpoint documentation is available in the `proposals/admin.7` man page, but these aren't implemented yet, and may be subject to change during implementation.
- **Configuration:** Telodendria now no longer uses a traditional configuration file. All configuration has now moved to the database. This allows the daemon configuration to be modified at runtime, and in fact, that's the recommended way of doing it now. Use the `/_telodendria/admin/config` API endpoint to update Telodendria's configuration, and restart Telodendria using `/_telodendria/admin/restart`. I refactored the configuration parser to be more robust, and now endpoints that require the configuration file can simply do a `ConfigLock()` to get access to the current configuration. Do note that the configuration API endpoint is not complete yet. Currently, it only supports retrieving the whole configuration and replacing the whole configuration. I hope to add support for incremental updates to the configuration before the next release.
- **New Tools:** Telodendria has some new command line tools for performing common tasks. I've implemented `http`, a simple HTTP client in the style of `curl`, and `json`, a simple JSON parser in the style of `jq`. Note that these are both very primitive tools, which is by design. They're meant to fulfill the needs of Telodendria, and nothing more.
## Matrix Specification
Here's what's been cooking on the Matrix side of things:
- **User Interactive Authentication (UIA):** UIA for password and registration token authentication is just about complete. I'm wrapping up the fallback web pages soon, and then I'll build out the registration token administrator API, and then all of the Client Authentication of the Matrix specification should be complete!
- **User Profiles:** Support for fetching and setting display names and avatar URLs has been added.
- **Account Management:** Users can now change their passwords and get details about their account.
I'm sure there's been a few other new things as well, but at the moment they are not coming to mind.
## What's Next?
Next up on the `TODO` list are these:
- **Configuration:** As mentioned above, there's some work that needs to be done on the configuration API.
- **User Data, Capabilities Negotation:** I'm hoping to wrap up these small sections of the Matrix specification before the next release. The former might actually be just about complete, I just have to take a look at it to verify.
- **Rate Limiting:** Rate limiting is going to be a major project. It'll require storing more state, as well as discovering more information about a client.
There are also a few things a bit further out in the future, but I'm thinking about them nonetheless:
- **HTTP/1.1:** Currently Telodendria is HTTP/1.0 only, which works fine, but requires some unfortunate workarounds. Implementing some very common HTTP/1.1 features will allow Telodendria to take advantage of persistent connections, chunked encoding, and more. Persistent connections will be useful particularly now that the overhead of TLS is a factor. HTTP/1.1 will require some major refactoring, so expect it to be a large project.
- **Testing:** Over the last few weeks or so, I've been learning Perl, so we can potentially get some Sytest action going once Telodendria has some more functionality. I expect these tests to rapidly and drastically increase the quality of Telodendria.
Finally, if you're looking for a way to get involved, look no further than this list:
- **Continuous Integration:** We briefly talked about CI a few weeks ago in the other Matrix rooms. I'm continuing to think about what this looks like for Telodendria, whether it's GitHub Actions, or something home-grown. I tend to lean toward the latter in the long run, but the former may be a good way to get our feet wet with CI.
- **Administrator Tools:** At some point, I would like to build a command line client for the administrator API. Whether this looks like an `ncurses` application, or a more simple command line application, I am not yet sure, but if you have an interest in this or want to start building such a thing, feel free to discuss this with me more so we can work out some of the specifics.
- **Documentation:** There's always documentation that has to get updated before the release. The code isn't worth very much if it isn't maintainable, and documentation goes a long way in ensuring that Telodendria is maintainable. I've been thinking about a way to put the documentation in the header files as specially-formatted C comments, and then generate the man pages from the header files. I think that would make maintaining the documentation much easier, and it would force people that touch the header files to make sure the documentation is up to date—particularly me, since I have a bad habit of not documenting new functions—but I know I don't want to bring in something like Doxygen, so I'd want to roll my own documentation generator, which wouldn't be difficult at all, I just don't know exactly what that looks like quite yet.
These things can happen fairly independently of Telodendria's development, so it doesn't matter when they happen, and I'll get to them eventually, but if you are looking for something to do, maybe these things would be a good place to start.
## Telodendria Project
Maybe you all are sick of me continually bringing this up, but open source projects don't build themselves! It takes a lot of time and effort to build up a large and successful open source project, which is exactly what I'm hoping to accomplish with Telodendria. I know that process is not quick or easy, but you can help make it quicker and easier!
- **Get Involved:** Don't be afraid to get involved! If you have thoughts or ideas, please don't hesitate to share them, and if you are willing and able, do write some code. Your help is always greatly appreciated. Consult the list above if you need some inspiration.
- **Donations:** Telodendria has expenses. I don't have any trouble meeting those expenses at the moment, but I certainly would appreciate some help covering them, and if you like what I'm doing, maybe throw a little something my way for my efforts as well. Telodendria is currently not something I can make a living at, which unfortunately means it is relegated to a side project, but if you want me to put more effort in to the project, this is a great incentive, because the more I can make on Telodendria, the less time I have to spend doing other things in an effort to sustain myself.
- **Reach Out:** The public Matrix rooms are a great place for discussion, and I'd encourage you to keep all discussion on Telodendria there, but if you want to reach out to me personally, whether it's to discuss an idea you have or express a concern in private, you are more than welcome to do so.

View file

@ -0,0 +1,18 @@
---
title: Blog
content:
items:
- '@self.children'
limit: 99999
order:
by: date
dir: desc
pagination: false
url_taxonomy_filters: true
feed:
limit: 10
---
# Blog
The official Telodendria blog. Find previous editions in the [#telodendria-newsletter:bancino.net](https://matrix.to/#/#telodendria-newsletter:bancino.net) Matrix room.

View file

@ -0,0 +1,11 @@
---
title: 'Cytoplasm v0.4.0 Released!'
date: '01-11-2023 12:35'
feed:
limit: 10
---
Hey everyone. Not a lot has been happening with Telodendria over the last few months, thanks to my schedule and some unforeseen circumstances that have come up, but I do have a few pieces of exciting news to share:
- **Cytoplasm v0.4.0:** Cytoplasm now lives in its own git repository, which is available on [our Gitea instance](https://git.telodendria.io/Telodendria/Cytoplasm). I just published the first stable release of it. From now on, it will be developed along side but relatively independent of Telodendria, so expect the version numbers to get a little out of sync with Telodendria. Note that this does mean that Cytoplasm and Telodendria have to be built separately now, which does slightly increase the complexity of building Telodendria.
- **Make:** I replaced all of the custom build scripts with a `configure` script in both Telodendria and Cytoplasm that generates a standard POSIX `Makefile`. This makes building a bit more familiar to people that know C, and is in general just a better practice than rolling your own scripts. This milestone is a part of the [Gitea Migration](https://git.telodendria.io/Telodendria/Telodendria/projects/3), which is well under way.

View file

@ -0,0 +1,60 @@
---
title: 'February Status Update'
date: '23-02-2023 21:19'
feed:
limit: 10
---
Maybe these are more monthly now than weekly, but it is Thursday, and thanks to the poor weather where I'm at, I've been staying at home a lot and working on Telodendria quite a bit. Here's the latest progress report on the code:
- **User Interactive Authentication:** I'm working on properly implementing user interactive authentication. Previously, I'd just hacked together a dummy version of it for registration, but now it's time to do things right. The new `Uia` API will allow endpoints to build up the authentication flows they support, and then execute those flows. I've got most of the API in place, now I'm just finishing up the implementation details.
- **Account Management:** I've built out the `User` API a bit. There's a few notable things that come with it:
- **Refresh Tokens:** Refresh tokens can now actually be used to refresh access tokens. `/_matrix/client/v3/refresh` should be fully functional now.
- **Account Logout:** Work has been done on the `/_matrix/client/v3/logout` endpoint. It should be functional, although we have yet to implement `/_matrix/client/v3/logout/all`.
- **It Works!** Telodendria now has an "It Works!" page in the style of Synapse, primarily as a proof-of-concept for the other HTML pages we'll need (login/auth fallback pages) as well as the content repository.
- **Admin API:** We've started thinking about what a Telodendria administrator API will look like. It's not very well defined at all, and no code has been written yet, but effort has been made on writing up some spec documents.
And of course, I can't forget about the project management side of things:
- **Documentation:** The documentation is now _mostly_ up to date. A lot of code is being written at the moment, so there's pending documentation to be written, but the documentation is a lot more up to date than it was the last time I wrote a newsletter.
- **Website:** I hacked together a simple way to generate the index page based on the documentation available in the `man/` folder. This way, I don't have to manually update the website every time I add a `man` page. I might change this later, but I think it's good enough for now.
- **Donations:** Open source is a lot of work. I do find it personally rewarding, and I think that's very important, but Telodendria isn't going to build itself. It takes very real time and money to make things happen. I'm bringing this up again because I happened to stumble across [core-js](https://github.com/zloirock/core-js) and read the tragic story of its owner. If you aren't donating time to a project you find interesting, consider donating money, at the very least to show the developers you appreciate what they're doing. This time I'm not necessarily pleading for you to donate to _me_ in particular—though that's always appreciated—I just ask that you'd consider the idea of donating to open source in general if you care about it.
## Memory
I have a lot to say about memory management in Telodendria, so I thought it best to give it its own section.
### Backtraces
I spent probably a good 5 or so hours building up the Memory API so that it supports backtraces. I had it so that every time a call to a memory allocation/de-allocation function is made, we get the full call stack and store it in the block's metadata. The reason I wanted to do this is because it's getting harder and harder to debug memory issues, as the code is getting more and more complicated. So I thought that having bactraces in the log output whenever we have an invalid pointer or a memory leak would help significantly, because whenever I use Valgrind, I find I'm a lot more productive. My thinking is that the hook function could print the backtrace to the log whenever a memory error occurs, be it a leak or a bad pointer, so we know exactly where things went wrong.
The theory is sound, and I thought the implementation would be too, but alas, there is no POSIX-specified way of getting backtraces programmatically. I discovered that `libexecinfo` would do what I needed, especially because it ships with the modern BSDs, and the functionality it provides is built into Glibc. So I built out a beautiful backtrace API and even debugged a few memory issues with it. There was only one problem: Telodendria could no longer be built as a static binary; it had to be build dynamically so that the symbol table could be baked into it. If you've read the documentation, you know that goes against one of the project's goals, so that should have been the first indicator that I was going down a bad path. But it was a sacrifice I thought I was willing to make for pretty backtraces that could help me find memory issues.
Then I tried to compile on Alpine Linux, which famously does not use Glibc, but Musl libc. Of course it just so happens that Musl doesn't support backtraces. Apparently you can `apk add libexecinfo libexecinfo-dev` to get the backtrace functions, but I could not figure out how to link `libexecinfo` and Musl; apparently they are not compatible. I'm guessing `libexecinfo` is for those using Glibc on Alpine for whatever reason. So unfortunately, I had no choice but to scrap my backtrace idea because even though it's technically possible and in fact rather elegant, it's just not portable and I don't want to pull in a library like `libunwind` or `libbacktrace` just for something we shouldn't need to use much anyway.
That's my story about backtraces. The reason I bring this up is because it prompted everything that follows.
### A Major Refactoring
After spending the last two days in Valgrind hunting down memory issues, I think it looks like Telodendria will have to undergo a major refactoring in the area of string management. A serious paradigm shift needs to take place. Currently, it's way too easy to make mistakes with strings, causing memory errors that are extremely difficult to track down, particularly when interacting with the JSON and the database APIs. As I work on this project myself, as well as debug the patches from other people, I'm finding that it is extremely difficult to write good, bug-free code in Telodendria.
Let me explain the problem: right now, we kind of just duplicate (or don't duplicate) strings on an as-needed basis when we need to pass them into a function we know is going to hang onto it for a while. What this means is that the caller of a function has to know whether or not that function is going to hang onto the string passed to it and then do exactly one of two things:
- Duplicate the string and pass the duplicate into the function, hanging on to the original.
- Just let the function "have" the pointer to the string and explicitly don't free it, because the caller knows it's still in use.
This is problematic because oftentimes, we actually have both of these things happening in the same function. That is, a string gets duplicated and passed into a function that will hang onto a pointer, but then later on, that same string is _not_ duplicated and just straight up passed into another such function when the string is no longer needed anymore. The result is extremely ambiguous code; it's hard to tell if the duplication, or lack thereof, is intentional or just a programmer error. The only way to know is to comprehensively understand what all the functions are doing with their strings and where all the references to a string are at any given time in Telodendria's execution.
Believe it or not, there actually was a reason I chose to do things this way, and that was to avoid unnecessary string duplication. I was writing code with the goal in mind that Telodendria should be memory-efficient so it shouldn't be duplicating memory any more than it has to. Indeed, I still think this is the most efficient approach, as opposed to duplicating unconditionally and then freeing the original if necessary. But the problem is, the code is just too hard to understand and too hard to debug.
A part of simplicity is not necessarily efficiency, it's readability and write-ability. Obviously I want Telodendria to be efficient, but not at the cost of my own time and sanity. I've come to be okay with spending a few more bytes and clock cycles if it means the code is easier to maintain—that's why the Memory API exists in the first place, for example—because I'm going to be maintaining this thing for a long time. I want to be able to fully understand the code, including the drawbacks, so I think the code should be written in a way that is easy to understand, particularly because the code is getting extremely complex.
For that reason, I think I want to do string duplication unconditionally on the other end of the function call. That is, if a function is going to hang onto a string for a while by way of returning a reference that has a pointer to the string inside it somewhere, then that function itself should go ahead and duplicate it, so that the calling function does not have consequences for the caller. The caller should be able to operate on the string whether or not the function does something with the string. In this way, I want to make the code base a little more functional and less procedural; the difference being that functions cannot modify their input and produce new output, whereas procedures modify their input and produce no output.
For example, `JsonValueString()` should duplicate the strings passed into it instead of just generating a reference to that string. This alone would probably solve 75%-85% of our memory errors. Every caller of `JsonValueString()` just has to free any variables it passed into `JsonValueString()`, otherwise, if it itself was passed the string, it just lets its own caller free the string, and it can re-use those strings without worrying about references as needed. Basically what this does is allow us to keep track of strings a lot easier. Instead of having to think about where a string is referenced in other functions and objects, we can just get rid of it when we're done with it because anything that still needs it will have its own copy.
Another example is `HashMap`, which should do the same thing, since it takes string keys. This way we don't need the recently-added`HashMapGetKey()` function, we can just internally manage our strings and free them when we delete them from the map. Literal strings are duplicated, but then they're freed when the map is, or when the key is removed. We don't need any logic to detect whether the string is on the heap or not, therefore reducing our dependency on the Memory API.
I'm still weighing the advantages and disadvantages of this approach. I think it comes at the cost of memory usage; our memory usage will go up in the case of literal strings (of which there are quite a few in Telodendria), as they'll be continuously duplicated on the heap in a few places, but at the same time, we're duplicating strings so much as it is because we can't have a single string reference in more than one place anyway. The other obvious downside is that this change is a _huge_ refactor. Someone's going to have to go through and (1) add the appropriate `StrDuplicate()` calls and (2) track down all of the resulting memory leaks due to callers expecting strings to be taken care of by somebody else. That someone is probably going to be me, but even though it's a big time and effort commitment, I think it's worth it, because the code will become**so** much more readable and writable. Everyone that works on Telodendria will be less prone to errors because we don't have to mentally keep track of as much when we're writing code, and that alone should speed up future development by making the current contributors more efficient, and lowering the barrier of entry for potential contributors.
Thoughts? Concerns? Let me know about them over in #telodendria-general:bancino.net!

View file

@ -0,0 +1,34 @@
---
title: 'January Status Update'
date: '17-01-2023 21:21'
feed:
limit: 10
---
Hey everyone, it's another one of those Tuesday newsletters, which means I'm either a few days late (since I didn't write one last week), or a few days early, depending on how you want to look at it. I think I'm early, because I was going to post this on Thursday, but I was just too excited to wait.
## What's Happening
Here's what's been happening, and what's going to happen. These are the things that have been on my mind when I sit down to work on **Telodendria**:
- **Client Authentication:** I'm proud to report that the client authentication part of the Matrix specification is well on its way to being completed in the near future. I did some work yesterday and got the user login logic in place, so now clients can complete the registration flow at `/_matrix/client/v3/register`, and they should also be able to log in and get access tokens using `/_matrix/client/v3/login`. I was able to sign up for an account with Element, and then log in with it, although Element just hangs after logging in because of course there's no sync endpoints or anything yet. Still, kind of neat. The next thing to do is implement the refresh token and logout endpoints.
- **Non-JSON Endpoints:** There are a few scenarios in which an endpoint may _not_ return JSON, such as the endpoints for downloading files from the content repository. Unfortunately, the way things are currently set up, endpoints can only return JSON objects. So I will have to refactor the way routes are handled to allow for these non-JSON endpoints. This will have the added benefit of allowing me to add a static home page (resembling Synapse's "it works!" page), as well as implement a fallback login HTML page as described by the specification.
- **Documentation:** At some point I really need to work on cleaning up the docs, because they are a bit out of date now. In the future, I'm going to try a little harder to not get so out of control. I'd like to keep the documentation in sync with the code as much as possible.
- **v0.2.0 Release:** I know I may have implied last time that `v0.2.0` was right around the corner, but I'd like to finish up some of the aforementioned tasks before I cut a new release, and that's going to take a little time, so I'm delaying the release a little bit. Not because things are going slow, but because they're going fast and I want to squeeze as much into it as I can! Check out the `TODO.txt` file for exactly what I have left to get done. It may be a few months before we see another release. But that's okay, because I've said from the beginning that even if development is fast-paced, I don't want the release cadence to be that fast.
## Contributing
I just want to say thank you to @allonsenfantsdelapatrie:devhonk.tk for submitting patches and reporting issues at an impressive rate over the last few weeks. I really appreciate the work that's been getting done as a result. We've caught quite a few of bugs, addressed some security concerns, and implemented a number of features related to client authentication. I also spent some time brainstorming how **Telodendria**'s database schema is going to work, thanks entirely to an early prototype patch that was submitted a few weeks ago.
As always, anyone is welcome and encouraged to contribute. Feel free to chat with me in #telodendria-general:bancino.net, or report an issue to #telodendria-issues:bancino.net. Right now, the following areas need help:
- **Client Authentication:** The existing code for client authentication should probably be tested a bit more. I'd encourage everyone following this project to spin up **Telodendria** and give it a whirl with their favorite Matrix client and see how creating an account and logging in works. Additionally, there's more work to be done regarding refreshing access tokens, and logging users out.
- **Documentation:** I could always use help with documentation! There's lots to write, and I'm not really looking forward to writing it, so if anyone wants their name in some `man` pages, feel free to hop in here.
- **Integration Testing:** I'd like to eventually test **Telodendria** using [SyTest](https://github.com/matrix-org/sytest) and [Complement](https://github.com/matrix-org/complement), but I've been too lazy to get them set up properly. If you have any experience with these testing suites, you're definitely welcome to try to get **Telodendria** running in them and submitting patches for any necessary changes.
- **Donations:** As always, if you don't want to write code or documentation, but want to help out, you can always donate using the links on the project website (linked in this room's topic).
**Fun fact:** Running `grep -R TODO src/` in Telodendria's project directory will generate a list of `TODO` items that you may be interested in. Whenever there's further work to be done on the code, we add a comment with the word `TODO` in it. Do also be sure to check out the `TODO.txt`, which contains items that haven't even been coded up yet.
## That's All
That's all for this week!

View file

@ -0,0 +1,23 @@
---
title: 'March Status Update'
date: '03-03-2023 21:18'
feed:
limit: 10
---
Hey everyone!
As you might have noticed, there's been a lot of activity in the last week or so, and I'm hoping that will continue, although I am getting busy again so I'd expect things to slow down a little bit.
- **The Refactor:** The string-memory-management refactor actually went really smooth. There wasn't a ton that had to be changed; just a few low-level things and everything else clicked into place. The difference is incredible though; the code is a lot simpler, and writing new code is so much easier than it was before.
- **Ports:** A few fixes were committed that make Telodendria more portable, particularly to barebones systems that implement _just enough_ POSIX to be useful. As a result, I was able to build Telodendria on Haiku OS and Android. There's talk of a MINIX port, but that's no small task, as even the MINIX porting documentation says that "porting software to MINIX from Linux or FreeBSD is non-trivial in most cases..."
- **User Interactive Authentication:** The `Uia` API is now pretty much stable, although there are plenty of `TODO` comments scattered throughout. It works for single-stage flows, but needs a bit more work for multi-stage flows, and right now we have only implemented dummy and password authentication. Next up to work on is registration token authentication.
- **Bug Fixes:** A number of bug fixes have been made throughout the code. The most notable fixes were made in the JSON parser, which now properly handles empty arrays, and prohibits trailing commas.
- **Static Pages:** Matrix homeservers are expected to serve a number of static HTML pages. I haven't implemented any of the user-interactive authentication static fallback pages yet, but Telodendria now has a login fallback page that should be fully functional, and as a bonus, the JS has been made to work with LibreJS. I've built out an `Html` API that does the hard work of building HTML pages so we can just start dumping HTML, CSS, and JS to the client without having to worry about all the boilerplate HTML stuff.
One thing you'll notice is that all static pages are baked into the C source code as string literals, instead of loaded off the filesystem at runtime. The reason I've opted to do it this way is twofold:
- I want Telodendria to be as self-contained as possible.
- It avoids the some of the security pitfalls of running a web server.
We are well on our way to having client authentication completed. Registration, login, and basic logout are fully functional. We just have to implement `/_matrix/client/v3/logout/all`, finish up user interactive authentication registration tokens, and add a few trivial endpoints for deactivating users, changing passwords, and things of that nature.
I've got plenty of other thoughts on stuff that is in Telodendria's future, but I think that's good for this week. As always, if you like what's going on, you're more than welcome to donate (links at https://telodendria.io), or just say hi in #telodendria-general:bancino.net.

View file

@ -0,0 +1,137 @@
---
title: 'Matrix Protocol Overview'
date: '01-07-2023 21:14'
feed:
limit: 10
---
- **Spec Mirror:** Just as a reminder, I have a full mirror of the Matrix specification available at https://telodendria.io/spec.matrix.org. I very recently updated it to `v1.7`, and you can also download a tarball of the spec for offline viewing if you want: [`spec-v1.7.tar.gz`](https://telodendria.io/spec-v1.7.tar.gz).
- **GitHub Mirror:** The GitHub mirror is now up to date. It was a few weeks behind because my script broke due to some changes in my `.ssh/config`. I fixed those so now we're back up and running.
- **Where Code?** You may have noticed that I haven't been writing a lot of code lately. Instead, I've been living in the Matrix specification, trying to wrap my mind around it almost every free minute I have. The fruit of my labor is the rest of this newsletter, as I've set out to document my thought process so I don't forget it when I actually go to write the code.
## Matrix Homeserver Implementation Part 1: Protocol Overview
In this newsletter, I want to sketch out my implementation of the Matrix specification that I plan on using, both to clarify my own thoughts, and because I am struggling to find resources explaining how Matrix homeservers are actually implemented. It's difficult to read the source code for the existing implementations because they are so large, so their code bases require such a deep understanding in order to even understand the concepts of how they work. Instead of writing all of the code first and just dumping it onto my CVS server, I feel like I can do a better service to the Matrix community by actually explaining more of the concepts than what the specification does so that the implementation becomes a lot easier to deduce for
oneself. For me, writing code is the easy part once I understand a topic. So, without further ado, let's get into the technical details of the Matrix specification.
A Room in Matrix terminology is simply a directed acyclic graph (DAG) of Events. An Event is a—mostly—self-contained unit of data which contains a few pieces of information required by the homeserver, but otherwise has few restrictions on what can be contained in it. The meaning of an event is derived entirely by the client; the server uses a few pieces to maintain the relationships between and authentication chain of the events in a room, but otherwise does not care what an event contains.
Events are related to each other both chronologically, and by how they authorize each other to exist in a room. In a sense, a Room is actually two DAGs created out of the same set of events; one in which the edges of the DAG express a chronological relationship, where events are connected by an edge to the events that came before them, and the other where the edges express a state relationship, where events are connected by an edge to the events that authorize them to exist in the graph.
A Matrix homeserver traverses each of these DAGs primarily to perform the following two tasks:
- State Resolution
- Linearization
State Resolution is the process by which the homeserver traverses a room's DAG to determine what the correct State of the room is at any given point in time, namely, at any given Event in the room. As we will see in a moment, this is actually a rather complicated process due to the distributed and decentralized nature of the protocol.
Linearization is the process by which the homeserver traverses a room's DAG and organizes events into a stream of events, one after another, for the client to process. While I have yet to find the details of this process in the Matrix specification, my current understanding is that this is just a topological sort, which is also used in state resolution.
The Matrix specification defines State as "a map of (event\_type, state\_key) to event\_id." In other words, State is a collection of key-value pairs where the key is the event type and a special state key, and the value is an event ID. State is used to describe a room, including giving it a name, topic, and aliases, as well as recording what users are a part of the room. It is also used to authorize events, because the State describes who has permissions to send
what types of events.
A room's State is updated when a State Event is sent to the room. Each state event must have a type and a state key, which is typically a blank string, indicating that it applies to the entire room, but in some specific cases is the ID of a user to indicate that the state only applies to that user, such as when a user joins or leaves a room. When the state event is sent, it updates the room state at that point in time to make its type and state key point to itself. The state event describes the changes in state to be applied. This is how Matrix stores all the information about a room.
State resolution algorithms are necessary because Matrix is a decentralized protocol where every homeserver that is participating in a room has its own copy of the room. The room does not belong to any one homeserver, rather all homeservers contribute to it equally. This can become problematic if the homeservers are unable to communicate with each other quickly enough, because it is likely that clients will still be sending messages to the server, and the
server will still apply them to the room. This means that the room's state can potentially be updated independently on each homeserver participating in the room, so when the homeservers are able to sync up, they need some way to arrive at a consistent state.
All of this information is easily derived from the Matrix specification itself. But what is less clear is how to go about actually implementing all this in code. Matrix is a very complex protocol with a lot of algorithms and data structures. These are explained in the specification
with a practical degree of precision, but what is not explained is how to actually store the data and when to run the algorithms on it. Both of these are implementation details left to the developers of homeservers, as they should be.
What follows is by no means the only way to implement the specification, I'm sure, but it is the way I have gone about it in my mind as a homeserver developer, though I must admit that I have looked at very little code while thinking through this, and I have yet to write any of the code that implements this process. Nevertheless, I hope I can offer some clarification for those that want to understand how Matrix homeservers actually do the things that the specification says they need to do.
The most important thing to figure out is what a homeserver actually needs to store in the database apart from room state. As far as I have deduced, this is essentially just a list of events at the bottom—or top, depending on how you want to look at it—of each room's event DAG. I tend to want to call these the leaves, although the DAG is not exactly a tree so I am not sure if the term really applies, but either way, the homeserver needs to be able to keep track of all the tips of the forks in the room. Ideally there will only be one tip most of the time, but it's possible that there may be multiple when federation occurs. The homeserver records these events not only for traversal purposes, as required for state resolution and linearization, but also to know what the previous events are when a client sends an event. In order to be able to effectively attach client events to the DAG properly, we must have a list of the most recent events.
The algorithm for maintaining this list is simple; when an event arrives at the homeserver from another homeserver, it iterates over all of the events it specifies to be its previous events, and removes them from the list if they are present. It then adds the incoming event to the list. This works because it ensures that no "leaves" are lost, and no leaves that are no longer leaves remain in the list. Perhaps a visual representation is necessary to effectively demonstrate this concept.
Consider a Room with events E1, E2, and E3 that looks like this:
```
E1
|
E2
|
E3
```
Now consider that event E4 arrives and specifies E2 as its previous event because the homeserver that E4 originated on was not aware of E3 at the time. The DAG now looks like this:
```
E1
|
E2---+
| |
E3 E4
```
When E4 arrives, E2 is not in the list of most recent events, so nothing is removed from it. E4 is then added to the list, because it is now a leaf. This covers the situation in which a fork is
created. Now, consider that event E5 arrives at the server and specifies E3 as its previous event. The DAG now looks like this:
```
E1
|
E2---+
| |
E3 E4
|
E5
```
Since E5 specifies E3 as its previous event, E3 is removed from the list and E5 is added. E4 is still in the list. Now lets look at what happens when an event E6 arrives that resolves the fork by specifying both E4 and E5 as its previous events. The DAG now looks like this:
```
E1
|
E2---+
| |
E3 E4
| |
E5 |
| |
E6---+
```
Since both E4 and E5 were specified as previous events, they are both removed from the room's stored list and then E6 is added. The fork has been resolved.
When a client sends an event to the homeserver, it specifies no previous events. The homeserver must determine what they are for itself. Since the homeserver keeps a list, it simply copies this list to the client event. If there is more than one previous event, the client's event resolves the forks as shown in the previous diagram.
Homeservers may also find it desirable to cache the state of the room at each event so that it does not have to continuously re-run the state resolution algorithm whenever the state is needed. As far as I know, all homeservers do this, but it is not strictly required by the specification, and it would be advantageous if the homeserver implementation could cope with previous states missing from the cache by simply recalculating them as necessary.
Other than the DAG's current leaves and the state at each event, a Matrix homeserver has to store nothing else for a room. Everything that clients and other servers will ask for can be derived from the resolution of the state at each of the current leaves, or the state at a particular event. This even includes the room version, which is derived simply by opening up the event at the m.room.create key in the room state and checking the version there.
Now that we know what we are storing, we can move on to how we actually ingress events. The Matrix specification is unclear as to what is supposed to happen when events are received at the API endpoints. There are two ways an event can arrive. It can arrive via the federation API from another homeserver, or it can arrive from a client. Both are handled slightly differently.
When a homeserver receives an event from another homeserver, the event already has a list of previous events, so the homeserver should perform the following tasks in order when an event arrives:
1. Calculate the content hash as described in the specification's appendices. If the room for which the event has arrived is version 3 or newer, this hash is used to generate the event ID. In all room versions, the hash should be verified against the hash provided in the event itself.
2. Perform a size check as described in the client-server API portion of the specification and reject the event if it is too large.
3. Execute the state resolution algorithm on the event to get the state of the room at the time it was sent.
4. Perform an auth check by verifying that the events that the incoming event says authorize it actually do. Reject the event if the auth check fails.
5. Update the room's list of previous events as described above.
6. Store the event itself.
7. Optionally, cache the resolved state for quick reference in the future.
When a homeserver receives an event from a client, the event is not fully formed and ready to be added to the room, so the procedure is a little different:
1. Copy the room's previous events list to the event.
2. Execute the state resolution algorithm on the event to get the state of the room at the time the event was sent.
3. Use the resolved state to generate the event's list of authorization events. This involves checking all state events in the resolved state that would—but don't necessarily—authorize the event to be in the room. This includes power level events and room membership events.
4. Compute the content hash and add it to the event.
5. Perform the size check and reject the event if it is too large.
6. Perform the same auth check as you would for events incoming through the federation API.
7. Update the room's list of previous events as described above.
8. Store the event itself.
9. Optionally, cache the resolved state.
As you can see, there are a few differences in the order that things must happen in depending on how the event arrives. Note that the size check is performed so late in the process because the size limit stated in the specification depends on all parts of the event being present, so we have to generate those parts first.
The process for ingressing events should now be clearer, although the state resolution algorithm is still a black box. It is important to note that the algorithm is not static; it depends entirely on what version of room we are dealing with. The Matrix specification defines many room versions each with their own nuances, and room version 2 introduces an entirely new state resolution algorithm. There are currently two state resolution algorithms that have to
be implemented by a homeserver. It is not enough only to implement the latest algorithm because many rooms exist in the Matrix community that still use the old algorithm.
The state resolution algorithms are thoroughly documented in the Matrix specification for each room version, however these algorithms can be a little cryptic at first glance. I hope to explain the state resolution algorithms in terms of practical implementation in future newsletters, so stay tuned for those. I will detail how to derive the inputs for the state resolution algorithm, what the output means, and then how to actually go about implementing the algorithms.
## That's All!
I hope you enjoy these types of posts. They're really more for me than anyone else, because it's helpful to me to write all my thoughts down, but I certainly hope they are beneficial to others as well. If I missed something, feel free to let me know in #telodendria-general:bancino.net!
As always, if you like my work and you want to support it, consider sending a few dollars to me using the links at https://telodendria.io.

View file

@ -0,0 +1,14 @@
---
title: 'Newsletter Is Now Blog'
date: '01-11-2023 20:53'
feed:
limit: 10
---
First of all, welcome to the new Telodendria website! This website is powered by Grav, a simple, yet elegant CMS that is a much-needed upgrade from the previous hand-crafted HTML page that was being served as Telodendria's home page. I'm pleased to give Telodendria a much more professional online look, and I'm hoping that this website will be a good introduction to Telodendria for new users.
This blog will serve as the primary way I report on all things Telodendria, including new releases, large upcoming changes, and Matrix research. Up to this point, I've been using the Matrix room [#telodendria-newsletter:bancino.net](https://matrix.to/#/#telodendria-newsletter:bancino.net) to post newsletter entries, but I think having a proper blog will be a much better solution, because then even people that don't have Matrix accounts or just don't want to join yet another room can still follow along and keep up with the progress. It'll also allow me to reach people that maybe might create a new Matrix account because of Telodendria, who knows!
Effective today, I will no longer post any new newsletters to the Matrix room. Instead, anything I would put there will be a blog post instead. If you want notifications, take advantage of the syndicate options. This website supports Atom, RSS, and JSON feeds.
This first post is just to notify of these changes. Keep an eye out for new posts!

View file

@ -0,0 +1,347 @@
---
title: 'On Matrix''s Release Cadence & State Resolution v1'
date: '03-07-2023 21:12'
feed:
limit: 10
---
For those of you in the United States, tomorrow is Independence Day! 🎆
I've had a long weekend because of the holiday, which is why you get two newsletters so close together, enjoy!
## On Matrix's Release Cadence
In last week's [TWIM](https://matrix.org/blog/2023/06/30/this-week-in-matrix-2023-06-30/), the following was announced:
> Room version 11 has been accepted! A gentle reminder for Matrix client and homeserver implementations to update and incorporate the new changes. The full list can be found [in the MSC itself](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/3820-rooms-v11.md) (mainly changes to redaction semantics and related fields).
>
> While this means that room version 11 is now considered stable, it's not recommended that homeserver implementations set their default room version for newly created rooms to 11 yet. We recommend waiting a few months for client and other homeserver implementations to gain support first.
It appears to me that the authors of the Matrix Specification ("spec") expect developers to keep up with the MSCs and implement them as soon as they are accepted, but before the next version of the official spec is released. I don't really agree with this way of doing things, so I just want to clarify how I expect Telodendria to deal with new spec versions going forward.
It is my understanding that new versions of the spec are released every quarter. This is far too frequent, in my opinion, because I think spec stability is really important. The C standard, for example, updates roughly once a decade. I would be much happier if the spec updated at roughly this pace. The reason for this is that I want time to actually implement the spec, test that implementation, and get it in the hands of users so it can be stable. If I'm constantly adding new features to Telodendria because of new spec versions, I don't know how Telodendria will ever be stable.
My goal is to be as confident in Telodendria as I am in my SMTP server, for example. With mail servers, once you set them up, you never think twice about them because they always just do what they're supposed to. I want Telodendria to be the same way. I want it to always just work. And I don't want it to be updating constantly. I don't want a version that's only 6 months old to be out of date. If Matrix is to stand the test of time, the spec authors can't overwhelm developers with new features constantly. End users don't necessarily care about getting new features all the time, they just want something that is reliable and won't change on them in unexpected ways.
When I created the Telodendria CVS repository, the spec was at `v1.3`. It is now at `v1.7` about a year later. For a new homeserver developer like myself, this is incredibly overwhelming! I can't even fully wrap my mind around the current spec version—let alone implement it—before another version comes out with more API endpoints and features. I have therefore determined the following regarding Telodendria's relationship with new specification versions:
- Telodendria will not implement any MSC before it arrives in an officially published spec at https://spec.matrix.org. There will be no experimental or unofficial changes, and when an MSC is finalized and marked as accepted, it must be formally added to the spec. Therefore, I won't even consider the existence of Room Version 11, for example, until it shows up in spec version `v1.8` at the earliest. The reason for this is because I want Telodendria to be stable. Yes, that means it's always going to be behind on the latest features, but that is a good thing the way I see it because it gives Telodendria the the chance to be reliable. I shouldn't have to keep up with the spec authors and follow their activity so I know what's happening with the spec; I should be able to implement the stable spec and refine my implementation until the next spec version is released.
- Telodendria's first stable release will implement spec version `v1.7`. In the time between now and the release date for Telodendria's first stable release, I expect there to be many more releases of the Matrix specification, but I am freezing Telodendria's view of the spec until I've implemented everything in `v.1.7`. I have to draw the line somewhere because otherwise I'll never get a stable release of Telodendria out, and I don't think anyone wants that. I am therefore drawing the line here; Telodendria's first stable release, even if it comes 5 years from now, will implement the version of the spec that's out right now. I'm pretty sure it won't be 5 years though, but you get my point.
- Telodendria's versioning scheme when it becomes stable will indicate which spec version is being implemented. This means that the first stable release will carry the version number `v1.7.0`. Expect point releases as I fix bugs, but don't expect `v1.8.0` until spec `v1.8` is fully implemented.
- Telodendria will not skip spec versions. It's highly likely that multiple versions of the spec will be released in the time it takes me to implement `v1.7`. I'm not going to skip the in-between spec versions though; I'm going to implement each one in the order it came out simply because it would be too overwhelming to try to track all the changes across spec releases and combine them into one large Telodendria release. In other words, don't expect Telodendria `v1.9.0` until `v1.8.0` is released, implementing spec version `v1.8`. I'm hoping that the changes to the spec will be small enough between versions that I can comfortably implement them without getting too behind, but I know I'll probably be quite a bit behind for the first release.
What this really comes down to is that I don't buy into the rapid pace at which the spec is developed. Many core internet technologies—DNS, SMTP, _etc_, to name a few—have remained virtually unchanged since their introduction many decades ago, and I believe that in order to get Matrix to become a core internet technology, it has to stabilize. It has to, at some point, be finished, even if it is not perfect. That's the point of a standard; it is to be something that doesn't change, or changes very little, so it can be relied upon for many years to come. Maybe that's not the goal of the spec authors, I'm not sure. All I know is that a new spec release every quarter is simply unsustainable. We don't see four new versions of SMTP every single year, and that's because SMTP just works. Of course it is not perfect. It has its flaws, but nonetheless, people depend on it to just work and it does. My vision for Matrix is a similar situation. It doesn't have to be perfect. It probably never will be. It just has to work. And right now, it works, so I'd be hesitant to keep changing it.
## Matrix Homeserver Implementation Part 2: State Resolution v1
In my previous newsletter, I gave an overview of the Matrix protocol and provided some helpful information for actually implementing the core functionality that homeservers adhering to the spec are expected to provide. I left off on State Resolution, which I felt deserved its own series of newsletters because covering it will take at least as many words as that newsletter ended up being.
This newsletter is going to have a lot of pseudocode. As I mentioned in my previous newsletter, Matrix state resolution is well-documented from a conceptual standpoint, so I don't feel the need to explain the concepts yet again. Instead, I'll focus on the far more task of translating those concepts into code. I should point out right now that my pseudocode is not Python-esque like most---rather, it resembles C, which I find easier to read and write. C is much less ambiguous. But maybe I think that just because I'm a C programmer and not a Python programmer. Regardless of your preferred language, I hope you can see past the syntax and understand what I'm writing. The data structures used can be easily inferred, although I may be biased because they very closely resemble Telodendria's actual code.
To recap briefly the important takeaways from Part 1, State is a collection of key-value pairs where the key is an event type and a state key provided by an event, and the value is an event ID. This collection of key-value pairs describes a room in its entirety; from name, topic, and aliases to user membership and permissions, everything anyone needs to know about a room is derived from the room's State.
You can think of room state as a collection of pointers to the state events that determine what the room looks like, who's in it, and what kind of events they're allowed to send. We know why state is so important, and we know where state resolution fits into the overall implementation of a homeserver, but we have yet to figure out how it actually works. As I mentioned in my last newsletter, there are two algorithms, with room version 1 using the original algorithm, and versions 2 and beyond using the new and improved algorithm. In this post, I'll set up the framework primarily for the original algorithm, but some of these details will apply also to the improved algorithm.
First, we need to figure out what the state resolution algorithm's inputs and outputs are. At a high level, we can say that the state resolution algorithm's input is a single event, and its output is the complete state of the Matrix room before that event was added to the room's directed acyclic graph (DAG). So, your state resolution function is going to look roughly like this:
```c
HashMap *
StateResolve(Event *);
```
Note that both state resolution algorithms are defined recursively; that is, you must have resolved the state for each previous event specified by the input event before you can resolve the state for the current event. This might look like this:
```c
HashMap *
StateResolve(Event *e)
{
Array *states;
size_t i;
for (i = 0; i < ArraySize(e->prev_events); i++)
{
Event *p = ArrayGet(e->prev_events, i);
HashMap *state = StateResolve(p);
/*
* state is now the room state before p. Remember to
* apply p to state if p is a state event so that
* state becomes the room state *after* p.
*/
ArrayAdd(states, state);
}
/*
* states is now the set of the previous states of the
* of the room to be resolved. We can continue with the
* resolution algorithm.
*/
}
```
An efficient homeserver will not resolve the room state at an event more than once; room state should be cached for each event that it is calculated for so that the algorithm does not need to compute redundant results. The implication of this is that a Matrix homeserver stores snapshot of the room state before each and every event in the room.
The next step in state resolution now depends on the algorithm we are using. If the room version is 1, then we have to use the original state resolution algorithm. Otherwise, we use the new, improved state resolution algorithm. We should modify the signature of our `StateResolve()` function to accommodate this:
```c
HashMap *
StateResolve(int roomVersion, Event *e);
```
To make things easier, after we perform the logic shown above and we have our set of states, lets break out each version of the algorithm into its own function:
```c
HashMap *
StateResolve(int roomVersion, Event *e)
{
Array *states; /* Array of HashMaps */
/* ... Logic shown above ... */
switch (roomVersion)
{
case 1:
return StateResolveV1(states);
break;
default:
return StateResolveV2(states);
}
}
```
Let us now implement the original state resolution algorithm. The first step is as follows:
> "Start by setting R to the union of the states to be resolved, excluding any conflicting events."
What this means is that we have to build up a new partial state map by looking at each state in the incoming array of states and adding all the keys that don't already exist in the partial state. If a key does have a conflict, then we remove it from the partial state and collect the conflicting events events to resolve them. This process looks something like this:
```c
HashMap *
StateResolveV1(Array *states)
{
HashMap *r;
HashMap *conflicted;
size_t i;
for (i = 0; i < ArraySize(states); i++)
{
HashMap *state = ArrayGet(states, i);
char *eventType;
HashMap *stateKeys;
while (HashMapIterate(state, &eventType, &stateKeys))
{
char *stateKey;
char *eventId;
while (HashMapIterate(stateKeys, &stateKey, &eventId))
{
/* (eventType, stateKey) -> eventId */
if (HashMapGet(HashMapGet(r, eventType), stateKey))
{
/*
* Conflicted state, add to conflicted and
* remove from r.
*/
}
else
{
/* Add this state pair to r */
}
}
}
}
}
```
We now have R, which perfectly maps a state tuple to a single event ID. However, we also have a conflicted set, which maps a state tuple to more than one event ID. This is what we have to resolve onto R. The original state resolution algorithm states that we must deal specially with the following types of events:
- `m.room.power_levels`
- `m.room.join_rules`
- `m.room.member`
For each of these event types, we are to collect all the event IDs that have these types in the conflicted set into a list. We will then sort that list by depth and event ID---well, specifically the SHA-1 hash of the event ID. Then, we will iterate over each list, adding the first event, and then making sure the rest of the events are allowed by the authorization rules in R. If they are, we keep updating R with the events, otherwise, we stop and continue on to the next list. This process is explained a little more clearly in the Matrix specification, but here's some pseudocode that shows how it's done:
```c
HashMap *
StateResolveV1(Array *states)
{
HashMap *r;
HashMap *conflicted;
Array *authTypes;
size_t i;
/* ... Logic shown above ... */
ArrayAdd(authTypes, "m.room.power_levels");
ArrayAdd(authTypes, "m.room.join_rules");
ArrayAdd(authTypes, "m.room.member");
for (i = 0; i < ArraySize(authTypes); i++)
{
char *authType = ArrayGet(authTypes, i);
HashMap *stateKeys = HashMapGet(conflicted, authType);
Array *events;
char *stateKey;
Array *eventIds;
char *eventId;
Event *e;
/* Assemble all the events of the given type */
while (HashMapIterate(stateKeys, &stateKey, &eventIds))
{
size_t j;
for (j = 0; j < ArraySize(eventIds); j++)
{
eventId = ArrayGet(eventIds, i);
ArrayAdd(events, eventId);
}
}
/* Sort ascending by depth, descending by SHA-1 of ID */
ArraySort(events, EventCompareFunc);
/* Add first event to R */
eventId = ArrayGet(eventIds, 0);
e = EventGet(eventId);
HashMapSet(HashMapGet(r, e->type), e->state_key, eventId);
}
}
```
The `EventCompareFunc()` referenced in the above snippet is implemented roughly as follows:
```c
int
EventCompareFunc(Event *e1, Event *e2)
{
int depth1 = e1->depth;
int depth2 = e2->depth;
if (depth1 > depth2)
{
return 1;
}
else if (depth1 < depth2)
{
return -1;
}
else
{
char *sha1 = Sha1(e1->event_id);
char *sha2 = Sha1(e2->event_id);
/* Descending */
return strcmp(sha1, sha2) * -1;
}
}
```
The above code actually covers the next two steps in the algorithm as well; it sorts the event lists and it adds the first event in the list to R. The next step is to process the rest of the events in the list, which is as simple as iterating over it and performing the auth check using the partial state R.
We should then clean up the conflicted set, removing the type keys that we've processed so far, because we will have to iterate over all the remaining type keys.
But first, suppose we have this function:
```c
int
AuthCheck(HashMap *state, Event *e);
```
This function takes in a room state and an event and determines whether or not that event is allowed to be in the room based on the authorization rules determined by the room state, such as membership, power levels, and the like. This is going to be the same function used when receiving events from the client-server API or the federation API. We also need it for state resolution. `AuthCheck()` should follow the authorization rules for the room version we are working with, so in practice, the function should also take a room version. Refer to the relevant section of the specification. It doesn't make sense to lay out an implementation here because the logic is very clearly laid out in the specification.
Assuming we have such a function, we can continue on with our algorithm. After we add the first event from our sorted list to R, we can use the `AuthCheck()` function on the remainder of the events, like this:
```c
HashMap *
StateResolveV1(Array *states)
{
HashMap *r;
HashMap *conflicted;
size_t i;
/* ... Logic shown above ... */
for (i = 0; i < ArraySize(authTypes); i++)
{
char *authType = ArrayGet(authTypes, i);
size_t j;
Array *events;
/* ... Logic shown above ... */
for (j = 1; j < ArraySize(events); j++)
{
char *eventId = ArrayGet(events, j);
Event *e = EventGet(eventId);
if (AuthCheck(r, e))
{
HashMapSet(HashMapGet(r, e->type), e->state_key, eventId);
}
else
{
break;
}
}
HashMapDelete(conflicted, authType);
}
}
```
Finally, one step remains in the algorithm:
> "... for all other conflicts, just pick the event with the highest depth and the lowest sha1(event\_id) that passes authentication in R and add it to R."
All that the specification is telling us to do here is to iterate over the remaining conflicted events, sorting them like we sorted all the other events, and then work backwards through the list until we find an event that passes authentication against R. That looks like this:
```c
HashMap *
StateResolveV1(Array *states)
{
HashMap *r;
HashMap *conflicted;
size_t i;
char *eventType;
HashMap *stateKeys;
/* ... Logic shown above ... */
while (HashMapIterate(conflicted, &eventType, &stateKeys))
{
char *stateKey;
Array *events;
while (HashMapIterate(stateKeys, &stateKey, &events))
{
ArraySort(events, EventCompareFunc);
for (i = ArraySize(events) - 1; i >= 0; i--)
{
char *eventId = ArrayGet(events, i);
Event *e = EventGet(eventId);
if (AuthCheck(r, e))
{
HashMapSet(HashMapGet(r, eventType), stateKey, eventId);
break;
}
}
}
}
}
```
And that should be it! This is the basic algorithm for state resolution v1. While some details have been omitted—notably the auth check and the details of some of the primitive data structures—this should fundamentally work. I haven't tested any of this code, and I know for certain it is missing a lot of error checking, and there are probably a few things that don't exactly work as shown, but other than that, this should get the idea across, and I would expect that something similar to this will end up in the code base.
## What's Next?
I'm getting kind of anxious to stop theorizing so much and actually start writing code again. Before I tackle state resolution v2, I'll probably spend a bit of time implementing a lot of the details I've laid out here and in the last newsletter. I have to figure out some of the abstractions that need to be put in place so that the nuances of each room version, including the state resolution algorithm and the auth checks, can be isolated without duplicating a bunch of code but also not creating an unreadable mess. I also just want to get things working, so for the time being I will probably just focus on implementing room version 1, and then once I know how that comes out, I'll have a better idea of the abstractions needed to make other room version implementations elegant.

View file

@ -0,0 +1,100 @@
---
title: 'Telodendria v0.3.0 Released!'
date: '10-06-2023 21:16'
feed:
limit: 10
---
Gosh, it's been such a long time. I regret that this summer is not going how I would've originally planned it because of some complications with my job, but I'm making the most of it. I do have a handful of exciting things to share, and I'm hoping to keep up the hard work on Telodendria.
## Version 0.3.0
I won't go into the details here, but Telodendria `v0.3.0` has finally been released after a few delays, although it is on schedule for `v0.2.0`, which was released very early, so I'm kind of right on schedule. I am currently hoping to get `v0.4.0` out by the end of this year, but we will see if that happens or not.
I'm not going to hash out all the changes in this section, that's what [the change log](https://telodendria.io/man/man7/telodendria-changelog.7.html#v0.3.0) is for. As always, if you haven't joined #telodendria-releases:bancino.net, that's where you can get concise notifications about new releases.
## Cytoplasm
**Cytoplasm** is what I'm calling our new general-purpose C library that supports Telodendria. I think `Cytoplasm/README.txt` explains it best, so I've copied some of it below.
```
Cytoplasm is a general-purpose C library and runtime stub for
creating high-level (particularly networked and multi-threaded) C
applications. It allows applications to take advantage of the speed,
flexibility, and simplicity of the C programming language, while
providing helpful code to perform various complex tasks. Cytoplasm
provides high-level data structures, a basic logging facility, an
HTTP client and server, and more.
Cytoplasm aims not to only do one thing well, but to do many things
good enough. The primary target of Cytoplasm is simple, yet higher
level C applications that have to perform relatively complex tasks,
but don't want to pull in a large number of dependencies.
Cytoplasm is extremely opinionated on the way programs using it are
written. It strives to create a comprehensive and tightly-integrated
programming environment, while also maintaining C programming
correctness. It doesn't do any macro magic or make C look like
anything other than C. It is written entirely in C89, and depends
only on a POSIX environment. This differentiates it from other
general-purpose libraries that often require modern compilers and
non-standard language and environment features. Cytoplasm is intended
to be extremely portable and simple, while still providing some of
the functionality expected in higher-level programming languages
in a platform-agnostic manner. In the case of TLS, Cytoplasm wraps
low-level TLS libraries to offer a single, unified interface to TLS
so that programs do not have to care about the underlying
implementation.
Originally a part of Telodendria (https://telodendria.io), a Matrix
homeserver written in C, Cytoplasm was split off into its own project
due to the desire of some Telodendria developers to use Telodendria's
code in other projects. Cytoplasm is still a Telodendria project,
and is maintained along side of Telodendria itself, even living in
the same CVS module, but it is designed specifically to be distributed
and used totally independent of Telodendria.
The name "Cytoplasm" was chosen for a few reasons. It plays off the
precedent set up by the Matrix organization in naming projects after
the parts of a neuron. It also speaks to the function of Cytoplasm.
The cytoplasm of a cell is the supporting material. It is what gives
the cell its shape, and it facilitates the movement of materials
to the other cell parts. Likewise, Cytoplasm aims to provide a
support mechanism for C applications that have to perform complex
tasks.
```
Eventually it is my hope that Cytoplasm will get a nice spot on the Telodendria website (either its own page or subdomain), and that tarballs for it will be published independently of the Telodendria tarballs, but for now, everything is bundled in with Telodendria.
## Status Update
Here are some things I mentioned in the last newsletter, as well as a few new ones:
- **Ports:** I removed all the documentation for the Ports staging module because nobody is using it. It looks like Telodendria has been ported to OpenBSD, which means that I don't have to maintain the port myself, which I am glad about.
- **User Interactive Authentication:** The Matrix specification is implemented here, I just have to implement the administrator API for dealing with registration tokens.
- **Configuration API:** I am still working on it. You can currently replace the entire configuration, but not yet only one or two settings. That feature should come with `v0.4.0`.
- **User Data & Capabilities:** Capabilities are hard-coded, but the endpoint exists now. I haven't implemented the directory search yet.
- **Continuous Integration:** It looks like progress is being made on getting some CI up and running for Telodendria, which is very exciting.
- **Documentation:** Cytoplasm includes `hdoc`, a simple CLI tool for generating `man` pages by parsing a C header file. This tool is very primitive, but suits Telodendria's needs just fine.
## What Have I Been Working On Lately?
You may have noticed that things have been rather quiet around here. While I haven't committed much or written a lot of newsletters, I've still been hard at work; every free minute I have is spent on Telodendria. At the moment, I've been trying to debug some nasty memory corruption bugs, but I simply cannot figure out how to reliably reproduce them. I cannot pinpoint what makes the issue happen, because it seems so random. I've torn Telodendria down to basically nothing in my own working directory, and I still can't figure it out because it is so hit-or-miss whether or not I can actually get the issue to show itself. At some point I will put together some documentation on this, but I've been working on this for almost a month now, so I think it's time to just move on and deal with it later. I can assure you all that I will not release `v1.0.0` with this still being a problem, but for now, I think my limited energy is best spent moving Telodendria forward and building out more of the Matrix specification. I do not want all development to stall just because of this, I have to keep moving on, particularly because I don't have very much time to spend on this project.
## What's Next?
From the `TODO.txt` file:
```
[~] /_telodendria/admin/config endpoint
[ ] Update only certain values
[ ] Client-Server API
[ ] 6: Filtering
[ ] 7: Events
[ ] 8: Rooms
[~] 9: User Data
[x] Profiles
[ ] Directory
```
This is the fun stuff! I'm excited to get into this, because it is the meat of the specification. With the next release, Telodendria just might be usable for local user communication, which is really amazing to think about.

View file

@ -0,0 +1,76 @@
---
title: 'The People Have Spoken'
date: '05-09-2023 21:07'
feed:
limit: 10
---
Hey everyone, thanks for the great discussion over in `#telodendria-general:bancino.net`. This newsletter is a formal acknowledgment of that discussion, and an announcement of some of the changes I'm going to implement as a result.
As always, I apologize for the lack of development that's been going on around here. I'm going to try to keep things moving as best I can, and I think some of the changes coming down the pipeline are going to make it easier for me to get help so this isn't just a one-man show.
## The People Have Spoken!
First off, I just want to say that I appreciate all the suggestions I got the other day. Clearly there is a lot of room for improvement on my part, and I want you all to know that I am committed to making Telodendria the best it can be. The project goals themselves (written in C, minimal dependencies) are still solid and unchanged, but it has become apparent to me that the _way_ we go about achieving those goals needs to change a bit to actually be successful. It has come to my attention that the development process in specific is extremely unfriendly. This needs to change if Telodendria is going to have a strong community around it. Additionally, I realize that Telodendria's target audience is ill-defined. This also needs to change if we hope to "market" Telodendria to the masses. I know we can't appeal to everyone, but by clearly defining and targeting a specific audience, we will get quality contributors that share our interests. Finally, I want to place a stronger emphasis on _convention_, and less on strict _standards_ compliance. Up to this point, Telodendria has striven for strictly POSIX compliance, which has caused some noticeable divergence from the typical conventions of projects of this nature. We need to fix this so that new contributors are not burdened by having to learn an unfamiliar, custom build system, for example.
It was also mentioned that I haven't been very transparent in my personal development of Telodendria. While I strive to write newsletters detailing all of my thoughts, this is not enough. My release roadmap is poorly defined, my development checklist is sloppy, and I have no real, hard timelines for anything. You all rightfully expect better from me as the lead of what I hope will grow to into a healthy, sustainable project. I want to be more professional and do things correctly; it was always my goal to build an open-source product that is useful to others, and not just some small casual side project, so I apologize for conducting operations as casually as I have been.
I value the community's input, and I've really contemplated it because I don't want this to be a one-person project; I do want a strong community that can help move the project forward when I inevitably run out of time to put in long hours writing code. Even though some of the changes requested go against my initial personal convictions and ideas for how I initially wanted this project to be developed, I want to be flexible and realize that project needs change, and what worked for me, a single developer, isn't going to work for the entire community. With that being said, I am genuinely excited to announce some changes that I hope will move Telodendria forward by making it easier for contributors to get involved, easier for end-users to read documentation, and easier for the common person to understand what Telodendria is all about and why we, the community, feel that it's important.
## Development Process Changes
There are a number of large project management level changes that I have implemented, am in the process of implementing, and am intending to implement:
- **Migration to Git:** Git is the _de facto_ version control system. It has the highest adoption, and for good reason—it is a spectacularly useful piece of software. Other version control systems are more specialized and more obscure, making them hard to use for most users. For that reason, _effective immediately_, I am migrating the entire project from CVS over SSH to Git over HTTPS. Here's what this looks like:
- **Gitea** is our new code forge. Our instance is available here: [https://git.telodendria.io](https://git.telodendria.io). It should be fully operational, and registration is open for all—whether you're planning on becoming a hardcore developer, want to submit issues and feature requests, or just want to feel like you're a part of the action, create your account as soon as possible to ensure you get the username you want! Note that there is a repository limit of 1 per user; the intention here being that the one repository would be a fork of the Telodendria repo that you can use to submit pull requests. Please be courteous and do not create multiple accounts; respect the space and others that may want to use it.
- **GitHub:** For the moment, the GitHub mirror has been deactivated. The code is still up to date at the time of writing, but no new changes are being pushed right now. I **will** eventually set up Gitea to automatically push changes to GitHub as well. I also want to note that I will for the time being still not allow issues and pull requests at GitHub. All issues and pull requests will have to be made on the Gitea instance to be recognized. At some point in the future, if I can figure out how to sync issues between Gitea and GitHub, I may do that so that users can submit issues via GitHub, but that will be a future project.
- **The choice between the two was hard:** I didn't take the decision lightly to exclusively use Gitea. A lot of thought went into this, but ultimately, I decided that a self-hosted Gitea instance is better than GitHub because I really don't want to depend on a large, centralized corporation for this project. I want the metadata surrounding the project to stay just as open as the code itself, and not be locked into a large vendor. That goes against everything Telodendria, and in fact, Matrix itself, stands for. The _only_ downside that I could find for not using GitHub is that new contributors will have to create an account on our Gitea instance, whereas they may have already had a GitHub account. Other factors were continuous integration and GitHub Pages, but I think rolling our own website will be easier.
- **Patches Room:** Our patches room, `#telodendria-patches:bancino.net`, will be shut down as soon as I get around to it. It won't be needed anymore, because pull requests will suffice.
- **Issues Room:** Our issues room, `#telodendria-issues:bancino.net`, will also be shut down. It won't be needed anymore, because Gitea issues will suffice.
- **Releases Room:** Our releases room, `#telodendria-releases:bancino.net`, will be shut down too. It won't be needed anymore, because Git releases will suffice, and I am fairly certain that Gitea provides an RSS feed for that if notifications are desired. I can also just post release announcements to the main room.
- **CVS Repository:** Anonymous CVS access is still available, but the CVS repository will not be updated anymore. All references to it will be removed from the documentation as quickly as possible so that people don't try to go there to get the code. As far as I can tell, this won't be an issue at all because everyone hates CVS.
- **New Website:** We will need a new website eventually. The current website design is not a great appeal to potential contributors or the general public. I'm thinking something along the lines of a single static page that has some pretty CSS and does the following in order:
- Markets Telodendria as the lightest, easiest to configure Matrix homeserver. Our target audience is those of small, maybe even embedded deployments. The website should appeal to these sorts of users.
- Provides links to downloads and user documentation, both of which will be hosted on the Gitea instance. The `man` page documentation will be going away. Documentation will become markdown files that are rendered in Gitea instead. The user documentation should be distinct from the developer documentation.
- Provides links to accept donations.
- Answers the following questions:
- What can Telodendria _do_?
- _When_ will I be able to chat with my friends using Element?
The website needs to clearly target end users and potential _new_ contributors. We can't mix up our developer audience and our consumer audience. The Gitea instance will have the `README`, which will target our developers.
- **Note:** The migration to Gitea is going to break our current tarball repository at `https://telodendria.io/pub`. Tarballs will just be made available as Git tags. I won't _delete_ the current `/pub` directory, but I'm not going to deploy tarballs there anymore.
Among the changes will also be the deletion of the #telodendria-ports:bancino.net, because there are no active users in that room. We can just use the main room for discussing ports if necessary. #telodendria-newsletter:bancino.net will stay for the time being, but I may try to figure out how to move newsletters to the main website for better visibility.
## Repository Structure Changes
With the management changes, there are also going to be some substantial repository structure changes:
- The `TODO.txt` file will be deleted. Gitea issues will be created for everything that's currently in there, and I'll be sure to set up the projects and milestones in Gitea to accurately reflect the information currently in `TODO.txt`.
- All documentation in `man` will need to be converted to Markdown and placed in a new `/docs` directory in the repo. We'll have `/docs/user` and `/docs/dev` for user and developer documentation respectively. I am opting _not_ to use the Gitea Wiki feature, because using plain Markdown files that live in the repo allows us to ship the documentation with each release automatically, allowing it to be used offline. Additionally, mirrors of the code (such as the GitHub mirror) will automatically mirror the documentation too.
- Proposals will become Gitea issues and be removed from the repository.
- Releases will be published as Git tags and an appropriate Gitea release. Change logs will be maintained as markdown in the repository, but copied to the release page when the release is made.
- The build system will be switching to `Make`. If we can only maintain two `Makefiles` (one for Cytoplasm, and one for Telodendria), that would be ideal, but if we have to ship a `GNUmakefile` as well, we can do that as necessary. The current system of shell scripts works great, but it's not conventional. Using `Make` will make Telodendria much more familiar for people that develop in C, and everyone in general seems to be more comfortable with a project that uses the standard tooling available to it instead of building something custom.
## Project Lead Changes
Here's what I'm committing myself to:
- **Transparency:** I want to be more transparent. Gitea will make this easier, I think. I will utilize all the features of Gitea that are available, including Projects and Milestones, and I will keep these as up-to-date as possible. I will open issues for everything that comes to mind, and I will make changes to the main repository in the same manner that I expect everyone else to—fork the main repo and make pull requests. I will frequently update #telodendria-general:bancino.net with the things that I am currently working on or thinking about.
- **TWIM:** I'll try my best to make more TWIM announcements as I make progress. I think a TWIM is definitely in order for this week, as there are a lot of big changes happening, but I'll try to make this a weekly thing, even if it's as simple as stating that there were no commits in a given week.
- **Defining Project Goals:** I'm going to work hard to try to define a new set of project goals, including who the target audience is and more importantly _why_ such an audience should care about Telodendria.
- **Timely:** If you submit a pull request or issue, I will review it and provide feedback in a timely manner.
- **Involved:** I _am_ very busy, but this project is really important to me, so I'm going to be as involved as I can in leading the project in the direction that it should go.
- **Open:** Your opinion matters. Don't ever hesitate to share it with me so we can discuss it. I'm not promising that I'll change my mind or change the project direction on a whim, but I will at least consider all the opinions of the community if they are respectful and constructive.
## Stay Tuned, and Get Involved!
This whole process is going to take some time, so please be patient as things progress. My first order of business from here is going to be to migrate everything out of `TODO.txt` and into Gitea issues/projects/milestones. From there, I'll start converting documentation to markdown and adjusting it as necessary. You all are more than welcome to pitch in and help out in this process as well. Now that we're using Git, you all can use the standard pull request procedure that you're familiar with. And don't hesitate at all to start opening issues on the repository! That is a valid and much needed form of contribution too.
And, as always, if you want to financially support the project—time and computers aren't free after all—you can find the links here and on the main website:
- [LiberaPay](https://liberapay.com/Telodendria/donate)
- [Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU)
## That's All
That's it from me for now, but share your comments with me! In particular, let me know if I missed anything important.

View file

@ -0,0 +1,10 @@
---
title: Sponsors
---
# No Sponsors Yet 😞
Will you be the first?
- [LiberaPay](https://liberapay.com/Telodendria/donate)
- [Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU)

View file

@ -0,0 +1,5 @@
---
title: 'Source Code'
external_url: 'https://git.telodendria.io/Telodendria/Telodendria'
---

View file

@ -0,0 +1,5 @@
---
title: Issues
external_url: 'https://git.telodendria.io/Telodendria/Telodendria/issues'
---

View file

@ -0,0 +1,5 @@
---
title: Download
external_url: 'https://git.telodendria.io/Telodendria/Telodendria/releases'
---

View file

@ -0,0 +1,5 @@
---
title: Cytoplasm
external_url: 'https://git.telodendria.io/Telodendria/Cytoplasm'
---

View file

@ -0,0 +1,5 @@
---
title: 'Get Started'
external_url: 'https://git.telodendria.io/Telodendria/telodendria/src/branch/master/docs/README.md'
---

1
user/plugins/.gitkeep Normal file
View file

@ -0,0 +1 @@
/* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved. */

View file

@ -0,0 +1,19 @@
title: Telodendria
default_lang: en
author:
name: 'Jordan Bancino'
email: jordan@bancino.net
taxonomies:
- category
- tag
metadata:
description: 'Grav is an easy to use, yet powerful, open source flat-file CMS'
summary:
enabled: true
format: short
size: 300
delimiter: '==='
redirects: null
routes: null
blog:
route: /blog

View file

@ -0,0 +1,236 @@
absolute_urls: false
timezone: null
param_sep: ':'
wrapped_site: false
reverse_proxy_setup: false
force_ssl: false
force_lowercase_urls: true
custom_base_url: null
username_regex: '^[a-z0-9_-]{3,16}$'
pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
intl_enabled: true
http_x_forwarded:
protocol: true
host: false
port: true
ip: true
languages:
supported: null
default_lang: null
include_default_lang: true
include_default_lang_file_extension: true
translations: true
translations_fallback: true
session_store_active: false
http_accept_language: false
override_locale: false
pages_fallback_only: false
debug: false
home:
alias: /home
hide_in_urls: false
pages:
type: regular
dirs:
- 'page://'
theme: bancino
order:
by: default
dir: asc
list:
count: 20
dateformat:
default: null
short: 'jS M Y'
long: 'F jS \a\t g:ia'
publish_dates: true
process:
markdown: true
twig: false
twig_first: false
never_cache_twig: false
events:
page: true
twig: true
markdown:
extra: true
auto_line_breaks: false
auto_url_links: false
escape_markup: false
special_chars:
'>': gt
'<': lt
valid_link_attributes:
- rel
- target
- id
- class
- classes
types:
- html
- htm
- xml
- txt
- json
- rss
- atom
append_url_extension: null
expires: 604800
cache_control: null
last_modified: false
etag: true
vary_accept_encoding: false
redirect_default_code: '302'
redirect_trailing_slash: 1
redirect_default_route: 0
ignore_files:
- .DS_Store
ignore_folders:
- .git
- .idea
ignore_hidden: true
hide_empty_folders: false
url_taxonomy_filters: true
frontmatter:
process_twig: false
ignore_fields:
- form
- forms
cache:
enabled: true
check:
method: file
driver: auto
prefix: g
purge_at: '0 4 * * *'
clear_at: '0 3 * * *'
clear_job_type: standard
clear_images_by_default: false
cli_compatibility: false
lifetime: 604800
gzip: false
allow_webserver_gzip: false
redis:
socket: '0'
password: null
database: null
server: null
port: null
memcache:
server: null
port: null
memcached:
server: null
port: null
twig:
cache: true
debug: true
auto_reload: true
autoescape: true
undefined_functions: true
undefined_filters: true
safe_functions: { }
safe_filters: { }
umask_fix: false
assets:
css_pipeline: false
css_pipeline_include_externals: true
css_pipeline_before_excludes: true
css_minify: true
css_minify_windows: false
css_rewrite: true
js_pipeline: false
js_pipeline_include_externals: true
js_pipeline_before_excludes: true
js_module_pipeline: false
js_module_pipeline_include_externals: true
js_module_pipeline_before_excludes: true
js_minify: true
enable_asset_timestamp: false
enable_asset_sri: false
collections:
jquery: 'system://assets/jquery/jquery-3.x.min.js'
errors:
display: 1
log: true
log:
handler: file
syslog:
facility: local6
tag: grav
debugger:
enabled: false
provider: clockwork
censored: false
shutdown:
close_connection: true
twig: true
images:
default_image_quality: 85
cache_all: false
cache_perms: '0755'
debug: false
auto_fix_orientation: true
seofriendly: false
cls:
auto_sizes: false
aspect_ratio: false
retina_scale: '1'
defaults:
loading: auto
watermark:
image: 'system://images/watermark.png'
position_y: center
position_x: center
scale: 33
watermark_all: false
media:
enable_media_timestamp: false
unsupported_inline_types: null
allowed_fallback_types: null
auto_metadata_exif: false
upload_limit: 2097152
session:
enabled: true
initialize: true
timeout: 1800
name: grav-site
uniqueness: path
secure: false
secure_https: true
httponly: true
samesite: Lax
split: true
domain: null
path: null
gpm:
releases: stable
official_gpm_only: true
verify_peer: true
http:
method: auto
enable_proxy: true
proxy_url: null
proxy_cert_path: null
concurrent_connections: 5
verify_peer: true
verify_host: true
accounts:
type: regular
storage: file
avatar: gravatar
flex:
cache:
index:
enabled: true
lifetime: 60
object:
enabled: true
lifetime: 600
render:
enabled: true
lifetime: 600
strict_mode:
yaml_compat: false
twig_compat: false
blueprint_compat: false

View file

@ -0,0 +1,32 @@
enabled: true
production-mode: true
grid-size: grid-lg
header-fixed: true
header-animated: true
header-dark: false
header-transparent: false
sticky-footer: true
blog-page: /blog
spectre:
exp: false
icons: false
custom_logo:
user/themes/bancino/images/logo/Telodendria-196x196.png:
name: Telodendria-196x196.png
full_path: Telodendria-196x196.png
type: image/png
size: 34653
path: user/themes/bancino/images/logo/Telodendria-196x196.png
custom_logo_mobile:
user/themes/bancino/images/logo/Telodendria-196x196.png:
name: Telodendria-196x196.png
full_path: Telodendria-196x196.png
type: image/png
size: 34653
path: user/themes/bancino/images/logo/Telodendria-196x196.png
streams:
schemes:
theme:
type: ReadOnlyStream
prefixes:
'': [user/themes/bancino, user/themes/quark]

View file

@ -0,0 +1,14 @@
enabled: true
production-mode: true
grid-size: grid-lg
header-fixed: true
header-animated: true
header-dark: false
header-transparent: false
sticky-footer: true
blog-page: /blog
spectre:
exp: false
icons: false
custom_logo: { }
custom_logo_mobile: { }

View file

@ -0,0 +1,26 @@
enabled: true
production-mode: true
grid-size: grid-lg
header-fixed: true
header-animated: true
header-dark: false
header-transparent: false
sticky-footer: true
blog-page: /blog
spectre:
exp: false
icons: false
streams:
schemes:
theme:
type: ReadOnlyStream
prefixes:
'': [user/themes/bancino, user/themes/quark]
custom_logo:
user/themes/quark/images/logo/Telodendria-196x196.png:
name: Telodendria-196x196.png
full_path: Telodendria-196x196.png
type: image/png
size: 34653
path: user/themes/quark/images/logo/Telodendria-196x196.png
custom_logo_mobile: { }

1
user/themes/.gitkeep Normal file
View file

@ -0,0 +1 @@
/* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved. */

View file

@ -0,0 +1,8 @@
<?php
namespace Grav\Theme;
class Bancino extends Quark
{
}
?>

View file

@ -0,0 +1,29 @@
enabled: true
production-mode: true
grid-size: grid-lg
header-fixed: true
header-animated: true
header-dark: false
header-transparent: false
sticky-footer: true
blog-page: /blog
spectre:
exp: false
icons: false
custom_logo:
user/themes/quark/images/logo/avatar.jpg:
name: avatar.jpg
full_path: avatar.jpg
type: image/jpeg
size: 962964
path: user/themes/quark/images/logo/avatar.jpg
custom_logo_mobile: { }
streams:
schemes:
theme:
type: ReadOnlyStream
prefixes:
'':
- user/themes/bancino
- user/themes/quark

View file

@ -0,0 +1,177 @@
name: Bancino
slug: bancino
type: theme
version: 2.0.4
description: My personal website theme.
icon: microchip
author:
name: Jordan Bancino
email: jordan@bancino.net
url: https://bancino.net
homepage: https://bancino.net
demo: https://bancino.net
keywords: quark, spectre, theme, core, modern, fast, responsive, html5, css3
bugs: https://bancino.net
license: MIT
dependencies:
- { name: grav, version: '>=1.6.0' }
- { name: quark, version: '>=2.0.4' }
form:
validation: loose
fields:
production-mode:
type: toggle
label: Production mode
help: When enabled, Quark will render with minified CSS
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
grid-size:
type: select
label: THEME_QUARK.ADMIN.GRID_SIZE
help: The maximum width of the theme
size: small
options:
'': None (full width)
grid-xl: Extra Large
grid-lg: Large
grid-md: Medium
header_section:
type: section
title: Header Defaults
underline: true
custom_logo:
type: file
label: Custom Logo
size: large
destination: 'theme://images/logo'
multiple: false
markdown: true
description: Will be used instead of default logo `theme://images/grav-logo.svg`
accept:
- image/*
custom_logo_mobile:
type: file
label: Mobile Custom Logo
size: large
destination: 'theme://images/logo'
multiple: false
accept:
- image/*
header-fixed:
type: toggle
label: Fixed header
help: When enabled, the header will be fixed at the top of the browser
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
header-animated:
type: toggle
label: Animated
help: When enabled, the header will animate to a smaller header when scrolling
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
header-dark:
type: toggle
label: Dark Style
help: When enabled, a dark-friendly style will be used
highlight: 0
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
header-transparent:
type: toggle
label: Transparent
help: When enabled, a transparent style will be used
highlight: 0
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
footer_section:
type: section
title: Footer Defaults
underline: true
sticky-footer:
type: toggle
label: Sticky footer
help: When enabled, the footer will be sticky at the bottom of the browser
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
blog_section:
type: section
title: Blog Defaults
underline: true
blog-page:
type: text
label: Blog Page
help: The route to the blog page when working with blog sidebar
size: medium
default: '/blog'
spectre_section:
type: section
title: Spectre.css Options
underline: true
spectre.exp:
type: toggle
label: Experimentals CSS
help: When enabled, the `spectre-exp.css` file will be included
highlight: 0
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
spectre.icons:
type: toggle
label: Icons CSS
help: When enabled, the `spectre-icons.css` file will be included
highlight: 0
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool

View file

@ -0,0 +1,46 @@
:root {
--accent: #7b8333;
--accent-bg: rgba(123, 131, 51, 0.15);
}
a, a:visited {
color: var(--accent);
font-weight: bold;
}
a:hover {
text-decoration: underline;
color: var(--accent) !important;
}
a.btn {
color: var(--accent);
border-color: var(--accent);
}
.btn:hover {
border-color: var(--accent);
}
.mobile-menu span {
background: var(--accent) !important;
}
.notices.green {
color: var(--accent);
border-left-color: var(--accent);
background: var(--accent-bg);
}
code {
color: var(--accent);
background: var(--accent-bg);
}
.dropmenu ul li a.active, .dropmenu ul li a:focus, .dropmenu ul li a:hover {
color: var(--accent) !important;
}
.treemenu li a.active, .treemenu li a:focus, .treemenu li a:hover {
color: var(--accent) !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -0,0 +1,5 @@
<section id="footer" class="section bg-gray">
<section class="container {{ grid_size }}">
<p>&copy; 2022-2023 Jordan Bancino.</p>
</section>
</section>