Home

Awesome

Lemmy Schedule

An app that makes it possible to schedule posts on Lemmy.

Am I the only one who thinks it's funny how the name "Lemmy Schedule" sounds like "let me schedule"?

<!-- TOC --> <!-- TOC -->

Interesting parts

How it works?

It uses the excellent Symfony framework and even more excellent Symfony Messenger.

Basically, it creates a Messenger job and configures a delay for the job that is the same as the time of the post. The job is then received, processed and deleted (depends on your particular messenger backend).

The default deployment using the provided serverless configuration has a little twist on that: to be fully serverless, the messenger backend is faked to be an EventBridge service and it only allows creating jobs, which it does by creating a scheduled job which in turn posts to a console command app:run-sync.

The console command takes the job, overwrites the transport config to force it to run synchronously in the command and thus the need for a queue consumer is avoided. I'm still not sure whether this is a genius idea or a blasphemy. But it saves me money for hosting, so I'm gonna go with genius.

Instead of database it uses a cache backend which can be any PSR-6 backend, the default configuration uses a DynamoDB one.

Building the app locally

Note that this is a tutorial for a fully local deployment, depending on the method you'll be using for deployment (Docker, AWS serverless), you might not need to set up everything mentioned below, feel free to skip to the specific guides below and use this only as a reference if you want a deeper understanding of how the app works.

Here's a brief tutorial on how to build a production version of the app.

Prerequisites

Configuring

All configuration is made using environment variables, those can either be real environment variables or ones set in .env.local file.

If an option is marked as required, but also has a default value, you don't have to provide your own value if you're ok with the default

Environment variableDescriptionDefaultRequired
APP_ENVcan be either prod or devdevyes
APP_SECRETa random string used for signing cookies etc., should be unique and, well, as the name implies, secretdo not use the default value, always provide oneyes
DYNAMODB_CACHE_TABLEthe name of the AWS DynamoDB table to use for cache, ignore this option if you don't use DynamoDB for cachecacheno
AWS_REGIONthe name of the AWS region to use, you can ignore this if you're not using DynamoDB for cache and AWS EventBridge as a job schedulereu-central-1no
MESSENGER_TRANSPORT_DSNthe DSN for job transport, more belowredis://localhost:6379/messagesyes
DOMAIN_NAMEthe domain this will be running on, only needed when running on AWS serverlessno
CONSOLE_FUNCTIONthe AWS ARN of the console function, only needed when running on AWS serverlessno
ROLE_ARNthe AWS ARN of the role used for running jobs, only needed when running on AWS serverlessno
DEFAULT_INSTANCEthe default instance for logging inlemmings.wolrdyes
FILE_UPLOADER_CLASSthe class that handles uploading of files, note that this needs to be set prior to compiling the service containerApp\FileUploader\LocalFileUploaderyes
LOCAL_FILE_UPLOADER_PATHthe filesystem path to store files in temporarily - the default uses a volatile location that might get deleted often, so it's advised to change it%kernel.project_dir%/var/imagesno
S3_FILE_UPLOADER_BUCKETthe bucket to use for storing files temporarily, only used if file uploader is set to AWS S3no
APP_CACHE_DIRthe directory for storing cache, it should be some permanent directoryno
APP_LOG_DIRthe directory for storing logsno
SINGLE_INSTANCE_MODEset to either 1 or 0, 1 means that only users from the instance specified in DEFAULT_INSTANCE can log in0yes
IMGUR_ACCESS_TOKENset to Imgur access token if you want to enable Imgurno
UNREAD_POSTS_BOT_JWTthe JWT token for the bot user that will send reports with unread posts - if it's not provided, the whole unread posts functionality will be disabled
UNREAD_POSTS_BOT_INSTANCEthe instance the bot for unread post reports is on - if it's not provided, the whole unread posts functionality will be disabled
DEFAULT_POST_LANGUAGEthe numerical ID of the language that will be preselected when scheduling a post. For list of IDs see this enum0yes
FLAG_COMMUNITY_GROUPSwhether community groups, an experimental feature, should be enabled0no
SOURCE_URLthe URL of the source code, used for new version checks and for a footer link - if you plan on maintaining a fork, you can change it to your fork URLhttps://github.com/RikudouSage/LemmyScheduleyes
NEW_VERSION_CHECKset to 1 or 0 to enable or disable checks for a new version1yes

Job transports

Job transport is what manages where the scheduled jobs are stored and where does the messenger component consume them from. You can see the full list of officially supported transports in the official Symfony documentation.

If you're not familiar with the Messenger component, I advise you to go with Redis which is the simplest to configure. The Redis DSN looks like this: redis://localhost:6379/messages.

This configures the server (localhost), port (6379) and key (messages) where the jobs will be stored. For more complex examples visit the official documentation.

File uploading

Currently, there are two options for storing uploaded files — locally or in AWS S3. In either case, the files are deleted once the job that references them is triggered.

These correspond to these FILE_UPLOADER_CLASS values:

Note that this environment variable needs to be set prior to compiling the service container

For local, you need to also specify LOCAL_FILE_UPLOADER_PATH which is a directory in which the file uploads will be stored. You can use a special variable %kernel.project_dir% that gets replaced with the path to where the project is stored.

If you go with S3, you also need to specify S3_FILE_UPLOADER_BUCKET which is the name of the bucket where the files will be stored.

File providers

File providers are handlers for uploading images. Some of them might need configuration before they show up in the UI.

Currently supported ones:

Cache

If you're not using DynamoDB for cache, delete the file config/packages/prod/rikudou_dynamo_db_cache.yaml (before compiling the service container).

Building

Done!

Running

If you want to test it out, run the development php server using php -S 127.0.0.1:8000, otherwise follow some guide to set up a webserver, Apache being the easiest solution.

Self-hosting - AWS

If you intend to self-host using docker, skip this section

Note: This flow can also always be checked in publish.yaml

Prerequisites:

Environment variables

These variables are provided automatically and you cannot change them:

You need to set these environment variables (as real environment variables, not as part of .env.local):

You may set these environment variables as well (as real environment variables, not as part of .env.local):

Deploying

If everything works out, you should be able to visit the domain which you specified in DOMAIN_NAME and the app should be running there!

Self-hosting - docker

Configuration

You need to configure these variables:

Other variables which might need changing:

Volumes

Some permanent files are accessible at these locations:

Ports

The app runs on port 80 inside the container, bind the port to any port you want.

Running

Taking the above into account, here is a docker command used for the production build (replace the APP_SECRET):

If you have a Redis instance under different URL (replace redis.example.com with your hostname and lemmy_schedule with the Redis key you want to use):

Docker compose

You can easily run the app using docker compose like so:

version: "3.7"

services:
  redis:
    image: redis
    hostname: redis
    command: redis-server --save 60 1 --loglevel warning # make Redis dump the contents to disk and restore them on start
    volumes:
      - redis_data:/data
  lemmy_schedule:
    image: ghcr.io/rikudousage/lemmy-schedule:latest
    ports:
      - "8000:80" # replace 8000 with the port you want your app to run on
    environment:
      APP_SECRET: $APP_SECRET # actually create the secret, don't just use this value
      DEFAULT_INSTANCE: my.instance.tld
    volumes:
      - ./volumes/lemmy-schedule-cache:/opt/runtime-cache
      - ./volumes/lemmy-schedule-uploads:/opt/uploaded-files
    depends_on:
      - redis

volumes:
  redis_data: