{
  "path": "/3mjfkujc6h22r",
  "site": "at://did:plc:nuc33thnsiqzhytkleyr5jek/site.standard.publication/3mjdo3xyaoc2l",
  "tags": [
    "atproto",
    "centos",
    "bluesky",
    "pds",
    "podman",
    "self-hosting",
    "rhel"
  ],
  "$type": "site.standard.document",
  "title": "Bluesky PDS with Podman on CentOS Stream 10",
  "content": {
    "$type": "pub.leaflet.content",
    "pages": [
      {
        "id": "019d87f5-8e94-799c-9d33-1ff7beac4827",
        "$type": "pub.leaflet.pages.linearDocument",
        "blocks": [
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "This is based on the following documentation from the official Bluesky GitHub:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.unorderedList",
              "children": [
                {
                  "$type": "pub.leaflet.blocks.unorderedList#listItem",
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "facets": [
                      {
                        "index": {
                          "byteEnd": 37,
                          "byteStart": 0
                        },
                        "features": [
                          {
                            "uri": "https://github.com/bluesky-social/pds",
                            "$type": "pub.leaflet.richtext.facet#link"
                          }
                        ]
                      }
                    ],
                    "plaintext": "https://github.com/bluesky-social/pds"
                  }
                },
                {
                  "$type": "pub.leaflet.blocks.unorderedList#listItem",
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "facets": [
                      {
                        "index": {
                          "byteEnd": 48,
                          "byteStart": 0
                        },
                        "features": [
                          {
                            "uri": "https://github.com/bluesky-social/deploy-recipes",
                            "$type": "pub.leaflet.richtext.facet#link"
                          }
                        ]
                      }
                    ],
                    "plaintext": "https://github.com/bluesky-social/deploy-recipes"
                  }
                }
              ]
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 406,
                    "byteStart": 385
                  },
                  "features": [
                    {
                      "uri": "https://tilde.zone/@hyperreal",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 487,
                    "byteStart": 446
                  },
                  "features": [
                    {
                      "uri": "https://github.com/bluesky-social/deploy-recipes",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                }
              ],
              "plaintext": "We'll keep SELinux in enforcing mode and install a policy module to allow the PDS to work. CentOS Stream is not an officially supported distribution by the upstream PDS maintainers -- this is my own working setup -- so please do not bother them with support questions for a CentOS Stream host. In lieu of that, you're welcome to direct any questions or issues with this setup to me at @hyperreal@tilde.zone in the fediverse, or submit an issue at github.com/bluesky-social/deploy-recipes."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Minimum server requirements"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.unorderedList",
              "children": [
                {
                  "$type": "pub.leaflet.blocks.unorderedList#listItem",
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "OS: CentOS Stream 10"
                  }
                },
                {
                  "$type": "pub.leaflet.blocks.unorderedList#listItem",
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "RAM: 1 GB"
                  }
                },
                {
                  "$type": "pub.leaflet.blocks.unorderedList#listItem",
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "CPU cores: 1"
                  }
                },
                {
                  "$type": "pub.leaflet.blocks.unorderedList#listItem",
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "Storage: 20 GB SSD"
                  }
                },
                {
                  "$type": "pub.leaflet.blocks.unorderedList#listItem",
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "Architectures: amd64, arm64"
                  }
                },
                {
                  "$type": "pub.leaflet.blocks.unorderedList#listItem",
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "Number of PDS users: 1-20"
                  }
                }
              ]
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Ensure you have a firewall installed along with fail2ban, and that proper security precautions are taken for your server, such as SSH hardening."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Other requirements:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.unorderedList",
              "children": [
                {
                  "$type": "pub.leaflet.blocks.unorderedList#listItem",
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "Public IPv4 address"
                  }
                },
                {
                  "$type": "pub.leaflet.blocks.unorderedList#listItem",
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "Public DNS name"
                  }
                },
                {
                  "$type": "pub.leaflet.blocks.unorderedList#listItem",
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "Public inbound internet access permitted on port 80/tcp and 443/tcp"
                  }
                },
                {
                  "$type": "pub.leaflet.blocks.unorderedList#listItem",
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "A reverse-proxy server, such as Caddy. This guide assumes you'll use Caddy and it is installed."
                  }
                }
              ]
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Preliminary actions"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Install Podman and epel-release."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "sudo dnf install -y '@container-management' epel-release",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 3,
              "plaintext": "SELinux policy module"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "SELinux is not required, but it is recommended for RHEL-like distributions. To set SELinux in enforcing mode, run the following command as root."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "setenforce 1",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 80,
                    "byteStart": 58
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 100,
                    "byteStart": 93
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 132,
                    "byteStart": 123
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "To make this persist across reboots, you may need to edit /etc/sysconfig/selinux. Change the SELINUX variable to the value enforcing. You can do this with the following command, which is idempotent if it is already set to enforcing."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "sudo sed -i 's/SELINUX=permissive/SELINUX=enforcing/' /etc/sysconfig/selinux",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "You also need to install an SELinux policy module so that SELinux doesn't deny the container processes used by the PDS."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 22,
                    "byteStart": 16
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Create the file pds.te."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "module pds 1.0;\n\nrequire {\n        type container_runtime_t;\n        type var_run_t;\n        type container_t;\n        type default_t;\n        class file { create lock map open read setattr unlink write };\n        class dir { add_name remove_name write };\n        class unix_stream_socket connectto;\n        class sock_file write;\n}\n\n# ============= container_t ==============\nallow container_t container_runtime_t:unix_stream_socket connectto;\nallow container_t default_t:dir { add_name remove_name write };\nallow container_t default_t:file { create lock map open read setattr unlink write };\nallow container_t var_run_t:sock_file write;\n",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Compile and install the module."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "checkmodule -M -m -o pds.mod pds.te\nsemodule_package -o pds.pp -m pds.mod\nsudo semodule -i pds.pp",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "If SELinux still denies some processes, you can check for them with the following command."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "sudo ausearch -m avc -ts recent | sudo audit2allow",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Installing the PDS Podman quadlet"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 81,
                    "byteStart": 41
                  },
                  "features": [
                    {
                      "uri": "https://github.com/bluesky-social/deploy-recipes",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 134,
                    "byteStart": 119
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "The systemd quadlet files are taken from github.com/bluesky-social/deploy-recipes. The only modification I made is the EnvironmentFile setting."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 81,
                    "byteStart": 58
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "The directory under which you should place these files is /etc/containers/systemd."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 53,
                    "byteStart": 16
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Create the file /etc/containers/systemd/pds.container."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "systemd",
              "plaintext": "[Unit]\nDescription=Bluesky Personal Data Server service\nBefore=caddy.service\n\n[Container]\nLabel=app=pds\nImage=ghcr.io/bluesky-social/pds:0.4\nAutoUpdate=registry\nPod=pds.pod\nEnvironmentFile=/etc/pds.env\n\n[Install]\nWantedBy=multi-user.target default.target\n",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 53,
                    "byteStart": 22
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Next, create the file /etc/containers/systemd/pds.pod."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "systemd",
              "plaintext": "[Pod]\nVolume=pds.volume:/pds\nPublishPort=127.0.0.1:3000:3000\n# if you map 3000:3000 instead of 127.0.0.1:3000:3000\n# the PDS will be accessible without the reverse proxy. You probably don't want that!",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 59,
                    "byteStart": 25
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Finally, create the file /etc/containers/systemd/pds.volume."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "systemd",
              "plaintext": "[Unit]\nDescription=Bluesky PDS Volume\n\n[Volume]\nLabel=app=pds",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "These files comprise a systemd quadlet. Quadlets enable Podman containers to run as systemd services. It's a declarative way to configure containers that fits in with the systemd ecosystem."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 84,
                    "byteStart": 65
                  },
                  "features": [
                    {
                      "uri": "https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                }
              ],
              "plaintext": "More information on how to configure quadlets can be found here: podman-systemd.unit."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "It's necessary to have all of these files together in the same directory, as they depend on each other."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Setting up pds.env"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 27,
                    "byteStart": 20
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Here is the default pds.env. You should edit it to your specific use."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "PDS_HOSTNAME=pds.example.com\nPDS_JWT_SECRET=\nPDS_ADMIN_PASSWORD=\nPDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=\nPDS_DATA_DIRECTORY=/pds #mapped to volume\nPDS_BLOBSTORE_DISK_LOCATION=/pds/blocks\nPDS_BLOB_UPLOAD_LIMIT=104857600\n\n# if you want to use s3 or compatible, use these variables and comment DISK_LOCATION\n\n# Object Storage\n\nPDS_BLOBSTORE_S3_BUCKET=your-bucket-name\nPDS_BLOBSTORE_S3_ENDPOINT=https://s3.example.com\n# PDS_BLOBSTORE_S3_FORCE_PATH_STYLE=true #depends on your provider\nPDS_BLOBSTORE_S3_ACCESS_KEY_ID=your-access-key-id\nPDS_BLOBSTORE_S3_REGION=your-region\nPDS_BLOBSTORE_S3_SECRET_ACCESS_KEY=your-secret-key\n\nPDS_DID_PLC_URL=https://plc.directory\nPDS_BSKY_APP_VIEW_URL=https://api.bsky.app\nPDS_BSKY_APP_VIEW_DID=did:web:api.bsky.app\nPDS_REPORT_SERVICE_URL=https://mod.bsky.app\nPDS_REPORT_SERVICE_DID=did:plc:ar7c4by46qjdydhdevvrndac\nPDS_CRAWLERS=https://bsky.network\nLOG_ENABLED=true\nPDS_EMAIL_SMTP_URL=\nPDS_EMAIL_FROM_ADDRESS=",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 29,
                    "byteStart": 15
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "You should set PDS_JWT_SECRET to a pseudo-random value generated with the following command."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "openssl rand --hex 16",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 31,
                    "byteStart": 13
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Likewise for PDS_ADMIN_PASSWORD."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "openssl rand --hex 16",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 62,
                    "byteStart": 21
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "You also need to set PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX to the value generated with this command."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 26,
                    "byteStart": 11
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 51,
                    "byteStart": 38
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 76,
                    "byteStart": 69
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 92,
                    "byteStart": 80
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "As per the EnvironmentFile setting in pds.container, we should store pds.env at /etc/pds.env."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Starting the PDS"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 90,
                    "byteStart": 66
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "The following systemd command will load the files we placed under /etc/containers/systemd/ and generate systemd unit files from them."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "sudo systemctl daemon-reload",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 43,
                    "byteStart": 32
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "You should now be able to query pds.service by running the following command. Note that it will show the unit as inactive until it is started."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "sudo systemctl status pds.service",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Now we can start the units."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "sudo systemctl start pds.service",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 121,
                    "byteStart": 88
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "This should pull in the PDS container image and start it. You can check the status with sudo systemctl status pds.service."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Caddy configuration"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "A valid Caddy configuration should look like this."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "{\n email myemail@example.com\n on_demand_tls {\n  ask http://localhost:3000/tls-check\n }\n}\n\n# PDS\n\n*.pds.example.com, pds.example.com {\n tls {\n  on_demand\n }\n reverse_proxy http://localhost:3000\n}\n\n# Anything else for your server",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "pdsadmin.sh"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 26,
                    "byteStart": 15
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 76,
                    "byteStart": 72
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 112,
                    "byteStart": 86
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "You can create pdsadmin.sh and put it somewhere in your system's binary PATH, such as /usr/local/bin/pdsadmin.sh."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "#!/usr/bin/env bash\nset -o errexit\nset -o nounset\nset -o pipefail\n\nPDSADMIN_BASE_URL=\"https://raw.githubusercontent.com/bluesky-social/pds/main/pdsadmin\"\nexport PDS_ENV_FILE=\"/etc/pds.env\"\n\n# Command to run\n\nCOMMAND=\"${1:-help}\"\nshift || true\n\n# we don't actually need root here since it only is required\n\n# Download the script, if it exists\n\nSCRIPT_URL=\"${PDSADMIN_BASE_URL}/${COMMAND}.sh\"\nSCRIPT_FILE=\"$(mktemp /tmp/pdsadmin.${COMMAND}.XXXXXX)\"\n\nif [[ \"${COMMAND}\" == \"update\" ]]; then\n  echo \"ERROR: self-update not supported via podman\"\n  exit 1\nfi\n\nif ! curl --fail --silent --show-error --location --output \"${SCRIPT_FILE}\" \"${SCRIPT_URL}\"; then\n  echo \"ERROR: ${COMMAND} not found\"\n  exit 2\nfi\n\nchmod +x \"${SCRIPT_FILE}\"\nif \"${SCRIPT_FILE}\" \"$@\"; then\n  rm -f \"${SCRIPT_FILE}\"\nfi",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Make the file executable."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "sudo chmod +x /usr/local/bin/pdsadmin.sh",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 27,
                    "byteStart": 16
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "You can now run pdsadmin.sh with no arguments to see usage info. You'll of course need to create an account on your PDS."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Verifying your PDS is online and accessible"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Visit https://your-domain.net/xrpc/_health in your browser. Or run the following command in your terminal."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "curl https://your-domain.net/xrpc/_health",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "You should receive a JSON response with a version, similar to this."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "json",
              "plaintext": "{\"version\":\"0.4.204\"}",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 86,
                    "byteStart": 80
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "You'll also need to check that WebSockets are working. You can do this with the wsdump tool. You'll need the latest version of Golang to install it."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "sudo dnf install -y golang",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 25,
                    "byteStart": 19
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Now to install the wsdump tool:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "go install github.com/nrxr/wsdump@latest",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Then run it:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "wsdump \"wss://your-domain.net/xrpc/com.atproto.sync.subscribeRepos?cursor=0\"",
              "syntaxHighlightingTheme": "catppuccin-mocha"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 170,
                    "byteStart": 164
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Note that there will be no events on the WebSocket until they are created in the PDS, so the above command may continue to run with no output. You'll have to press CTRL-C to stop it."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Closing"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 277,
                    "byteStart": 248
                  },
                  "features": [
                    {
                      "uri": "https://github.com/bluesky-social/pds",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 320,
                    "byteStart": 312
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "That's how to setup a Bluesky PDS on CentOS Stream 10. Additionally, this setup should also work on AlmaLinux 10, Rocky Linux 10, and Fedora 43, but will not work on any earlier versions of those distributions. I recommend reading the README.md at github.com/bluesky-social/pds for more information on using the pdsadmin command, setting up SMTP, and troubleshooting."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 106,
                    "byteStart": 85
                  },
                  "features": [
                    {
                      "uri": "https://tilde.zone/@hyperreal",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 180,
                    "byteStart": 140
                  },
                  "features": [
                    {
                      "uri": "https://github.com/bluesky-social/deploy-recipes",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                }
              ],
              "plaintext": "If you have any questions or issues with this setup, feel free to reach out to me at @hyperreal@tilde.zone. You may also submit an issue at github.com/bluesky-social/deploy-recipes, and either I or someone else will help troubleshoot the issue."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": ""
            }
          }
        ]
      }
    ]
  },
  "description": "",
  "publishedAt": "2026-04-13T19:08:21.236Z"
}