Mirror from bluesky-social/pds
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 6f5e85a47c0c8ac4af3f883fa77df875728cabf4 402 lines 12 kB view raw
1#!/bin/bash 2set -o errexit 3set -o nounset 4set -o pipefail 5 6# Disable prompts for apt-get. 7export DEBIAN_FRONTEND="noninteractive" 8 9# System info. 10PLATFORM="$(uname --hardware-platform || true)" 11DISTRIB_CODENAME="$(lsb_release --codename --short || true)" 12DISTRIB_ID="$(lsb_release --id --short | tr '[:upper:]' '[:lower:]' || true)" 13 14# Secure generator comands 15GENERATE_SECURE_SECRET_CMD="openssl rand --hex 16" 16GENERATE_K256_PRIVATE_KEY_CMD="openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32" 17 18# The Docker compose file. 19COMPOSE_URL="https://raw.githubusercontent.com/bluesky-social/pds/main/compose.yaml" 20 21# System dependencies. 22REQUIRED_SYSTEM_PACKAGES=" 23 ca-certificates 24 curl 25 gnupg 26 lsb-release 27 openssl 28 xxd 29" 30# Docker packages. 31REQUIRED_DOCKER_PACKAGES=" 32 docker-ce 33 docker-ce-cli 34 docker-compose-plugin 35 containerd.io 36" 37 38PUBLIC_IP="" 39METADATA_URLS=() 40METADATA_URLS+=("http://169.254.169.254/v1/interfaces/0/ipv4/address") # Vultr 41METADATA_URLS+=("http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address") # DigitalOcean 42METADATA_URLS+=("http://169.254.169.254/2021-03-23/meta-data/public-ipv4") # AWS 43METADATA_URLS+=("http://169.254.169.254/hetzner/v1/metadata/public-ipv4") # Hetzner 44 45PDS_DATADIR="${1:-/pds}" 46PDS_HOSTNAME="${2:-}" 47PDS_ADMIN_EMAIL="${3:-}" 48PDS_DID_PLC_URL="https://plc.bsky-sandbox.dev" 49PDS_BSKY_APP_VIEW_ENDPOINT="https://api.bsky-sandbox.dev" 50PDS_BSKY_APP_VIEW_DID="did:web:api.bsky-sandbox.dev" 51PDS_CRAWLERS="https://bgs.bsky-sandbox.dev" 52 53function usage { 54 local error="${1}" 55 cat <<USAGE >&2 56ERROR: ${error} 57Usage: 58sudo bash $0 59 60Please try again. 61USAGE 62 exit 1 63} 64 65function main { 66 # Check that user is root. 67 if [[ "${EUID}" -ne 0 ]]; then 68 usage "This script must be run as root. (e.g. sudo $0)" 69 fi 70 71 # Check for a supported architecture. 72 # If the platform is unknown (not uncommon) then we assume x86_64 73 if [[ "${PLATFORM}" == "unknown" ]]; then 74 PLATFORM="x86_64" 75 fi 76 if [[ "${PLATFORM}" != "x86_64" ]] && [[ "${PLATFORM}" != "aarch64" ]] && [[ "${PLATFORM}" != "arm64" ]]; then 77 usage "Sorry, only x86_64 and aarch64/arm64 are supported. Exiting..." 78 fi 79 80 # Check for a supported distribution. 81 SUPPORTED_OS="false" 82 if [[ "${DISTRIB_ID}" == "ubuntu" ]]; then 83 if [[ "${DISTRIB_CODENAME}" == "focal" ]]; then 84 SUPPORTED_OS="true" 85 echo "* Detected supported distribution Ubuntu 20.04 LTS" 86 elif [[ "${DISTRIB_CODENAME}" == "jammy" ]]; then 87 SUPPORTED_OS="true" 88 echo "* Detected supported distribution Ubuntu 22.04 LTS" 89 fi 90 elif [[ "${DISTRIB_ID}" == "debian" ]]; then 91 if [[ "${DISTRIB_CODENAME}" == "bullseye" ]]; then 92 SUPPORTED_OS="true" 93 echo "* Detected supported distribution Debian 11" 94 fi 95 fi 96 97 if [[ "${SUPPORTED_OS}" != "true" ]]; then 98 echo "Sorry, only Ubuntu 20.04, 22.04, and Debian 11 are supported by this installer. Exiting..." 99 exit 1 100 fi 101 102 103 # 104 # Attempt to determine server's public IP. 105 # 106 107 # First try using the hostname command, which usually works. 108 if [[ -z "${PUBLIC_IP}" ]]; then 109 PUBLIC_IP=$(hostname --all-ip-addresses | awk '{ print $1 }') 110 fi 111 112 # Prevent any private IP address from being used, since it won't work. 113 if [[ "${PUBLIC_IP}" =~ ^(127\.|10\.|172\.1[6-9]\.|172\.2[0-9]\.|172\.3[0-1]\.|192\.168\.) ]]; then 114 PUBLIC_IP="" 115 fi 116 117 # Check the various metadata URLs. 118 if [[ -z "${PUBLIC_IP}" ]]; then 119 for METADATA_URL in "${METADATA_URLS[@]}"; do 120 METADATA_IP="$(timeout 2 curl --silent --show-error "${METADATA_URL}" | head --lines=1 || true)" 121 if [[ "${METADATA_IP}" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then 122 PUBLIC_IP="${METADATA_IP}" 123 break 124 fi 125 done 126 fi 127 128 if [[ -z "${PUBLIC_IP}" ]]; then 129 PUBLIC_IP="Server's IP" 130 fi 131 132 # 133 # Prompt user for required variables. 134 # 135 if [[ -z "${PDS_HOSTNAME}" ]]; then 136 cat <<INSTALLER_MESSAGE 137--------------------------------------- 138 Add DNS Record for Public IP 139--------------------------------------- 140 141 From your DNS provider's control panel, create the required 142 DNS record with the value of your server's public IP address. 143 144 + Any DNS name that can be resolved on the public internet will work. 145 + Replace example.com below with any valid domain name you control. 146 + A TTL of 600 seconds (10 minutes) is recommended. 147 148 Example DNS record: 149 150 NAME TYPE VALUE 151 ---- ---- ----- 152 example.com A ${PUBLIC_IP:-Server public IP} 153 *.example.com A ${PUBLIC_IP:-Server public IP} 154 155 **IMPORTANT** 156 It's recommended to wait 3-5 minutes after creating a new DNS record 157 before attempting to use it. This will allow time for the DNS record 158 to be fully updated. 159 160INSTALLER_MESSAGE 161 162 if [[ -z "${PDS_HOSTNAME}" ]]; then 163 read -p "Enter your public DNS address (e.g. example.com): " PDS_HOSTNAME 164 fi 165 fi 166 167 if [[ -z "${PDS_HOSTNAME}" ]]; then 168 usage "No public DNS address specified" 169 fi 170 171 if [[ "${PDS_HOSTNAME}" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then 172 usage "Invalid public DNS address (must not be an IP address)" 173 fi 174 175 # Admin email 176 if [[ -z "${PDS_ADMIN_EMAIL}" ]]; then 177 read -p "Enter an admin email address (e.g. you@example.com): " PDS_ADMIN_EMAIL 178 fi 179 if [[ -z "${PDS_ADMIN_EMAIL}" ]]; then 180 usage "No admin email specified" 181 fi 182 183 if [[ -z "${PDS_ADMIN_EMAIL}" ]]; then 184 read -p "Enter an admin email address (e.g. you@example.com): " PDS_ADMIN_EMAIL 185 fi 186 if [[ -z "${PDS_ADMIN_EMAIL}" ]]; then 187 usage "No admin email specified" 188 fi 189 190 191 if [[ -e "${PDS_DATADIR}/pds.sqlite" ]]; then 192 echo 193 echo "ERROR: pds is already configured in ${PDS_DATADIR}" 194 echo 195 echo "To do a clean re-install:" 196 echo "------------------------------------" 197 echo "1. Stop the service" 198 echo 199 echo " sudo systemctl stop pds" 200 echo 201 echo "2. Delete the data directory" 202 echo 203 echo " sudo rm -rf ${PDS_DATADIR}" 204 echo 205 echo "3. Re-run this installation script" 206 echo 207 echo " sudo bash ${0}" 208 echo 209 echo "For assistance, contact support@pds.com" 210 exit 1 211 fi 212 213 # 214 # Install system packages. 215 # 216 if lsof -v >/dev/null 2>&1; then 217 while true; do 218 apt_process_count="$(lsof -n -t /var/cache/apt/archives/lock /var/lib/apt/lists/lock /var/lib/dpkg/lock | wc --lines || true)" 219 if (( apt_process_count == 0 )); then 220 break 221 fi 222 echo "* Waiting for other apt process to complete..." 223 sleep 2 224 done 225 fi 226 227 apt-get update 228 apt-get install --yes ${REQUIRED_SYSTEM_PACKAGES} 229 230 # 231 # Install Docker 232 # 233 if ! docker version >/dev/null 2>&1; then 234 echo "* Installing Docker" 235 mkdir --parents /etc/apt/keyrings 236 237 # Remove the existing file, if it exists, 238 # so there's no prompt on a second run. 239 rm --force /etc/apt/keyrings/docker.gpg 240 curl --fail --silent --show-error --location "https://download.docker.com/linux/${DISTRIB_ID}/gpg" | \ 241 gpg --dearmor --output /etc/apt/keyrings/docker.gpg 242 243 echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/${DISTRIB_ID} ${DISTRIB_CODENAME} stable" >/etc/apt/sources.list.d/docker.list 244 245 apt-get update 246 apt-get install --yes ${REQUIRED_DOCKER_PACKAGES} 247 fi 248 249 # 250 # Configure the Docker daemon so that logs don't fill up the disk. 251 # 252 if ! [[ -e /etc/docker/daemon.json ]]; then 253 echo "* Configuring Docker daemon" 254 cat <<'DOCKERD_CONFIG' >/etc/docker/daemon.json 255{ 256 "log-driver": "json-file", 257 "log-opts": { 258 "max-size": "500m", 259 "max-file": "4" 260 } 261} 262DOCKERD_CONFIG 263 systemctl restart docker 264 else 265 echo "* Docker daemon already configured! Ensure log rotation is enabled." 266 fi 267 268 # 269 # Create data directory. 270 # 271 if ! [[ -d "${PDS_DATADIR}" ]]; then 272 echo "* Creating data directory ${PDS_DATADIR}" 273 mkdir --parents "${PDS_DATADIR}" 274 fi 275 chmod 700 "${PDS_DATADIR}" 276 277 # 278 # Configure Caddy 279 # 280 if ! [[ -d "${PDS_DATADIR}/caddy/data" ]]; then 281 echo "* Creating Caddy data directory" 282 mkdir --parents "${PDS_DATADIR}/caddy/data" 283 fi 284 if ! [[ -d "${PDS_DATADIR}/caddy/etc/caddy" ]]; then 285 echo "* Creating Caddy config directory" 286 mkdir --parents "${PDS_DATADIR}/caddy/etc/caddy" 287 fi 288 289 echo "* Creating Caddy config file" 290 cat <<CADDYFILE >"${PDS_DATADIR}/caddy/etc/caddy/Caddyfile" 291{ 292 email ${PDS_ADMIN_EMAIL} 293} 294 295*.${PDS_HOSTNAME}, ${PDS_HOSTNAME} { 296 tls { 297 on_demand 298 } 299 reverse_proxy http://localhost:3000 300} 301CADDYFILE 302 303 # 304 # Create the PDS env config 305 # 306 cat <<PDS_CONFIG >"${PDS_DATADIR}/pds.env" 307PDS_HOSTNAME=${PDS_HOSTNAME} 308PDS_JWT_SECRET=$(eval "${GENERATE_SECURE_SECRET_CMD}") 309PDS_ADMIN_PASSWORD=$(eval "${GENERATE_SECURE_SECRET_CMD}") 310PDS_REPO_SIGNING_KEY_K256_PRIVATE_KEY_HEX=$(eval "${GENERATE_K256_PRIVATE_KEY_CMD}") 311PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=$(eval "${GENERATE_K256_PRIVATE_KEY_CMD}") 312PDS_DB_SQLITE_LOCATION=${PDS_DATADIR}/pds.sqlite 313PDS_BLOBSTORE_DISK_LOCATION=${PDS_DATADIR}/blocks 314PDS_DID_PLC_URL=${PDS_DID_PLC_URL} 315PDS_BSKY_APP_VIEW_ENDPOINT=${PDS_BSKY_APP_VIEW_ENDPOINT} 316PDS_BSKY_APP_VIEW_DID=${PDS_BSKY_APP_VIEW_DID} 317PDS_CRAWLERS=${PDS_CRAWLERS} 318PDS_CONFIG 319 320 # 321 # Download and install pds launcher. 322 # 323 echo "* Downloading pds compose file" 324 curl \ 325 --silent \ 326 --show-error \ 327 --fail \ 328 --output "${PDS_DATADIR}/compose.yaml" \ 329 "${COMPOSE_URL}" 330 331 # Replace the /pds paths with the ${PDS_DATADIR} path. 332 sed --in-place "s|/pds|${PDS_DATADIR}|g" "${PDS_DATADIR}/compose.yaml" 333 334 # 335 # Create the systemd service. 336 # 337 echo "* Starting the pds systemd service" 338 cat <<SYSTEMD_UNIT_FILE >/etc/systemd/system/pds.service 339[Unit] 340Description=Bluesky PDS Service 341Documentation=https://github.com/bluesky-social/pds 342Requires=docker.service 343After=docker.service 344 345[Service] 346Type=oneshot 347RemainAfterExit=yes 348WorkingDirectory=${PDS_DATADIR} 349ExecStart=/usr/bin/docker compose --file ${PDS_DATADIR}/compose.yaml up --detach 350ExecStop=/usr/bin/docker compose --file ${PDS_DATADIR}/compose.yaml down 351 352[Install] 353WantedBy=default.target 354SYSTEMD_UNIT_FILE 355 356 systemctl daemon-reload 357 systemctl enable pds 358 systemctl restart pds 359 360 # Enable firewall access if ufw is in use. 361 if ufw status >/dev/null 2>&1; then 362 if ! ufw status | grep --quiet '^80[/ ]'; then 363 echo "* Enabling access on TCP port 80 using ufw" 364 ufw allow 80/tcp >/dev/null 365 fi 366 if ! ufw status | grep --quiet '^443[/ ]'; then 367 echo "* Enabling access on TCP port 443 using ufw" 368 ufw allow 443/tcp >/dev/null 369 fi 370 fi 371 372 cat <<INSTALLER_MESSAGE 373======================================================================== 374PDS installation successful! 375------------------------------------------------------------------------ 376 377Check service status : sudo systemctl status pds 378Watch service logs : sudo docker logs -f pds 379Backup service data : ${PDS_DATADIR} 380 381Required Firewall Ports 382------------------------------------------------------------------------ 383Service Direction Port Protocol Source 384------- --------- ---- -------- ---------------------- 385HTTP TLS verification Inbound 80 TCP Any 386HTTP Control Panel Inbound 443 TCP Any 387 388Required DNS entries 389------------------------------------------------------------------------ 390Name Type Value 391------- --------- --------------- 392${PDS_HOSTNAME} A ${PUBLIC_IP} 393*.${PDS_HOSTNAME} A ${PUBLIC_IP} 394 395Detected public IP of this server: ${PUBLIC_IP} 396 397======================================================================== 398INSTALLER_MESSAGE 399} 400 401# Run main function. 402main