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