Installation and administration

Installing Peristyle

Everything the person hosting an instance does: start it, configure it, back it up and manage its accounts. For everyday use, see the user guide.

Quick start with Docker Compose

Peristyle fits in one Docker image, next to PostgreSQL. You need Docker with Docker Compose on an amd64 machine, and the docker-compose.yml and .env.example files of the repository on the server: the image is downloaded from ghcr.io/zeguym/peristyle.

  1. Prepare the configuration

    Copy the sample file, then replace POSTGRES_PASSWORD with a real password.

    cp .env.example .env
  2. Start

    Docker Compose downloads the image and starts Peristyle and PostgreSQL.

    docker compose up -d
  3. Create the administrator account

    Open http://<server>:8085 and create an account: the first account administers the instance. Then create the other accounts, or send invitations.

  4. Close registration

    Once everyone is in, turn off Open registration in Administration → Settings, or set ALLOW_REGISTRATION=false in .env and run docker compose up -d again.

Change the port with PERISTYLE_PORT in .env.

Updating

Back up the instance first, then download the new image. The database is updated at startup.

docker compose pull
docker compose up -d

PERISTYLE_VERSION in .env selects the image: latest by default, a version (1.2.3) or a minor line (1.2).

Building from the sources

With a full copy of the repository:

docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build

Using an existing PostgreSQL server

Remove the db service from docker-compose.yml and point ConnectionStrings__peristyle to your server, with a dedicated database and user.

Synology NAS

Installation goes through Container Manager and the same docker-compose.yml file.

  1. Copy the files

    Copy docker-compose.yml and .env.example to a folder on the NAS, then .env.example to .env, and change POSTGRES_PASSWORD.

  2. Create the project

    In Container Manager → Project → Create, point to that folder. You can also run docker compose up -d over SSH.

  3. Create the accounts

    Open http://<nas-ip>:8085, create the administrator account, then the family accounts.

  4. Close registration

    Set ALLOW_REGISTRATION=false in .env and start the project again, or use Administration → Settings.

Reverse proxy

To publish the instance over HTTPS, put it behind a reverse proxy. On a Synology NAS, open Control Panel → Login Portal → Advanced → Reverse Proxy:

  • source: https://peristyle.example.com;
  • destination: http://localhost:8085;
  • custom headers: turn on WebSocket.
WebSocket is required, whatever the proxy: the interface runs over a permanent connection and does not work without it.

Peristyle only trusts X-Forwarded-* headers from the addresses listed in TRUSTED_PROXIES (addresses or CIDR networks, comma separated). When empty, the whole local network is accepted.

Installing on a phone requires HTTPS with a recognised certificate. Over HTTP on the local network, or with a self-signed certificate, Peristyle still works in the browser, without an installed application or offline page.

Backups

A full backup has two parts: the database and the /data volume. Keep your .env file too.

With the db service of docker-compose.yml, export the database like this:

docker compose exec -T db pg_dump -U peristyle peristyle > peristyle.sql

The /data volume holds:

FolderContent
archives/the copies of archived pages
image-cache/the image cache, which can be rebuilt
locales/, themes/, plugins/dropped languages, themes and extensions
keys/the keys that encrypt the session cookies and the SMTP password saved in the administration
Without keys/, users have to sign in again and the SMTP password must be entered again.

Scheduled backup

Copy backup.sh next to docker-compose.yml. The script writes three dated files: the database (.dump), the /data volume without the image cache (.tar.gz) and a copy of .env. It then removes backups older than the given period, only if everything succeeded.

sh backup.sh /path/to/backups 14

Without arguments, it writes to ./backups and keeps 14 days. It only covers the db service of docker-compose.yml, not an existing PostgreSQL server.

On a Synology NAS, open Control Panel → Task Scheduler → Create → Scheduled Task → User-defined script:

  • user: root;
  • schedule: daily, at a quiet hour;
  • task settings: sh /volume1/docker/peristyle/backup.sh /volume1/backups/peristyle 14;
  • notification: send run details by e-mail on abnormal termination.

Start the task once with Run and check the files. If the script cannot find the container, the Container Manager project is not named after the folder: add COMPOSE_PROJECT_NAME=<project name> to .env.

On another Linux server, one crontab line is enough:

0 3 * * * sh /opt/peristyle/backup.sh /srv/backups/peristyle 14
A backup on the same disk does not protect against a NAS failure. Copy the folder elsewhere, for example with Hyper Backup. It holds .env, hence the database password: keep that copy private.

Restoring

From the docker-compose.yml folder, with the .env of the backup and the same major version of PostgreSQL:

docker compose up -d db
docker compose stop web
docker compose exec -T db pg_restore -U peristyle -d peristyle --clean --if-exists --no-owner < peristyle-YYYY-MM-DD_HHMM.dump
docker run --rm -i --volumes-from "$(docker compose ps -aq web)" alpine:3.22 tar xzf - -C / < peristyle-YYYY-MM-DD_HHMM.tar.gz
docker compose up -d

On a new installation, run docker compose up -d first to create the containers and volumes.

Configuration

Settings come from environment variables (Peristyle__Name, nested with __). Most of them can also be changed in Administration → Settings or Administration → Email. The order of precedence is: value saved in the administration, then environment variable, then default.

The .env column gives the name to use in that file. A variable with no name in this column is not passed on by docker-compose.yml: add it under environment: of the web service, or use the administration.

Server settings

The words on restart mark those that only take effect at the next startup.

Variable.envDefaultRole
Peristyle__AllowRegistrationALLOW_REGISTRATIONtrue (false in docker-compose.yml without that line)Allows sign-up. The very first account, administrator, is always allowed.
Peristyle__AllowPrivateNetworkFeeds—falseAllows feeds on the local network (192.168.x.x…). On restart.
Peristyle__RefreshIntervalMinutesREFRESH_INTERVAL_MINUTES30Feed refresh interval.
Peristyle__MaxConcurrentFetches—4Simultaneous downloads.
Peristyle__InitialArticlesPerSubscription—50Recent articles marked unread when subscribing to a feed already known to the instance.
Peristyle__ArticleRetentionDaysARTICLE_RETENTION_DAYS90Article retention in days (0 = unlimited). Starred articles are always kept.
Peristyle__DisableFailingFeedAfterDaysDISABLE_FAILING_FEED_AFTER_DAYS30Disables a feed failing for this many days (0 = never). Retry brings it back.
Peristyle__SourceTimeoutSeconds—60Maximum time to fetch and parse a feed.
Peristyle__ProcessorTimeoutSeconds—10Maximum time of one processing step on an article. Beyond that, the step is skipped.
Peristyle__ArchiveQuotaMegabytes—1024Archive space per user, in MB (0 = unlimited).
Peristyle__MaxConcurrentArchives—1Pages archived at the same time. On restart.
Peristyle__LinkCheckIntervalDaysLINK_CHECK_INTERVAL_DAYS7Checks each bookmark’s address every N days (0 = never). A broken link is checked again the next day. On restart.
Peristyle__LinkCheckBatchSize—200Addresses checked at most per pass (one pass per hour).
Peristyle__ImageProxyIMAGE_PROXYtrueImage proxy: browsers no longer load images from the original sites. On restart.
Peristyle__ImageCacheMegabytesIMAGE_CACHE_MEGABYTES500Maximum size of the image cache, in MB.
Peristyle__ImageCacheDays—30How long an image stays in the cache, in days.
Peristyle__MaxImageBytes—5242880Maximum size of an image going through the proxy, in bytes (in MB in the administration).
Peristyle__Plugins__RequireApproval—trueA dropped extension must be approved before it is loaded. On restart.
Peristyle__DefaultLanguageDEFAULT_LANGUAGEfrLanguage when the browser asks for none available, and for untranslated texts. On restart.

Infrastructure

These variables stay in the environment, so a mistake in one of them can always be fixed.

Variable.envDefaultRole
ConnectionStrings__peristyle——PostgreSQL connection string. docker-compose.yml builds it from POSTGRES_PASSWORD.
Peristyle__TrustedProxiesTRUSTED_PROXIES—See Reverse proxy.
Peristyle__DataPath—/data in the imageKeys, archives, image cache, dropped languages, themes and extensions.
Peristyle__Plugins__SafeModePLUGINS_SAFE_MODEfalseStarts without any dropped extension. Bundled extensions stay active.
TZTZEurope/ParisTime zone of the instance, as a name such as America/Montreal. Dates are shown in this zone for every account: there is no per-account setting.
DOTNET_GCHeapHardLimit——.NET heap cap, in hexadecimal (0x10000000 = 256 MB).
OTEL_EXPORTER_OTLP_ENDPOINT——OpenTelemetry export, optional. Nothing is sent without this variable.

E-mail (SMTP)

Without an SMTP server and a public address, Peristyle sends nothing: the administrator passes password and invitation links on by hand. With them, Peristyle sends:

  • Forgot your password? links, only to a confirmed address;
  • the confirmation of an address given at sign-up or changed in Settings → E-mail address;
  • invitations;
  • password links sent by an administrator, and a test e-mail.

Every e-mail is written in the recipient’s language: the language of their account, otherwise the language of the page they used, otherwise Peristyle__DefaultLanguage. Invitations use the language picked by the administrator, and the registration page opens in it.

Set the server in Administration → Email or with the variables below. Values saved in the administration take precedence; the password is encrypted there with the keys of /data/keys.

Variable.envDefaultRole
Peristyle__PublicUrlPUBLIC_URL—Address people use to open the instance, such as https://reader.example.org. Every link in an e-mail starts with it. Empty: no e-mail.
Peristyle__Smtp__HostSMTP_HOST—SMTP server. Empty: no e-mail.
Peristyle__Smtp__PortSMTP_PORT587Port (465 implicit SSL, 25 or 1025 local relay).
Peristyle__Smtp__SecuritySMTP_SECURITYAutoAuto (SSL on port 465, STARTTLS elsewhere when offered), StartTls, SslOnConnect or None.
Peristyle__Smtp__UserNameSMTP_USERNAME—User name. Empty: no authentication.
Peristyle__Smtp__PasswordSMTP_PASSWORD—Password.
Peristyle__Smtp__FromSMTP_FROM—Sender address, required.
Peristyle__Smtp__FromNameSMTP_FROM_NAMEPeristyleSender name.

Examples: Gmail smtp.gmail.com:587 and Outlook.com smtp-mail.outlook.com:587 with an app password; Synology MailPlus Server or another relay of the NAS with None on the local network.

API tokens

Each user creates their tokens in Settings → Applications, one per application, with an optional expiry (see the user guide). Peristyle only keeps the SHA-256 hash of a token: it is shown only when created and can be revoked at any time.

An application sends its token in the Authorization header. GET /api/me checks it: the answer gives the user name, or a 401 error when the token is invalid, revoked or expired.

curl -H "Authorization: Bearer ps_…" https://peristyle.example.com/api/me

A disabled account can no longer use its tokens.

Extensions

Article processing goes through extensions. There are three kinds:

  • Shipped with Peristyle: podcasts, automatic full article, lead image of the full article, YouTube videos and slideshow. Active by default, they can be disabled.
  • Per feed: such an extension only applies to the feeds where a subscriber turned it on, in Feed settings… → Content. The choice is shared by the feed’s subscribers and reprocesses its 20 most recent articles.
  • Dropped: a folder /data/plugins/<id>/ holding manifest.json, the assemblies and, if needed, wwwroot/ for scripts and styles. It stays Pending until approved in Administration → Extensions.

A modified extension goes back to pending, because Peristyle compares the SHA-256 hash of its folder. Decisions apply at restart.

An extension runs inside Peristyle with full rights. Approval protects against mistakes (wrong file, unwanted update), not against an attacker who can already write to the volume. Only approve code you trust.

If an extension prevents startup, start Peristyle with PLUGINS_SAFE_MODE=true: no dropped extension is loaded, and you can disable it.

Languages

The interface is available in French and English. Everyone picks a language in their Settings or on the sign-in page; otherwise Peristyle follows the browser.

To add a language or change a few texts without rebuilding the image, drop a file into /data/locales/, named after the language code (de.json, pt-BR.json, or fr.json to override French), then restart the container.

{
  "Language": { "Name": "Deutsch" },
  "Nav": { "AllArticles": "Alle Artikel", "Starred": "Favoriten" }
}
  • Only the keys present are replaced; any missing text is shown in the default language.
  • Keys can be nested, as above, or flat ("Nav.Starred": "…").
  • {0}, {1}… are parameters; keys ending in .One and .Other are the singular and plural forms.
  • E-mails are translated the same way (Email.* keys).
The data volume of docker-compose.yml is a named volume. To drop files easily, replace it with a server folder, for example ./data:/data.

Themes

Everyone picks a theme in their Settings: Same as system, Light, Dark or an installed theme.

An installed theme is a folder of /data/themes/, whose name (lowercase letters, digits, dashes) is its identifier. It holds two required files and, if needed, .woff2 fonts and images.

/data/themes/sepia/
├── manifest.json   { "name": "Sépia", "author": "…", "version": "1.0.0", "scheme": "light" }
├── theme.css       redefines the CSS variables it wants
└── fonts/…         referenced relatively: url(fonts/literata.woff2)
  • scheme (light or dark) gives the base palette; the theme only redefines what it wants to change.
  • The available variables are listed at the top of src/Peristyle.Web/wwwroot/app.css, and samples/themes/sepia is a full example.
  • No JavaScript and no external resource: a theme.css containing @import, an absolute url() or a url() going up (../) is ignored, with a warning in the logs.
  • Themes are read at startup: restart the container after adding one or changing a manifest.

Administration

The Administration entry of the sidebar appears for administrators. The first account is one, and can appoint others. Each topic has its own tab.

Dashboard

An overview of the instance, with its version: Accounts, Feeds (including failing, disabled or overdue ones), Content and Server (database size and status, running queues). Refresh computes the figures again.

Accounts

The list of accounts, with their address, last sign-in and data volume. On someone else’s account:

  • Make administrator or Remove admin rights;
  • Disable or Enable: a disabled account can no longer sign in or use its tokens;
  • New password link: a link to send to the person, valid once, within 24 hours;
  • Send the link by e-mail, when the account’s address is confirmed and e-mail is configured;
  • Delete: type the account name, then Delete permanently.
Deleting erases the account’s subscriptions, bookmarks and archives, and cannot be undone. At least one active administrator always remains.

Invitations

An invitation link lets someone create an account even when registration is closed. It works only once. The Registration card shows whether it is Open or Closed.

In New invitation, say who it is for (optional) and pick a validity of 1, 7 or 30 days, then Create link. Copy the link right away: it will not be shown again. When e-mail is configured, you can also enter an address and choose the E-mail language.

The list gives the state of each invitation: Pending, Used or Expired. Revoke cancels a pending invitation.

Settings

The server settings, by topic: Registration, Feeds, Retention, Archives, Bookmarks, Images, Language and Extensions. Each line says where its value comes from.

Save keeps the value in the database, where it overrides the environment variable. Reset goes back to that variable. When a setting marked on restart has changed, a banner offers Restart Peristyle: the application stops cleanly and Docker starts it again.

Email

Status says whether sending is On or Off: it needs at least the public address, the server and the sender address. Outgoing server (SMTP) holds the settings of the E-mail (SMTP) section, with no restart. Send a test e-mail checks the configuration; any server error is shown on the page.

Extensions

Each extension shows its version, its hash, the capabilities it asks for (network, readable, script, frame), its hooks and, once loaded, its calls, errors and average time.

  • Bundled extension: Disable or Enable.
  • Dropped extension: Approve, Disable or Reject. An invalid or incompatible extension cannot be approved; its problems are listed.

The statuses are Pending, Approved, Rejected, Disabled and Failed. A decision applies at restart: Restart Peristyle appears when needed.

Storage

The space used, calculated when the page opens, which can take a few seconds:

  • Database, by category;
  • Files: Archived pages and Image cache;
  • By user: a feed followed by several people is shared between them pro rata.