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