Skip to content

Instantly share code, notes, and snippets.

@rampadc
Last active March 30, 2025 11:07
Show Gist options
  • Save rampadc/7bbf3616a064453fb7e6bf83fdb52725 to your computer and use it in GitHub Desktop.
Save rampadc/7bbf3616a064453fb7e6bf83fdb52725 to your computer and use it in GitHub Desktop.
Set up Storyteller with TTS

Installation Instructions

  1. Save the script above as storyteller in a directory in your PATH (e.g., /usr/local/bin/)
  2. Make it executable: chmod +x /usr/local/bin/storyteller
  3. Initialize the environment: storyteller init

Usage

After installation, you can use the following commands:

  • storyteller init - Set up the environment (only needed once)
  • storyteller configure - Configure host settings (localhost or 0.0.0.0)
  • storyteller start - Start the Storyteller web server
  • storyteller stop - Stop the server
  • storyteller restart - Restart the server
  • storyteller status - Check if the server is running
  • storyteller logs - View server logs
  • storyteller autostart - Configure the server to start automatically at boot
  • storyteller help - Show available commands

Features

  1. Environment Management:

    • Automatically installs Node.js v20 (with NVM support)
    • Sets up Yarn via corepack
    • Installs Python 3.12 using Miniconda
    • Installs system dependencies (ffmpeg, sqlite3, espeak-ng)
  2. Server Management:

    • Background operation with PID tracking
    • Log capture and viewing
    • Server status monitoring
  3. Platform Support:

    • Works on Debian-based Linux (Ubuntu, etc.)
    • Works on RHEL-based Linux (CentOS, Fedora, etc.)
    • Works on macOS
  4. Auto-Start Configuration:

    • systemd service on Linux
    • LaunchAgent on macOS
  5. Host Configuration:

    • Toggle between localhost and 0.0.0.0 for network accessibility
#!/usr/bin/env bash
set -e
# Configuration variables
STORYTELLER_DIR="$HOME/.storyteller"
LOG_FILE="$STORYTELLER_DIR/storyteller.log"
PID_FILE="$STORYTELLER_DIR/storyteller.pid"
HOST="localhost" # Default host
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored messages
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# Function to check if the server is running
is_running() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if ps -p "$pid" > /dev/null; then
return 0 # True, is running
fi
fi
return 1 # False, not running
}
# Function to detect OS
detect_os() {
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
if [ -f /etc/debian_version ]; then
echo "debian"
elif [ -f /etc/redhat-release ]; then
echo "rhel"
else
echo "linux"
fi
elif [[ "$OSTYPE" == "darwin"* ]]; then
echo "macos"
else
echo "unknown"
fi
}
# Function to install system dependencies
install_system_dependencies() {
print_message "$BLUE" "Installing system dependencies..."
OS=$(detect_os)
case $OS in
debian)
sudo apt-get update
sudo apt-get install -y ffmpeg sqlite3 libsqlite3-dev espeak-ng curl
;;
rhel)
sudo yum install -y ffmpeg sqlite sqlite-devel espeak-ng curl
;;
macos)
if ! command -v brew &> /dev/null; then
print_message "$YELLOW" "Homebrew not found. Installing..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
brew install ffmpeg sqlite espeak-ng
;;
*)
print_message "$RED" "Unsupported OS. Please install ffmpeg, sqlite3, and espeak-ng manually."
exit 1
;;
esac
print_message "$GREEN" "System dependencies installed successfully."
}
# Function to install Node.js
install_nodejs() {
print_message "$BLUE" "Setting up Node.js v20..."
if command -v nvm &> /dev/null; then
print_message "$BLUE" "NVM detected. Using it to install Node.js v20..."
nvm install 20
nvm use 20
else
if ! command -v node &> /dev/null || [[ $(node --version) != v20* ]]; then
OS=$(detect_os)
case $OS in
debian)
print_message "$BLUE" "Installing Node.js v20 on Debian-based system..."
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
;;
rhel)
print_message "$BLUE" "Installing Node.js v20 on RHEL-based system..."
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
sudo yum install -y nodejs
;;
macos)
print_message "$BLUE" "Installing Node.js v20 on macOS..."
brew install node@20
brew link --force node@20
;;
*)
print_message "$RED" "Unsupported OS. Please install Node.js v20 manually."
exit 1
;;
esac
fi
fi
print_message "$GREEN" "Node.js $(node --version) installed successfully."
# Enable yarn with corepack
print_message "$BLUE" "Enabling yarn with corepack..."
if command -v corepack &> /dev/null; then
corepack enable
else
sudo npm install -g corepack
corepack enable
fi
print_message "$GREEN" "Yarn enabled successfully."
}
# Function to install Miniconda and Python 3.12
install_python() {
print_message "$BLUE" "Setting up Python 3.12 with Miniconda..."
if ! command -v conda &> /dev/null; then
print_message "$BLUE" "Miniconda not found. Installing..."
OS=$(detect_os)
MINICONDA_URL=""
if [[ $OS == "macos" ]]; then
MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh"
# Check for Apple M1/M2
if [[ $(uname -m) == "arm64" ]]; then
MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh"
fi
else
MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh"
fi
curl -fsSL $MINICONDA_URL -o /tmp/miniconda.sh
bash /tmp/miniconda.sh -b -p $HOME/miniconda
rm /tmp/miniconda.sh
# Add to PATH
export PATH="$HOME/miniconda/bin:$PATH"
# Initialize for future shell sessions
$HOME/miniconda/bin/conda init bash
fi
# Ensure conda is in PATH for this session
export PATH="$HOME/miniconda/bin:$PATH"
# Create environment if it doesn't exist
if ! conda env list | grep -q "storyteller"; then
conda create -y --name storyteller python=3.12
fi
print_message "$GREEN" "Python 3.12 with Miniconda installed successfully."
}
# Function to setup the storyteller repository
setup_repository() {
print_message "$BLUE" "Setting up Storyteller repository..."
if [ -d "$STORYTELLER_DIR" ]; then
# Check if the directory is a valid Git repository
if [ -d "$STORYTELLER_DIR/.git" ]; then
print_message "$YELLOW" "Storyteller directory exists. Updating..."
cd "$STORYTELLER_DIR"
git pull
else
print_message "$RED" "Directory exists, but it's not a valid Git repository. Re-cloning..."
rm -rf "$STORYTELLER_DIR" # Remove the invalid directory
git clone https://gitlab.com/rampadc/storyteller.git "$STORYTELLER_DIR" # Re-clone the repository
cd "$STORYTELLER_DIR"
fi
else
print_message "$BLUE" "Cloning Storyteller repository..."
git clone https://gitlab.com/rampadc/storyteller.git "$STORYTELLER_DIR"
cd "$STORYTELLER_DIR"
fi
git checkout feat/add-tts
print_message "$BLUE" "Installing dependencies..."
yarn install
print_message "$GREEN" "Repository setup completed successfully."
}
# Function to start the server
start_server() {
if is_running; then
print_message "$YELLOW" "Storyteller is already running."
return 0
fi
print_message "$BLUE" "Starting Storyteller server..."
# Determine the host to use
HOST_ARG=""
if [ "$HOST" = "0.0.0.0" ]; then
HOST_ARG="--host 0.0.0.0"
fi
# Start the server in the background
cd "$STORYTELLER_DIR"
# Activate conda environment
source ~/.bash_profile
conda activate storyteller
# Start the server and redirect output to log file
nohup yarn dev:web $HOST_ARG > "$LOG_FILE" 2>&1 &
disown
# Save the PID
echo $! > "$PID_FILE"
print_message "$GREEN" "Storyteller server started (PID: $(cat $PID_FILE))."
print_message "$GREEN" "You can access it at http://$HOST:8001"
}
# Function to stop the server
stop_server() {
if ! is_running; then
print_message "$YELLOW" "Storyteller is not running."
return 0
fi
print_message "$BLUE" "Stopping Storyteller server..."
local pid=$(cat "$PID_FILE")
kill $pid
# Wait for the process to terminate
for i in {1..10}; do
if ! ps -p $pid > /dev/null; then
rm "$PID_FILE"
print_message "$GREEN" "Storyteller server stopped."
return 0
fi
sleep 1
done
# Force kill if still running
if ps -p $pid > /dev/null; then
print_message "$YELLOW" "Force stopping Storyteller server..."
kill -9 $pid
rm "$PID_FILE"
print_message "$GREEN" "Storyteller server force stopped."
fi
}
# Function to view server logs
view_logs() {
if [ -f "$LOG_FILE" ]; then
if command -v less &> /dev/null; then
less +F "$LOG_FILE"
else
tail -f "$LOG_FILE"
fi
else
print_message "$YELLOW" "No logs found. Has the server been started yet?"
fi
}
# Function to setup systemd service (Linux only)
setup_service() {
OS=$(detect_os)
if [[ $OS != "debian" && $OS != "rhel" ]]; then
print_message "$RED" "Systemd service setup is only available on Linux."
return 1
fi
print_message "$BLUE" "Setting up Storyteller systemd service..."
# Create service file
local service_file="/tmp/storyteller.service"
cat > $service_file << EOL
[Unit]
Description=Storyteller Web Server
After=network.target
[Service]
Type=simple
User=$(whoami)
WorkingDirectory=$STORYTELLER_DIR
Environment="PATH=$HOME/miniconda/bin:$PATH"
ExecStart=$HOME/miniconda/bin/conda run -n storyteller $STORYTELLER_DIR/node_modules/.bin/yarn dev:web $(if [ "$HOST" = "0.0.0.0" ]; then echo "--host 0.0.0.0"; fi)
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOL
# Install service
sudo mv $service_file /etc/systemd/system/storyteller.service
sudo systemctl daemon-reload
sudo systemctl enable storyteller.service
print_message "$GREEN" "Systemd service installed. You can now use:"
print_message "$GREEN" " sudo systemctl start storyteller"
print_message "$GREEN" " sudo systemctl stop storyteller"
print_message "$GREEN" " sudo systemctl status storyteller"
}
# Function to setup launchd service (macOS only)
setup_launchd() {
OS=$(detect_os)
if [[ $OS != "macos" ]]; then
print_message "$RED" "LaunchAgent setup is only available on macOS."
return 1
fi
print_message "$BLUE" "Setting up Storyteller LaunchAgent..."
# Create plist file
local plist_file="$HOME/Library/LaunchAgents/com.storyteller.app.plist"
mkdir -p "$HOME/Library/LaunchAgents"
cat > "$plist_file" << EOL
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.storyteller.app</string>
<key>ProgramArguments</key>
<array>
<string>$HOME/miniconda/bin/conda</string>
<string>run</string>
<string>-n</string>
<string>storyteller</string>
<string>${STORYTELLER_DIR}/node_modules/.bin/yarn</string>
<string>dev:web</string>
$(if [ "$HOST" = "0.0.0.0" ]; then echo "<string>--host</string><string>0.0.0.0</string>"; fi)
</array>
<key>WorkingDirectory</key>
<string>${STORYTELLER_DIR}</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>$HOME/miniconda/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>${LOG_FILE}</string>
<key>StandardErrorPath</key>
<string>${LOG_FILE}</string>
</dict>
</plist>
EOL
# Load service
launchctl unload "$plist_file" 2>/dev/null || true
launchctl load -w "$plist_file"
print_message "$GREEN" "LaunchAgent installed. Storyteller will start on login."
print_message "$GREEN" "You can use:"
print_message "$GREEN" " launchctl start com.storyteller.app"
print_message "$GREEN" " launchctl stop com.storyteller.app"
}
# Function to configure the host
configure_host() {
print_message "$BLUE" "Current host configuration: $HOST"
echo "Select host configuration:"
echo "1) localhost (default, only accessible from this machine)"
echo "2) 0.0.0.0 (accessible from other machines on the network)"
read -p "Enter your choice [1/2]: " choice
case $choice in
2)
HOST="0.0.0.0"
;;
*)
HOST="localhost"
;;
esac
# Save the configuration
echo "export HOST=\"$HOST\"" > "$STORYTELLER_DIR/config"
print_message "$GREEN" "Host configuration updated to: $HOST"
# If the server is running, ask to restart
if is_running; then
read -p "Server is running. Would you like to restart to apply changes? [y/N]: " restart
if [[ $restart =~ ^[Yy]$ ]]; then
stop_server
start_server
else
print_message "$YELLOW" "Changes will apply after next restart."
fi
fi
}
# Function to initialize the setup
init() {
print_message "$BLUE" "Initializing Storyteller..."
# Create directories if they don't exist
mkdir -p "$STORYTELLER_DIR"
# Load configuration if exists
if [ -f "$STORYTELLER_DIR/config" ]; then
source "$STORYTELLER_DIR/config"
fi
# Install system dependencies
install_system_dependencies
# Install Node.js and yarn
install_nodejs
# Install Python with Miniconda
install_python
# Setup repository
setup_repository
print_message "$GREEN" "Storyteller initialized successfully."
}
# Function to show help
show_help() {
echo "Storyteller CLI"
echo "Usage: storyteller [command]"
echo ""
echo "Commands:"
echo " start Start the Storyteller server"
echo " stop Stop the Storyteller server"
echo " restart Restart the Storyteller server"
echo " status Show the status of the Storyteller server"
echo " logs View the server logs"
echo " init Initialize the environment (run this first)"
echo " configure Configure server settings"
echo " autostart Configure the server to start automatically at boot"
echo " help Show this help message"
}
# Main function to parse arguments and call appropriate functions
main() {
# Check if Storyteller is initialized
if [ ! -d "$STORYTELLER_DIR" ] && [ "$1" != "init" ] && [ "$1" != "help" ]; then
print_message "$YELLOW" "Storyteller is not initialized. Run 'storyteller init' first."
exit 1
fi
# Load configuration if exists
if [ -f "$STORYTELLER_DIR/config" ]; then
source "$STORYTELLER_DIR/config"
fi
# Parse arguments
case "$1" in
start)
start_server
;;
stop)
stop_server
;;
restart)
stop_server
start_server
;;
status)
if is_running; then
print_message "$GREEN" "Storyteller is running (PID: $(cat $PID_FILE))."
print_message "$GREEN" "It can be accessed at http://$HOST:8001"
else
print_message "$YELLOW" "Storyteller is not running."
fi
;;
logs)
view_logs
;;
init)
init
;;
configure)
configure_host
;;
autostart)
OS=$(detect_os)
if [[ $OS == "macos" ]]; then
setup_launchd
elif [[ $OS == "debian" || $OS == "rhel" ]]; then
setup_service
else
print_message "$RED" "Autostart is not supported on this platform."
fi
;;
help|--help|-h|"")
show_help
;;
*)
print_message "$RED" "Unknown command: $1"
show_help
exit 1
;;
esac
}
# Run the main function with all arguments
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment