Skip to content

Nix Module

OpenPost can also be deployed through a NixOS module. This is the production setup behind https://openpost.rgo.pt (my instance).

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

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_DATABASE_PATH = "/data/db/openpost.db";
        OPENPOST_MEDIA_PATH = "/data/media";
        OPENPOST_MEDIA_URL = "https://${cfg.domain}/media";
        OPENPOST_APP_URL = "https://${cfg.domain}";
        OPENPOST_EXTRA_CORS_ORIGINS = "https://${cfg.domain}";
        OPENPOST_DISABLE_REGISTRATIONS = "true";
        LINKEDIN_DISABLE_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 = ''
          OPENPOST_JWT_SECRET=${config.sops.placeholder.openpost_jwt_secret}
          OPENPOST_ENCRYPTION_KEY=${config.sops.placeholder.openpost_encryption_key}
          X_CLIENT_ID=${config.sops.placeholder.openpost_twitter_client_id}
          X_CLIENT_SECRET=${config.sops.placeholder.openpost_twitter_client_secret}
          X_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.