|
#!/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 "$@" |