Skip to content

Nix Module

OpenPost can also be deployed through a NixOS module. This is the production setup behind https://op.rgo.pt.

Source

What this example shows

  • Running OpenPost as an OCI container
  • Persisting SQLite and media storage under /var/lib/openpost
  • Supplying secrets through sops
  • Wiring public callback and media URLs to the deployed domain
  • Exposing the service through your existing reverse proxy layer

Current module

The snippet below is refreshed at docs build time from the source repository above. If the fetch fails, the docs fall back to the last generated copy committed in this repo.

Source: rodrgds/nix-config/modules/services/openpost/default.nix

nix
# OpenPost - Multi-platform social media posting
# https://github.com/rodrgds/openpost
{
  config,
  lib,
  pkgs,
  ...
}:
let
  cfg = config.vps.openpost;

  # Host port (external) - must be unique per service
  openpostHostPort = 8090;
  # Container port (internal) - OpenPost listens on 8080 inside container
  openpostContainerPort = 8080;
in
{
  options.vps.openpost = {
    enable = lib.mkEnableOption "OpenPost social media scheduler";

    domain = lib.mkOption {
      type = lib.types.str;
      default = "openpost.rgo.pt";
      description = "Domain for OpenPost";
    };

    timezone = lib.mkOption {
      type = lib.types.str;
      default = "Europe/Lisbon";
      description = "Timezone for OpenPost";
    };
  };

  config = lib.mkIf cfg.enable {
    # Create persistent directories
    # Note: Container runs as user 'openpost' (UID 1000)
    systemd.tmpfiles.rules = [
      "d /var/lib/openpost 0755 root root -"
      "d /var/lib/openpost/data 0755 1000 1000 -"
      "d /var/lib/openpost/data/db 0755 1000 1000 -"
      "d /var/lib/openpost/data/media 0755 1000 1000 -"
    ];

    # OpenPost application
    virtualisation.oci-containers.containers.openpost = {
      image = "ghcr.io/rodrgds/openpost:latest";

      environment = {
        OPENPOST_PORT = toString openpostContainerPort;
        OPENPOST_DB_PATH = "/data/db/openpost.db";
        OPENPOST_MEDIA_PATH = "/data/media";
        OPENPOST_MEDIA_URL = "https://${cfg.domain}/media";
        OPENPOST_FRONTEND_URL = "https://${cfg.domain}";
        OPENPOST_CORS_EXTRA_ORIGINS = "https://${cfg.domain}";
        OPENPOST_DISABLE_LINKEDIN_THREAD_REPLIES = "true";
        TZ = cfg.timezone;
      };

      environmentFiles = [
        config.sops.templates.openpost-env.path
      ];

      volumes = [
        "/var/lib/openpost/data:/data"
      ];

      ports = [
        "127.0.0.1:${toString openpostHostPort}:${toString openpostContainerPort}"
      ];

      extraOptions = [
        "--network=podman"
        "--health-cmd=wget --spider http://localhost:${toString openpostContainerPort}/api/v1/health"
        "--health-interval=30s"
        "--health-timeout=3s"
        "--health-retries=3"
      ];
    };

    # Secrets
    sops.templates = {
      "openpost-env" = {
        content = ''
          JWT_SECRET=${config.sops.placeholder.openpost_jwt_secret}
          ENCRYPTION_KEY=${config.sops.placeholder.openpost_encryption_key}
          TWITTER_CLIENT_ID=${config.sops.placeholder.openpost_twitter_client_id}
          TWITTER_CLIENT_SECRET=${config.sops.placeholder.openpost_twitter_client_secret}
          TWITTER_REDIRECT_URI=https://${cfg.domain}/api/v1/accounts/x/callback
          LINKEDIN_CLIENT_ID=${config.sops.placeholder.openpost_linkedin_client_id}
          LINKEDIN_CLIENT_SECRET=${config.sops.placeholder.openpost_linkedin_client_secret}
          LINKEDIN_REDIRECT_URI=https://${cfg.domain}/api/v1/accounts/linkedin/callback
          THREADS_CLIENT_ID=${config.sops.placeholder.openpost_threads_client_id}
          THREADS_CLIENT_SECRET=${config.sops.placeholder.openpost_threads_client_secret}
          THREADS_REDIRECT_URI=https://${cfg.domain}/api/v1/accounts/threads/callback
          MASTODON_REDIRECT_URI=https://${cfg.domain}/api/v1/accounts/mastodon/callback
          MASTODON_SERVERS=${config.sops.placeholder.openpost_mastodon_servers}
        '';
        mode = "0444";
      };
    };

    # Caddy reverse proxy
    vps.caddy.internalPorts.openpost = openpostHostPort;
  };
}

Released under the MIT License.