From 0a14b743f72f7366af096c5d710cf248c92c34e4 Mon Sep 17 00:00:00 2001 From: Matej Date: Sat, 7 Nov 2026 14:50:55 +0100 Subject: [PATCH 1/8] add bluesky --- ct/bluesky-pds.sh | 62 ++++++++++++ install/bluesky-pds-install.sh | 180 +++++++++++++++++++++++++++++++++ json/bluesky-pds.json | 63 ++++++++++++ 3 files changed, 305 insertions(+) create mode 100755 ct/bluesky-pds.sh create mode 100755 install/bluesky-pds-install.sh create mode 100644 json/bluesky-pds.json diff --git a/ct/bluesky-pds.sh b/ct/bluesky-pds.sh new file mode 100755 index 0000000000..5952c9d904 --- /dev/null +++ b/ct/bluesky-pds.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/DevScripts/main" +_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func" +source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func") + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: Community Scripts contributor +# License: MIT | https://github.com/community-scripts/DevScripts/raw/main/LICENSE +# Source: https://github.com/bluesky-social/pds + +APP="Bluesky PDS" +var_tags="${var_tags:-social;atproto}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-4096}" +var_disk="${var_disk:-25}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_unprivileged="${var_unprivileged:-1}" +var_arm64=1 +export var_pds_hostname="${var_pds_hostname:-}" +export var_admin_email="${var_admin_email:-}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if check_for_gh_branch "bluesky-pds" "bluesky-social/atproto" "main"; then + msg_info "Stopping Bluesky PDS" + systemctl stop pds + msg_ok "Stopped Bluesky PDS" + CLEAN_INSTALL=1 fetch_and_deploy_gh_branch "bluesky-pds" "bluesky-social/atproto" "main" + NODE_VERSION="24" setup_nodejs + $STD npm install --global pnpm@11.11.0 + export PUPPETEER_SKIP_DOWNLOAD=true + cd /opt/bluesky-pds + $STD pnpm install --frozen-lockfile --filter 'pds-service...' + $STD pnpm run --recursive --stream --workspace-concurrency 1 --filter 'pds-service...' build -- --force + $STD pnpm install --prod --filter 'pds-service...' --shamefully-hoist --frozen-lockfile --prefer-offline --config.confirmModulesPurge=false + msg_info "Starting Bluesky PDS" + systemctl start pds + msg_ok "Started Bluesky PDS" + msg_ok "Updated Bluesky PDS" + fi + if check_for_gh_release "goat" "bluesky-social/goat"; then + fetch_and_deploy_gh_release "goat" "bluesky-social/goat" "binary" + fi + exit +} + +start +build_container + +description +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} has been successfully initialized!${CL}" +echo -e "${INFO}${YW}PDS URL:${CL} ${BGN}https://${var_pds_hostname:-}${CL}" +echo -e "${INFO}${YW}Administration:${CL} run ${GN}pdsadmin help${CL} inside the container" diff --git a/install/bluesky-pds-install.sh b/install/bluesky-pds-install.sh new file mode 100755 index 0000000000..a77cd396a8 --- /dev/null +++ b/install/bluesky-pds-install.sh @@ -0,0 +1,180 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: Community Scripts contributor +# License: MIT | https://github.com/community-scripts/DevScripts/raw/main/LICENSE +# Source: https://github.com/bluesky-social/pds + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +if [[ -z "${var_pds_hostname:-}" ]]; then + read -r -p "${TAB3}Public PDS domain (for example example.com): " var_pds_hostname +fi +var_pds_hostname="$(printf '%s' "$var_pds_hostname" | tr '[:upper:]' '[:lower:]')" +if [[ ! "$var_pds_hostname" =~ ^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$ ]]; then + msg_error "Invalid Bluesky PDS domain. Use a public DNS name such as example.com." + exit 1 +fi + +if [[ -z "${var_admin_email:-}" ]]; then + read -r -p "${TAB3}Email address for Caddy/Let's Encrypt: " var_admin_email +fi +if [[ ! "$var_admin_email" =~ ^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$ ]]; then + msg_error "Invalid email address." + exit 1 +fi + +msg_info "Installing Bluesky PDS build dependencies" +$STD apt install -y build-essential ca-certificates curl git gpg apt-transport-https libsqlite3-dev libvips-dev openssl pkg-config python3 sqlite3 xxd debian-keyring debian-archive-keyring +msg_ok "Installed Bluesky PDS build dependencies" + +NODE_VERSION="24" setup_nodejs +$STD npm install --global pnpm@11.11.0 +export PUPPETEER_SKIP_DOWNLOAD=true +fetch_and_deploy_gh_branch "bluesky-pds" "bluesky-social/atproto" "main" +cd /opt/bluesky-pds + +msg_info "Building Bluesky PDS from source" +$STD pnpm install --frozen-lockfile --filter 'pds-service...' +$STD pnpm run --recursive --stream --workspace-concurrency 1 --filter 'pds-service...' build -- --force +$STD pnpm install --prod --filter 'pds-service...' --shamefully-hoist --frozen-lockfile --prefer-offline --config.confirmModulesPurge=false +msg_ok "Built Bluesky PDS" + +fetch_and_deploy_gh_release "goat" "bluesky-social/goat" "binary" + +msg_info "Installing Caddy" +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor --yes -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list >/dev/null +chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg /etc/apt/sources.list.d/caddy-stable.list +$STD apt update +$STD apt install -y caddy +msg_ok "Installed Caddy" + +msg_info "Creating Bluesky PDS data directory" +install -d -m 0700 /opt/bluesky-pds_data +PDS_ADMIN_PASSWORD="$(openssl rand -hex 24)" +PDS_JWT_SECRET="$(openssl rand -hex 32)" +PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX="$(openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32)" +cat </opt/bluesky-pds_data/pds.env +PDS_HOSTNAME=${var_pds_hostname} +PDS_JWT_SECRET=${PDS_JWT_SECRET} +PDS_ADMIN_PASSWORD=${PDS_ADMIN_PASSWORD} +PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=${PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX} +PDS_DATA_DIRECTORY=/opt/bluesky-pds_data +PDS_BLOBSTORE_DISK_LOCATION=/opt/bluesky-pds_data/blocks +PDS_BLOB_UPLOAD_LIMIT=314572800 +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 +LOG_ENABLED=true +PDS_RATE_LIMITS_ENABLED=true +PDS_INVITE_REQUIRED=true +PDS_CONFIG +chmod 0600 /opt/bluesky-pds_data/pds.env +msg_ok "Created Bluesky PDS configuration and secrets" + +msg_info "Creating Bluesky PDS systemd service" +cat <<'SYSTEMD_UNIT' >/etc/systemd/system/pds.service +[Unit] +Description=Bluesky Personal Data Server +Documentation=https://github.com/bluesky-social/pds +Wants=network-online.target +After=network-online.target + +[Service] +Type=simple +WorkingDirectory=/opt/bluesky-pds/services/pds +Environment=NODE_ENV=production +Environment=PDS_PORT=3000 +Environment=UV_USE_IO_URING=0 +EnvironmentFile=/opt/bluesky-pds_data/pds.env +ExecStart=/usr/bin/node --heapsnapshot-signal=SIGUSR2 --enable-source-maps --import=@atproto/pds/telemetry index.ts +Restart=on-failure +RestartSec=5 +TimeoutStopSec=30 +LimitNOFILE=65536 +NoNewPrivileges=yes +PrivateTmp=yes +ProtectSystem=strict +ProtectHome=yes +ReadWritePaths=/opt/bluesky-pds_data + +[Install] +WantedBy=multi-user.target +SYSTEMD_UNIT +systemctl daemon-reload +systemctl enable -q --now pds +msg_ok "Created Bluesky PDS systemd service" + +msg_info "Configuring Caddy HTTPS" +cat </etc/caddy/Caddyfile +{ + email ${var_admin_email} + on_demand_tls { + ask http://127.0.0.1:3000/tls-check + } +} + +${var_pds_hostname}, *.${var_pds_hostname} { + tls { + on_demand + } + reverse_proxy http://127.0.0.1:3000 +} +CADDYFILE +install -d -m 0755 /etc/systemd/system/caddy.service.d +cat <<'CADDY_OVERRIDE' >/etc/systemd/system/caddy.service.d/pds.conf +[Service] +Environment=GODEBUG=multipathtcp=0 +CADDY_OVERRIDE +systemctl daemon-reload +systemctl enable -q caddy +systemctl restart caddy +msg_ok "Configured Caddy HTTPS reverse proxy" + +msg_info "Installing native Bluesky PDS administration command" +cat <<'PDSADMIN' >/usr/local/bin/pdsadmin +#!/usr/bin/env bash +set -Eeuo pipefail +set -a +source /opt/bluesky-pds_data/pds.env +set +a +export PDS_HOST="http://127.0.0.1:3000" +exec goat pds admin "$@" +PDSADMIN +chmod 0755 /usr/local/bin/pdsadmin +msg_ok "Installed pdsadmin command" + +for attempt in {1..30}; do + if curl -fsS http://127.0.0.1:3000/xrpc/_health >/dev/null; then + break + fi + sleep 2 +done +if ! curl -fsS http://127.0.0.1:3000/xrpc/_health >/dev/null; then + msg_error "Bluesky PDS did not become healthy. Check: journalctl -u pds -n 100 --no-pager" + exit 1 +fi + +motd_ssh +customize +cleanup_lxc + +msg_ok "Bluesky PDS installation completed" +echo -e "${INFO}${YW}PDS URL:${CL} ${BGN}https://${var_pds_hostname}${CL}" +echo -e "${INFO}${YW}Health check:${CL} ${GN}https://${var_pds_hostname}/xrpc/_health${CL}" +echo -e "${INFO}${YW}PDS administration:${CL} ${GN}pdsadmin help${CL}" +echo -e "${INFO}${YW}Create an account:${CL} ${GN}pdsadmin account create --handle HANDLE.${var_pds_hostname} --email EMAIL --password PASSWORD${CL}" +echo -e "${INFO}${YW}Admin password is stored in:${CL} ${GN}/opt/bluesky-pds_data/pds.env${CL}" +echo -e "${INFO}${YW}Logs:${CL} ${GN}journalctl -u pds -f${CL}" +echo -e "${INFO}${YW}Required DNS:${CL} ${GN}A ${var_pds_hostname} and A *.${var_pds_hostname} pointing to this LXC${CL}" +echo -e "${INFO}${YW}Required inbound ports:${CL} ${GN}TCP 80 and 443${CL}" diff --git a/json/bluesky-pds.json b/json/bluesky-pds.json new file mode 100644 index 0000000000..eea8903e1c --- /dev/null +++ b/json/bluesky-pds.json @@ -0,0 +1,63 @@ +{ + "name": "Bluesky PDS", + "slug": "bluesky-pds", + "categories": [0], + "date_created": "2026-11-07", + "type": "ct", + "updateable": true, + "privileged": false, + "interface_port": 443, + "documentation": "https://github.com/bluesky-social/pds", + "website": "https://bsky.social/", + "repository": "https://github.com/bluesky-social/pds", + "architectures": ["amd64", "arm64"], + "platforms": ["pve"], + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/bluesky.webp", + "description": "Host a federated Bluesky Personal Data Server (PDS) and manage accounts on your own domain. This deployment builds and runs the official TypeScript PDS directly in the LXC without Docker.", + "install_methods": [ + { + "type": "default", + "script": "ct/bluesky-pds.sh", + "config_path": "/opt/bluesky-pds_data/pds.env", + "resources": { + "cpu": 2, + "ram": 4096, + "hdd": 25, + "os": "Debian", + "version": "13" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "app_vars": [ + { + "name": "var_pds_hostname", + "label": "Public PDS domain", + "type": "text", + "required": true + }, + { + "name": "var_admin_email", + "label": "Let's Encrypt contact email", + "type": "text", + "required": true + } + ], + "notes": [ + { + "text": "Create A records for your PDS domain and its wildcard (*.your-domain) pointing to this LXC before installation.", + "type": "warning" + }, + { + "text": "TCP ports 80 and 443 must reach this LXC. They must not be in use by another service inside the container.", + "type": "warning" + }, + { + "text": "This is a source-built, community-maintained native deployment; it is not the upstream Docker installer and needs testing before production use.", + "type": "info" + } + ] +} From c088a21fa29fb91b45b733aafe9e3ecb1526c115 Mon Sep 17 00:00:00 2001 From: Matej Date: Sat, 7 Nov 2026 15:15:36 +0100 Subject: [PATCH 2/8] Fix Bluesky PDS installer filename --- install/{bluesky-pds-install.sh => blueskypds-install.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename install/{bluesky-pds-install.sh => blueskypds-install.sh} (100%) diff --git a/install/bluesky-pds-install.sh b/install/blueskypds-install.sh similarity index 100% rename from install/bluesky-pds-install.sh rename to install/blueskypds-install.sh From 5dc4cbb3d969a42572c8a78ce0f51db925911dc2 Mon Sep 17 00:00:00 2001 From: Matej Date: Sat, 7 Nov 2026 15:30:30 +0100 Subject: [PATCH 3/8] Fix Bluesky PDS pnpm version --- install/blueskypds-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/blueskypds-install.sh b/install/blueskypds-install.sh index a77cd396a8..aa80aef0c4 100755 --- a/install/blueskypds-install.sh +++ b/install/blueskypds-install.sh @@ -35,7 +35,7 @@ $STD apt install -y build-essential ca-certificates curl git gpg apt-transport-h msg_ok "Installed Bluesky PDS build dependencies" NODE_VERSION="24" setup_nodejs -$STD npm install --global pnpm@11.11.0 +$STD npm install --global pnpm@12.16.1 export PUPPETEER_SKIP_DOWNLOAD=true fetch_and_deploy_gh_branch "bluesky-pds" "bluesky-social/atproto" "main" cd /opt/bluesky-pds From c9d4bcc70a7b65022302bee7e1570c374f77ed56 Mon Sep 17 00:00:00 2001 From: Matej Date: Sat, 7 Nov 2026 15:49:35 +0100 Subject: [PATCH 4/8] Fix Bluesky PDS build command --- install/blueskypds-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/blueskypds-install.sh b/install/blueskypds-install.sh index aa80aef0c4..e90956af41 100755 --- a/install/blueskypds-install.sh +++ b/install/blueskypds-install.sh @@ -42,7 +42,7 @@ cd /opt/bluesky-pds msg_info "Building Bluesky PDS from source" $STD pnpm install --frozen-lockfile --filter 'pds-service...' -$STD pnpm run --recursive --stream --workspace-concurrency 1 --filter 'pds-service...' build -- --force +$STD pnpm run --recursive --stream --workspace-concurrency 1 --filter 'pds-service...' build --force $STD pnpm install --prod --filter 'pds-service...' --shamefully-hoist --frozen-lockfile --prefer-offline --config.confirmModulesPurge=false msg_ok "Built Bluesky PDS" From 43ce4f9ab6b9620c3d57e8dd31b92f8371165347 Mon Sep 17 00:00:00 2001 From: Matej Date: Sat, 7 Nov 2026 16:09:59 +0100 Subject: [PATCH 5/8] Fix Bluesky PDS pnpm dependency installation --- ct/bluesky-pds.sh | 6 +++--- install/blueskypds-install.sh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ct/bluesky-pds.sh b/ct/bluesky-pds.sh index 5952c9d904..23843e2377 100755 --- a/ct/bluesky-pds.sh +++ b/ct/bluesky-pds.sh @@ -35,11 +35,11 @@ function update_script() { msg_ok "Stopped Bluesky PDS" CLEAN_INSTALL=1 fetch_and_deploy_gh_branch "bluesky-pds" "bluesky-social/atproto" "main" NODE_VERSION="24" setup_nodejs - $STD npm install --global pnpm@11.11.0 + $STD npm install --global pnpm@12.16.1 export PUPPETEER_SKIP_DOWNLOAD=true cd /opt/bluesky-pds - $STD pnpm install --frozen-lockfile --filter 'pds-service...' - $STD pnpm run --recursive --stream --workspace-concurrency 1 --filter 'pds-service...' build -- --force + $STD pnpm install --frozen-lockfile --filter 'pds-service...' --shamefully-hoist + $STD pnpm run --recursive --stream --workspace-concurrency 1 --filter 'pds-service...' build --force $STD pnpm install --prod --filter 'pds-service...' --shamefully-hoist --frozen-lockfile --prefer-offline --config.confirmModulesPurge=false msg_info "Starting Bluesky PDS" systemctl start pds diff --git a/install/blueskypds-install.sh b/install/blueskypds-install.sh index e90956af41..97536bf05b 100755 --- a/install/blueskypds-install.sh +++ b/install/blueskypds-install.sh @@ -41,7 +41,7 @@ fetch_and_deploy_gh_branch "bluesky-pds" "bluesky-social/atproto" "main" cd /opt/bluesky-pds msg_info "Building Bluesky PDS from source" -$STD pnpm install --frozen-lockfile --filter 'pds-service...' +$STD pnpm install --frozen-lockfile --filter 'pds-service...' --shamefully-hoist $STD pnpm run --recursive --stream --workspace-concurrency 1 --filter 'pds-service...' build --force $STD pnpm install --prod --filter 'pds-service...' --shamefully-hoist --frozen-lockfile --prefer-offline --config.confirmModulesPurge=false msg_ok "Built Bluesky PDS" From 4ceddb9878976359d251b09fe1f893d842cbba0f Mon Sep 17 00:00:00 2001 From: Matej Date: Sat, 7 Nov 2026 19:26:36 +0100 Subject: [PATCH 6/8] remove caddy --- ct/bluesky-pds.sh | 7 +--- install/blueskypds-install.sh | 70 ++++++----------------------------- json/bluesky-pds.json | 18 ++++----- 3 files changed, 22 insertions(+), 73 deletions(-) diff --git a/ct/bluesky-pds.sh b/ct/bluesky-pds.sh index 23843e2377..719db0c44f 100755 --- a/ct/bluesky-pds.sh +++ b/ct/bluesky-pds.sh @@ -2,12 +2,10 @@ _CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/DevScripts/main" _cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func" source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func") - # Copyright (c) 2021-2026 community-scripts ORG # Author: Community Scripts contributor # License: MIT | https://github.com/community-scripts/DevScripts/raw/main/LICENSE # Source: https://github.com/bluesky-social/pds - APP="Bluesky PDS" var_tags="${var_tags:-social;atproto}" var_cpu="${var_cpu:-2}" @@ -18,13 +16,11 @@ var_version="${var_version:-13}" var_unprivileged="${var_unprivileged:-1}" var_arm64=1 export var_pds_hostname="${var_pds_hostname:-}" -export var_admin_email="${var_admin_email:-}" header_info "$APP" variables color catch_errors - function update_script() { header_info check_container_storage @@ -51,7 +47,6 @@ function update_script() { fi exit } - start build_container @@ -60,3 +55,5 @@ msg_ok "Completed Successfully!\n" echo -e "${CREATING}${GN}${APP} has been successfully initialized!${CL}" echo -e "${INFO}${YW}PDS URL:${CL} ${BGN}https://${var_pds_hostname:-}${CL}" echo -e "${INFO}${YW}Administration:${CL} run ${GN}pdsadmin help${CL} inside the container" +echo -e "${INFO}${YW}Cloudflare Tunnel:${CL} route ${var_pds_hostname:-} to HTTP://:3000" +echo -e "${INFO}${YW}No Caddy or public inbound ports are required on this LXC when using Cloudflare Tunnel." diff --git a/install/blueskypds-install.sh b/install/blueskypds-install.sh index 97536bf05b..253b7deb27 100755 --- a/install/blueskypds-install.sh +++ b/install/blueskypds-install.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash - # Copyright (c) 2021-2026 community-scripts ORG # Author: Community Scripts contributor # License: MIT | https://github.com/community-scripts/DevScripts/raw/main/LICENSE @@ -12,26 +11,16 @@ catch_errors setting_up_container network_check update_os - if [[ -z "${var_pds_hostname:-}" ]]; then - read -r -p "${TAB3}Public PDS domain (for example example.com): " var_pds_hostname + read -r -p "${TAB3}Public PDS domain (for example pds.example.com): " var_pds_hostname fi var_pds_hostname="$(printf '%s' "$var_pds_hostname" | tr '[:upper:]' '[:lower:]')" if [[ ! "$var_pds_hostname" =~ ^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$ ]]; then - msg_error "Invalid Bluesky PDS domain. Use a public DNS name such as example.com." + msg_error "Invalid Bluesky PDS domain. Use a public DNS name such as pds.example.com." exit 1 fi - -if [[ -z "${var_admin_email:-}" ]]; then - read -r -p "${TAB3}Email address for Caddy/Let's Encrypt: " var_admin_email -fi -if [[ ! "$var_admin_email" =~ ^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$ ]]; then - msg_error "Invalid email address." - exit 1 -fi - msg_info "Installing Bluesky PDS build dependencies" -$STD apt install -y build-essential ca-certificates curl git gpg apt-transport-https libsqlite3-dev libvips-dev openssl pkg-config python3 sqlite3 xxd debian-keyring debian-archive-keyring +$STD apt install -y build-essential ca-certificates curl git gpg apt-transport-https libsqlite3-dev libvips-dev openssl pkg-config python3 sqlite3 xxd msg_ok "Installed Bluesky PDS build dependencies" NODE_VERSION="24" setup_nodejs @@ -39,7 +28,6 @@ $STD npm install --global pnpm@12.16.1 export PUPPETEER_SKIP_DOWNLOAD=true fetch_and_deploy_gh_branch "bluesky-pds" "bluesky-social/atproto" "main" cd /opt/bluesky-pds - msg_info "Building Bluesky PDS from source" $STD pnpm install --frozen-lockfile --filter 'pds-service...' --shamefully-hoist $STD pnpm run --recursive --stream --workspace-concurrency 1 --filter 'pds-service...' build --force @@ -47,15 +35,6 @@ $STD pnpm install --prod --filter 'pds-service...' --shamefully-hoist --frozen-l msg_ok "Built Bluesky PDS" fetch_and_deploy_gh_release "goat" "bluesky-social/goat" "binary" - -msg_info "Installing Caddy" -curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor --yes -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg -curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list >/dev/null -chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg /etc/apt/sources.list.d/caddy-stable.list -$STD apt update -$STD apt install -y caddy -msg_ok "Installed Caddy" - msg_info "Creating Bluesky PDS data directory" install -d -m 0700 /opt/bluesky-pds_data PDS_ADMIN_PASSWORD="$(openssl rand -hex 24)" @@ -75,6 +54,7 @@ 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 +PDS_SERVICE_HANDLE_DOMAINS=.${var_pds_hostname} LOG_ENABLED=true PDS_RATE_LIMITS_ENABLED=true PDS_INVITE_REQUIRED=true @@ -89,7 +69,6 @@ Description=Bluesky Personal Data Server Documentation=https://github.com/bluesky-social/pds Wants=network-online.target After=network-online.target - [Service] Type=simple WorkingDirectory=/opt/bluesky-pds/services/pds @@ -107,7 +86,6 @@ PrivateTmp=yes ProtectSystem=strict ProtectHome=yes ReadWritePaths=/opt/bluesky-pds_data - [Install] WantedBy=multi-user.target SYSTEMD_UNIT @@ -115,32 +93,6 @@ systemctl daemon-reload systemctl enable -q --now pds msg_ok "Created Bluesky PDS systemd service" -msg_info "Configuring Caddy HTTPS" -cat </etc/caddy/Caddyfile -{ - email ${var_admin_email} - on_demand_tls { - ask http://127.0.0.1:3000/tls-check - } -} - -${var_pds_hostname}, *.${var_pds_hostname} { - tls { - on_demand - } - reverse_proxy http://127.0.0.1:3000 -} -CADDYFILE -install -d -m 0755 /etc/systemd/system/caddy.service.d -cat <<'CADDY_OVERRIDE' >/etc/systemd/system/caddy.service.d/pds.conf -[Service] -Environment=GODEBUG=multipathtcp=0 -CADDY_OVERRIDE -systemctl daemon-reload -systemctl enable -q caddy -systemctl restart caddy -msg_ok "Configured Caddy HTTPS reverse proxy" - msg_info "Installing native Bluesky PDS administration command" cat <<'PDSADMIN' >/usr/local/bin/pdsadmin #!/usr/bin/env bash @@ -148,27 +100,29 @@ set -Eeuo pipefail set -a source /opt/bluesky-pds_data/pds.env set +a -export PDS_HOST="http://127.0.0.1:3000" +# Goat rejects loopback/private IPs as an SSRF safeguard; use the public PDS URL. +export PDS_HOST="https://${PDS_HOSTNAME}" exec goat pds admin "$@" PDSADMIN chmod 0755 /usr/local/bin/pdsadmin msg_ok "Installed pdsadmin command" +msg_info "Waiting for Bluesky PDS health check" for attempt in {1..30}; do - if curl -fsS http://127.0.0.1:3000/xrpc/_health >/dev/null; then + if curl -fsS http://127.0.0.1:3000/xrpc/_health >/dev/null 2>&1; then break fi sleep 2 done -if ! curl -fsS http://127.0.0.1:3000/xrpc/_health >/dev/null; then +if ! curl -fsS http://127.0.0.1:3000/xrpc/_health >/dev/null 2>&1; then msg_error "Bluesky PDS did not become healthy. Check: journalctl -u pds -n 100 --no-pager" exit 1 fi +msg_ok "Bluesky PDS health check passed" motd_ssh customize cleanup_lxc - msg_ok "Bluesky PDS installation completed" echo -e "${INFO}${YW}PDS URL:${CL} ${BGN}https://${var_pds_hostname}${CL}" echo -e "${INFO}${YW}Health check:${CL} ${GN}https://${var_pds_hostname}/xrpc/_health${CL}" @@ -176,5 +130,5 @@ echo -e "${INFO}${YW}PDS administration:${CL} ${GN}pdsadmin help${CL}" echo -e "${INFO}${YW}Create an account:${CL} ${GN}pdsadmin account create --handle HANDLE.${var_pds_hostname} --email EMAIL --password PASSWORD${CL}" echo -e "${INFO}${YW}Admin password is stored in:${CL} ${GN}/opt/bluesky-pds_data/pds.env${CL}" echo -e "${INFO}${YW}Logs:${CL} ${GN}journalctl -u pds -f${CL}" -echo -e "${INFO}${YW}Required DNS:${CL} ${GN}A ${var_pds_hostname} and A *.${var_pds_hostname} pointing to this LXC${CL}" -echo -e "${INFO}${YW}Required inbound ports:${CL} ${GN}TCP 80 and 443${CL}" +echo -e "${INFO}${YW}Cloudflare Tunnel:${CL} ${GN}Create a Published Application route for ${var_pds_hostname} to HTTP://:3000${CL}" +echo -e "${INFO}${YW}No Caddy or public inbound ports are required on this LXC when using Cloudflare Tunnel.${CL}" diff --git a/json/bluesky-pds.json b/json/bluesky-pds.json index eea8903e1c..3787637425 100644 --- a/json/bluesky-pds.json +++ b/json/bluesky-pds.json @@ -6,14 +6,14 @@ "type": "ct", "updateable": true, "privileged": false, - "interface_port": 443, + "interface_port": 3000, "documentation": "https://github.com/bluesky-social/pds", "website": "https://bsky.social/", "repository": "https://github.com/bluesky-social/pds", "architectures": ["amd64", "arm64"], "platforms": ["pve"], "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/bluesky.webp", - "description": "Host a federated Bluesky Personal Data Server (PDS) and manage accounts on your own domain. This deployment builds and runs the official TypeScript PDS directly in the LXC without Docker.", + "description": "Host a federated Bluesky Personal Data Server (PDS) and manage accounts on your own domain. This deployment builds and runs the official TypeScript PDS directly in the LXC without Docker or an in-container reverse proxy.", "install_methods": [ { "type": "default", @@ -38,23 +38,21 @@ "label": "Public PDS domain", "type": "text", "required": true - }, - { - "name": "var_admin_email", - "label": "Let's Encrypt contact email", - "type": "text", - "required": true } ], "notes": [ { - "text": "Create A records for your PDS domain and its wildcard (*.your-domain) pointing to this LXC before installation.", + "text": "Create a Cloudflare Tunnel Published Application route for your PDS hostname. Set the service to HTTP and point it to http://:3000. The PDS must be reachable from the machine running cloudflared.", "type": "warning" }, { - "text": "TCP ports 80 and 443 must reach this LXC. They must not be in use by another service inside the container.", + "text": "Do not put Cloudflare Access authentication in front of the PDS. Ensure WebSockets are enabled. No Caddy, in-container TLS certificate, or public inbound ports are needed when using Cloudflare Tunnel.", "type": "warning" }, + { + "text": "For accounts using the PDS-managed subdomain handles, configure the DNS and hostname routing required for those handles. Custom-domain handles such as krtko.me use an _atproto TXT record pointing to the account DID.", + "type": "info" + }, { "text": "This is a source-built, community-maintained native deployment; it is not the upstream Docker installer and needs testing before production use.", "type": "info" From b7a70f07b16f57af703fb8e37ad2bed09f5b2444 Mon Sep 17 00:00:00 2001 From: Matej Novota Date: Sat, 7 Nov 2026 21:04:00 +0100 Subject: [PATCH 7/8] Remove Cloudflare Tunnel instructions Removed instructions for Cloudflare Tunnel from the installation script. --- install/blueskypds-install.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/install/blueskypds-install.sh b/install/blueskypds-install.sh index 253b7deb27..04c6b23fd2 100755 --- a/install/blueskypds-install.sh +++ b/install/blueskypds-install.sh @@ -130,5 +130,3 @@ echo -e "${INFO}${YW}PDS administration:${CL} ${GN}pdsadmin help${CL}" echo -e "${INFO}${YW}Create an account:${CL} ${GN}pdsadmin account create --handle HANDLE.${var_pds_hostname} --email EMAIL --password PASSWORD${CL}" echo -e "${INFO}${YW}Admin password is stored in:${CL} ${GN}/opt/bluesky-pds_data/pds.env${CL}" echo -e "${INFO}${YW}Logs:${CL} ${GN}journalctl -u pds -f${CL}" -echo -e "${INFO}${YW}Cloudflare Tunnel:${CL} ${GN}Create a Published Application route for ${var_pds_hostname} to HTTP://:3000${CL}" -echo -e "${INFO}${YW}No Caddy or public inbound ports are required on this LXC when using Cloudflare Tunnel.${CL}" From cc26f0e3d23c77c82576a89ea89819dc70830ecb Mon Sep 17 00:00:00 2001 From: Matej Novota Date: Sat, 7 Nov 2026 21:10:03 +0100 Subject: [PATCH 8/8] Revise PDS notes for clarity and accuracy Updated notes for PDS configuration and removed outdated information. --- json/bluesky-pds.json | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/json/bluesky-pds.json b/json/bluesky-pds.json index 3787637425..71f1e1397f 100644 --- a/json/bluesky-pds.json +++ b/json/bluesky-pds.json @@ -42,17 +42,13 @@ ], "notes": [ { - "text": "Create a Cloudflare Tunnel Published Application route for your PDS hostname. Set the service to HTTP and point it to http://:3000. The PDS must be reachable from the machine running cloudflared.", + "text": "Create A records for your PDS domain and its wildcard (*.your-domain) pointing to this LXC before installation.", "type": "warning" }, { - "text": "Do not put Cloudflare Access authentication in front of the PDS. Ensure WebSockets are enabled. No Caddy, in-container TLS certificate, or public inbound ports are needed when using Cloudflare Tunnel.", + "text": "PDS running ob TCP port 3000.", "type": "warning" }, - { - "text": "For accounts using the PDS-managed subdomain handles, configure the DNS and hostname routing required for those handles. Custom-domain handles such as krtko.me use an _atproto TXT record pointing to the account DID.", - "type": "info" - }, { "text": "This is a source-built, community-maintained native deployment; it is not the upstream Docker installer and needs testing before production use.", "type": "info"