{"id":50897815,"url":"https://github.com/mataroablog/mataroa","last_synced_at":"2026-07-22T07:01:13.909Z","repository":{"id":41529879,"uuid":"267422616","full_name":"mataroablog/mataroa","owner":"mataroablog","description":"naked blogging platform","archived":false,"fork":false,"pushed_at":"2026-06-19T10:32:39.000Z","size":1874,"stargazers_count":353,"open_issues_count":26,"forks_count":27,"subscribers_count":7,"default_branch":"main","last_synced_at":"2026-06-19T12:23:16.591Z","etag":null,"topics":["blogging","django","export","markdown","minimal","naked","platform"],"latest_commit_sha":null,"homepage":"https://mataroa.blog","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mataroablog.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2020-05-27T20:45:08.000Z","updated_at":"2026-06-19T10:32:44.000Z","dependencies_parsed_at":"2025-12-16T03:04:41.971Z","dependency_job_id":null,"html_url":"https://github.com/mataroablog/mataroa","commit_stats":null,"previous_names":["mataroablog/mataroa"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/mataroablog/mataroa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mataroablog%2Fmataroa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mataroablog%2Fmataroa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mataroablog%2Fmataroa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mataroablog%2Fmataroa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mataroablog","download_url":"https://codeload.github.com/mataroablog/mataroa/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mataroablog%2Fmataroa/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35751644,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-22T02:00:06.236Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blogging","django","export","markdown","minimal","naked","platform"],"created_at":"2026-06-16T01:31:30.081Z","updated_at":"2026-07-22T07:01:13.896Z","avatar_url":"https://github.com/mataroablog.png","language":"Python","funding_links":[],"categories":["django"],"sub_categories":[],"readme":"# mataroa\n\nNaked blogging platform.\n\n## Table of Contents\n\n- [Community](#community)\n    - [Tools](#tools)\n- [Local Development](#local-development)\n    - [Subdomains](#subdomains)\n    - [Docker](#docker)\n    - [Dependencies](#dependencies)\n    - [Environment variables](#environment-variables)\n    - [Database](#database)\n    - [Serve](#serve)\n    - [Testing](#testing)\n    - [Code linting and formatting](#code-linting-and-formatting)\n- [Contributing](#contributing)\n    - [Directory structure](#directory-structure)\n    - [Coding Conventions](#coding-conventions)\n    - [Git Commit Message Guidelines](#git-commit-message-guidelines)\n    - [Feature Release Checklist](#feature-release-checklist)\n    - [Dependency Policy](#dependency-policy)\n- [Operations](#operations)\n    - [Deployment](#deployment)\n    - [Billing](#billing)\n    - [Recurring Tasks](#recurring-tasks)\n    - [Post by email](#post-by-email)\n    - [Database Backup](#database-backup)\n    - [Server Migration Checklist](#server-migration-checklist)\n    - [On Server Outage](#on-server-outage)\n- [License](#license)\n\n## Community\n\nMain repository on GitHub:\n[github.com/mataroablog/mataroa](https://github.com/mataroablog/mataroa)\n\nMirror on sr.ht:\n[git.sr.ht/~sirodoht/mataroa](https://git.sr.ht/~sirodoht/mataroa)\n\nBug tracking on GitHub:\n[github.com/mataroablog/mataroa/issues](https://github.com/mataroablog/mataroa/issues)\n\nCommunity mailing list on sr.ht:\n[lists.sr.ht/~sirodoht/mataroa-community](https://lists.sr.ht/~sirodoht/mataroa-community)\n\n### Tools\n\n* [mataroa-cli](https://github.com/mataroablog/mataroa-cli)\n* [Mataroa Telegram Bot](https://github.com/jorphex/Mataroa-Telegram-Bot)\n\n## Local Development\n\nThis is a [Django](https://www.djangoproject.com/) codebase. Check out the\n[Django docs](https://docs.djangoproject.com/) for general technical\ndocumentation.\n\nThe Django project is [`mataroa`](mataroa). There is one Django app,\n[`main`](main), with all business logic. Application CLI commands are generally\ndivided into two categories, those under `python manage.py` and those under\n`make`.\n\n### Subdomains\n\nMataroa works primarily with subdomain, thus one cannot access the basic web app\nusing the standard `http://127.0.0.1:8000` or `http://localhost:8000` URLs. What we do\nfor local development is add a few custom entries on our `/etc/hosts` system file.\n\nImportant note: there needs to be an entry of each user account created in the local\ndevelopment environment, so that the web server can respond to it.\n\nThe first line is the main needed: `mataroalocal.blog`. The rest are included as\nexamples of other users one can create in their local environment. The\neasiest way to create them is to go through the sign up page\n(`http://mataroalocal.blog:8000/accounts/create/` using default values).\n\n```\n# /etc/hosts\n\n127.0.0.1 mataroalocal.blog\n\n127.0.0.1 paul.mataroalocal.blog\n127.0.0.1 random.mataroalocal.blog\n127.0.0.1 anyusername.mataroalocal.blog\n```\n\nThis will enable us to access mataroa locally (once we start the web server) at\n[http://mataroalocal.blog:8000/](http://mataroalocal.blog:8000/)\nand if we make a user account with username `paul`, then we will be able to access it at\n[http://paul.mataroalocal.blog:8000/](http://paul.mataroalocal.blog:8000/)\n\n### Docker\n\n\u003e [!NOTE]\n\u003e This is the last step for initial Docker setup. See the \"Environment variables\"\n\u003e section below, for further configuration details.\n\nTo set up a development environment with Docker and Docker Compose, run the following\nto start the web server and database:\n\n```sh\ndocker compose up\n```\n\nIf you have also configured hosts as described above in the \"Subdomains\"\nsection, mataroa should now be locally accessible at\n[http://mataroalocal.blog:8000/](http://mataroalocal.blog:8000/)\n\nNote: The database data are saved in the git-ignored `docker-postgres-data` docker\nvolume, located in the root of the project.\n\n### Dependencies\n\nWe use `uv` for dependency management and virtual environments.\n\n```sh\nuv sync --all-groups\n```\n\nSee [Dependency Policy](#dependency-policy) for more details on adding/upgrading dependencies.\n\n### Environment variables\n\nA file named `.envrc` is used to define the environment variables required for\nthis project to function. One can either export it directly or use\n[direnv](https://github.com/direnv/direnv). There is an example environment\nfile one can copy as base:\n\n```sh\ncp .envrc.example .envrc\n```\n\nWhen on Docker, to change or populate environment variables, edit the `environment`\nkey of the `web` service either directly on `docker-compose.yml` or by overriding it\nusing the standard named git-ignored `docker-compose.override.yml`.\n\n```yaml\n# docker-compose.override.yml\n\nservices:\n  web:\n    environment:\n      EMAIL_HOST_USER: smtp-user\n      EMAIL_HOST_PASSWORD: smtp-password\n```\n\nFinally, stop and start `docker compose up` again. It should pick up the override file\nas it has the default name `docker-compose.override.yml`.\n\n### Database\n\nThis project is using one PostreSQL database for persistence.\n\nOne can use the provided script to set up a local Postgres database (user `mataroa`,\npasswordless):\n\n```sh\n./setup-database-localdev.sh\n```\n\nAnd also easily reset it (drop database and user):\n\n```sh\n./reset-database-localdev.sh\n```\n\nAfter setting the `DATABASE_URL` ([see above](#environment-variables)), create\nthe database schema with:\n\n```sh\nuv python manage.py migrate\n```\n\nInitialising the database with some sample development data is possible with:\n\n```sh\nuv python manage.py loaddata dev-data\n```\n\n* `dev-data` is defined in [`main/fixtures/dev-data.json`](main/fixtures/dev-data.json)\n* Credentials of the fixtured user are `admin` / `admin`.\n\n### Serve\n\nTo run the Django development server:\n\n```sh\nuv python manage.py runserver\n```\n\nIf you have also configured hosts as described above in the \"Subdomains\"\nsection, mataroa should now be locally accessible at\n[http://mataroalocal.blog:8000/](http://mataroalocal.blog:8000/)\n\n### Testing\n\nUsing the Django test runner:\n\n```sh\nuv run python manage.py test\n```\n\nFor coverage, run:\n\n```sh\nuv run coverage run --source='.' --omit '.venv/*' manage.py test\nuv run coverage report -m\n```\n\n### Code linting and formatting\n\nWe use [ruff](https://github.com/astral-sh/ruff) for Python linting and code formatting.\n\nTo lint:\n\n```sh\nuv run ruff check\n```\n\nTo apply automatic fixes if they exist:\n\n```sh\nuv run ruff check --fix\n```\n\nTo format:\n\n```sh\nuv run ruff format --check\n```\n\n## Contributing\n\nMain repository on GitHub:\n[github.com/mataroablog/mataroa](https://github.com/mataroablog/mataroa)\n\nMirror repository on sr.ht:\n[git.sr.ht/~sirodoht/mataroa](https://git.sr.ht/~sirodoht/mataroa)\n\nReport bugs on GitHub:\n[github.com/mataroablog/mataroa/issues](https://github.com/mataroablog/mataroa/issues)\n\nContribute on GitHub with Pull Requests:\n[github.com/mataroablog/mataroa/pulls](https://github.com/mataroablog/mataroa/pulls)\n\nContribute with email patches on sr.ht:\n[~sirodoht/public-inbox@lists.sr.ht](mailto:~sirodoht/public-inbox@lists.sr.ht).\nSee how to contribute using email patches on [git-send-email.io](https://git-send-email.io/).\n\n### Directory structure\n\nHere, an overview of the project's code sources is presented. The purpose is\nfor the reader to understand what kind of functionality is located where in\nthe source code.\n\nAll business logic of the application is in one Django app: [`main`](main).\n\nCondensed and commented sources file tree:\n\n```\n.\n├── .build.yml # SourceHut CI build config\n├── .envrc.example # example direnv file\n├── .github/ # GitHub Actions config files\n├── Caddyfile # configuration for Caddy webserver\n├── Dockerfile\n├── docker-compose.yml\n├── export_base_epub/ # base sources for epub export functionality\n├── export_base_hugo/ # base sources for hugo export functionality\n├── export_base_zola/ # base sources for zola export functionality\n├── main/\n│   ├── admin.py\n│   ├── apps.py\n│   ├── denylist.py # list of various keywords allowed and denied\n│   ├── feeds.py # django rss functionality\n│   ├── fixtures/\n│   │   └── dev-data.json # sample development data\n│   ├── forms.py\n│   ├── management/ # commands under `python manage.py`\n│   │   └── commands/\n│   │       ├── checkstripe.py\n│   │       ├── mailexports.py\n│   │       ├── mailsummary.py\n│   │       ├── processnotifications.py\n│   │       └── testbulkmail.py\n│   ├── middleware.py # mostly subdomain routing\n│   ├── migrations/\n│   ├── models.py\n│   ├── sitemaps.py\n│   ├── static/\n│   ├── templates\n│   │   ├── main/ # HTML templates for most pages\n│   │   ├── assets/\n│   │   │   ├── drag-and-drop-upload.js\n│   │   │   └── style.css\n│   │   ├── partials/\n│   │   │   ├── footer.html\n│   │   │   ├── footer_blog.html\n│   │   │   └── webring.html\n│   │   └── registration/\n│   ├── tests/\n│   │   ├── test_api.py\n│   │   ├── test_billing.py\n│   │   ├── test_blog.py\n│   │   └── testdata/\n│   ├── text_processing.py # markdown and text utilities\n│   ├── urls.py\n│   ├── validators.py # custom form and field validators\n│   └── views/\n│       ├── api.py\n│       ├── billing.py\n│       ├── general.py\n├── manage.py\n└── mataroa\n    ├── asgi.py\n    ├── settings.py # django configuration file\n    ├── urls.py\n    └── wsgi.py\n```\n\n#### [`main/urls.py`](main/urls.py)\n\nAll urls are in this module. They are visually divided into several sections:\n\n* general, includes index, dashboard, static pages\n* user system, includes signup, settings, logout\n* blog posts, the CRUD operations of\n* blog extras, includes rss and newsletter features\n* comments, related to the blog post comments\n* billing, subscription and card related\n* blog import, export, webring\n* images CRUD\n* analytics list and details\n* pages CRUD\n\n#### [`main/views/`](main/views/)\n\nThe majority of business logic is organized in the `views/` directory, split across\nseveral modules for better organization.\n\nGenerally,\n[Django class-based generic views](https://docs.djangoproject.com/en/3.2/topics/class-based-views/generic-display/)\nare used most of the time as they provide useful functionality abstracted away.\n\nThe Django source code [for generic views](https://github.com/django/django/tree/main/django/views/generic)\nis also extremely readable:\n\n* [base.py](https://github.com/django/django/blob/main/django/views/generic/base.py): base `View` and `TemplateView`\n* [list.py](https://github.com/django/django/blob/main/django/views/generic/list.py): `ListView`\n* [edit.py](https://github.com/django/django/blob/main/django/views/generic/edit.py): `UpdateView`, `DeleteView`, `FormView`\n* [detail.py](https://github.com/django/django/blob/main/django/views/generic/detail.py): `DetailView`\n\n[Function-based views](https://docs.djangoproject.com/en/3.2/intro/tutorial01/#write-your-first-view)\nare used in cases where the CRUD/RESTful design pattern is not clear such as\n`notification_unsubscribe_key` where we unsubscribe an email via a GET operation.\n\n##### [`main/views/general.py`](main/views/general.py)\n\nThe main view module containing most of the application's business logic:\n\n* indexes, dashboard, static pages\n* user CRUD and login/logout\n* posts CRUD\n* comments CRUD\n* images CRUD\n* pages CRUD\n* webring\n* analytics\n* notifications subscribe/unsubscribe\n\n##### [`main/views/api.py`](main/views/api.py)\n\nThis module contains all API related views. These views have their own\napi key based authentication.\n\n##### [`main/views/export.py`](main/views/export.py)\n\nThis module contains all views related to the export capabilities of mataroa.\n\nThe way the exports work is by reading the base files from the repository root:\n[export_base_hugo](export_base_hugo/), [export_base_zola](export_base_zola/),\n[export_base_epub](export_base_epub/) for Hugo, Zola, and epub respectively.\nAfter reading, we replace some strings on the configurations, generate posts\nas markdown strings, and zip-archive everything in-memory. Finally, we respond\nusing the appropriate content type (`application/zip` or `application/epub`) and\n[Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition)\n`attachment`.\n\n##### [`main/views/billing.py`](main/views/billing.py)\n\nThis module contains all billing and subscription related views. It's designed to\nsupport one payment processor, Stripe.\n\n##### [`main/views/moderation.py`](main/views/moderation.py)\n\nThis module contains all views related to moderating the content of the platform.\n\n#### [`main/tests/`](main/tests/)\n\nAll tests are under this directory. They are divided into several modules,\nbased on the functionality and the views they test.\n\nEverything uses the built-in Python `unittest` module along with standard\nDjango testing facilities.\n\n#### [`main/models.py`](main/models.py) and [`main/migrations/`](main/migrations/)\n\n`main/models.py` is where the database schema is defined, translated into\nDjango ORM-speak. This always displays the latest schema.\n\n`main/migrations/` includes all incremental migrations required to reach\nthe schema defined in `main/models.py` starting from an empty database.\n\nWe use the built-in Django commands to generate and execute migrations, namely\n`makemigrations` and `migrate`. For example, the steps to make a schema change\nwould be something like:\n\n1. Make the change in `main/models.py`. See\n[Django Model field reference](https://docs.djangoproject.com/en/3.2/ref/models/fields/).\n1. Run `python manage.py makemigrations` to auto-generate the migrations.\n1. Potentially refactor the auto-generated migration file (located at `main/migrations/XXXX_auto_XXXXXXXX.py`)\n1. Run `python manage.py migrate` to execute migrations.\n1. Also `make format` before committing.\n\n#### [`main/forms.py`](main/forms.py)\n\nHere a collection of Django-based forms resides, mostly in regards to user creation,\nupload functionalities (for post import or image upload), and card details\nsubmission.\n\nSee [Django Form fields reference](https://docs.djangoproject.com/en/3.2/ref/forms/fields/).\n\n#### [`main/text_processing.py`](main/text_processing.py)\n\nThis module contains utilities for processing text content, including markdown\nrendering, syntax highlighting, and text transformations used throughout the\napplication.\n\n#### [`main/templates/assets/style.css`](main/templates/assets/style.css)\n\nOn mataroa, a user can enable an option, Theme Zia Lucia, and get a higher font\nsize by default. Because we need to change the body font-size value, we render\nthe CSS. It is not static. This is why it lives inside the templates directory.\n\n### Coding Conventions\n\n1. All files should end with a new line character.\n1. Python code should be formatted with [ruff](https://github.com/astral-sh/ruff).\n\n### Git Commit Message Guidelines\n\nWe follow some simple non-austere git commit message guidelines.\n\n* Start with a verb\n    * `add`\n    * `change`\n    * `delete`\n    * `fix`\n    * `refactor`\n    * `tweak`\n    * et al.\n* Start with a lowercase letter\n    * eg. `change analytic page path to the same of page slug`\n* Do not end with a fullstop\n\n### Feature Release Checklist\n\nThis document provides a comprehensive checklist for releasing a new version of Mataroa.\n\n1. Finish with all commits, push to main\n1. Verify `CHANGELOG.md` is up-to-date and add release date\n1. Verify CI is green\n1. Bump in in `pyproject.toml` with `git commit -m \"release v1.x\"`\n1. Run `uv lock`\n1. Create Git tag: `git tag -a v1.x -m \"v1.x\"`\n1. Push tag: `git push origin --tags` and `git push srht --tags`\n\n### Dependency Policy\n\nThe mataroa project has an unusually strict yet usually unclear dependency policy.\n\nVague rules include:\n\n* No third-party Django apps.\n* All Python / PyPI packages should be individually vetted.\n    * Packages should be published from community-trusted organisations or developers.\n    * Packages should be actively maintained (though not necessarily actively developed).\n    * Packages should hold a high quality of coding practices.\n* No JavaScript libraries / dependencies.\n\nCurrent list of top-level PyPI dependencies (source at [`pyproject.toml`](pyproject.toml)):\n\n* [Django](https://pypi.org/project/Django/)\n* [psycopg](https://pypi.org/project/psycopg/)\n* [gunicorn](https://pypi.org/project/gunicorn/)\n* [Markdown](https://pypi.org/project/Markdown/)\n* [Pygments](https://pypi.org/project/Pygments/)\n* [bleach](https://pypi.org/project/bleach/)\n* [stripe](https://pypi.org/project/stripe/)\n\n#### Adding a new dependency\n\nAfter approving a dependency, add it using `uv`:\n\n1. Ensure `uv` is installed and a virtualenv exists (managed by `uv`).\n1. Add the dependency to `pyproject.toml` and lockfile with:\n   - Runtime: `uv add PACKAGE`\n   - Dev-only: `uv add --dev PACKAGE`\n1. Install/sync dependencies: `uv sync`\n\n#### Upgrading dependencies\n\nWhen a new Django version is out it’s a good idea to upgrade everything.\n\nSteps:\n\n1. Update the lockfile: `uv lock --upgrade` and review output changes.\n1. Spot non-patch level version bumps and examine release notes.\n1. Install updated deps: `uv sync`\n1. Unless something comes up, make sure tests and smoke tests pass.\n1. Deploy new dependency versions.\n\n## Operations\n\nDetails for people hosting their own mataroa instance.\n\n### Deployment\n\n#### 1. Choose domain name\n\nFor the purposes of this guide let us assume `example.com` is the domain name of choice.\n\n#### 2. Setup DNS\n\nBoth `example.com` and `*.example.com` should point to the mataroa server IP. Verify changes have\npropagated before provisioning:\n\n```sh\ndig example.com A\ndig *.example.com A\ndig randomsubdomain.example.com A\n```\n\n#### 3. Configuration\n\nSet up the config environment for provisioning. The config environment remains on your local machine.\n\n```sh\ncd deploy/\n\n# make a copy of the example envrc file\ncp .envrc.example .envrc\n\n# edit parameters as required\nvim .envrc\n\n# load variables into environment\nsource .envrc\n```\n\n#### 4. Provision\n\nFinally, run the provisioning script:\n\n```sh\ncd deploy/\n./provision.sh\n```\n\nThis script will:\n\n* install essential packages (gcc, git, rclone, postgresql, etc.)\n* install and configure Caddy web server\n* create the deploy user with proper permissions\n* set up the postgres database\n* install uv\n* clone the mataroa repository\n* configure all systemd services and timers\n* run initial Django migrations and collect static files\n* enable and start all systemd services\n\nNote: Caddy will automatically obtain and manage SSL certificates for your domain and all\nsubdomains using Let's Encrypt. The certificate generation for each domain happens when that domain\nis first accessed, which leads to increased latency for that first page visit.\n\n#### 5. Future deployments\n\nTo deploy new changes:\n\n```sh\ncd deploy/\n./deploy.sh\n```\n\nThis will connect to the server and:\n\n* pull the latest code from git\n* update python dependencies\n* run database migrations\n* collect static files\n* reload the gunicorn/django service\n\n#### Useful Commands\n\nTo reload the gunicorn process:\n\n```sh\nsudo systemctl reload mataroa\n```\n\nTo reload Caddy:\n\n```sh\nsystemctl restart caddy  # root only\n```\n\ngunicorn logs:\n\n```sh\njournalctl -fb -u mataroa\n```\n\nCaddy logs:\n\n```sh\njournalctl -fb -u caddy\n```\n\nGet an overview with systemd status:\n\n```sh\nsystemctl status caddy\nsystemctl status mataroa\n```\n\n### Billing\n\nOne can deploy mataroa without setting up billing functionalities. This is\nthe default case. To handle payments and subscriptions this project uses\n[Stripe](https://stripe.com/). To enable Stripe and payments, one needs to have\na Stripe account with a single\n[Product](https://stripe.com/docs/billing/prices-guide) (eg. \"Mataroa Premium\nPlan\").\n\nTo configure, add the following variables from your Stripe account to your\n`.envrc`:\n\n```sh\nexport STRIPE_API_KEY=\"sk_test_XXX\"\nexport STRIPE_PUBLIC_KEY=\"pk_test_XXX\"\nexport STRIPE_WEBHOOK_SECRET=\"whsec_XXX\"\nexport STRIPE_PRICE_ID=\"price_XXX\"\n```\n\n### Recurring Tasks\n\nWe don't use cron but systemd timers for jobs that need to run recurringly.\n\n#### Process email notifications\n\n```sh\npython manage.py processnotifications\n```\n\nSends notification emails for new blog posts.\n\nTriggers daily at 10AM server time.\n\n#### Email blog exports\n\n```sh\npython manage.py mailexports\n```\n\nEmails users their blog exports.\n\nTriggers monthly, first day of the month, 6AM server time.\n\n#### Daily summary\n\n```sh\npython manage.py mailsummary\n```\n\nSends mataroa daily moderation summary.\n\nTriggers daily at 00:15 server time.\n\n### Database Backup\n\nWe use the script [`backup-database.sh`](./deploy/backup-database.sh) to dump the database and\nupload it into an S3-compatible object storage cloud using [rclone](https://rclone.org/).\n\nTo create a database dump run:\n\n```sh\npg_dump -Fc --no-acl mataroa -h localhost -U mataroa -f /home/deploy/mataroa.dump -W\n```\n\nTo restore a database dump run:\n\n```sh\npg_restore --disable-triggers -j 4 -v -h localhost -cO --if-exists -d mataroa -U mataroa -W mataroa.dump\n```\n\n### Server Migration Checklist\n\nNothing lasts forever. One day you might want to migrate your mataroa platform to another server.\nMataroa stores everything, images including, in the PostgreSQL database. Naughty indeed, yet it\nmakes it much easier to backup and migrate.\n\n\u003e [!CAUTION]\n\u003e This migration process involves downtime.\n\nTo start with, one a migrator has setup their new server we recommend that they they test everything\nin another domain, other than the main (existing) one:\n\n1. Find a test domain name and set up DNS pointing to the new server\n1. Set up `deploy/.envrc` for test domain name\n1. Run `deploy/provision.sh` for new server via test domain name\n1. Verify everything in the new server with the test domain works\n1. Install old server's public key to new server's authorized keys so that we can scp the database dump\n\nThen, for the migration:\n\n1. Change Caddyfile to not reverse proxy to mataroa in the new server (so that we switch it on when we are ready)\n1. Change Caddyfile to not reverse proxy to mataroa in the old server. This means down time begins.\n1. Disconnect old server from public IP (we assume a floating IP is used)\n1. Start the final database dump in the old server. Important: this assumes we are connected to the old server via another non-floating IP.\n1. Change `/etc/systemd/system/mataroa.env` to point to the proper domain name (not the test one)\n1. Install floating IP to new server (restart server to verify)\n1. Once database dump finishes, secure copy it to the new server\n1. Restore database dump in new server\n1. Restore Caddyfile and restart Caddy\n\nFinally, once the new server everything works don't forget to verify DEBUG is 0.\n\nThe above assume the migrator has a floating IP that they can move around. If\nnot, there are two problems. The migrator needs to change DNS records but much\nmore problematically all custom domains stop working :/ For this reason we\nshould implement CNAME custom domains. However, CNAME custom domains do not\nsupport root domains, so what's the point anyway you ask. Good question. I don't\nknow. I only hope I never decide to switch away from Hetzner.\n\n### On Server Outage\n\nSo, mataroa is down. What do we do?\n\nFirst, we panic. We run around in circles with our hands up in despair. It's important to do this\nfirst, don't think of it as a joke. Once that's done:\n\n#### 1. Check Caddy\n\nCaddy is the first point of contact inside the server from the outside world.\n\nFirst ssh into server:\n\n```sh\nssh root@mataroa.blog\n```\n\nCaddy runs as a systemd service. Check status with:\n\n```sh\nsystemctl status caddy\n```\n\nExit with `q`. If the service is not running and is errored restart with:\n\n```sh\nsystemctl restart caddy\n```\n\nIf restart does not work, check logs:\n\n```sh\njournalctl -u caddy -r\n```\n\n`-r` is for reverse. Use `-f` to follow logs real time:\n\n```sh\njournalctl -u caddy -f\n```\n\nTo search within all logs do slash and then the keyword itself, eg: `/keyword-here`, then hit enter.\n\nThe config for Caddy is:\n\n```sh\ncat /etc/caddy/Caddyfile\n```\n\nThe systemd config for Caddy is:\n\n```sh\ncat /etc/systemd/system/multi-user.target.wants/caddy.service\n```\n\n#### 2. Check gunicorn\n\nAfter Caddy receives the request, it forwards it to gunicorn. Gunicorn is what manages the mataroa\nDjango instances, so it's named `mataroa`. It runs as a systemd service.\n\nTo see status:\n\n```sh\nsystemctl status mataroa\n```\n\nTo restart:\n\n```sh\nsystemctl restart mataroa\n```\n\nTo see logs:\n\n```sh\njournalctl -u mataroa -r\n```\n\nand to follow them:\n\n```sh\njournalctl -u mataroa -f\n```\n\nThe systemd config for mataroa/gunicorn is:\n\n```sh\ncat /etc/systemd/system/multi-user.target.wants/mataroa.service\n```\n\nThe env variables in a separate file:\n\n```sh\ncat /etc/systemd/system/mataroa.env\n```\n\n#### 3. How to hotfix code\n\nHere's where the code lives and how to access it:\n\n```sh\nsudo -i -u deploy\ncd /var/www/mataroa/\n\n# load env variables from the systemd env file:\n# /etc/systemd/system/mataroa.env\n# note: this file is root permissioned\n\nuv run python manage.py\n```\n\nIf you make a change in the source code files (inside `/var/www/mataroa`) you need to restart the\nservice for the changes to take effect:\n\n```sh\nsystemctl restart mataroa\n```\n\n## License\n\nCopyright Mataroa Contributors\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of the GNU Affero General Public License as published by the Free\nSoftware Foundation, version 3.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmataroablog%2Fmataroa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmataroablog%2Fmataroa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmataroablog%2Fmataroa/lists"}