diff --git a/plex/plex-backup.md b/plex/plex-backup.md index 90dfc7a..b751576 100644 --- a/plex/plex-backup.md +++ b/plex/plex-backup.md @@ -4,6 +4,29 @@ This document provides comprehensive documentation for the enhanced `backup-plex.sh` script. This advanced backup solution includes database integrity checking with automatic repair, performance monitoring, parallel processing, intelligent notifications, and WAL file handling. +## Enhanced Service Management Integration + +The backup script integrates seamlessly with the enhanced `plex.sh` service management script: + +- **Porcelain Mode**: Uses `--porcelain` for clean, automation-friendly output +- **Color Detection**: Automatically adapts to terminal capabilities when run interactively +- **Error Handling**: Proper integration with the enhanced error handling in `plex.sh` +- **Non-Interactive Operation**: Designed for automated backup scenarios +- **Service Presence Detection**: Gracefully handles systems without Plex installed + +Example service management calls: + +```bash +# Stop Plex with porcelain output for automation +/home/acedanger/shell/plex/plex.sh --porcelain stop + +# Start Plex after backup completion +/home/acedanger/shell/plex/plex.sh --porcelain start + +# View logs for troubleshooting backup issues +/home/acedanger/shell/plex/plex.sh logs --lines 50 +``` + ## Script Overview **Author:** Peter Wood @@ -145,11 +168,11 @@ This line defines the log file path, including the current date and time in the ```bash if systemctl is-active --quiet plexmediaserver.service; then - /home/acedanger/shell/plex/plex.sh stop || { echo "Failed to stop plexmediaserver.service"; exit 1; } + /home/acedanger/shell/plex/plex.sh --porcelain stop || { echo "Failed to stop plexmediaserver.service"; exit 1; } fi ``` -This block checks if the Plex Media Server service is running. If it is, the script stops the service using a custom script (`plex.sh`). +This block checks if the Plex Media Server service is running. If it is, the script stops the service using the custom `plex.sh` script with porcelain mode enabled for automation. The `--porcelain` option ensures clean, non-interactive output suitable for backup scripts. ### 4. Backup Plex Database Files and Preferences @@ -204,11 +227,11 @@ This command sends a notification upon completion of the backup process, indicat ```bash if systemctl is-enabled --quiet plexmediaserver.service; then - /home/acedanger/shell/plex/plex.sh start || { echo "Failed to start plexmediaserver.service"; exit 1; } + /home/acedanger/shell/plex/plex.sh --porcelain start || { echo "Failed to start plexmediaserver.service"; exit 1; } fi ``` -This block checks if the Plex Media Server service is enabled. If it is, the script restarts the service using a custom script (`plex.sh`). +This block checks if the Plex Media Server service is enabled. If it is, the script restarts the service using the custom `plex.sh` script with porcelain mode for automation. The `--porcelain` option ensures clean output without ANSI escape sequences when called from the backup script. ### 9. Legacy Cleanup diff --git a/plex/plex-management.md b/plex/plex-management.md index c0e170e..964ec9c 100644 --- a/plex/plex-management.md +++ b/plex/plex-management.md @@ -51,6 +51,37 @@ This script is part of a comprehensive Plex management suite: ## Enhanced Features +### Smart Terminal Detection and Color Output + +The script includes intelligent terminal detection and color management: + +- **Automatic Color Detection**: Detects terminal capabilities and adjusts output accordingly +- **Environment Variable Support**: Respects `NO_COLOR` environment variable for accessibility +- **Porcelain Mode**: `--porcelain` or `-p` provides machine-readable output for automation +- **ANSI Escape Handling**: Properly handles color codes when called from other scripts + +### System Integration and Automation + +- **update.sh Integration**: Automatically called during system updates with simple output mode +- **Non-Interactive Operation**: Designed to work seamlessly when called from other scripts +- **Service Presence Detection**: Gracefully handles systems without Plex installed +- **Root User Prevention**: Explicitly prevents running as root for security + +### Environment Variables + +The script supports several environment variables for customization: + +```bash +# Disable color output entirely +NO_COLOR=1 ./plex.sh status + +# Force porcelain mode for machine-readable output +./plex.sh --porcelain start + +# Check terminal capabilities +TERM=dumb ./plex.sh status # Forces simple output +``` + ### Smart Service Management The enhanced script includes intelligent service operations: @@ -92,7 +123,6 @@ Shows: - **Network Validation**: Verifies port availability and network configuration - **Recovery Capabilities**: Automatic recovery from common service issues - ## Command Line Usage ### Basic Operations @@ -111,37 +141,61 @@ Shows: ./plex.sh status ``` -### Advanced Options +### Available Commands + +The script supports the following commands: ```bash -# Enhanced status with detailed information -./plex.sh status --verbose +# Basic service operations +./plex.sh start # Start Plex Media Server +./plex.sh stop # Stop Plex Media Server +./plex.sh restart # Restart Plex Media Server (also accepts 'reload') +./plex.sh status # Show detailed service status (also accepts 'info') +./plex.sh logs # Display recent Plex Media Server logs +./plex.sh help # Show help message (also accepts '--help' or '-h') -# Force restart (ignores current state) -./plex.sh restart --force +# Logs command with options +./plex.sh logs # Display last 20 lines of Plex logs +./plex.sh logs -n 50 # Display last 50 lines of Plex logs +./plex.sh logs -f # Follow logs in real-time +./plex.sh logs --follow --lines 100 # Follow logs with custom line count -# Safe stop with extended wait time -./plex.sh stop --safe +# Porcelain mode for machine-readable output +./plex.sh --porcelain start # Machine-readable service output +./plex.sh --porcelain status # Machine-readable status output -# Start with configuration validation -./plex.sh start --validate +# Environment variable examples +NO_COLOR=1 ./plex.sh status # Disable colors +./plex.sh --porcelain start # Machine-readable output ``` +**Note**: The script automatically detects when it's being called from other scripts (like `update.sh`) and adjusts its output accordingly. + ### Integration with Other Scripts -The `plex.sh` script is designed to work seamlessly with other Plex management scripts: +The `plex.sh` script is designed to work seamlessly with other Plex management scripts and system automation: ```bash +# Used by update.sh during system updates (automatic porcelain output) +./update.sh # Automatically calls plex.sh --porcelain stop/start + # Used by backup script for safe service management ./backup-plex.sh # Automatically calls plex.sh stop/start # Used by recovery scripts for service control ./recover-plex-database.sh # Uses plex.sh for service management -# Used by testing scripts for service validation -./integration-test-plex.sh # Validates service operations +# Manual integration with porcelain output +./plex.sh --porcelain start # For use in scripts/automation ``` +### Security and Safety Features + +- **Root Prevention**: Script refuses to run as root user for security +- **Safe Defaults**: Uses conservative settings for service operations +- **Error Handling**: Comprehensive error checking and reporting +- **Graceful Degradation**: Works on systems without Plex installed + ## Detailed Operation Steps ### Start Operation Process @@ -191,14 +245,57 @@ The status command provides comprehensive system information: - **Recent Activity**: Latest log entries and system events - **Performance Metrics**: Uptime, resource usage, response times +### Logs Operation Details + +The logs command provides flexible log viewing capabilities: + +```bash +# Basic log viewing (last 20 lines) +./plex.sh logs + +# Custom line count +./plex.sh logs -n 50 +./plex.sh logs --lines 100 + +# Real-time log following +./plex.sh logs -f +./plex.sh logs --follow + +# Combined options +./plex.sh logs --follow --lines 50 +``` + +**Logs Command Features:** + +- **Flexible Line Count**: Specify number of log lines to display (default: 20) +- **Real-time Following**: Use `-f` or `--follow` to tail logs in real-time +- **Systemd Integration**: Uses `journalctl` for reliable log access +- **Porcelain Mode Support**: Works with `--porcelain` for script automation +- **Error Handling**: Graceful handling when service is not available + +**Logs Command Options:** + +- `-n, --lines NUMBER`: Number of log lines to display (default: 20) +- `-f, --follow`: Follow logs in real-time (like `tail -f`) +- Can be combined with global `--porcelain` option for clean output + ## Configuration and Dependencies +### Environment Variable Configuration + +The script respects several environment variables for customization: + +- **`NO_COLOR`**: Set to `1` to disable all color output (accessibility compliance) +- **`--porcelain`**: Command-line option to force machine-readable output (for automation) +- **`TERM`**: Automatically detected; `dumb` terminals get simple output + ### System Requirements - **Operating System**: systemd-based Linux distribution -- **Permissions**: sudo access for systemctl operations +- **Permissions**: sudo access for systemctl operations (script prevents running as root) - **Network**: Port 32400/tcp available for Plex communications -- **Dependencies**: systemctl, curl (for network validation), ps (for process monitoring) +- **Dependencies**: systemctl, basic shell utilities +- **Service**: plexmediaserver.service (gracefully handles absence) ### Configuration Validation @@ -218,6 +315,30 @@ The script integrates with the broader Plex management ecosystem: - **Testing Framework**: Utilized by integration tests for service validation - **Monitoring Systems**: Provides status information for monitoring dashboards +### System Update Integration + +The script is designed to work seamlessly with system update processes: + +- **Automatic Integration**: Called by `update.sh` during system updates +- **Service Management**: Safely stops Plex before package updates, restarts after completion +- **Porcelain Mode**: Uses `--porcelain` for clean, log-friendly output +- **Graceful Handling**: Properly handles systems without Plex installed +- **Error Prevention**: Prevents interruption of package manager operations + +Example system update integration: + +```bash +# In update.sh - automatically stops Plex before updates +/home/acedanger/shell/plex/plex.sh --porcelain stop + +# Package updates occur here... + +# In update.sh - automatically starts Plex after updates +/home/acedanger/shell/plex/plex.sh --porcelain start +``` + +This integration ensures that Plex databases and processes don't interfere with system package updates while maintaining service availability. + ## Error Handling and Troubleshooting ### Common Issues and Solutions diff --git a/plex/plex.sh b/plex/plex.sh index f422813..6172a9c 100755 --- a/plex/plex.sh +++ b/plex/plex.sh @@ -79,6 +79,9 @@ readonly PLEX_SERVICE="plexmediaserver" SCRIPT_NAME="$(basename "$0")" readonly SCRIPT_NAME +# Global variables for command-line options +PORCELAIN_MODE=false + # 🎭 ASCII symbols for compatible output readonly CHECKMARK="✓" readonly CROSS="✗" @@ -90,11 +93,11 @@ readonly SPARKLES="✦" # 📊 Function to print fancy headers print_header() { - if use_colors; then + if use_colors && [[ "$PORCELAIN_MODE" != "true" ]]; then echo -e "\n${PURPLE}${BOLD}+==============================================================+${RESET}" echo -e "${PURPLE}${BOLD}| ${SPARKLES} PLEX MEDIA SERVER ${SPARKLES} |${RESET}" echo -e "${PURPLE}${BOLD}+==============================================================+${RESET}\n" - else + elif [[ "$PORCELAIN_MODE" != "true" ]]; then echo "" echo "+==============================================================" echo "| ${SPARKLES} PLEX MEDIA SERVER ${SPARKLES} |" @@ -105,7 +108,9 @@ print_header() { # 🎉 Function to print completion footer print_footer() { - if use_colors; then + if [[ "$PORCELAIN_MODE" == "true" ]]; then + return # No footer in porcelain mode + elif use_colors; then echo -e "\n${DIM}${CYAN}\\--- Operation completed ${SPARKLES} ---/${RESET}\n" else echo "" @@ -120,7 +125,10 @@ print_status() { local message="$2" local color="$3" - if use_colors; then + if [[ "$PORCELAIN_MODE" == "true" ]]; then + # Porcelain mode: simple, machine-readable output + echo "${status} ${message}" + elif use_colors; then echo -e "${color}${BOLD}[${status}]${RESET} ${message}" else echo "[${status}] ${message}" @@ -134,9 +142,9 @@ show_loading() { local spin='-\|/' local i=0 - # For non-interactive terminals or when called from other scripts, + # For non-interactive terminals, porcelain mode, or when called from other scripts, # use a simpler approach - if ! use_colors || [[ "${PLEX_SIMPLE_OUTPUT:-}" == "1" ]]; then + if ! use_colors || [[ "$PORCELAIN_MODE" == "true" ]]; then echo "⌛ ${message}..." wait "$pid" echo "⌛ ${message} ✓" @@ -219,6 +227,28 @@ show_detailed_status() { local service_status service_status=$(systemctl is-active "$PLEX_SERVICE" 2>/dev/null || echo "inactive") + if [[ "$PORCELAIN_MODE" == "true" ]]; then + # Porcelain mode: simple output + echo "status ${service_status}" + + if [[ "$service_status" == "active" ]]; then + local uptime + uptime=$(systemctl show "$PLEX_SERVICE" --property=ActiveEnterTimestamp --value | xargs -I {} date -d {} "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "Unknown") + local memory_usage + memory_usage=$(systemctl show "$PLEX_SERVICE" --property=MemoryCurrent --value 2>/dev/null || echo "0") + if [[ "$memory_usage" != "0" ]] && [[ "$memory_usage" =~ ^[0-9]+$ ]]; then + memory_usage="$(( memory_usage / 1024 / 1024 )) MB" + else + memory_usage="Unknown" + fi + echo "started ${uptime}" + echo "memory ${memory_usage}" + echo "service ${PLEX_SERVICE}" + fi + return + fi + + # Interactive mode with styled output if use_colors; then echo -e "\n${BOLD}${BLUE}+==============================================================+${RESET}" echo -e "${BOLD}${BLUE}| SERVICE STATUS |${RESET}" @@ -287,80 +317,202 @@ show_detailed_status() { ;; esac - # Show recent logs - if use_colors; then - echo -e "\n${DIM}${CYAN}+--- Recent Service Logs (24h) ---+${RESET}" - else - echo "" - echo "+--- Recent Service Logs (24h) ---+" - fi - - # Try to get logs with sudo, fall back to user permissions - local logs - if logs=$(sudo journalctl -u "$PLEX_SERVICE" --no-pager -n 5 --since "24 hours ago" --output=short 2>/dev/null); then - if [[ -n "$logs" && "$logs" != "-- No entries --" ]]; then - if use_colors; then - echo -e "${DIM}${logs}${RESET}" + # Show recent logs only in interactive mode + if [[ "$PORCELAIN_MODE" != "true" ]]; then + if use_colors; then + echo -e "\n${DIM}${CYAN}+--- Recent Service Logs (24h) ---+${RESET}" + else + echo "" + echo "+--- Recent Service Logs (24h) ---+" + fi + + # Try to get logs with sudo, fall back to user permissions + local logs + if logs=$(sudo journalctl -u "$PLEX_SERVICE" --no-pager -n 5 --since "24 hours ago" --output=short 2>/dev/null); then + if [[ -n "$logs" && "$logs" != "-- No entries --" ]]; then + if use_colors; then + echo -e "${DIM}${logs}${RESET}" + else + echo "${logs}" + fi else - echo "${logs}" + if use_colors; then + echo -e "${DIM}${YELLOW}No recent log entries found${RESET}" + else + echo "No recent log entries found" + fi fi else - if use_colors; then - echo -e "${DIM}${YELLOW}No recent log entries found${RESET}" + # Fallback: try without sudo + logs=$(journalctl -u "$PLEX_SERVICE" --no-pager -n 5 --since "24 hours ago" 2>/dev/null || echo "Unable to access logs") + if [[ "$logs" == "Unable to access logs" || "$logs" == "-- No entries --" ]]; then + if use_colors; then + echo -e "${DIM}${YELLOW}Unable to access recent logs (try: sudo journalctl -u ${PLEX_SERVICE})${RESET}" + else + echo "Unable to access recent logs (try: sudo journalctl -u ${PLEX_SERVICE})" + fi else - echo "No recent log entries found" + if use_colors; then + echo -e "${DIM}${logs}${RESET}" + else + echo "${logs}" + fi fi fi - else - # Fallback: try without sudo - logs=$(journalctl -u "$PLEX_SERVICE" --no-pager -n 5 --since "24 hours ago" 2>/dev/null || echo "Unable to access logs") - if [[ "$logs" == "Unable to access logs" || "$logs" == "-- No entries --" ]]; then - if use_colors; then - echo -e "${DIM}${YELLOW}Unable to access recent logs (try: sudo journalctl -u ${PLEX_SERVICE})${RESET}" - else - echo "Unable to access recent logs (try: sudo journalctl -u ${PLEX_SERVICE})" - fi + + if use_colors; then + echo -e "${DIM}${CYAN}+----------------------------------+${RESET}" else - if use_colors; then - echo -e "${DIM}${logs}${RESET}" - else - echo "${logs}" - fi + echo "+----------------------------------+" fi fi +} + +# 📋 Enhanced logs function +show_logs() { + local lines=100 + local follow=false - if use_colors; then - echo -e "${DIM}${CYAN}+----------------------------------+${RESET}" + # Parse arguments for logs command + while [[ $# -gt 0 ]]; do + case $1 in + -f|--follow) + follow=true + shift + ;; + -[0-9]*|[0-9]*) + # Extract number from argument like -50 or 50 + lines="${1#-}" + shift + ;; + *) + # Assume it's a number of lines + if [[ "$1" =~ ^[0-9]+$ ]]; then + lines="$1" + fi + shift + ;; + esac + done + + if [[ "$PORCELAIN_MODE" == "true" ]]; then + # Porcelain mode: simple output without decorations + if [[ "$follow" == "true" ]]; then + sudo journalctl -u "$PLEX_SERVICE" --no-pager -f --output=short-iso 2>/dev/null || \ + journalctl -u "$PLEX_SERVICE" --no-pager -f --output=short-iso 2>/dev/null || \ + echo "Unable to access logs" + else + sudo journalctl -u "$PLEX_SERVICE" --no-pager -n "$lines" --output=short-iso 2>/dev/null || \ + journalctl -u "$PLEX_SERVICE" --no-pager -n "$lines" --output=short-iso 2>/dev/null || \ + echo "Unable to access logs" + fi + return + fi + + # Interactive mode with styled output + if [[ "$follow" == "true" ]]; then + if use_colors; then + echo -e "${BOLD}${CYAN}Following Plex Media Server logs (Ctrl+C to stop)...${RESET}\n" + else + echo "Following Plex Media Server logs (Ctrl+C to stop)..." + echo "" + fi + + sudo journalctl -u "$PLEX_SERVICE" --no-pager -f --output=short 2>/dev/null || \ + journalctl -u "$PLEX_SERVICE" --no-pager -f --output=short 2>/dev/null || { + if use_colors; then + echo -e "${RED}Unable to access logs. Try: sudo journalctl -u ${PLEX_SERVICE} -f${RESET}" + else + echo "Unable to access logs. Try: sudo journalctl -u ${PLEX_SERVICE} -f" + fi + } else - echo "+----------------------------------+" + if use_colors; then + echo -e "${BOLD}${CYAN}Recent Plex Media Server logs (last ${lines} lines):${RESET}\n" + else + echo "Recent Plex Media Server logs (last ${lines} lines):" + echo "" + fi + + local logs + if logs=$(sudo journalctl -u "$PLEX_SERVICE" --no-pager -n "$lines" --output=short 2>/dev/null); then + if [[ -n "$logs" && "$logs" != "-- No entries --" ]]; then + if use_colors; then + echo -e "${DIM}${logs}${RESET}" + else + echo "${logs}" + fi + else + if use_colors; then + echo -e "${YELLOW}No log entries found${RESET}" + else + echo "No log entries found" + fi + fi + else + # Fallback: try without sudo + logs=$(journalctl -u "$PLEX_SERVICE" --no-pager -n "$lines" --output=short 2>/dev/null || echo "Unable to access logs") + if [[ "$logs" == "Unable to access logs" || "$logs" == "-- No entries --" ]]; then + if use_colors; then + echo -e "${YELLOW}Unable to access logs. Try: ${WHITE}sudo journalctl -u ${PLEX_SERVICE} -n ${lines}${RESET}" + else + echo "Unable to access logs. Try: sudo journalctl -u ${PLEX_SERVICE} -n ${lines}" + fi + else + if use_colors; then + echo -e "${DIM}${logs}${RESET}" + else + echo "${logs}" + fi + fi + fi fi } # 🔧 Show available commands show_help() { if use_colors; then - echo -e "${BOLD}${WHITE}Usage:${RESET} ${CYAN}${SCRIPT_NAME}${RESET} ${YELLOW}${RESET}" + echo -e "${BOLD}${WHITE}Usage:${RESET} ${CYAN}${SCRIPT_NAME}${RESET} ${YELLOW}[OPTIONS] ${RESET}" echo "" echo -e "${BOLD}${WHITE}Available Commands:${RESET}" echo -e " ${GREEN}${BOLD}start${RESET} ${ROCKET} Start Plex Media Server" echo -e " ${YELLOW}${BOLD}stop${RESET} ${STOP_SIGN} Stop Plex Media Server" echo -e " ${BLUE}${BOLD}restart${RESET} ${RECYCLE} Restart Plex Media Server" echo -e " ${CYAN}${BOLD}status${RESET} ${INFO} Show detailed service status" + echo -e " ${PURPLE}${BOLD}logs${RESET} 📋 Show recent service logs" echo -e " ${PURPLE}${BOLD}help${RESET} ${SPARKLES} Show this help message" echo "" + echo -e "${BOLD}${WHITE}Options:${RESET}" + echo -e " ${WHITE}-p, --porcelain${RESET} Simple, machine-readable output" + echo "" + echo -e "${BOLD}${WHITE}Logs Command Usage:${RESET}" + echo -e " ${DIM}${SCRIPT_NAME} logs${RESET} Show last 100 log lines" + echo -e " ${DIM}${SCRIPT_NAME} logs 50${RESET} Show last 50 log lines" + echo -e " ${DIM}${SCRIPT_NAME} logs -f${RESET} Follow logs in real-time" + echo "" echo -e "${DIM}${WHITE}Examples:${RESET}" - echo -e " ${DIM}${SCRIPT_NAME} start # Start the Plex service${RESET}" - echo -e " ${DIM}${SCRIPT_NAME} status # Show current status${RESET}" + echo -e " ${DIM}${SCRIPT_NAME} start # Start the Plex service${RESET}" + echo -e " ${DIM}${SCRIPT_NAME} status --porcelain # Machine-readable status${RESET}" + echo -e " ${DIM}${SCRIPT_NAME} logs -f # Follow logs in real-time${RESET}" else - echo "Usage: ${SCRIPT_NAME} " + echo "Usage: ${SCRIPT_NAME} [OPTIONS] " echo "" echo "Available Commands:" echo " start ${ROCKET} Start Plex Media Server" echo " stop ${STOP_SIGN} Stop Plex Media Server" echo " restart ${RECYCLE} Restart Plex Media Server" echo " status ${INFO} Show detailed service status" + echo " logs 📋 Show recent service logs" echo " help ${SPARKLES} Show this help message" echo "" + echo "Options:" + echo " -p, --porcelain Simple, machine-readable output" + echo "" + echo "Logs Command Usage:" + echo " ${SCRIPT_NAME} logs Show last 100 log lines" + echo " ${SCRIPT_NAME} logs 50 Show last 50 log lines" + echo " ${SCRIPT_NAME} logs -f Follow logs in real-time" + echo "" echo "Examples:" echo " ${SCRIPT_NAME} start # Start the Plex service" echo " ${SCRIPT_NAME} status # Show current status" @@ -377,19 +529,47 @@ main() { exit 1 fi - # Check if no arguments provided - if [[ $# -eq 0 ]]; then + # Parse command line arguments + local command="" + local args=() + + while [[ $# -gt 0 ]]; do + case $1 in + -p|--porcelain) + PORCELAIN_MODE=true + shift + ;; + -h|--help|help) + command="help" + shift + ;; + start|stop|restart|reload|status|info|logs) + command="${1,,}" # Convert to lowercase + shift + # Collect remaining arguments for the command (especially for logs) + args=("$@") + break + ;; + *) + echo "Unknown option or command: $1" >&2 + exit 3 + ;; + esac + done + + # Check if no command provided + if [[ -z "$command" ]]; then print_header show_help exit 1 fi # Show header for all operations except help - if [[ "${1,,}" != "help" ]] && [[ "${1,,}" != "--help" ]] && [[ "${1,,}" != "-h" ]]; then + if [[ "$command" != "help" ]]; then print_header fi - case "${1,,}" in # Convert to lowercase + case "$command" in "start") start_plex ;; @@ -402,12 +582,15 @@ main() { "status"|"info") show_detailed_status ;; - "help"|"--help"|"-h") + "logs") + show_logs "${args[@]}" + ;; + "help") print_header show_help ;; *) - print_status "${CROSS}" "Unknown command: ${RED}${BOLD}$1${RESET}" "${RED}" + print_status "${CROSS}" "Unknown command: ${RED}${BOLD}$command${RESET}" "${RED}" echo "" show_help exit 1