diff --git a/.env-sample b/.env-sample index 8bbcca5..fdef042 100644 --- a/.env-sample +++ b/.env-sample @@ -3,7 +3,7 @@ ##### # Full stack on `docker compose up`; use `docker compose --profile backend up` for backend only -COMPOSE_PROFILES=backend,franky +COMPOSE_PROFILES=backend,pubky-app # mainnet or testnet network NETWORK=testnet # mainnet @@ -21,7 +21,7 @@ POSTGRES_DB=pubky_homeserver # # Testnet NEXT_PUBLIC_HOMESERVER=8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo -NEXT_PUBLIC_NEXUS=http://localhost:8080 +NEXT_PUBLIC_NEXUS_URL=http://localhost:8080 NEXT_PUBLIC_TESTNET=true NEXT_PUBLIC_DEFAULT_HTTP_RELAY=http://localhost:15412/link/ NEXT_PUBLIC_PKARR_RELAYS=["https://pkarr.pubky.app","https://pkarr.pubky.org"] # Ignored when using Testnet @@ -30,10 +30,10 @@ NEXT_ENABLE_PLAUSIBLE=false # Mainnet # NEXT_PUBLIC_HOMESERVER=8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo -# NEXT_PUBLIC_NEXUS=http://localhost:8080 +# NEXT_PUBLIC_NEXUS_URL=http://localhost:8080 # NEXT_PUBLIC_TESTNET=false # NEXT_PUBLIC_DEFAULT_HTTP_RELAY=https://httprelay.staging.pubky.app/link/ # # Replace with these for mainnet Staging servers # NEXT_PUBLIC_HOMESERVER=ufibwbmed6jeq9k4p583go95wofakh9fwpp4k734trq79pd9u1uy -# NEXT_PUBLIC_NEXUS=https://nexus.staging.pubky.app +# NEXT_PUBLIC_NEXUS_URL=https://nexus.staging.pubky.app diff --git a/Readme.md b/Readme.md index 027d1ac..b5c864a 100644 --- a/Readme.md +++ b/Readme.md @@ -1,93 +1,125 @@ -# 💻 Pubky Docker +# Pubky Docker -One click setup to run locally an example full Pubky Social (App) stack. This orchestration will run: +One-click setup to run a local Pubky Social stack. This orchestration can run: -- [Pkarr relay](https://github.com/pubky/pkarr): -- [Pubky Homeserver](https://github.com/pubky/pubky-core/tree/main/pubky-homeserver): Instance of pubky decentralized data storage. -- [Pubky Nexus](https://github.com/pubky/pubky-nexus): aggregator and indexer of `/pub/pubky.app` data that creates a powerful social-media-like API -- [Pubky App](https://github.com/pubky/pubky-app): client for the pubky social media app. +- [Pubky Homeserver](https://github.com/pubky/pubky-core/tree/main/pubky-homeserver), from `pubky/pubky-core` +- [Pubky Nexus](https://github.com/pubky/pubky-nexus), from `pubky/pubky-nexus` +- [Homegate](https://github.com/pubky/homegate), from `pubky/homegate` +- [Pubky App](https://github.com/pubky/pubky-app), as the `pubky-app` Compose service -## ⚠️ Warning +Third-party infrastructure images (Postgres, Neo4j, Redis, Redis Insight, WireMock) are pulled from their public registries. -Running the full stack is overkill if your goal is only to develop an application using Pubky. +## Warning -For application development, use the official client libraries instead: +Running the full stack is overkill if your goal is only to develop an application using Pubky. For application development, use the official client libraries instead: - JavaScript: https://www.npmjs.com/package/@synonymdev/pubky - Rust: https://crates.io/crates/pubky -Only run this full orchestration if you're specifically experimenting with the complete stack with interest on the Nexus indexer and the social frontend client. +Only run this full orchestration if you are experimenting with the complete stack, especially the Nexus indexer and the social frontend. -## Using public docker images +## Local Setup From Source (Recommended) -All images are stored in public [registry](https://hub.docker.com/u/synonymsoft) and by default the `latest` tag is used. +For contributors and local development, use `pubky-docker-cli.sh`. It clones the service repositories, checks out the refs you choose, builds Pubky images from source, and starts the stack. The public Docker registry is not used for Pubky services on this path. -The image tag and registry are environment variables, so if needed they could be changed +Run the script from this directory: -``` -REGISTRY - registry by default synonymsoft -PUBKY_APP_TAG - tag for pubky-app/client by default latest -PUBKY_NEXUS_TAG - tag for pubky-nexus by default latest -HOMESERVER_TAG - tag for homeserver by default latest -PKARR_TAG - tag for pkarr by default latest +```bash +./pubky-docker-cli.sh ``` -Make a copy of `.env-sample` into `.env` and set your preferences for `mainnet` or `testnet`. +The script will: -Run: +- Check that `git`, Docker, and Docker Compose are available. +- Check that Git can read from GitHub before attempting clones. +- Copy `.env-sample` to `.env` if `.env` does not already exist. +- If `.build-state` has a complete record for the selected services, list those commits and offer to start the stack immediately or proceed to ref selection. +- Ask for a commit, tag, or branch for each service. Press Enter to use the head of the repository's default branch. +- Clone or update the service repositories next to this directory. +- Build local Docker images only for services whose checked-out commit changed. +- Start the stack with Docker Compose. -``` -docker compose up -d -``` +The directory containing this project can be named `pubky-docker`, `docker`, or anything else. Repositories are cloned beside that directory. -## Building from scratch +### Backend Only -### ⚙️ Setup +If you want to run your own frontend separately: -This setup builds directly from the `pubky/pkarr`, `pubky/pubky-core`, `pubky/pubky-nexus`, and `pubky/pubky-app` repositories. +```bash +./pubky-docker-cli.sh --backend-only +``` -Make a copy of `.env-sample` into `.env` and set your preferences for `mainnet` or `testnet`. +This still clones, builds, and runs `pubky-core`, `pubky-nexus`, and `homegate`. -To run entire stack: -```bash -docker compose up +### Directory Layout + +After running the script, your workspace will look similar to this: + +```text +your_working_directory/ +├── pubky-docker/ +├── pubky-core/ +├── pubky-nexus/ +├── homegate/ +└── pubky-app/ ``` -To run without pubky-app client: +### Re-running + +Run the script again to pick new refs: + ```bash -docker compose --profile backend up +./pubky-docker-cli.sh ``` -### 📁 Directory Structure Requirement +For existing repositories, the script refuses to change refs if there are local changes. Commit, stash, or clean those changes first, then rerun. + +The script records the last built commit per Compose service in `.build-state`. On later runs, unchanged services skip the image build step. If `.build-state` is complete for your selected profile set, you can start the stack without going through ref selection again. -Before running `docker compose up`, ensure the following four repositories are cloned **at the same directory level** as `pubky-docker`. This is necessary because the Docker setup references them via relative paths. +## Using Public Docker Images -Your directory should look like this: +All Pubky service images are published on the public [Synonymsoft registry](https://hub.docker.com/u/synonymsoft). By default, Compose uses the `latest` tag. +Image tags and registry can be overridden in `.env`: + +```text +REGISTRY # default: synonymsoft +HOMESERVER_TAG # default: latest +PUBKY_NEXUS_TAG # default: latest +PUBKY_APP_TAG # default: latest +HOMEGATE_TAG # default: latest ``` -your_working_directory/ -├── pubky-docker/ # this project! -├── pkarr/ -├── pubky-core/ -├── pubky-nexus/ -├── pubky-app/ + +Copy `.env-sample` to `.env` and set your preferences for `mainnet` or `testnet`: + +```bash +cp .env-sample .env ``` -Clone each required repository: +Start the full stack (profiles are set via `COMPOSE_PROFILES` in `.env`): +```bash +docker compose up -d ``` -git clone https://github.com/pubky/pubky-docker.git # this repository -git clone https://github.com/pubky/pkarr.git -git clone https://github.com/pubky/pubky-core.git -git clone https://github.com/pubky/pubky-nexus.git -git clone https://github.com/pubky/pubky-app.git + +Backend only: + +```bash +docker compose --profile backend up -d ``` -Then navigate into `pubky-docker`, configure your `.env`, and run: +This path does not clone or build service repositories. You only need the compose files from this project and a configured `.env`. + +## Manual Compose (After Preparing Repositories) +If you have already cloned the service repositories and checked out refs yourself, you can use Compose directly: + +```bash +docker compose --profile backend --profile pubky-app up -d ``` -cd pubky-docker -cp .env-sample .env -# edit .env to choose between mainnet or testnet -docker compose up + +Backend only: + +```bash +docker compose --profile backend up -d ``` diff --git a/docker-compose.yml b/docker-compose.yml index 14af3ec..1d47358 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: homeserver: profiles: [backend] container_name: homeserver - image: "${REGISTRY:-synonymsoft}/homeserver:${HOMESERVER_TAG:-latest}" + image: "${REGISTRY:-synonymsoft}/homeserver-${HOMESERVER_ENV:-testnet}:${HOMESERVER_TAG:-latest}" restart: always build: context: ../pubky-core @@ -126,13 +126,13 @@ services: - nexus-redis restart: always - # 4. Franky Social UI - franky: - profiles: [franky] - container_name: franky - image: "${REGISTRY:-synonymsoft}/franky-${NETWORK:-testnet}:${FRANKY_TAG:-latest}" + # 4. Pubky App Social UI + pubky-app: + profiles: [pubky-app] + container_name: pubky-app + image: "${REGISTRY:-synonymsoft}/pubky-app-${NETWORK:-testnet}:${PUBKY_APP_TAG:-latest}" build: - context: ../franky + context: ../pubky-app args: NEXT_PUBLIC_DB_VERSION: ${NEXT_PUBLIC_DB_VERSION:-2} NEXT_PUBLIC_DB_NAME: ${NEXT_PUBLIC_DB_NAME:-franky} @@ -163,6 +163,7 @@ services: NEXT_PUBLIC_HOMEGATE_URL: ${NEXT_PUBLIC_HOMEGATE_URL:-http://localhost:6300} NEXT_PUBLIC_MODERATION_ID: ${NEXT_PUBLIC_MODERATION_ID:-euwmq57zefw5ynnkhh37b3gcmhs7g3cptdbw1doaxj1pbmzp3wro} NEXT_PUBLIC_MODERATED_TAGS: ${NEXT_PUBLIC_MODERATED_TAGS:-["nudity"]} + NEXT_ENABLE_PLAUSIBLE: ${NEXT_ENABLE_PLAUSIBLE:-false} BASE_URL_SUPPORT: ${BASE_URL_SUPPORT:-https://support.pubky.app} SUPPORT_API_ACCESS_TOKEN: ${SUPPORT_API_ACCESS_TOKEN:-test-token} SUPPORT_ACCOUNT_ID: ${SUPPORT_ACCOUNT_ID:-1} diff --git a/homeserver.config.toml b/homeserver.config.toml index 6759524..a2451be 100644 --- a/homeserver.config.toml +++ b/homeserver.config.toml @@ -11,3 +11,8 @@ public_ip = "172.18.0.4" [derive] testnet_host = "homeserver" + +[admin] +enabled = true +listen_socket = "0.0.0.0:6288" +admin_password = "admin" diff --git a/pubky-docker-cli.sh b/pubky-docker-cli.sh new file mode 100755 index 0000000..b9bed63 --- /dev/null +++ b/pubky-docker-cli.sh @@ -0,0 +1,373 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_NAME="$(basename "$0")" +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +WORKSPACE_DIR="$(dirname "$SCRIPT_DIR")" + +BACKEND_ONLY=false +BUILD_SERVICES=() +PREPARED_COMMITS=() + +readonly GITHUB_CHECK_REPO="https://github.com/pubky/pubky-core.git" +readonly BUILD_STATE_FILE="$SCRIPT_DIR/.build-state" + +usage() { + cat <&2 + exit 1 +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || fail "Required command '$1' was not found." +} + +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + --backend-only) + BACKEND_ONLY=true + ;; + --help|-h) + usage + exit 0 + ;; + *) + fail "Unknown option: $1" + ;; + esac + shift + done +} + +check_requirements() { + require_command git + require_command docker + + if ! docker compose version >/dev/null 2>&1; then + fail "Docker Compose is not available. Install Docker with Compose v2 support." + fi +} + +check_github_access() { + log "Checking GitHub clone access..." + + if ! git ls-remote "$GITHUB_CHECK_REPO" HEAD >/dev/null 2>&1; then + fail "Could not access GitHub via git. Check your network connection and Git/GitHub configuration, then try again." + fi +} + +copy_env_file() { + local env_sample="$SCRIPT_DIR/.env-sample" + local env_file="$SCRIPT_DIR/.env" + + [ -f "$env_sample" ] || fail "Missing $env_sample." + + if [ -f "$env_file" ]; then + log "Using existing .env." + return + fi + + cp "$env_sample" "$env_file" + log "Created .env from .env-sample." +} + +default_branch_for() { + local repo_url="$1" + local branch + + branch="$(git ls-remote --symref "$repo_url" HEAD 2>/dev/null | sed -n 's#^ref: refs/heads/\([^[:space:]]*\)[[:space:]]*HEAD$#\1#p')" + + if [ -z "$branch" ]; then + branch="main" + fi + + printf '%s\n' "$branch" +} + +prompt_ref() { + local name="$1" + local default_branch="$2" + local ref + + printf 'Commit, tag, or branch for %s [%s]: ' "$name" "$default_branch" >&2 + IFS= read -r ref || ref="" + + if [ -z "$ref" ]; then + ref="$default_branch" + fi + + printf '%s\n' "$ref" +} + +ensure_clean_repo() { + local repo_dir="$1" + local status + + status="$(git -C "$repo_dir" status --porcelain)" + + if [ -n "$status" ]; then + fail "$repo_dir has local changes. Commit, stash, or clean them before changing refs." + fi +} + +checkout_ref() { + local repo_dir="$1" + local ref="$2" + + git -C "$repo_dir" fetch --tags origin + + if git -C "$repo_dir" show-ref --verify --quiet "refs/remotes/origin/$ref"; then + git -C "$repo_dir" checkout --detach "origin/$ref" + return + fi + + git -C "$repo_dir" checkout --detach "$ref" +} + +clone_or_update_repo() { + local name="$1" + local repo_url="$2" + local target_dir="$3" + local ref="$4" + + if [ -d "$target_dir/.git" ]; then + log "Updating $name in $target_dir..." + ensure_clean_repo "$target_dir" + checkout_ref "$target_dir" "$ref" + return + fi + + if [ -e "$target_dir" ]; then + fail "$target_dir exists but is not a Git repository." + fi + + log "Cloning $name into $target_dir..." + git clone "$repo_url" "$target_dir" + checkout_ref "$target_dir" "$ref" +} + +print_repo_summary() { + local name="$1" + local target_dir="$2" + local commit + + commit="$(git -C "$target_dir" rev-parse --short HEAD)" + log "Prepared $name at $commit." +} + +required_services() { + local services=(homeserver nexusd homegate) + + if [ "$BACKEND_ONLY" = false ]; then + services+=(pubky-app) + fi + + printf '%s\n' "${services[@]}" +} + +previous_built_commit() { + local service="$1" + local existing_service + local existing_commit + + [ -f "$BUILD_STATE_FILE" ] || return 0 + + while read -r existing_service existing_commit; do + if [ "$existing_service" = "$service" ]; then + printf '%s\n' "$existing_commit" + return 0 + fi + done < "$BUILD_STATE_FILE" + + return 0 +} + +build_state_is_complete() { + local service + local commit + + [ -f "$BUILD_STATE_FILE" ] || return 1 + + while IFS= read -r service; do + commit="$(previous_built_commit "$service")" + [ -n "$commit" ] || return 1 + done < <(required_services) +} + +print_build_state() { + local service + local commit + local short_commit + + log "Existing build state ($BUILD_STATE_FILE):" + + while IFS= read -r service; do + commit="$(previous_built_commit "$service")" + short_commit="${commit:0:12}" + log " $service: $short_commit" + done < <(required_services) +} + +maybe_offer_resume_prompt() { + local choice + + build_state_is_complete || return 1 + + print_build_state + printf '\n[s] Start stack now [c] Choose refs: ' >&2 + IFS= read -r choice || choice="" + + choice="$(printf '%s' "$choice" | tr '[:upper:]' '[:lower:]')" + + case "$choice" in + c|choose) + return 1 + ;; + *) + return 0 + ;; + esac +} + +mark_service_for_build_if_needed() { + local service="$1" + local commit="$2" + local previous_commit + + PREPARED_COMMITS+=("$service|$commit") + previous_commit="$(previous_built_commit "$service")" + + if [ "$previous_commit" = "$commit" ]; then + log "Source unchanged for $service; skipping image build." + return + fi + + BUILD_SERVICES+=("$service") +} + +record_built_commit() { + local service="$1" + local commit="$2" + local state_dir + local temp_file + local existing_service + local existing_commit + + state_dir="$(dirname "$BUILD_STATE_FILE")" + temp_file="$BUILD_STATE_FILE.tmp" + + mkdir -p "$state_dir" + + if [ -f "$BUILD_STATE_FILE" ]; then + while read -r existing_service existing_commit; do + [ -n "$existing_service" ] || continue + [ "$existing_service" != "$service" ] || continue + printf '%s %s\n' "$existing_service" "$existing_commit" + done < "$BUILD_STATE_FILE" > "$temp_file" + else + : > "$temp_file" + fi + + printf '%s %s\n' "$service" "$commit" >> "$temp_file" + mv "$temp_file" "$BUILD_STATE_FILE" +} + +record_built_commits() { + local entry + local service + local commit + + for entry in "${PREPARED_COMMITS[@]}"; do + IFS='|' read -r service commit <