Last active
November 26, 2024 16:24
-
-
Save sgpthomas/a709db720f2e1ef4860c79354af71b80 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| version: '3.9' | |
| services: | |
| pds: | |
| container_name: pds | |
| image: ghcr.io/bluesky-social/pds:0.4 | |
| network_mode: host | |
| restart: unless-stopped | |
| volumes: | |
| - type: bind | |
| source: /pds | |
| target: /pds | |
| env_file: | |
| - /pds/pds.env | |
| watchtower: | |
| container_name: watchtower | |
| image: containrrr/watchtower:latest | |
| network_mode: host | |
| volumes: | |
| - type: bind | |
| source: /var/run/docker.sock | |
| target: /var/run/docker.sock | |
| restart: unless-stopped | |
| environment: | |
| WATCHTOWER_CLEANUP: true | |
| WATCHTOWER_SCHEDULE: "@midnight" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -o errexit | |
| set -o nounset | |
| set -o pipefail | |
| # System info. | |
| PLATFORM="$(uname --hardware-platform || true)" | |
| DISTRIB_CODENAME="$(lsb_release --codename --short || true)" | |
| DISTRIB_ID="$(lsb_release --id --short | tr '[:upper:]' '[:lower:]' || true)" | |
| # Secure generator comands | |
| GENERATE_SECURE_SECRET_CMD="openssl rand --hex 16" | |
| GENERATE_K256_PRIVATE_KEY_CMD="openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32" | |
| # The Docker compose file. | |
| COMPOSE_URL="https://raw.githubusercontent.com/bluesky-social/pds/main/compose.yaml" | |
| COMPOSE_NO_CADDY_URL="https://gist.githubusercontent.com/sgpthomas/a709db720f2e1ef4860c79354af71b80/raw/78da6f9e10837c41ea82c815ff5f1904d95c1de1/compose.yaml" | |
| # The pdsadmin script. | |
| PDSADMIN_URL="https://raw.githubusercontent.com/bluesky-social/pds/main/pdsadmin.sh" | |
| # System dependencies. | |
| REQUIRED_SYSTEM_PACKAGES=" | |
| ca-certificates | |
| curl | |
| gnupg | |
| jq | |
| lsb-release | |
| openssl | |
| sqlite3 | |
| xxd | |
| " | |
| # Docker packages. | |
| REQUIRED_DOCKER_PACKAGES=" | |
| containerd.io | |
| docker-ce | |
| docker-ce-cli | |
| docker-compose-plugin | |
| " | |
| PUBLIC_IP="" | |
| METADATA_URLS=() | |
| METADATA_URLS+=("http://169.254.169.254/v1/interfaces/0/ipv4/address") # Vultr | |
| METADATA_URLS+=("http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address") # DigitalOcean | |
| METADATA_URLS+=("http://169.254.169.254/2021-03-23/meta-data/public-ipv4") # AWS | |
| METADATA_URLS+=("http://169.254.169.254/hetzner/v1/metadata/public-ipv4") # Hetzner | |
| PDS_DATADIR="${1:-/pds}" | |
| PDS_HOSTNAME="${2:-}" | |
| PDS_ADMIN_EMAIL="${3:-}" | |
| PDS_DID_PLC_URL="https://plc.directory" | |
| PDS_BSKY_APP_VIEW_URL="https://api.bsky.app" | |
| PDS_BSKY_APP_VIEW_DID="did:web:api.bsky.app" | |
| PDS_REPORT_SERVICE_URL="https://mod.bsky.app" | |
| PDS_REPORT_SERVICE_DID="did:plc:ar7c4by46qjdydhdevvrndac" | |
| PDS_CRAWLERS="https://bsky.network" | |
| function usage { | |
| local error="${1}" | |
| cat <<USAGE >&2 | |
| ERROR: ${error} | |
| Usage: | |
| sudo bash $0 | |
| Please try again. | |
| USAGE | |
| exit 1 | |
| } | |
| function main { | |
| # Check that user is root. | |
| if [[ "${EUID}" -ne 0 ]]; then | |
| usage "This script must be run as root. (e.g. sudo $0)" | |
| fi | |
| # Check for a supported architecture. | |
| # If the platform is unknown (not uncommon) then we assume x86_64 | |
| if [[ "${PLATFORM}" == "unknown" ]]; then | |
| PLATFORM="x86_64" | |
| fi | |
| if [[ "${PLATFORM}" != "x86_64" ]] && [[ "${PLATFORM}" != "aarch64" ]] && [[ "${PLATFORM}" != "arm64" ]]; then | |
| usage "Sorry, only x86_64 and aarch64/arm64 are supported. Exiting..." | |
| fi | |
| # Enforce that the data directory is /pds since we're assuming it for now. | |
| # Later we can make this actually configurable. | |
| if [[ "${PDS_DATADIR}" != "/pds" ]]; then | |
| usage "The data directory must be /pds. Exiting..." | |
| fi | |
| # Check if PDS is already installed. | |
| if [[ -e "${PDS_DATADIR}/pds.sqlite" ]]; then | |
| echo | |
| echo "ERROR: pds is already configured in ${PDS_DATADIR}" | |
| echo | |
| echo "To do a clean re-install:" | |
| echo "------------------------------------" | |
| echo "1. Stop the service" | |
| echo | |
| echo " sudo systemctl stop pds" | |
| echo | |
| echo "2. Delete the data directory" | |
| echo | |
| echo " sudo rm -rf ${PDS_DATADIR}" | |
| echo | |
| echo "3. Re-run this installation script" | |
| echo | |
| echo " sudo bash ${0}" | |
| echo | |
| echo "For assistance, check https://github.com/bluesky-social/pds" | |
| exit 1 | |
| fi | |
| # | |
| # Attempt to determine server's public IP. | |
| # | |
| # First try using the hostname command, which usually works. | |
| if [[ -z "${PUBLIC_IP}" ]]; then | |
| PUBLIC_IP=$(hostname --all-ip-addresses | awk '{ print $1 }') | |
| fi | |
| # Prevent any private IP address from being used, since it won't work. | |
| if [[ "${PUBLIC_IP}" =~ ^(127\.|10\.|172\.1[6-9]\.|172\.2[0-9]\.|172\.3[0-1]\.|192\.168\.) ]]; then | |
| PUBLIC_IP="" | |
| fi | |
| # Check the various metadata URLs. | |
| if [[ -z "${PUBLIC_IP}" ]]; then | |
| for METADATA_URL in "${METADATA_URLS[@]}"; do | |
| METADATA_IP="$(timeout 2 curl --silent --show-error "${METADATA_URL}" | head --lines=1 || true)" | |
| if [[ "${METADATA_IP}" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then | |
| PUBLIC_IP="${METADATA_IP}" | |
| break | |
| fi | |
| done | |
| fi | |
| if [[ -z "${PUBLIC_IP}" ]]; then | |
| PUBLIC_IP="Server's IP" | |
| fi | |
| # | |
| # Prompt user for required variables. | |
| # | |
| if [[ -z "${PDS_HOSTNAME}" ]]; then | |
| cat <<INSTALLER_MESSAGE | |
| --------------------------------------- | |
| Add DNS Record for Public IP | |
| --------------------------------------- | |
| From your DNS provider's control panel, create the required | |
| DNS record with the value of your server's public IP address. | |
| + Any DNS name that can be resolved on the public internet will work. | |
| + Replace example.com below with any valid domain name you control. | |
| + A TTL of 600 seconds (10 minutes) is recommended. | |
| Example DNS record: | |
| NAME TYPE VALUE | |
| ---- ---- ----- | |
| example.com A ${PUBLIC_IP:-Server public IP} | |
| *.example.com A ${PUBLIC_IP:-Server public IP} | |
| **IMPORTANT** | |
| It's recommended to wait 3-5 minutes after creating a new DNS record | |
| before attempting to use it. This will allow time for the DNS record | |
| to be fully updated. | |
| INSTALLER_MESSAGE | |
| if [[ -z "${PDS_HOSTNAME}" ]]; then | |
| read -p "Enter your public DNS address (e.g. example.com): " PDS_HOSTNAME | |
| fi | |
| fi | |
| if [[ -z "${PDS_HOSTNAME}" ]]; then | |
| usage "No public DNS address specified" | |
| fi | |
| if [[ "${PDS_HOSTNAME}" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then | |
| usage "Invalid public DNS address (must not be an IP address)" | |
| fi | |
| # Admin email | |
| if [[ -z "${PDS_ADMIN_EMAIL}" ]]; then | |
| read -p "Enter an admin email address (e.g. you@example.com): " PDS_ADMIN_EMAIL | |
| fi | |
| if [[ -z "${PDS_ADMIN_EMAIL}" ]]; then | |
| usage "No admin email specified" | |
| fi | |
| if [[ -z "${PDS_ADMIN_EMAIL}" ]]; then | |
| read -p "Enter an admin email address (e.g. you@example.com): " PDS_ADMIN_EMAIL | |
| fi | |
| if [[ -z "${PDS_ADMIN_EMAIL}" ]]; then | |
| usage "No admin email specified" | |
| fi | |
| if ! [[ -e /etc/docker/daemon.json ]]; then | |
| echo "* Configuring Docker daemon" | |
| cat <<'DOCKERD_CONFIG' >/etc/docker/daemon.json | |
| { | |
| "log-driver": "json-file", | |
| "log-opts": { | |
| "max-size": "500m", | |
| "max-file": "4" | |
| } | |
| } | |
| DOCKERD_CONFIG | |
| systemctl restart docker | |
| else | |
| echo "* Docker daemon already configured! Ensure log rotation is enabled." | |
| fi | |
| # | |
| # Create data directory. | |
| # | |
| if ! [[ -d "${PDS_DATADIR}" ]]; then | |
| echo "* Creating data directory ${PDS_DATADIR}" | |
| mkdir --parents "${PDS_DATADIR}" | |
| fi | |
| chmod 700 "${PDS_DATADIR}" | |
| # | |
| # Configure Caddy | |
| # | |
| # ask if you want to configure caddy | |
| CONFIGURE_CADDY_PROMPT="" | |
| read -p "Configure Caddy automatically? (y/N): " CONFIGURE_CADDY_PROMPT | |
| if [[ "${CONFIGURE_CADDY_PROMPT}" =~ ^[Yy] ]]; then | |
| if ! [[ -d "${PDS_DATADIR}/caddy/data" ]]; then | |
| echo "* Creating Caddy data directory" | |
| mkdir --parents "${PDS_DATADIR}/caddy/data" | |
| fi | |
| if ! [[ -d "${PDS_DATADIR}/caddy/etc/caddy" ]]; then | |
| echo "* Creating Caddy config directory" | |
| mkdir --parents "${PDS_DATADIR}/caddy/etc/caddy" | |
| fi | |
| echo "* Creating Caddy config file" | |
| cat <<CADDYFILE >"${PDS_DATADIR}/caddy/etc/caddy/Caddyfile" | |
| { | |
| email ${PDS_ADMIN_EMAIL} | |
| on_demand_tls { | |
| ask http://localhost:3000/tls-check | |
| } | |
| } | |
| *.${PDS_HOSTNAME}, ${PDS_HOSTNAME} { | |
| tls { | |
| on_demand | |
| } | |
| reverse_proxy http://localhost:3000 | |
| } | |
| CADDYFILE | |
| fi | |
| # | |
| # Create the PDS env config | |
| # | |
| # Created here so that we can use it later in multiple places. | |
| PDS_ADMIN_PASSWORD=$(eval "${GENERATE_SECURE_SECRET_CMD}") | |
| cat <<PDS_CONFIG >"${PDS_DATADIR}/pds.env" | |
| PDS_HOSTNAME=${PDS_HOSTNAME} | |
| PDS_JWT_SECRET=$(eval "${GENERATE_SECURE_SECRET_CMD}") | |
| PDS_ADMIN_PASSWORD=${PDS_ADMIN_PASSWORD} | |
| PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=$(eval "${GENERATE_K256_PRIVATE_KEY_CMD}") | |
| PDS_DATA_DIRECTORY=${PDS_DATADIR} | |
| PDS_BLOBSTORE_DISK_LOCATION=${PDS_DATADIR}/blocks | |
| PDS_BLOB_UPLOAD_LIMIT=52428800 | |
| PDS_DID_PLC_URL=${PDS_DID_PLC_URL} | |
| PDS_BSKY_APP_VIEW_URL=${PDS_BSKY_APP_VIEW_URL} | |
| PDS_BSKY_APP_VIEW_DID=${PDS_BSKY_APP_VIEW_DID} | |
| PDS_REPORT_SERVICE_URL=${PDS_REPORT_SERVICE_URL} | |
| PDS_REPORT_SERVICE_DID=${PDS_REPORT_SERVICE_DID} | |
| PDS_CRAWLERS=${PDS_CRAWLERS} | |
| LOG_ENABLED=true | |
| PDS_CONFIG | |
| # | |
| # Download and install pds launcher. | |
| # | |
| if [[ "${CONFIGURE_CADDY_PROMPT}" =~ ^[Yy] ]]; then | |
| echo "* Downloading PDS compose file with caddy service" | |
| curl \ | |
| --silent \ | |
| --show-error \ | |
| --fail \ | |
| --output "${PDS_DATADIR}/compose.yaml" \ | |
| "${COMPOSE_URL}" | |
| else | |
| echo "* Downloading PDS compose file without caddy service" | |
| curl \ | |
| --silent \ | |
| --show-error \ | |
| --fail \ | |
| --output "${PDS_DATADIR}/compose.yaml" \ | |
| "${COMPOSE_NO_CADDY_URL}" | |
| fi | |
| # Replace the /pds paths with the ${PDS_DATADIR} path. | |
| sed --in-place "s|/pds|${PDS_DATADIR}|g" "${PDS_DATADIR}/compose.yaml" | |
| # | |
| # Create the systemd service. | |
| # | |
| echo "* Starting the pds systemd service" | |
| cat <<SYSTEMD_UNIT_FILE >/etc/systemd/system/pds.service | |
| [Unit] | |
| Description=Bluesky PDS Service | |
| Documentation=https://github.com/bluesky-social/pds | |
| Requires=docker.service | |
| After=docker.service | |
| [Service] | |
| Type=oneshot | |
| RemainAfterExit=yes | |
| WorkingDirectory=${PDS_DATADIR} | |
| ExecStart=/usr/bin/docker compose --file ${PDS_DATADIR}/compose.yaml up --detach | |
| ExecStop=/usr/bin/docker compose --file ${PDS_DATADIR}/compose.yaml down | |
| [Install] | |
| WantedBy=default.target | |
| SYSTEMD_UNIT_FILE | |
| systemctl daemon-reload | |
| systemctl enable pds | |
| systemctl restart pds | |
| cat <<INSTALLER_MESSAGE | |
| ======================================================================== | |
| PDS installation successful! | |
| ------------------------------------------------------------------------ | |
| Check service status : sudo systemctl status pds | |
| Watch service logs : sudo docker logs -f pds | |
| Backup service data : ${PDS_DATADIR} | |
| PDS Admin command : pdsadmin | |
| Required Firewall Ports | |
| ------------------------------------------------------------------------ | |
| Service Direction Port Protocol Source | |
| ------- --------- ---- -------- ---------------------- | |
| HTTP TLS verification Inbound 80 TCP Any | |
| HTTP Control Panel Inbound 443 TCP Any | |
| Required DNS entries | |
| ------------------------------------------------------------------------ | |
| Name Type Value | |
| ------- --------- --------------- | |
| ${PDS_HOSTNAME} A ${PUBLIC_IP} | |
| *.${PDS_HOSTNAME} A ${PUBLIC_IP} | |
| Detected public IP of this server: ${PUBLIC_IP} | |
| To see pdsadmin commands, run "pdsadmin help" | |
| ======================================================================== | |
| INSTALLER_MESSAGE | |
| CREATE_ACCOUNT_PROMPT="" | |
| read -p "Create a PDS user account? (y/N): " CREATE_ACCOUNT_PROMPT | |
| if [[ "${CREATE_ACCOUNT_PROMPT}" =~ ^[Yy] ]]; then | |
| pdsadmin account create | |
| fi | |
| } | |
| # Run main function. | |
| main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment