跳到內容
Oleaflyv0.4.6

Run a server

Every Oleafly Server setting, storing files on disk or in an S3 bucket, running behind a proxy, limits for classes on one network, logs, backups, restores and updates.

本頁內容尚未翻譯。

This page covers running Oleafly Server day to day. To install it, start with Run a collaboration server.

The server reads its settings from environment variables. A variable that is set but empty counts as not set. If a setting is missing or malformed, the server doesn’t start, and its log says which one.

VariableWhat it doesDefault
OLEAFLY_PUBLIC_URLThe HTTPS address people enter in Oleafly. It can include a path, such as https://www.cs.example.edu/oleafly. Required. The Compose and Coolify files set it for you.None
OLEAFLY_BINDThe address and port the server listens on.0.0.0.0:8080
OLEAFLY_DATA_DIRThe folder for the database and stored files./data
OLEAFLY_SERVER_NAMEThe name people see in Oleafly, such as Smith Lab.Oleafly Server
OLEAFLY_SERVER_ICON_URLThe HTTPS address of a square image shown next to the name.None
OLEAFLY_SETUP_CODEYour own one-time setup code: at least 12 letters or digits, with dashes or spaces if you like. Without it, the server makes one.Generated
OLEAFLY_GITHUB_CLIENT_ID, OLEAFLY_GITHUB_CLIENT_SECRETTurn on sign-in with GitHub.Off
OLEAFLY_GITHUB_URLThe address of a GitHub Enterprise Server.https://github.com
OLEAFLY_GITHUB_API_URLThe GitHub API address, if yours is unusual.https://api.github.com, or /api/v3 under OLEAFLY_GITHUB_URL
OLEAFLY_GITHUB_LABELThe text on the GitHub sign-in button.GitHub
OLEAFLY_OIDC_<NAME>_ISSUER, OLEAFLY_OIDC_<NAME>_CLIENT_ID, OLEAFLY_OIDC_<NAME>_CLIENT_SECRETTurn on an OpenID Connect provider called <NAME>.Off
OLEAFLY_OIDC_<NAME>_LABELThe text on its sign-in button, such as University login.<NAME>
OLEAFLY_OIDC_<NAME>_TRUST_EMAILTreat every email address from this provider as verified. Read Verified email addresses first.false
OLEAFLY_SMTP_URL, OLEAFLY_MAIL_FROMEmail. Read Email.Off
OLEAFLY_BLOB_STOREWhere files such as figures and PDFs are stored. fs keeps them in OLEAFLY_DATA_DIR. An address such as s3://lab-bucket or s3://lab-bucket/oleafly keeps them in a bucket.fs
OLEAFLY_S3_ENDPOINTThe address of an S3-compatible store other than Amazon S3, such as MinIO or Cloudflare R2. Leave it empty for Amazon S3.Amazon S3
OLEAFLY_S3_REGIONThe bucket’s region.us-east-1
OLEAFLY_S3_ACCESS_KEY_ID, OLEAFLY_S3_SECRET_ACCESS_KEYThe key the server uses for the bucket. Required with an s3:// address.None
OLEAFLY_MAX_PROJECT_MBThe largest project the server accepts, in megabytes.200
OLEAFLY_MAX_FILE_MBThe largest single file, in megabytes.50
OLEAFLY_MAX_MEMBERS_PER_PROJECTThe most people on one project.20
OLEAFLY_SIGN_IN_BURSTHow many sign-in requests one network address can make in a burst. A sign-in takes about six, so the default covers 50 people at one address signing in at once.300
OLEAFLY_SIGN_IN_PER_MINUTEHow many sign-in requests one network address gets back each minute after the burst.120
OLEAFLY_EMAIL_LINKS_PER_IPHow many sign-in emails the server sends to people at one network address in 15 minutes.100
OLEAFLY_EMAIL_LINKS_PER_ADDRESSHow many sign-in emails one email address can get in 15 minutes. It can’t be more than OLEAFLY_EMAIL_LINKS_PER_IP.3
OLEAFLY_DEVICE_CODES_PER_IPHow many sign-in codes people at one network address can ask for in 10 minutes.100
OLEAFLY_SNAPSHOT_MEMORY_MBHow much memory, in megabytes, the server may use to build project snapshots.Half the container’s memory limit, or 1024 without a limit
OLEAFLY_BEHIND_PROXYTake each person’s address from the X-Forwarded-For header your proxy adds. The Compose, Coolify and Render files turn it on.false
OLEAFLY_PROXY_HOPSHow many proxies every request passes through before it reaches the server. It needs OLEAFLY_BEHIND_PROXY=true.1
OLEAFLY_LOG_LEVELHow much the server logs: error, warn, info, debug or trace. Libraries the server uses only log warnings unless you pick debug or trace.info
OLEAFLY_SECURITY_LOG_DAYSHow many days to keep security log entries, project access log entries and answered or expired guest invitations.365
OLEAFLY_ARCHIVE_DAYSHow many days to keep a project after its owner stops sharing it.30
OLEAFLY_POLL_HOLD_SECONDSHow long the server keeps a long-polling request open when no edits are waiting, from 1 to 40 seconds. Keep it a few seconds under the idle timeout of any proxy in front of the server.25

In the Compose .env file, put a value in single quotes if it contains $ or #. Settings that hold secrets can also be read from files. Read Keep secrets out of .env.

By default the server keeps everything in OLEAFLY_DATA_DIR: the database, the files people share, and project snapshots. Snapshots are saved copies of each project that let people join and catch up quickly.

The server can keep files and snapshots in Amazon S3 instead, or in another store with an S3-compatible API, such as MinIO or Cloudflare R2. The database stays in OLEAFLY_DATA_DIR either way, so the server still needs its data folder.

  1. Create a bucket and block public access to it.

  2. Create a key that can read, write and delete objects in that bucket and nothing else. On Amazon S3 that means s3:GetObject, s3:PutObject and s3:DeleteObject on arn:aws:s3:::lab-bucket/*, or on arn:aws:s3:::lab-bucket/oleafly/* if you use a folder.

  3. Set the variables, for example in .env:

    Terminal window
    OLEAFLY_BLOB_STORE=s3://lab-bucket/oleafly
    OLEAFLY_S3_REGION=eu-west-1
    OLEAFLY_S3_ACCESS_KEY_ID=your-access-key-id
    OLEAFLY_S3_SECRET_ACCESS_KEY=your-secret-access-key

For a store other than Amazon S3, also set OLEAFLY_S3_ENDPOINT to its address, such as https://minio.example.org:9000 or https://<account-id>.r2.cloudflarestorage.com, and set OLEAFLY_S3_REGION to the region it expects (auto for R2). Plain http:// works too, but use it only when the store sits on the same private network as the server.

The part after the bucket name is an optional folder. Each file is named after its SHA-256 hash and sits under the first two characters of that hash, so a file whose hash starts with 3f9a is stored as oleafly/3f/3f9a.... Snapshots have their own snapshots folder with a folder for each project. When the server reads a file or snapshot back, it checks the bytes against the hash and refuses one that doesn’t match. Without a bucket, the same layout is in the blobs folder inside OLEAFLY_DATA_DIR.

If the bucket refuses a request, that upload or download fails, and the server log shows what the bucket said, such as 403 Forbidden (AccessDenied). A server error, a timeout or a dropped connection is different: the server tries up to three times, with a short random pause in between. If it can’t delete an object, it notes that and tries again at each maintenance pass, every 6 hours, so an outage doesn’t leave objects behind.

When a project is shared, the owner’s app uploads its first snapshot and signs it with that device’s key. From then on the server builds every snapshot itself, from the previous snapshot and the changes people sent since. A new snapshot is built once a project has 1,000 changes past the last one. The server always keeps at least the last 2,000 changes, so a device that was away for a short time catches up from those and doesn’t download the whole project again.

Building a snapshot needs about ten times the size of the previous snapshot plus the changes since it. The server only starts a build that fits in OLEAFLY_SNAPSHOT_MEMORY_MB, and builds running at the same time share that amount. If a build doesn’t fit, the server skips it, logs how much memory it needs and keeps all the project’s changes. Devices then catch up from those changes a few megabytes at a time. Raise the setting and the next snapshot gets built. The server won’t store a snapshot larger than OLEAFLY_MAX_PROJECT_MB.

The server speaks plain HTTP, so something in front of it handles HTTPS: Caddy in the Compose setup, Coolify’s proxy, or a web server you already run, such as your university’s. Whatever sits in front has to:

  • accept requests as large as OLEAFLY_MAX_PROJECT_MB
  • add the client’s address to X-Forwarded-For

It should also pass WebSocket upgrades through to /ws and keep those connections open for at least an hour. Live editing uses them when it can.

Run the server where only the proxy can reach it. On the same machine, set OLEAFLY_BIND=127.0.0.1:8080. Then set OLEAFLY_BEHIND_PROXY=true.

Some networks block WebSocket connections, often a university or company network with a filtering proxy. Live editing still works there, more slowly. When two WebSocket attempts in a row fail while the server still answers over HTTPS, Oleafly switches to HTTP long-polling. It sends edits to /api/v1/projects/<project>/frames and waits for other people’s edits on /api/v1/projects/<project>/poll, holding each request open for up to OLEAFLY_POLL_HOLD_SECONDS. Every edit still arrives. Oleafly tries a WebSocket again every minute and goes back to it as soon as one connects.

Test connection in Oleafly and Connection checks in the admin console say when this happens: “Live edits take a slower route because this network blocks the usual one. They still arrive.”

If you run the proxy in front of the server, pass WebSocket upgrades through anyway. Long-polling works, but each batch of edits costs an extra request and a little more delay.

Some proxies close a request that stays quiet for less than 30 seconds, nginx with a short proxy_read_timeout for one. They answer long-polling requests with a 502 or 504 error, and edits arrive late. Set OLEAFLY_POLL_HOLD_SECONDS a few seconds under that timeout, such as 8 for a 10-second timeout.

The server notes a client address in two places: the security log, and the limits on how often one address can start a sign-in or open a live connection.

Without the setting, the server uses the address of whatever connected to it. Behind a proxy that’s always the proxy, so everyone shares one limit and the security log shows only the proxy’s address.

With OLEAFLY_BEHIND_PROXY=true, the server reads the X-Forwarded-For header and uses the last address in it, which is the one your proxy added. If the header is missing or holds no valid address, the server falls back to the connecting address. Nothing else changes. The server ignores X-Forwarded-Proto, X-Forwarded-Host and Forwarded, and builds every address it hands out from OLEAFLY_PUBLIC_URL.

Turn it on only when every request comes through your proxy. If people can also reach the server directly, anyone can send a made-up X-Forwarded-For, put any address in the security log and get around the sign-in limit.

With two proxies in a row, for example Cloudflare in front of Coolify’s proxy, set OLEAFLY_PROXY_HOPS=2. The server then takes the second address from the end. Count only the proxies that every request goes through. A Cloudflare Tunnel that leads straight to the server counts as one.

To check the number, sign in from your own computer and open Security log in the admin console. The newest entry should show your public address, the one a site such as ifconfig.me reports. If it shows a private or Cloudflare address instead, raise OLEAFLY_PROXY_HOPS by one.

Caddy passes WebSocket connections and X-Forwarded-For through without extra settings, and gets the certificate itself. Put HTTPS in front has the whole Caddyfile.

Get a certificate for the domain first, for example with certbot. Then save this as /etc/nginx/conf.d/oleafly.conf and run sudo nginx -t && sudo systemctl reload nginx:

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name collab.example.org;
ssl_certificate /etc/letsencrypt/live/collab.example.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/collab.example.org/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
client_max_body_size 200m;
}
}

client_max_body_size matches the default OLEAFLY_MAX_PROJECT_MB of 200. If you raise one, raise the other.

You need Apache 2.4.47 or newer, which Debian 12 and Ubuntu 22.04 and later have. Turn on the modules:

Terminal window
sudo a2enmod proxy proxy_http ssl

Save this as /etc/apache2/sites-available/oleafly.conf, then run sudo a2ensite oleafly && sudo systemctl reload apache2:

<VirtualHost *:443>
ServerName collab.example.org
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/collab.example.org/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/collab.example.org/privkey.pem
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8080/ upgrade=websocket timeout=3600
ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>

upgrade=websocket passes WebSocket connections through and timeout=3600 keeps them open for an hour. Apache adds the client’s address to X-Forwarded-For by itself.

If the server lives under a path, include the path in OLEAFLY_PUBLIC_URL, for example https://www.cs.example.edu/oleafly, and in the callback addresses you register with sign-in providers. The server answers both with and without that path, so your proxy can pass it on or strip it. With nginx, use location /oleafly/ { with the same lines inside, and keep proxy_pass http://127.0.0.1:8080; without a path so nginx passes the path on.

A whole class often signs in from one network address, such as a university’s NAT. The defaults are set for that:

  • Sign-ins: one address gets a burst of 300 sign-in requests, then two a second. A sign-in takes about six, so 50 people can sign in at once. Change these with OLEAFLY_SIGN_IN_BURST and OLEAFLY_SIGN_IN_PER_MINUTE.
  • Email links: 100 to one address in 15 minutes, and 3 to one email address. Change these with OLEAFLY_EMAIL_LINKS_PER_IP and OLEAFLY_EMAIL_LINKS_PER_ADDRESS.
  • Sign-in codes: 100 codes, and 10 wrong codes, from one address in 10 minutes. Change this with OLEAFLY_DEVICE_CODES_PER_IP.
  • Live connections have their own, larger limit, so a whole class behind one address can reconnect at once after a restart. Only connections that fail to prove their session use it up: a burst of 300, then ten a second. A connection that proves its session counts against its own device instead, which can reconnect 60 times in a burst, then once a second.

All IPv6 addresses in one /64 network count as one address. These limits only work per person when the server sees each person’s real address, so set up your proxy correctly.

The server pings each live connection every 30 seconds. It closes a connection that leaves those pings unanswered for 90 seconds, and one that stops reading what the server sends.

Project limits apply to everyone: OLEAFLY_MAX_PROJECT_MB for a whole project (200 MB by default), OLEAFLY_MAX_FILE_MB for one file (50 MB) and OLEAFLY_MAX_MEMBERS_PER_PROJECT for people on one project (20). The admin console’s Overview shows the current limits.

  • The server log is what docker compose logs oleafly-server, Coolify’s Logs or journalctl -u oleafly-server show. OLEAFLY_LOG_LEVEL sets how much it says.
  • The security log records sign-ins and refused sign-ins, devices, people, roles, settings, projects and guests, each with the time and the IP address. Admins and maintainers read it under Security log in the admin console. Entries older than OLEAFLY_SECURITY_LOG_DAYS days are deleted.
  • Project access logs record who opened a project, joined it or downloaded a copy, with the device name and the time. A project’s owner turns one on, when Allow project access logs is on, and only that owner can read it. The same retention applies.

What a full backup needs depends on where files are stored:

  • Files on disk (OLEAFLY_BLOB_STORE=fs, the default): the database, files and snapshots are all in the data folder. Back up the whole folder.
  • Files in a bucket: the data folder holds the database and the bucket holds files and snapshots. A copy of the database plus the bucket is a full backup. Turn on versioning for the bucket so an object deleted by mistake can be brought back.
Where the server keeps data, and what to back upWith files on disk, the default, the data folder holds the database and the blobs folder with files and snapshots, so you back up the whole folder. With files in a bucket, the data folder holds the database and an S3 bucket holds files and snapshots, so you back up the database and the bucket.Files on disk (default)Files in a bucketoleafly-serverData folderDatabase: accounts, settings, logsblobs: files and snapshotsBack up the whole folderoleafly-serverData folderDatabaseS3 bucketFiles andsnapshotsBack up the database and the bucketWhere the server keeps data, and what to back upWith files on disk, the default, the data folder holds the database and the blobs folder with files and snapshots, so you back up the whole folder. With files in a bucket, the data folder holds the database and an S3 bucket holds files and snapshots, so you back up the database and the bucket.Files on disk (default)Files in a bucketoleafly-serverData folderDatabase and blobs(files, snapshots)Back up the whole folderoleafly-serverData folderDatabaseS3 bucketFiles andsnapshotsBack up the databaseand the bucket
What a full backup needs depends on where files are stored.

Back up regularly and keep the copies private, since they hold every project. Your settings, such as .env, aren’t in the data folder, so keep a copy of them as well.

With Docker, the server stops for a few seconds while you copy the volume. Oleafly reconnects by itself. Run these commands in the server’s folder, /opt/oleafly-server if you used the installer.

Terminal window
cd /opt/oleafly-server
sudo docker compose stop oleafly-server
sudo docker run --rm -v oleafly-server_oleafly-data:/data:ro -v "$PWD":/backup debian:13.7-slim tar -C /data -czf /backup/oleafly-data-$(date +%F).tar.gz .
sudo docker compose start oleafly-server

With systemd:

Terminal window
sudo systemctl stop oleafly-server
sudo tar -C /var/lib/private/oleafly-server -czf /root/oleafly-data-$(date +%F).tar.gz .
sudo systemctl start oleafly-server

Copy the archive to another machine.

On Coolify, the volume’s name starts with the resource’s ID. Find it with docker volume ls --filter name=oleafly-data, use it in place of oleafly-server_oleafly-data, and stop and start the server from Coolify’s dashboard instead of with docker compose.

A backup you’ve never restored is only a hope. This drill starts a second copy of the server from a backup, leaves the running one alone, and checks that both are the same server. Use the date of your own archive in place of 2026-10-11.

Terminal window
cd /opt/oleafly-server
sudo docker volume create oleafly-restore-drill
sudo docker run --rm -v oleafly-restore-drill:/data -v "$PWD":/backup:ro debian:13.7-slim tar -C /data -xzf /backup/oleafly-data-2026-10-11.tar.gz
sudo docker run -d --name oleafly-restore-drill -v oleafly-restore-drill:/data -e OLEAFLY_PUBLIC_URL=https://restore-drill.invalid -e OLEAFLY_GITHUB_CLIENT_ID=restore-drill -e OLEAFLY_GITHUB_CLIENT_SECRET=restore-drill "$(sudo docker compose images -q oleafly-server)"
sudo docker run --rm --network container:oleafly-restore-drill curlimages/curl:8.22.0 -fsS --retry 10 --retry-connrefused --retry-delay 1 http://127.0.0.1:8080/.well-known/oleafly-server
curl -fsS https://collab.example.org/.well-known/oleafly-server

The server_id in the two answers must match. The drill copy has a placeholder GitHub sign-in that can’t sign anyone in, and no open port, so nobody can use it. Remove it when you’re done:

Terminal window
sudo docker rm -f oleafly-restore-drill
sudo docker volume rm oleafly-restore-drill

To restore for real with Docker Compose, stop the server, replace the volume’s contents with the archive, and start it again:

Terminal window
cd /opt/oleafly-server
sudo docker compose stop oleafly-server
sudo docker run --rm -v oleafly-server_oleafly-data:/data -v "$PWD":/backup:ro debian:13.7-slim sh -c 'find /data -mindepth 1 -delete && tar -C /data -xzf /backup/oleafly-data-2026-10-11.tar.gz'
sudo docker compose start oleafly-server

With systemd:

Terminal window
sudo systemctl stop oleafly-server
sudo find /var/lib/private/oleafly-server -mindepth 1 -delete
sudo tar -C /var/lib/private/oleafly-server -xzf /root/oleafly-data-2026-10-11.tar.gz
sudo systemctl start oleafly-server

A restored server is missing the changes people made after the backup. Each app notices when it reconnects, whether it stayed open or was opened later, and sends its own missing changes again. Nobody has to do anything for that. A change that builds on someone else’s missing change waits until that person opens the project, because only their app can send their changes. Ask everyone who was editing around the time of the failure to open the project once.

An archive only holds what was there when you made it. Litestream (opens in a new tab) copies each change to the server’s database to an S3-compatible bucket about a second after it happens. The Compose file includes it as an optional service.

Litestream copies the database only. If files are in a bucket, the bucket already holds files and snapshots, so the Litestream copy and the bucket together can bring back a lost disk. If files are on disk, keep making archives of the volume as well, because Litestream doesn’t copy the blobs folder.

  1. Create a bucket, or a folder in one, that only this server’s Litestream uses. Don’t share it with OLEAFLY_BLOB_STORE. Give Litestream a key that can list the bucket and read, write and delete objects in it.

  2. Add the details to .env:

    Terminal window
    LITESTREAM_REPLICA_URL=s3://lab-backups/oleafly-server
    LITESTREAM_REGION=eu-west-1
    LITESTREAM_ACCESS_KEY_ID=your-access-key-id
    LITESTREAM_SECRET_ACCESS_KEY=your-secret-access-key
    COMPOSE_PROFILES=litestream

    For a store other than Amazon S3, also set LITESTREAM_ENDPOINT to its address and LITESTREAM_REGION to the region it expects (auto for R2). COMPOSE_PROFILES makes every docker compose command include Litestream.

  3. Start it:

    Terminal window
    sudo docker compose up -d
  4. Check that it copied the database. This prints a line with snapshot complete:

    Terminal window
    sudo docker compose logs litestream | grep 'snapshot complete'

Litestream takes a full copy once a day and keeps a week of copies and the changes between them. The schedule is in litestream.yml.

This drill restores the database from the bucket into a scratch volume, starts a second copy of the server on it, and checks that both are the same server. The running server isn’t touched.

Terminal window
cd /opt/oleafly-server
sudo docker run --rm -v oleafly-litestream-drill:/data "$(sudo docker compose images -q oleafly-server)" --version
sudo docker compose run --rm --no-deps -v oleafly-litestream-drill:/restore litestream restore -config /etc/litestream.yml -o /restore/oleafly-server.sqlite3 /data/oleafly-server.sqlite3
sudo docker run -d --name oleafly-litestream-drill -v oleafly-litestream-drill:/data -e OLEAFLY_PUBLIC_URL=https://restore-drill.invalid -e OLEAFLY_GITHUB_CLIENT_ID=restore-drill -e OLEAFLY_GITHUB_CLIENT_SECRET=restore-drill "$(sudo docker compose images -q oleafly-server)"
sudo docker run --rm --network container:oleafly-litestream-drill curlimages/curl:8.22.0 -fsS --retry 10 --retry-connrefused --retry-delay 1 http://127.0.0.1:8080/.well-known/oleafly-server
curl -fsS https://collab.example.org/.well-known/oleafly-server

The first command creates the scratch volume with the owner the server needs. The server_id in the last two answers must match. Remove the copy when you’re done:

Terminal window
sudo docker rm -f oleafly-litestream-drill
sudo docker volume rm oleafly-litestream-drill

When the database file is damaged or gone but the rest of the data folder is fine, stop the server and Litestream, remove what’s left of the database, restore it and start again:

Terminal window
cd /opt/oleafly-server
sudo docker compose stop oleafly-server litestream
sudo docker run --rm -v oleafly-server_oleafly-data:/data debian:13.7-slim rm -rf /data/oleafly-server.sqlite3 /data/oleafly-server.sqlite3-wal /data/oleafly-server.sqlite3-shm /data/.oleafly-server.sqlite3-litestream
sudo docker compose run --rm --no-deps litestream restore -config /etc/litestream.yml /data/oleafly-server.sqlite3
sudo docker compose up -d

After a lost disk with files in a bucket, run the commands above on the new, empty volume and keep the same bucket settings in .env. Every project comes back with its files and snapshots.

After a lost disk with files on disk, restore your newest archive and leave it at that. Don’t put a newer database from Litestream on top of an older archive. The database would point at files and snapshots the archive doesn’t have, and those projects wouldn’t open.

Back up first. The server updates its database the first time a new release starts, so going back to an older release means restoring the backup you took before updating.

  • One-command install: run curl -fsSL https://oleafly.com/server/install.sh | sudo sh -s -- update. It keeps .env, gets the new release’s files and image, and restarts the server.
  • Docker Compose by hand: download the new release’s files into the server’s folder as in Docker Compose by hand, then run sudo docker compose up -d.
  • Coolify: select Redeploy. With the published image, that pulls the newest release under the image’s tag.
  • Render: open the service and start a manual deploy, which pulls the image again.
  • Railway: deploy the service again from its menu. Railway can also update an image from GitHub’s registry by itself.
  • systemd: install the new release as in Download the server, then run sudo systemctl restart oleafly-server.

Oleafly checks the server’s version every time it connects. If the app and the server can’t work together, Oleafly says which one needs an update.

Image preview

AI answers couldn’t load

Try again, or search the documentation.