From e896d360d255677c862c61cb41c5cedca4b8b6ac Mon Sep 17 00:00:00 2001 From: Tyler Conzett Date: Fri, 6 Mar 2026 14:02:38 -0600 Subject: [PATCH 1/9] Created init script to help get a new installation started. Updated .gitignore to ignore backup env files from the init script. --- .gitignore | 2 + init_runestone.sh | 1022 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1024 insertions(+) create mode 100644 init_runestone.sh diff --git a/.gitignore b/.gitignore index e7aaa972..ccda8c1f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ __pycache__ .ssh .nova .env +.env.backup* logfiles datashop build_info @@ -33,3 +34,4 @@ _build **/test/_static bake.log WARP.md +init_runestone.log \ No newline at end of file diff --git a/init_runestone.sh b/init_runestone.sh new file mode 100644 index 00000000..16f4791c --- /dev/null +++ b/init_runestone.sh @@ -0,0 +1,1022 @@ +#!/bin/bash + +################################################################################ +# Runestone First-Time Setup Wizard +################################################################################ +# +# This script automates the initialization of a Runestone server for first-time +# users. It guides you through: +# - Validating prerequisites (Docker, Git) +# - Configuring the .env file with BOOK_PATH +# - Pulling Docker images and starting services +# - Initializing the database +# - Optionally adding and building your first book +# - Optionally creating a course +# +# REQUIREMENTS: +# - Docker Desktop with Docker Compose 2.20.2+ (current: 2.38.2) +# - Git +# - For Windows: WSL2 with this script run from WSL terminal +# +# USAGE: +# ./init_runestone.sh +# +# The script will prompt you for required information and guide you through +# each step of the setup process. +# +################################################################################ + +set -e # Exit on error + +# Color codes for output formatting +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly CYAN='\033[0;36m' +readonly BOLD='\033[1m' +readonly NC='\033[0m' # No Color + +# Log file for debugging +readonly LOG_FILE="init_runestone.log" + +# Platform detection globals +IS_WSL=false +IS_MACOS=false +IS_LINUX=false + +################################################################################ +# Utility Functions +################################################################################ + +# Print functions for wizard-style output +print_header() { + echo -e "\n${BOLD}${BLUE}═══════════════════════════════════════════════════════════════${NC}" + echo -e "${BOLD}${BLUE} $1${NC}" + echo -e "${BOLD}${BLUE}═══════════════════════════════════════════════════════════════${NC}\n" +} + +print_step() { + echo -e "${CYAN}${BOLD}==>${NC} ${BOLD}$1${NC}" +} + +print_success() { + echo -e "${GREEN}[OK]${NC} $1" +} + +print_error() { + echo -e "${RED}[X] ERROR:${NC} $1" >&2 +} + +print_warning() { + echo -e "${YELLOW}[!] WARNING:${NC} $1" +} + +print_info() { + echo -e "${BLUE}[i]${NC} $1" +} + +# Log to file and console +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE" +} + +# Prompt for yes/no with default +prompt_yes_no() { + local prompt="$1" + local default="${2:-y}" + local response + + if [[ "$default" == "y" ]]; then + prompt="$prompt [Y/n]: " + else + prompt="$prompt [y/N]: " + fi + + read -r -p "$(echo -e ${BOLD}${prompt}${NC})" response + response="${response:-$default}" + + [[ "$response" =~ ^[Yy]$ ]] +} + +# Prompt for numbered menu choice +prompt_menu() { + local prompt="$1" + local -n result_var=$2 # nameref to output variable + shift 2 + local options=("$@") + local user_choice + + while true; do + read -r -p "$(echo -e ${BOLD}${prompt}${NC})" user_choice + + # Validate choice is a number within range + if [[ "$user_choice" =~ ^[0-9]+$ ]] && [ "$user_choice" -ge 1 ] && [ "$user_choice" -le "${#options[@]}" ]; then + result_var=$user_choice + return 0 + else + echo "Invalid choice. Please enter a number between 1 and ${#options[@]}." + fi + done +} + +# Prompt for input with optional default (using nameref for return value) +prompt_input() { + local prompt="$1" + local default="$2" + local -n result_var=$3 # nameref to output variable + local response + + if [[ -n "$default" ]]; then + prompt="$prompt [$default]: " + else + prompt="$prompt: " + fi + + read -r -p "$(echo -e ${BOLD}${prompt}${NC})" response + result_var="${response:-$default}" +} + +################################################################################ +# Platform Detection +################################################################################ + +detect_platform() { + print_step "Detecting platform..." + + # Primary method: Check for wslinfo command (most reliable for WSL) + if command -v wslinfo &> /dev/null && wslinfo --version &> /dev/null; then + IS_WSL=true + local wsl_version=$(wslinfo --version 2>/dev/null | head -n 1 || echo "unknown") + print_success "Running on Windows WSL" + log "Platform: Windows WSL (detected via: wslinfo --version: $wsl_version)" + # Fallback methods for older WSL versions or edge cases + elif grep -qi microsoft /proc/version 2>/dev/null || \ + grep -qi wsl /proc/version 2>/dev/null || \ + [[ -n "${WSL_DISTRO_NAME}" ]] || \ + [[ -n "${WSLENV}" ]] || \ + [[ "$(uname -r)" == *Microsoft* ]] || \ + [[ "$(uname -r)" == *microsoft* ]] || \ + [[ "$(uname -r)" == *WSL* ]]; then + IS_WSL=true + print_success "Running on Windows WSL" + log "Platform: Windows WSL (detected via: fallback methods - /proc/version, WSL_DISTRO_NAME, WSLENV, or uname)" + elif [[ "$OSTYPE" == "darwin"* ]]; then + IS_MACOS=true + print_success "Running on macOS" + log "Platform: macOS" + elif [[ "$OSTYPE" == "linux-gnu"* ]] || [[ "$OSTYPE" == "linux" ]]; then + IS_LINUX=true + print_success "Running on Linux" + log "Platform: Linux" + else + print_error "Unknown platform: $OSTYPE" + log "Platform detection failed: OSTYPE=$OSTYPE, uname=$(uname -a)" + exit 1 + fi +} + +################################################################################ +# Prerequisite Validation +################################################################################ + +check_docker() { + print_step "Checking Docker installation..." + + if ! command -v docker &> /dev/null; then + print_error "Docker is not installed or not in PATH" + echo "" + echo "Please install Docker Desktop:" + echo " https://docs.docker.com/compose/install/" + echo "" + if $IS_WSL; then + echo "For Windows WSL: Install Docker Desktop for Windows" + echo " Make sure WSL integration is enabled in Docker Desktop settings" + fi + exit 1 + fi + + # Check if Docker daemon is running + if ! docker info &> /dev/null; then + print_error "Docker daemon is not running" + echo "" + echo "Please start Docker Desktop and try again" + exit 1 + fi + + print_success "Docker is installed and running" + log "Docker check: OK" +} + +check_docker_compose() { + print_step "Checking Docker Compose version..." + + if ! docker compose version &> /dev/null; then + print_error "Docker Compose (v2) is not available" + echo "" + echo "Please install Docker Compose 2.20.2 or later:" + echo " https://docs.docker.com/compose/install/" + exit 1 + fi + + local compose_version + compose_version=$(docker compose version --short 2>/dev/null || echo "0.0.0") + + print_success "Docker Compose version: $compose_version" + log "Docker Compose version: $compose_version" + + # Check minimum version (2.20.2) + local min_version="2.20.2" + if ! printf '%s\n' "$min_version" "$compose_version" | sort -V -C 2>/dev/null; then + print_warning "Docker Compose version is older than recommended ($min_version)" + echo " Current: $compose_version" + echo " Consider updating to the latest version" + echo "" + if ! prompt_yes_no "Continue anyway?"; then + echo "Exiting..." + exit 1 + fi + fi +} + +check_git() { + print_step "Checking Git installation..." + + if ! command -v git &> /dev/null; then + print_error "Git is not installed or not in PATH" + echo "" + echo "Please install Git:" + echo " https://git-scm.com/downloads" + exit 1 + fi + + local git_version + git_version=$(git --version | cut -d' ' -f3) + print_success "Git version: $git_version" + log "Git version: $git_version" +} + +validate_prerequisites() { + print_header "Validating Prerequisites" + + detect_platform + check_docker + check_docker_compose + check_git + + print_success "All prerequisites validated" + echo "" +} + +################################################################################ +# Environment Configuration +################################################################################ + +backup_env() { + if [[ -f .env ]]; then + local timestamp + timestamp=$(date +%Y%m%d_%H%M%S) + local backup_file=".env.backup.$timestamp" + + print_step "Backing up existing .env file..." + cp .env "$backup_file" + print_success "Backed up to: $backup_file" + log "Backed up .env to $backup_file" + fi +} + +create_env_from_sample() { + print_step "Creating .env file from sample.env..." + + if [[ ! -f sample.env ]]; then + print_error "sample.env not found in current directory" + echo "Please run this script from the rs directory" + exit 1 + fi + + cp sample.env .env + print_success "Created .env file" + log "Created .env from sample.env" +} + +convert_windows_path() { + local input_path="$1" + local -n output_var=$2 # nameref to output variable + + log "convert_windows_path called with: $input_path" + log "IS_WSL=$IS_WSL, IS_LINUX=$IS_LINUX, IS_MACOS=$IS_MACOS" + + # Check if it looks like a Windows path + # Pattern 1: C:\path or C:/path (with slash after colon) + # Pattern 2: C:path (backslashes were consumed by bash - no slash after colon) + if [[ "$input_path" =~ ^[A-Za-z]: ]]; then + log "Windows path pattern detected" + + # Extract drive letter (first character) and convert to lowercase using bash + local drive_letter="${input_path:0:1}" + drive_letter="${drive_letter,,}" # Bash 4+ lowercase conversion + + # Extract the rest of the path (after drive letter and colon) + local rest_of_path="${input_path:2}" + + # Remove leading slash or backslash if present + rest_of_path="${rest_of_path#/}" + rest_of_path="${rest_of_path#\\}" + + # Replace all backslashes with forward slashes + rest_of_path="${rest_of_path//\\//}" + + # Remove any duplicate slashes + rest_of_path="${rest_of_path//\/\//\/}" + + # Construct WSL path + local wsl_path="/mnt/${drive_letter}/${rest_of_path}" + + # Remove trailing slash if present + wsl_path="${wsl_path%/}" + + log "Converted to WSL path: $wsl_path" + + echo "" + print_info "Detected Windows path - converting to WSL format:" + echo " Windows: $input_path" + echo " WSL: $wsl_path" + echo "" + + if ! $IS_WSL; then + print_warning "Not running in WSL - path conversion may not work correctly" + echo " For Windows, please run this script from a WSL terminal" + echo " Current platform: IS_WSL=$IS_WSL, IS_LINUX=$IS_LINUX, IS_MACOS=$IS_MACOS" + echo "" + fi + + # Set output variable to converted path + output_var="$wsl_path" + else + # Not a Windows path, return as-is + log "Not a Windows path, returning as-is: $input_path" + output_var="$input_path" + fi +} + +prompt_for_book_path() { + local -n result_var=$1 # nameref to output variable + + print_step "Configuring BOOK_PATH..." + echo "" + echo "BOOK_PATH is the directory where your Runestone books will be stored." + echo "This should be a path on your host machine (not inside Docker)." + echo "" + + if $IS_WSL; then + echo "You can provide paths in either format:" + echo " Windows format: C:\\Projects\\runestone\\books" + echo " WSL format: /mnt/c/Projects/runestone/books" + echo " Linux format: /home/username/runestone/books" + echo "" + print_info "Windows paths will be automatically converted to WSL format" + echo "" + elif $IS_LINUX || $IS_MACOS; then + echo "Examples:" + echo " /home/username/runestone/books (Linux)" + echo " /Users/username/runestone/books (macOS)" + echo " ~/runestone/books (relative to home)" + echo "" + fi + + local user_input_path + prompt_input "Enter the full path to your books directory" "" user_input_path + + if [[ -z "$user_input_path" ]]; then + print_error "BOOK_PATH cannot be empty" + exit 1 + fi + + # Convert Windows paths to WSL format automatically + local original_path="$user_input_path" + local converted_path + convert_windows_path "$user_input_path" converted_path + + # Expand ~ to $HOME if present + converted_path="${converted_path/#\~/$HOME}" + + # Log the final path for debugging + log "BOOK_PATH after conversion: $converted_path (original: $original_path)" + + # Check if directory exists + if [[ ! -d "$converted_path" ]]; then + print_warning "Directory does not exist: $converted_path" + + # If it was a Windows path, provide helpful context + if [[ "$original_path" != "$converted_path" ]]; then + echo "" + echo "The Windows path was converted to: $converted_path" + echo "This directory will be created in WSL and should map to your Windows directory." + echo "" + fi + + if prompt_yes_no "Create this directory?"; then + if mkdir -p "$converted_path" 2>/dev/null; then + print_success "Created directory: $converted_path" + log "Created BOOK_PATH: $converted_path" + + # Verify it's accessible + if [[ ! -w "$converted_path" ]]; then + print_warning "Directory created but may not be writable" + echo " You may need to check permissions" + fi + else + print_error "Failed to create directory: $converted_path" + echo "" + echo "Possible issues:" + echo " - Invalid path format" + echo " - Insufficient permissions" + if [[ "$original_path" =~ ^[A-Za-z]:[/\\] ]]; then + echo " - For Windows paths, the drive must be accessible in WSL" + echo " - Try accessing the drive first: cd /mnt/c" + fi + exit 1 + fi + else + print_error "BOOK_PATH must exist. Please create it and run the script again." + exit 1 + fi + else + print_success "Directory exists: $converted_path" + log "Verified BOOK_PATH exists: $converted_path" + fi + + # Set the result via nameref + result_var="$converted_path" +} + +update_env_file() { + local book_path="$1" + + print_step "Updating .env file with BOOK_PATH..." + + # Use sed to replace the BOOK_PATH line + # This handles both commented and uncommented lines + if grep -q "^BOOK_PATH=" .env; then + # Replace existing uncommented line + sed -i.bak "s|^BOOK_PATH=.*|BOOK_PATH=$book_path|" .env + elif grep -q "^#.*BOOK_PATH=" .env; then + # Uncomment and replace + sed -i.bak "s|^#.*BOOK_PATH=.*|BOOK_PATH=$book_path|" .env + else + # Add new line + echo "BOOK_PATH=$book_path" >> .env + fi + + rm -f .env.bak + print_success "Updated BOOK_PATH in .env" + log "Set BOOK_PATH=$book_path in .env" +} + +configure_environment() { + print_header "Configuring Environment" + + # Show detected platform for debugging + log "configure_environment: IS_WSL=$IS_WSL, IS_LINUX=$IS_LINUX, IS_MACOS=$IS_MACOS" + + backup_env + create_env_from_sample + + local book_path + prompt_for_book_path book_path + update_env_file "$book_path" + + echo "" + print_success "Environment configuration complete" + echo "" +} + +################################################################################ +# Docker Service Initialization +################################################################################ + +pull_images() { + print_step "Pulling Docker images..." + echo "" + print_info "This may take several minutes on first run..." + echo "" + + if docker compose pull; then + print_success "Docker images pulled successfully" + log "Docker images pulled" + else + print_error "Failed to pull Docker images" + exit 1 + fi +} + +start_database() { + print_step "Starting database service..." + + if docker compose up -d db; then + print_success "Database service started" + log "Database service started" + else + print_error "Failed to start database service" + exit 1 + fi + + # Wait for database to be ready + print_info "Waiting for database to be ready..." + sleep 5 + + # Try to check if db is healthy (with timeout) + local max_attempts=30 + local attempt=0 + while [ $attempt -lt $max_attempts ]; do + if docker compose exec -T db pg_isready -U runestone &> /dev/null; then + print_success "Database is ready" + log "Database ready after $attempt attempts" + return 0 + fi + attempt=$((attempt + 1)) + sleep 1 + echo -n "." + done + + echo "" + print_warning "Could not verify database status, proceeding anyway..." +} + +init_database() { + print_step "Initializing database..." + echo "" + + # Check if database is already initialized by checking for the boguscourse + print_info "Checking if database is already initialized..." + if docker compose exec -T db psql -U runestone -d runestone_dev -tAc "SELECT 1 FROM courses WHERE course_name='boguscourse' LIMIT 1;" 2>/dev/null | grep -q "1"; then + echo "" + print_warning "Database appears to be already initialized (test data exists)" + echo "" + echo "Please choose an option:" + echo " 1. Skip initialization and continue" + echo " 2. Reset database (WARNING: destroys all data)" + echo " 3. Exit script" + echo "" + + local choice + prompt_menu "Enter your choice (1-3): " choice "Skip initialization" "Reset database" "Exit" + + case $choice in + 1) + print_success "Skipping database initialization" + log "Database initialization skipped - already initialized" + return 0 + ;; + 2) + print_step "Resetting database..." + echo "" + print_warning "This will destroy all existing data!" + if prompt_yes_no "Are you sure you want to reset the database?" "n"; then + print_info "Stopping services and removing volumes..." + docker compose down -v + echo "" + print_info "Starting database service..." + docker compose up -d db + echo "" + print_info "Waiting for database to be ready..." + sleep 5 + + # Continue to initialization below + print_success "Database reset complete" + log "Database reset and ready for initialization" + else + print_error "Database reset cancelled. Exiting." + exit 1 + fi + ;; + 3) + echo "" + print_info "Exiting script. You can manually handle the database with:" + echo " docker compose down -v # Remove volumes" + echo " docker compose up -d db # Restart database" + echo " ./init_runestone.sh # Re-run this script" + echo "" + exit 0 + ;; + esac + fi + + print_info "Creating tables and test users..." + echo "" + + if docker compose run --rm rsmanage rsmanage initdb; then + print_success "Database initialized successfully" + log "Database initialized" + else + print_error "Failed to initialize database" + echo "" + print_info "If you see 'duplicate key' errors, the database may already be initialized." + print_info "Run: docker compose down -v # to reset and start fresh" + exit 1 + fi +} + +start_all_services() { + print_step "Starting all Runestone services..." + echo "" + + if docker compose up -d; then + print_success "All services started" + log "All services started" + else + print_error "Failed to start services" + exit 1 + fi + + # Give services time to start + print_info "Waiting for services to initialize..." + sleep 5 +} + +verify_server_running() { + print_step "Verifying server is running..." + + local max_attempts=30 + local attempt=0 + + while [ $attempt -lt $max_attempts ]; do + if curl -s -f http://localhost > /dev/null 2>&1; then + print_success "Server is responding at http://localhost" + log "Server verified running" + return 0 + fi + attempt=$((attempt + 1)) + sleep 1 + echo -n "." + done + + echo "" + print_warning "Could not verify server is responding" + print_info "Check logs with: docker compose logs -f" +} + +initialize_services() { + print_header "Initializing Docker Services" + + pull_images + echo "" + start_database + echo "" + init_database + echo "" + start_all_services + echo "" + verify_server_running + echo "" +} + +################################################################################ +# Book Management +################################################################################ + +prompt_add_book() { + echo "" + print_header "Book Setup (Optional)" + echo "" + echo "Would you like to add a book to your Runestone server now?" + echo "You can also do this later using: docker compose run --rm rsmanage rsmanage addbookauthor" + echo "" + + prompt_yes_no "Add a book now?" "y" +} + +prompt_book_details() { + local -n repo_result=$1 # nameref for book_repo + local -n name_result=$2 # nameref for book_name + + print_step "Book Details" + echo "" + + # Provide suggestions for common books + echo "Popular Runestone books:" + echo " - overview: https://github.com/RunestoneInteractive/overview.git" + echo " - thinkcspy: https://github.com/RunestoneInteractive/thinkcspy.git" + echo " - pythonds: https://github.com/RunestoneInteractive/pythonds.git" + echo "" + + local input_repo + prompt_input "Enter the Git repository URL for the book" "" input_repo + + if [[ -z "$input_repo" ]]; then + print_error "Repository URL cannot be empty" + return 1 + fi + + # Try to extract book name from repo URL (e.g., overview from overview.git) + # Use bash parameter expansion instead of basename + local suggested_name + suggested_name="${input_repo##*/}" # Remove everything up to last / + suggested_name="${suggested_name%.git}" # Remove .git extension + + echo "" + local input_name + prompt_input "Enter the book name/document-id (basecourse)" "$suggested_name" input_name + + if [[ -z "$input_name" ]]; then + print_error "Book name cannot be empty" + return 1 + fi + + # Set results via nameref + repo_result="$input_repo" + name_result="$input_name" +} + +clone_book() { + local book_repo="$1" + local book_path="$2" + local -n cloned_name_result=$3 # nameref for result + + print_step "Cloning book repository..." + echo "" + + # Get book name from repo using bash parameter expansion + local book_name + book_name="${book_repo##*/}" # Remove everything up to last / + book_name="${book_name%.git}" # Remove .git extension + + local target_dir="$book_path/$book_name" + + if [[ -d "$target_dir" ]]; then + print_warning "Directory already exists: $target_dir" + if ! prompt_yes_no "Skip cloning and use existing directory?"; then + return 1 + fi + print_info "Using existing directory" + else + if git clone "$book_repo" "$target_dir"; then + print_success "Book cloned to: $target_dir" + log "Cloned $book_repo to $target_dir" + else + print_error "Failed to clone repository" + return 1 + fi + fi + + # Return result via nameref + cloned_name_result="$book_name" +} + +add_book_to_db() { + local book_name="$1" + + print_step "Adding book to database..." + echo "" + echo "You will be prompted for:" + echo " - document-id or basecourse: $book_name" + echo " - Runestone username of author/admin: testuser1" + echo "" + print_info "Press Enter to continue..." + read -r + + # Run the addbookauthor command interactively + # We'll provide the inputs via echo pipe + if echo -e "${book_name}\ntestuser1" | docker compose run --rm rsmanage rsmanage addbookauthor; then + print_success "Book added to database" + log "Added book $book_name to database" + else + print_error "Failed to add book to database" + return 1 + fi +} + +build_book() { + local book_name="$1" + + print_step "Building book..." + echo "" + + local is_pretex=false + if prompt_yes_no "Is this a PreTeXt book?" "n"; then + is_pretex=true + fi + + echo "" + print_info "Building $book_name... This may take several minutes..." + echo "" + + if $is_pretex; then + if docker compose run --rm rsmanage rsmanage build --ptx "$book_name"; then + print_success "Book built successfully" + log "Built PreTeXt book: $book_name" + else + print_error "Failed to build book" + return 1 + fi + else + if docker compose run --rm rsmanage rsmanage build "$book_name"; then + print_success "Book built successfully" + log "Built book: $book_name" + else + print_error "Failed to build book" + return 1 + fi + fi +} + +setup_book() { + if ! prompt_add_book; then + return 0 + fi + + # Get book details + local book_repo + local book_name + if ! prompt_book_details book_repo book_name; then + print_warning "Skipping book setup" + return 1 + fi + + # Get BOOK_PATH from .env using read loop to avoid command substitution + local book_path + while IFS='=' read -r key value; do + if [[ "$key" == "BOOK_PATH" ]]; then + book_path="$value" + break + fi + done < <(grep "^BOOK_PATH=" .env) + + # Clone the book + if ! clone_book "$book_repo" "$book_path" _unused; then + print_error "Failed to clone book" + return 1 + fi + + echo "" + + # Add to database + if ! add_book_to_db "$book_name"; then + print_warning "Failed to add book to database, but repository is cloned" + return 1 + fi + + echo "" + + # Build the book + if ! build_book "$book_name"; then + print_warning "Failed to build book, but it's added to database" + return 1 + fi + + # Show book URL + echo "" + print_success "Your book is ready!" + echo "" + echo -e "${BOLD}Access your book at:${NC}" + echo -e " ${CYAN}http://localhost/ns/books/published/${book_name}/index.html${NC}" + echo "" +} + +################################################################################ +# Course Management +################################################################################ + +prompt_add_course() { + echo "" + print_header "Course Setup (Optional)" + echo "" + echo "Would you like to create a course for students?" + echo "A base course was already created with your book." + echo "You can create additional courses later with: docker compose run --rm rsmanage rsmanage addcourse" + echo "" + + prompt_yes_no "Create a course now?" "n" +} + +create_course() { + print_step "Creating course..." + echo "" + echo "The rsmanage addcourse command will prompt you for course details." + echo "" + print_info "Press Enter to continue..." + read -r + + if docker compose run --rm rsmanage rsmanage addcourse; then + print_success "Course created successfully" + log "Course created" + else + print_warning "Failed to create course or cancelled" + return 1 + fi +} + +setup_course() { + if prompt_add_course; then + create_course + fi +} + +################################################################################ +# Final Summary +################################################################################ + +show_final_summary() { + print_header "Setup Complete!" + + echo -e "${GREEN}${BOLD}[OK] Runestone server is up and running!${NC}" + echo "" + + echo -e "${BOLD}Access your server:${NC}" + echo -e " ${CYAN}http://localhost${NC}" + echo "" + + echo -e "${BOLD}Default test credentials:${NC}" + echo " Username: testuser1" + echo " Password: xxx" + echo "" + + echo -e "${BOLD}Useful commands:${NC}" + echo " Stop server: docker compose stop" + echo " Start server: docker compose start" + echo " View logs: docker compose logs -f" + echo " Restart services: docker compose restart" + echo " Shut down: docker compose down" + echo "" + + echo -e "${BOLD}Additional management:${NC}" + echo " Add another book: docker compose run --rm rsmanage rsmanage addbookauthor" + echo " Build a book: docker compose run --rm rsmanage rsmanage build " + echo " Create course: docker compose run --rm rsmanage rsmanage addcourse" + echo " Add instructor: docker compose run --rm rsmanage rsmanage addinstructor" + echo " All commands: docker compose run --rm rsmanage rsmanage --help" + echo "" + + echo -e "${BOLD}Documentation:${NC}" + echo " See docs/source/running.rst for more information" + echo "" + + echo -e "${BOLD}Logs and debugging:${NC}" + echo " Setup log: $LOG_FILE" + echo "" +} + +################################################################################ +# Error Handling and Cleanup +################################################################################ + +cleanup_on_error() { + echo "" + print_error "Setup interrupted or failed" + echo "" + echo "Partial setup may have been completed. Check $LOG_FILE for details." + echo "" + echo "To resume or retry:" + echo " 1. Review any error messages above" + echo " 2. Fix any issues (e.g., Docker not running, missing directories)" + echo " 3. Run ./init_runestone.sh again" + echo "" + echo "To clean up and start fresh:" + echo " docker compose down" + echo " rm .env (or restore from backup)" + echo "" + exit 1 +} + +# Trap errors and interrupts +trap cleanup_on_error ERR INT TERM + +################################################################################ +# Main Flow +################################################################################ + +main() { + # Clear log file + echo "Runestone Setup Started at $(date)" > "$LOG_FILE" + + # Welcome message + print_header "Runestone Server Setup Wizard" + echo "This script will guide you through setting up your Runestone server." + echo "The process will:" + echo " 1. Validate prerequisites (Docker, Git)" + echo " 2. Configure your environment (.env file)" + echo " 3. Pull Docker images and start services" + echo " 4. Initialize the database" + echo " 5. Optionally help you add and build your first book" + echo "" + + if ! prompt_yes_no "Ready to begin?" "y"; then + echo "Setup cancelled." + exit 0 + fi + + # Execute setup steps + validate_prerequisites + configure_environment + initialize_services + setup_book + setup_course + + # Show final summary + show_final_summary + + log "Setup completed successfully" +} + +# Run main function +main "$@" From fec25a265cfeac2b403f404941924901a2558a43 Mon Sep 17 00:00:00 2001 From: Tyler Conzett Date: Fri, 6 Mar 2026 15:13:33 -0600 Subject: [PATCH 2/9] Updated documentation to inform the new initialization script. --- docs/source/running.rst | 68 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/docs/source/running.rst b/docs/source/running.rst index 4acaf74e..8a3475fd 100644 --- a/docs/source/running.rst +++ b/docs/source/running.rst @@ -3,6 +3,45 @@ Running a Runestone Server Maybe you just want to run your own server for a small course. Maybe you are an author who just wants to preview what your book will look like in Runestone. (Hint: Its going to look very much like the plain html, unless you have Java, C or C++ code) Or maybe you are interested in getting involved in the development of the Runestone tools, but want to experiment first. This document will help you get started without having to install a lot of software on your own machine, or having to worry about setting up a web server or database or any development environment. This will work for both linux and macOS on Apple Silicon or Intel based machines. It will also work on Windows, but you will need to install WSL2 and Docker Desktop for Windows. +Automated Setup (Recommended for First-Time Users) +--------------------------------------------------- + +For new users, we provide an automated setup script that handles all of the essential configuration steps for you. The script will guide you through the entire process with interactive prompts. + +**Prerequisites:** + +- Docker Desktop with Docker Compose 2.20.2 or later (current version: 2.38.2) +- Git +- For Windows: WSL2 with Docker Desktop WSL integration enabled + +**Quick Start:** + +#. Clone the Runestone repository: ``git clone https://github.com/RunestoneInteractive/rs.git`` + +#. Change to the rs directory: ``cd rs`` + +#. Run the setup script: ``./init_runestone.sh`` + + **Note for Windows users:** You must run this script from a WSL2 terminal, not from PowerShell or Command Prompt. + +The script will: + +- Validate that Docker and Git are installed and running +- Create and configure your ``.env`` file with the ``BOOK_PATH`` +- Pull all required Docker images +- Start and initialize the database +- Optionally clone, build, and configure your first book +- Optionally create a course for students + +After the script completes, you can access your Runestone server at http://localhost with the default test credentials (username: ``testuser1``, password: ``xxx``). + +**Note:** The script creates a log file (``init_runestone.log``) that you can review if you encounter any issues. + +Manual Setup +------------ + +If you prefer more control over the setup process, or if you're troubleshooting issues, you can set up the Runestone server manually by following these steps: + The Runestone server uses docker compose to start up a number of containers that work together to provide the Runestone environment. Here are the steps to get started: #. Install Docker and Docker Compose on your machine. You can find instructions for your operating system here: https://docs.docker.com/compose/install/ If you already have docker installed you can skip this step. but make sure that ``docker compose version`` tells you that you are running 2.20.2 or later. The current version is 2.38.2. @@ -50,6 +89,31 @@ The Runestone server uses docker compose to start up a number of containers that #. Additional commands are available (for example to add an instructor to a course). Run ``docker compose run --rm rsmanage rsmanage --help`` to see a list of available commands. -If you want to stop the server, you can run the following command: ``docker compose stop`` +Managing Your Server +-------------------- + +**Stopping and Starting:** + +- Stop the server: ``docker compose stop`` +- Start the server: ``docker compose start`` +- Restart services: ``docker compose restart`` +- Shut down and remove containers: ``docker compose down`` +- Shut down and remove volumes (WARNING: deletes all data): ``docker compose down -v`` + +**Viewing Logs:** + +To view logs from all services: ``docker compose logs -f`` + +**Common Management Tasks:** + +- Add another book: ``docker compose run --rm rsmanage rsmanage addbookauthor`` +- Build a book: ``docker compose run --rm rsmanage rsmanage build `` +- Build a PreTeXt book: ``docker compose run --rm rsmanage rsmanage build --ptx `` +- Create a course: ``docker compose run --rm rsmanage rsmanage addcourse`` +- Add an instructor to a course: ``docker compose run --rm rsmanage rsmanage addinstructor`` + +**Troubleshooting:** + +If you used the automated setup script (``init_runestone.sh``), check the ``init_runestone.log`` file in the rs directory for detailed information about the setup process. This log can help diagnose any issues that occurred during initialization. -Almost all of this could be scripted, and it would be an awesome idea for a PR if someone wanted to create a little script that would do all of this for you. If you are interested in doing that, please let us know and we can help you get started. +For database issues, you can reset the database by running ``docker compose down -v`` to remove all volumes, then re-run the initialization steps (either manually or via ``./init_runestone.sh``). From 8ab3f3eeda0a51680fd2b6a0ecbd133450558616 Mon Sep 17 00:00:00 2001 From: Tyler Conzett Date: Mon, 9 Mar 2026 21:13:57 -0500 Subject: [PATCH 3/9] Committing self-downloading init script to repo in order to test self-download implementation. --- init_runestone.sh | 240 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 232 insertions(+), 8 deletions(-) diff --git a/init_runestone.sh b/init_runestone.sh index 16f4791c..c06eed41 100644 --- a/init_runestone.sh +++ b/init_runestone.sh @@ -15,11 +15,21 @@ # # REQUIREMENTS: # - Docker Desktop with Docker Compose 2.20.2+ (current: 2.38.2) -# - Git +# - Git (only required if you want to clone book repositories) # - For Windows: WSL2 with this script run from WSL terminal # # USAGE: -# ./init_runestone.sh +# Standalone (one-line install - no repo clone needed): +# curl -fsSL https://raw.githubusercontent.com/RunestoneInteractive/rs/main/init_runestone.sh | bash +# +# Traditional (from cloned repo): +# git clone https://github.com/RunestoneInteractive/rs.git +# cd rs +# ./init_runestone.sh +# +# NOTE: Standalone mode downloads only configuration files (docker-compose.yml, +# sample.env). Application code runs inside pre-built Docker images from +# ghcr.io. Files are created in your current working directory. # # The script will prompt you for required information and guide you through # each step of the setup process. @@ -137,6 +147,159 @@ prompt_input() { result_var="${response:-$default}" } +################################################################################ +# File Management +################################################################################ + +# Download a file from GitHub's raw content +download_file_from_github() { + local filename="$1" + local target_path="$2" + local base_url="https://raw.githubusercontent.com/RunestoneInteractive/rs/main" + local file_url="${base_url}/${filename}" + + log "Attempting to download ${filename} from ${file_url}" + + # Try curl first + if command -v curl &> /dev/null; then + if curl -fsSL "${file_url}" -o "${target_path}" 2>> "$LOG_FILE"; then + # Verify file was downloaded and is not empty + if [[ -s "${target_path}" ]]; then + log "Successfully downloaded ${filename} using curl" + return 0 + else + log "Downloaded file ${filename} is empty" + rm -f "${target_path}" + return 1 + fi + else + log "Failed to download ${filename} using curl" + fi + fi + + # Fallback to wget + if command -v wget &> /dev/null; then + if wget -qO "${target_path}" "${file_url}" 2>> "$LOG_FILE"; then + # Verify file was downloaded and is not empty + if [[ -s "${target_path}" ]]; then + log "Successfully downloaded ${filename} using wget" + return 0 + else + log "Downloaded file ${filename} is empty" + rm -f "${target_path}" + return 1 + fi + else + log "Failed to download ${filename} using wget" + fi + fi + + # Both methods failed + log "Failed to download ${filename} - no working download tool" + return 1 +} + +# Ensure required configuration files exist +ensure_required_files() { + local has_compose=false + local has_sample=false + local in_standalone_mode=false + + # Check if files already exist + if [[ -f docker-compose.yml ]]; then + has_compose=true + log "Found existing docker-compose.yml" + fi + + if [[ -f sample.env ]]; then + has_sample=true + log "Found existing sample.env" + fi + + # If both files exist, we're in traditional mode + if $has_compose && $has_sample; then + print_info "Configuration files found - running in traditional mode" + log "Running in traditional mode (files exist)" + return 0 + fi + + # At least one file is missing - enter standalone mode + in_standalone_mode=true + log "Entering standalone mode - downloading missing files" + + echo "" + print_header "Standalone Mode" + print_info "Running in standalone mode - configuration files will be downloaded" + echo "" + echo "Files will be created in: ${BOLD}$(pwd)${NC}" + echo "" + echo "The following files will be downloaded from the official Runestone repository:" + if ! $has_compose; then + echo " - docker-compose.yml (Docker service configuration)" + fi + if ! $has_sample; then + echo " - sample.env (Environment variable template)" + fi + echo "" + print_info "Your Runestone server will run using pre-built Docker images" + print_info "No source code repository clone is required" + echo "" + + # Download missing files + local download_failed=false + + if ! $has_compose; then + print_step "Downloading docker-compose.yml..." + if download_file_from_github "docker-compose.yml" "./docker-compose.yml"; then + print_success "Downloaded docker-compose.yml" + else + print_error "Failed to download docker-compose.yml" + download_failed=true + fi + fi + + if ! $has_sample; then + print_step "Downloading sample.env..." + if download_file_from_github "sample.env" "./sample.env"; then + print_success "Downloaded sample.env" + else + print_error "Failed to download sample.env" + download_failed=true + fi + fi + + # Check if any downloads failed + if $download_failed; then + echo "" + print_error "Failed to download required files" + echo "" + echo "This may be due to:" + echo " - Network connectivity issues" + echo " - GitHub being unavailable" + echo " - Missing curl/wget tools" + echo "" + echo "Manual download instructions:" + echo " curl -fsSL https://raw.githubusercontent.com/RunestoneInteractive/rs/main/docker-compose.yml -o docker-compose.yml" + echo " curl -fsSL https://raw.githubusercontent.com/RunestoneInteractive/rs/main/sample.env -o sample.env" + echo "" + echo "Or clone the repository:" + echo " git clone https://github.com/RunestoneInteractive/rs.git" + echo " cd rs" + echo " ./init_runestone.sh" + echo "" + exit 1 + fi + + echo "" + print_success "Configuration files downloaded successfully" + echo "" + print_info "You can inspect these files before continuing if needed" + echo "" + + log "Standalone mode setup complete" + return 0 +} + ################################################################################ # Platform Detection ################################################################################ @@ -239,21 +402,61 @@ check_docker_compose() { fi } +check_docker_group() { + # Only check on native Linux (not WSL, not macOS) + # Docker Desktop on WSL and macOS handles permissions differently + if ! $IS_LINUX || $IS_WSL; then + log "Skipping docker group check (not native Linux)" + return 0 + fi + + print_step "Checking Docker group membership..." + + # Check if user is in docker group + if groups | grep -q docker || id -nG | grep -q docker; then + print_success "User is in docker group" + log "Docker group check: OK" + return 0 + fi + + # User is not in docker group + print_warning "You are not in the docker group" + echo "" + echo "Without docker group membership, you may need to run Docker commands with sudo." + echo "" + echo "To add yourself to the docker group, run these commands:" + echo -e " ${CYAN}sudo usermod -aG docker \$USER${NC}" + echo -e " ${CYAN}newgrp docker${NC}" + echo "" + echo "Or log out and back in for the change to take effect." + echo "" + log "Docker group check: WARNING - user not in docker group" + + # Non-fatal - allow user to continue with sudo if they want + if ! prompt_yes_no "Continue anyway?" "y"; then + echo "" + echo "Please add yourself to the docker group and run this script again." + exit 1 + fi + + echo "" +} + check_git() { print_step "Checking Git installation..." if ! command -v git &> /dev/null; then print_error "Git is not installed or not in PATH" echo "" - echo "Please install Git:" - echo " https://git-scm.com/downloads" - exit 1 + log "Git check: FAILED - not installed" + return 1 fi local git_version git_version=$(git --version | cut -d' ' -f3) print_success "Git version: $git_version" log "Git version: $git_version" + return 0 } validate_prerequisites() { @@ -262,7 +465,8 @@ validate_prerequisites() { detect_platform check_docker check_docker_compose - check_git + check_docker_group + ensure_required_files print_success "All prerequisites validated" echo "" @@ -824,6 +1028,16 @@ setup_book() { if ! prompt_add_book; then return 0 fi + + # Check for Git now that we know we need to clone a book + if ! check_git; then + print_warning "Skipping book setup" + echo "Please either:" + echo " 1. Install Git: https://git-scm.com/downloads" + echo " 2. Manually download your book repository and place it in your BOOK_PATH" + return 1 + fi + echo "" # Get book details local book_repo @@ -969,11 +1183,21 @@ cleanup_on_error() { echo "To resume or retry:" echo " 1. Review any error messages above" echo " 2. Fix any issues (e.g., Docker not running, missing directories)" - echo " 3. Run ./init_runestone.sh again" + echo " 3. Run the script again" echo "" echo "To clean up and start fresh:" echo " docker compose down" echo " rm .env (or restore from backup)" + + # Provide additional context if in standalone mode + if [[ -f docker-compose.yml ]] && [[ -f sample.env ]]; then + echo "" + echo "Downloaded configuration files:" + echo " - docker-compose.yml" + echo " - sample.env" + echo "These files will be reused if you run the script again." + fi + echo "" exit 1 } @@ -993,7 +1217,7 @@ main() { print_header "Runestone Server Setup Wizard" echo "This script will guide you through setting up your Runestone server." echo "The process will:" - echo " 1. Validate prerequisites (Docker, Git)" + echo " 1. Validate prerequisites (Docker)" echo " 2. Configure your environment (.env file)" echo " 3. Pull Docker images and start services" echo " 4. Initialize the database" From ddbc442d7cbeede887efde0393b3448ae5d622d1 Mon Sep 17 00:00:00 2001 From: Tyler Conzett Date: Mon, 9 Mar 2026 21:19:24 -0500 Subject: [PATCH 4/9] Moved required file check earlier in the process. --- init_runestone.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init_runestone.sh b/init_runestone.sh index c06eed41..8b82cd58 100644 --- a/init_runestone.sh +++ b/init_runestone.sh @@ -462,11 +462,11 @@ check_git() { validate_prerequisites() { print_header "Validating Prerequisites" + ensure_required_files detect_platform check_docker check_docker_compose check_docker_group - ensure_required_files print_success "All prerequisites validated" echo "" From 87ff8e9f3f55120e04a2b7c5b1ded45f51ff4afa Mon Sep 17 00:00:00 2001 From: Tyler Conzett Date: Mon, 9 Mar 2026 21:36:59 -0500 Subject: [PATCH 5/9] Fixed issues with formatted printing. Switched to two-line execution due to issues with stdin and pipes. --- docs/source/running.rst | 40 +++++++++++++++++++++++++++++++++++++--- init_runestone.sh | 9 +++++---- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/docs/source/running.rst b/docs/source/running.rst index 8a3475fd..17417914 100644 --- a/docs/source/running.rst +++ b/docs/source/running.rst @@ -3,10 +3,44 @@ Running a Runestone Server Maybe you just want to run your own server for a small course. Maybe you are an author who just wants to preview what your book will look like in Runestone. (Hint: Its going to look very much like the plain html, unless you have Java, C or C++ code) Or maybe you are interested in getting involved in the development of the Runestone tools, but want to experiment first. This document will help you get started without having to install a lot of software on your own machine, or having to worry about setting up a web server or database or any development environment. This will work for both linux and macOS on Apple Silicon or Intel based machines. It will also work on Windows, but you will need to install WSL2 and Docker Desktop for Windows. -Automated Setup (Recommended for First-Time Users) ---------------------------------------------------- +Standalone Installation (No Repository Clone) +---------------------------------------------- -For new users, we provide an automated setup script that handles all of the essential configuration steps for you. The script will guide you through the entire process with interactive prompts. +For the quickest setup, you can run the initialization script directly without cloning the repository. This is the recommended method for users who want to run a Runestone server without needing the source code: + +.. code-block:: bash + + curl -fsSL https://raw.githubusercontent.com/RunestoneInteractive/rs/main/init_runestone.sh -o init_runestone.sh + bash init_runestone.sh + +This will: + +- Download the initialization script to your current directory +- When you run it, the script will automatically fetch required configuration files (``docker-compose.yml`` and ``sample.env``) +- Guide you through the complete setup process with interactive prompts + +**Important Notes:** + +- The script creates files in your **current working directory**, so run it from where you want the Runestone configuration to live (e.g., ``mkdir ~/runestone && cd ~/runestone``) +- Your Runestone server runs using pre-built Docker images from GitHub Container Registry—no source code clone required +- Only the configuration files and your books directory are stored locally + +**Prerequisites:** + +- Docker Desktop with Docker Compose 2.20.2 or later (current version: 2.38.2) +- Git (if downloading books from GitHub repositories) +- For Windows: WSL2 with Docker Desktop WSL integration enabled + +**Note for Windows users:** You must run this command from a WSL2 terminal, not from PowerShell or Command Prompt. + +After the script completes, you can access your Runestone server at http://localhost with the default test credentials (username: ``testuser1``, password: ``xxx``). + +The script creates a log file (``init_runestone.log``) that you can review if you encounter any issues. + +Automated Setup (From Cloned Repository) +----------------------------------------- + +If you prefer to work from a cloned repository (recommended for developers and contributors), you can clone the repository first and run the setup script from within it. **Prerequisites:** diff --git a/init_runestone.sh b/init_runestone.sh index 8b82cd58..f7485815 100644 --- a/init_runestone.sh +++ b/init_runestone.sh @@ -19,8 +19,9 @@ # - For Windows: WSL2 with this script run from WSL terminal # # USAGE: -# Standalone (one-line install - no repo clone needed): -# curl -fsSL https://raw.githubusercontent.com/RunestoneInteractive/rs/main/init_runestone.sh | bash +# Standalone (two-line install - no repo clone needed): +# curl -fsSL https://raw.githubusercontent.com/RunestoneInteractive/rs/main/init_runestone.sh -o init_runestone.sh +# bash init_runestone.sh # # Traditional (from cloned repo): # git clone https://github.com/RunestoneInteractive/rs.git @@ -231,7 +232,7 @@ ensure_required_files() { print_header "Standalone Mode" print_info "Running in standalone mode - configuration files will be downloaded" echo "" - echo "Files will be created in: ${BOLD}$(pwd)${NC}" + echo -e "Files will be created in: ${BOLD}$(pwd)${NC}" echo "" echo "The following files will be downloaded from the official Runestone repository:" if ! $has_compose; then @@ -462,11 +463,11 @@ check_git() { validate_prerequisites() { print_header "Validating Prerequisites" - ensure_required_files detect_platform check_docker check_docker_compose check_docker_group + ensure_required_files print_success "All prerequisites validated" echo "" From 54612f1e77577009c78eb6da9e27966e47a9b145 Mon Sep 17 00:00:00 2001 From: Tyler Conzett Date: Tue, 10 Mar 2026 16:17:18 -0500 Subject: [PATCH 6/9] Remove unnecessary pause/prompt. --- init_runestone.sh | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/init_runestone.sh b/init_runestone.sh index f7485815..dc186891 100644 --- a/init_runestone.sh +++ b/init_runestone.sh @@ -972,13 +972,6 @@ add_book_to_db() { local book_name="$1" print_step "Adding book to database..." - echo "" - echo "You will be prompted for:" - echo " - document-id or basecourse: $book_name" - echo " - Runestone username of author/admin: testuser1" - echo "" - print_info "Press Enter to continue..." - read -r # Run the addbookauthor command interactively # We'll provide the inputs via echo pipe @@ -1218,11 +1211,12 @@ main() { print_header "Runestone Server Setup Wizard" echo "This script will guide you through setting up your Runestone server." echo "The process will:" - echo " 1. Validate prerequisites (Docker)" + echo " 1. Validate prerequisites" echo " 2. Configure your environment (.env file)" echo " 3. Pull Docker images and start services" echo " 4. Initialize the database" - echo " 5. Optionally help you add and build your first book" + echo " 5. Optionally help you add and build a book from a git repository" + echo " 6. Optionally help you create a new course for students" echo "" if ! prompt_yes_no "Ready to begin?" "y"; then From 924ab6e8f75bb33cafb41d4daba107ac47656de1 Mon Sep 17 00:00:00 2001 From: Tyler Conzett Date: Tue, 10 Mar 2026 16:29:55 -0500 Subject: [PATCH 7/9] Added default optional environment variables to suppress warnings that repeatedly appear when we run docker commands. --- init_runestone.sh | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/init_runestone.sh b/init_runestone.sh index dc186891..23dc635f 100644 --- a/init_runestone.sh +++ b/init_runestone.sh @@ -500,8 +500,22 @@ create_env_from_sample() { fi cp sample.env .env + + # Add default values for optional variables to suppress Docker Compose warnings + cat >> .env << 'EOF' + +# Optional variables (set to empty to suppress Docker Compose warnings) +HOSTNAME= +LOAD_BALANCER_HOST= +SPACES_KEY= +SPACES_SECRET= +EMAIL_SENDER= +EMAIL_SERVER= +EMAIL_LOGIN= +EOF + print_success "Created .env file" - log "Created .env from sample.env" + log "Created .env from sample.env with optional variable defaults" } convert_windows_path() { From db08c7bd4c831d0801e2dc40d84588ae39764c57 Mon Sep 17 00:00:00 2001 From: Tyler Conzett Date: Wed, 11 Mar 2026 14:54:54 -0500 Subject: [PATCH 8/9] Final touches on init script and updates to supporting documentation. --- README.rst | 31 ++++++ docs/source/running.rst | 209 ++++++++++++++++++++++++++-------------- init_runestone.sh | 8 +- 3 files changed, 173 insertions(+), 75 deletions(-) diff --git a/README.rst b/README.rst index bbd30f5c..5d10f473 100644 --- a/README.rst +++ b/README.rst @@ -21,6 +21,37 @@ If you want to do development on Runestone, or run your own server, you will nee Chat with us on `Discord `_ +Quick Start: Run Your Own Runestone Server +=========================================== + +Get a fully functional Runestone server running on your local machine with a single command: + +.. code-block:: bash + + bash <(curl -fsSL https://raw.githubusercontent.com/RunestoneInteractive/rs/main/init_runestone.sh) + +The setup script will guide you through an interactive installation process. + +**Prerequisites:** + +* Docker Desktop with Docker Compose 2.20.2 or later +* WSL2 (Windows users only - run the command from a WSL2 terminal) +* Git (optional, but recommended for cloning book repositories) + +**Before you start:** + +Navigate to the directory where you want Runestone configuration files created: + +.. code-block:: bash + + mkdir ~/runestone && cd ~/runestone + +**After installation:** + +* Access your server at http://localhost +* Log in with username: ``testuser1``, password: ``xxx`` +* See the `Running a Server guide `_ for complete documentation + Runestone MonoRepo ================== diff --git a/docs/source/running.rst b/docs/source/running.rst index 17417914..3dda6f31 100644 --- a/docs/source/running.rst +++ b/docs/source/running.rst @@ -3,125 +3,192 @@ Running a Runestone Server Maybe you just want to run your own server for a small course. Maybe you are an author who just wants to preview what your book will look like in Runestone. (Hint: Its going to look very much like the plain html, unless you have Java, C or C++ code) Or maybe you are interested in getting involved in the development of the Runestone tools, but want to experiment first. This document will help you get started without having to install a lot of software on your own machine, or having to worry about setting up a web server or database or any development environment. This will work for both linux and macOS on Apple Silicon or Intel based machines. It will also work on Windows, but you will need to install WSL2 and Docker Desktop for Windows. -Standalone Installation (No Repository Clone) ----------------------------------------------- +Quick Start +----------- -For the quickest setup, you can run the initialization script directly without cloning the repository. This is the recommended method for users who want to run a Runestone server without needing the source code: +Get Runestone running with a single command: .. code-block:: bash - curl -fsSL https://raw.githubusercontent.com/RunestoneInteractive/rs/main/init_runestone.sh -o init_runestone.sh - bash init_runestone.sh + bash <(curl -fsSL https://raw.githubusercontent.com/RunestoneInteractive/rs/main/init_runestone.sh) -This will: +The script will guide you through an interactive setup process, asking questions along the way to configure your Runestone server. -- Download the initialization script to your current directory -- When you run it, the script will automatically fetch required configuration files (``docker-compose.yml`` and ``sample.env``) -- Guide you through the complete setup process with interactive prompts +**Before running the command:** -**Important Notes:** +#. Navigate to where you want the configuration files created (e.g., ``mkdir ~/runestone && cd ~/runestone``) +#. Make sure you have the prerequisites installed (see below) +#. For Windows users: Run this from a WSL2 terminal, not PowerShell or Command Prompt -- The script creates files in your **current working directory**, so run it from where you want the Runestone configuration to live (e.g., ``mkdir ~/runestone && cd ~/runestone``) -- Your Runestone server runs using pre-built Docker images from GitHub Container Registry—no source code clone required -- Only the configuration files and your books directory are stored locally +**After setup completes:** -**Prerequisites:** +- Access your server at http://localhost +- Log in with username: ``testuser1``, password: ``xxx`` +- Check ``init_runestone.log`` if you encounter any issues -- Docker Desktop with Docker Compose 2.20.2 or later (current version: 2.38.2) -- Git (if downloading books from GitHub repositories) -- For Windows: WSL2 with Docker Desktop WSL integration enabled +Prerequisites +------------- -**Note for Windows users:** You must run this command from a WSL2 terminal, not from PowerShell or Command Prompt. +Before running the setup script, you need: -After the script completes, you can access your Runestone server at http://localhost with the default test credentials (username: ``testuser1``, password: ``xxx``). +**Required:** -The script creates a log file (``init_runestone.log``) that you can review if you encounter any issues. +- **Docker Desktop** with Docker Compose 2.20.2 or later (current version: 2.38.2) + + - Download from https://docs.docker.com/compose/install/ + - Make sure Docker Desktop is running before starting the setup + - For Windows: Enable WSL2 integration in Docker Desktop settings + - **Important**: Configure file sharing in Docker Desktop (Settings → Resources → File Sharing) to include the directory where you'll create your Runestone configuration and your ``BOOK_PATH`` directory. Without this, Docker won't be able to access your book files. -Automated Setup (From Cloned Repository) ------------------------------------------ +- **WSL2** (Windows users only) + + - The script must run from a WSL2 terminal (Ubuntu, Debian, etc.) + - Do not use PowerShell or Command Prompt + +**Optional:** + +- **Git** - Only needed if you want to clone book repositories + + - Most users will want to add books, so this is recommended + - You can install it later if you skip book setup initially + +How the Setup Script Works +--------------------------- + +The automated setup script performs several steps to get Runestone running. Understanding these steps can help you troubleshoot issues and better understand how Runestone works. + +**Step 1: Platform Detection and Prerequisite Validation** + +The script first detects your operating system (Linux, macOS, or Windows WSL2) and validates: + +- Docker is installed and running +- Docker Compose version is 2.20.2 or later +- Git is installed (optional, but will warn if missing) +- On Linux (non-WSL), checks if you're in the docker group + +**Step 2: Configuration File Setup** + +The script determines if you're running in "standalone mode" or "traditional mode": + +- **Standalone mode**: If ``docker-compose.yml`` and ``sample.env`` don't exist in the current directory, the script downloads them from the GitHub repository +- **Traditional mode**: If you've cloned the repository, it uses the existing files + +This allows the script to work whether you've cloned the repo or are starting from scratch. + +**Step 3: Environment Configuration (BOOK_PATH)** -If you prefer to work from a cloned repository (recommended for developers and contributors), you can clone the repository first and run the setup script from within it. +The script creates a ``.env`` file and prompts you to configure ``BOOK_PATH`` — the directory where your Runestone books will be stored. -**Prerequisites:** +**What is BOOK_PATH?** -- Docker Desktop with Docker Compose 2.20.2 or later (current version: 2.38.2) -- Git -- For Windows: WSL2 with Docker Desktop WSL integration enabled +- A directory on your host machine (not inside Docker) where book source code lives +- This directory is mounted into Docker containers so they can access and build your books +- Example paths: ``~/runestone/books``, ``/home/username/books``, ``C:\Projects\books`` (Windows) -**Quick Start:** +**Windows path handling:** -#. Clone the Runestone repository: ``git clone https://github.com/RunestoneInteractive/rs.git`` +If you're on WSL2, you can provide paths in Windows format (``C:\Projects\books``) and the script automatically converts them to WSL format (``/mnt/c/Projects/books``). -#. Change to the rs directory: ``cd rs`` +**What happens to BOOK_PATH:** -#. Run the setup script: ``./init_runestone.sh`` +- If the directory doesn't exist, the script offers to create it +- The path is saved to ``.env`` so Docker knows where to find your books +- You'll use this directory later when cloning book repositories - **Note for Windows users:** You must run this script from a WSL2 terminal, not from PowerShell or Command Prompt. +**Docker Desktop users:** Make sure this directory is shared with Docker. Go to Docker Desktop → Settings → Resources → File Sharing and add your ``BOOK_PATH`` directory to the list. This allows the Docker containers to access and build your books. -The script will: +**Step 4: Docker Image Pulling** -- Validate that Docker and Git are installed and running -- Create and configure your ``.env`` file with the ``BOOK_PATH`` -- Pull all required Docker images -- Start and initialize the database -- Optionally clone, build, and configure your first book -- Optionally create a course for students +The script runs ``docker compose pull`` to download pre-built Docker images from GitHub Container Registry (ghcr.io). -After the script completes, you can access your Runestone server at http://localhost with the default test credentials (username: ``testuser1``, password: ``xxx``). +**What are these images?** -**Note:** The script creates a log file (``init_runestone.log``) that you can review if you encounter any issues. +Runestone uses a microservices architecture with multiple servers: -Manual Setup ------------- +- ``db`` - PostgreSQL database server +- ``book_server`` - Serves interactive textbook content +- ``assignment_server`` - Handles assignments and grading +- ``rsmanage`` - Management tools for books and courses +- Several other services for authentication, logging, etc. -If you prefer more control over the setup process, or if you're troubleshooting issues, you can set up the Runestone server manually by following these steps: +These images are pre-built, so you don't need the Runestone source code to run the server. -The Runestone server uses docker compose to start up a number of containers that work together to provide the Runestone environment. Here are the steps to get started: +**Step 5: Database Initialization** -#. Install Docker and Docker Compose on your machine. You can find instructions for your operating system here: https://docs.docker.com/compose/install/ If you already have docker installed you can skip this step. but make sure that ``docker compose version`` tells you that you are running 2.20.2 or later. The current version is 2.38.2. - - If you are using Docker Desktop, you will probably need to add paths to to the list of virtual file shares. Go to Settings -> Resources -> File Sharing and add the path to the rs directory you will create below, as well as the ``BOOK_PATH`` directory you will create below. +The script starts the PostgreSQL database container (``docker compose up -d db``) and initializes it with the Runestone schema (``rsmanage initdb``). -#. Clone the Runestone repository to your local machine. You can do this by running the following command: ``git clone https://github.com/RunestoneInteractive/rs.git`` +**What gets created:** -#. Change to the rs directory: ``cd rs`` +- Database tables for users, courses, books, assignments, responses, etc. +- Test user accounts including ``testuser1`` (password: ``xxx``) +- Base courses for popular Runestone books (``overview``, ``thinkcspy``, ``pythonds``, etc.) that can be used when registering books -#. copy the sample.env file to .env: ``cp sample.env .env`` At a minimum you will then need to edit ``.env`` to provide a value for ``BOOK_PATH`` +This is the foundation that stores all your course data. -#. Run the command ``docker compose pull`` this will pull the prebuilt images for the runestone services from our public docker hub repository. This will take a while the first time you run it, but subsequent runs will be faster. +**Step 6: Start All Services** -#. Start the database server by running the following command: ``docker compose up -d db`` +The script starts all Runestone services (``docker compose up -d``), including: -#. Initialize the database by running the following command: ``docker compose run --rm rsmanage rsmanage initdb`` +- Web servers (book server, assignment server, etc.) +- Background workers for processing tasks +- An nginx reverse proxy to route requests -#. Start the Runestone server by running the following command: ``docker compose up -d`` +The services communicate with each other and the database to provide the full Runestone experience. -#. You should now be able to access the Runestone server by going to http://localhost in your web browser. You can log in with the username ``testuser1`` and the password ``xxx``. +**Step 7: Book Setup (Optional)** -#. Now add a book. ``cd /your/path/to/book`` (the same value you used for ``BOOK_PATH``) and clone a book. For example: ``git clone https://github.com/RunestoneInteractive/overview.git`` +If you choose to add a book, the script: -#. Add the book to the database. First return to the rs directory. Run ``docker compose run --rm rsmanage rsmanage addbookauthor`` You will then be prompted to enter some information about the book and the author.: +#. **Clones the repository**: Downloads the book source code to your ``BOOK_PATH`` directory (e.g., ``git clone https://github.com/RunestoneInteractive/overview.git``) - .. code-block:: text +#. **Registers the book**: Adds the book to the database using ``rsmanage addbookauthor``, creating a "base course" record associated with your user account - Using configuration: development - Using database: runestone_dev - Checking database connection - postgresql+asyncpg://bmiller:@localhost/runestone_dev - Database connection successful - document-id or basecourse : foobar - Runestone username of an author or admin: : testuser1 +#. **Builds the book**: Runs ``rsmanage build`` to convert the book source (RST or PreTeXt) into interactive HTML with embedded questions, visualizations, and other Runestone features + +**Base course vs. teaching course:** + +- A "base course" is the master version of the book content +- When teaching a class, you create a separate course that references the base course +- This allows multiple instructors to teach from the same book with different settings + +**Step 8: Course Creation (Optional)** + +If you choose to create a course, the script runs ``rsmanage addcourse`` which: + +- Creates a new course record in the database +- Links it to a base course (book) +- Sets up the course configuration (term dates, public/private status, etc.) +- Allows you to enroll students and manage assignments + +Students will register for your course and use the book content within that course context. + +**Setup Complete!** + +After these steps, your Runestone server is fully operational and accessible at http://localhost. + +Developer Setup (From Cloned Repository) +----------------------------------------- + +If you're contributing to Runestone development or want the source code locally, clone the repository first: + +.. code-block:: bash - If the course already exists in the database you will get an error message and the command will exit. + git clone https://github.com/RunestoneInteractive/rs.git + cd rs + ./init_runestone.sh - Note: This command sets up a "base course" This is not a course you would normally teach out of. You would normally create a course to teach from throught the web interface or using the ``addcourse`` sub command. +The script will detect that ``docker-compose.yml`` and ``sample.env`` already exist and run in "traditional mode" without downloading files. All the same setup steps occur — you just have the source code available for development. -#. Run the following command: ``docker compose run --rm rsmanage rsmanage build overview`` (replace ``overview`` with the name of the course you added in the previous step). If you are building a PreTeXt book you will need to run ``docker compose run --rm rsmanage rsmanage build --ptx overview`` +**Why clone the repository?** -#. You should now be able to access the overview book on your server running on localhost. The url to go directly to your book is is `http://localhost/ns/books/published/yourbook/index.html`` +- You want to modify Runestone server code +- You're contributing features or bug fixes +- You need to build Docker images locally instead of using pre-built ones +- You want to run tests or use additional development tools -#. If you want to create a course for students to use, you can run the following command: ``docker compose run --rm rsmanage rsmanage addcourse`` This will create a course with the name you provide. +For most users just running a Runestone server, cloning the repository is not necessary. -#. Additional commands are available (for example to add an instructor to a course). Run ``docker compose run --rm rsmanage rsmanage --help`` to see a list of available commands. +**For full development environment setup** (building from source, running tests, etc.), see the :doc:`developing` documentation. Managing Your Server -------------------- diff --git a/init_runestone.sh b/init_runestone.sh index 23dc635f..dcb27b80 100644 --- a/init_runestone.sh +++ b/init_runestone.sh @@ -19,9 +19,8 @@ # - For Windows: WSL2 with this script run from WSL terminal # # USAGE: -# Standalone (two-line install - no repo clone needed): -# curl -fsSL https://raw.githubusercontent.com/RunestoneInteractive/rs/main/init_runestone.sh -o init_runestone.sh -# bash init_runestone.sh +# Standalone (one-line install - no repo clone needed): +# bash <(curl -fsSL https://raw.githubusercontent.com/RunestoneInteractive/rs/main/init_runestone.sh) # # Traditional (from cloned repo): # git clone https://github.com/RunestoneInteractive/rs.git @@ -1104,7 +1103,8 @@ prompt_add_course() { print_header "Course Setup (Optional)" echo "" echo "Would you like to create a course for students?" - echo "A base course was already created with your book." + echo "Note: Base courses were created during database initialization." + echo "This creates a teaching/student course linked to a base course." echo "You can create additional courses later with: docker compose run --rm rsmanage rsmanage addcourse" echo "" From 0106fe4214cf2f19034675dd16f75cfe8729c30b Mon Sep 17 00:00:00 2001 From: Tyler Conzett Date: Fri, 13 Mar 2026 13:31:17 -0500 Subject: [PATCH 9/9] Reordered sections in running.rst to make more sense for new readers. --- docs/source/running.rst | 143 ++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 71 deletions(-) diff --git a/docs/source/running.rst b/docs/source/running.rst index 3dda6f31..122ce1dd 100644 --- a/docs/source/running.rst +++ b/docs/source/running.rst @@ -3,6 +3,32 @@ Running a Runestone Server Maybe you just want to run your own server for a small course. Maybe you are an author who just wants to preview what your book will look like in Runestone. (Hint: Its going to look very much like the plain html, unless you have Java, C or C++ code) Or maybe you are interested in getting involved in the development of the Runestone tools, but want to experiment first. This document will help you get started without having to install a lot of software on your own machine, or having to worry about setting up a web server or database or any development environment. This will work for both linux and macOS on Apple Silicon or Intel based machines. It will also work on Windows, but you will need to install WSL2 and Docker Desktop for Windows. +Prerequisites +------------- + +Before running the setup script, you need: + +**Required:** + +- **Docker Desktop** with Docker Compose 2.20.2 or later (current version: 2.38.2) + + - Download from https://docs.docker.com/compose/install/ + - Make sure Docker Desktop is running before starting the setup + - For Windows: Enable WSL2 integration in Docker Desktop settings + - **Important**: Configure file sharing in Docker Desktop (Settings → Resources → File Sharing) to include the directory where you'll create your Runestone configuration and your ``BOOK_PATH`` directory. Without this, Docker won't be able to access your book files. + +- **WSL2** (Windows users only) + + - The script must run from a WSL2 terminal (Ubuntu, Debian, etc.) + - Do not use PowerShell or Command Prompt + +**Optional:** + +- **Git** - Only needed if you want to clone book repositories + + - Most users will want to add books, so this is recommended + - You can install it later if you skip book setup initially + Quick Start ----------- @@ -17,7 +43,7 @@ The script will guide you through an interactive setup process, asking questions **Before running the command:** #. Navigate to where you want the configuration files created (e.g., ``mkdir ~/runestone && cd ~/runestone``) -#. Make sure you have the prerequisites installed (see below) +#. Make sure you have the prerequisites installed (see above) #. For Windows users: Run this from a WSL2 terminal, not PowerShell or Command Prompt **After setup completes:** @@ -26,31 +52,58 @@ The script will guide you through an interactive setup process, asking questions - Log in with username: ``testuser1``, password: ``xxx`` - Check ``init_runestone.log`` if you encounter any issues -Prerequisites -------------- +Managing Your Server +-------------------- -Before running the setup script, you need: +**Stopping and Starting:** -**Required:** +- Stop the server: ``docker compose stop`` +- Start the server: ``docker compose start`` +- Restart services: ``docker compose restart`` +- Shut down and remove containers: ``docker compose down`` +- Shut down and remove volumes (WARNING: deletes all data): ``docker compose down -v`` -- **Docker Desktop** with Docker Compose 2.20.2 or later (current version: 2.38.2) - - - Download from https://docs.docker.com/compose/install/ - - Make sure Docker Desktop is running before starting the setup - - For Windows: Enable WSL2 integration in Docker Desktop settings - - **Important**: Configure file sharing in Docker Desktop (Settings → Resources → File Sharing) to include the directory where you'll create your Runestone configuration and your ``BOOK_PATH`` directory. Without this, Docker won't be able to access your book files. +**Viewing Logs:** -- **WSL2** (Windows users only) - - - The script must run from a WSL2 terminal (Ubuntu, Debian, etc.) - - Do not use PowerShell or Command Prompt +To view logs from all services: ``docker compose logs -f`` -**Optional:** +**Common Management Tasks:** -- **Git** - Only needed if you want to clone book repositories +- Add another book: ``docker compose run --rm rsmanage rsmanage addbookauthor`` +- Build a book: ``docker compose run --rm rsmanage rsmanage build `` +- Build a PreTeXt book: ``docker compose run --rm rsmanage rsmanage build --ptx `` +- Create a course: ``docker compose run --rm rsmanage rsmanage addcourse`` +- Add an instructor to a course: ``docker compose run --rm rsmanage rsmanage addinstructor`` - - Most users will want to add books, so this is recommended - - You can install it later if you skip book setup initially +**Troubleshooting:** + +If you used the automated setup script (``init_runestone.sh``), check the ``init_runestone.log`` file in the rs directory for detailed information about the setup process. This log can help diagnose any issues that occurred during initialization. + +For database issues, you can reset the database by running ``docker compose down -v`` to remove all volumes, then re-run the initialization steps (either manually or via ``./init_runestone.sh``). + +Developer Setup (From Cloned Repository) +----------------------------------------- + +If you're contributing to Runestone development or want the source code locally, clone the repository first: + +.. code-block:: bash + + git clone https://github.com/RunestoneInteractive/rs.git + cd rs + ./init_runestone.sh + +The script will detect that ``docker-compose.yml`` and ``sample.env`` already exist and run in "traditional mode" without downloading files. All the same setup steps occur — you just have the source code available for development. + +**Why clone the repository?** + +- You want to modify Runestone server code +- You're contributing features or bug fixes +- You need to build Docker images locally instead of using pre-built ones +- You want to run tests or use additional development tools + +For most users just running a Runestone server, cloning the repository is not necessary. + +**For full development environment setup** (building from source, running tests, etc.), see the :doc:`developing` documentation. How the Setup Script Works --------------------------- @@ -166,55 +219,3 @@ Students will register for your course and use the book content within that cour After these steps, your Runestone server is fully operational and accessible at http://localhost. -Developer Setup (From Cloned Repository) ------------------------------------------ - -If you're contributing to Runestone development or want the source code locally, clone the repository first: - -.. code-block:: bash - - git clone https://github.com/RunestoneInteractive/rs.git - cd rs - ./init_runestone.sh - -The script will detect that ``docker-compose.yml`` and ``sample.env`` already exist and run in "traditional mode" without downloading files. All the same setup steps occur — you just have the source code available for development. - -**Why clone the repository?** - -- You want to modify Runestone server code -- You're contributing features or bug fixes -- You need to build Docker images locally instead of using pre-built ones -- You want to run tests or use additional development tools - -For most users just running a Runestone server, cloning the repository is not necessary. - -**For full development environment setup** (building from source, running tests, etc.), see the :doc:`developing` documentation. - -Managing Your Server --------------------- - -**Stopping and Starting:** - -- Stop the server: ``docker compose stop`` -- Start the server: ``docker compose start`` -- Restart services: ``docker compose restart`` -- Shut down and remove containers: ``docker compose down`` -- Shut down and remove volumes (WARNING: deletes all data): ``docker compose down -v`` - -**Viewing Logs:** - -To view logs from all services: ``docker compose logs -f`` - -**Common Management Tasks:** - -- Add another book: ``docker compose run --rm rsmanage rsmanage addbookauthor`` -- Build a book: ``docker compose run --rm rsmanage rsmanage build `` -- Build a PreTeXt book: ``docker compose run --rm rsmanage rsmanage build --ptx `` -- Create a course: ``docker compose run --rm rsmanage rsmanage addcourse`` -- Add an instructor to a course: ``docker compose run --rm rsmanage rsmanage addinstructor`` - -**Troubleshooting:** - -If you used the automated setup script (``init_runestone.sh``), check the ``init_runestone.log`` file in the rs directory for detailed information about the setup process. This log can help diagnose any issues that occurred during initialization. - -For database issues, you can reset the database by running ``docker compose down -v`` to remove all volumes, then re-run the initialization steps (either manually or via ``./init_runestone.sh``).