546 lines
19 KiB
Bash
546 lines
19 KiB
Bash
#!/bin/bash
|
|
|
|
# ===============================================
|
|
# WeChat Article API Service - Linux Deployment Script v2.0
|
|
# ===============================================
|
|
|
|
# Error handling
|
|
set -e # Exit on error
|
|
set -o pipefail # Catch errors in pipes
|
|
|
|
# Trap errors
|
|
trap 'echo -e "\n${RED}Error: Deployment failed at line $LINENO${NC}" >&2; exit 1' ERR
|
|
|
|
# Color definitions
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Configuration variables
|
|
PROJECT_NAME="wechat-article-api"
|
|
SERVICE_PORT=5000
|
|
PYTHON_VERSION="3.8"
|
|
VENV_NAME="venv"
|
|
DEPLOY_USER="wechat-api" # Dedicated service user
|
|
|
|
# Get current directory (compatible with different shells)
|
|
if [ -n "${BASH_SOURCE[0]}" ]; then
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
else
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
fi
|
|
INSTALL_DIR="$SCRIPT_DIR"
|
|
LOG_DIR="$INSTALL_DIR/logs"
|
|
|
|
# Get the actual user who ran sudo (if applicable)
|
|
if [ -n "$SUDO_USER" ]; then
|
|
REAL_USER="$SUDO_USER"
|
|
else
|
|
REAL_USER="$USER"
|
|
fi
|
|
|
|
# ===============================================
|
|
# Show welcome message
|
|
# ===============================================
|
|
show_welcome() {
|
|
echo
|
|
echo "========================================"
|
|
echo " WeChat Article API Deployment Tool v1.0.0"
|
|
echo "========================================"
|
|
echo
|
|
echo -e "${BLUE}Installation directory: $INSTALL_DIR${NC}"
|
|
echo -e "${BLUE}Service port: $SERVICE_PORT${NC}"
|
|
echo -e "${BLUE}Service user: $DEPLOY_USER${NC}"
|
|
echo
|
|
}
|
|
|
|
# ===============================================
|
|
# Check permissions
|
|
# ===============================================
|
|
check_permission() {
|
|
echo -e "${BLUE}Checking system and permissions...${NC}"
|
|
|
|
# Detect OS
|
|
if [ -f /etc/os-release ]; then
|
|
. /etc/os-release 2>/dev/null || true
|
|
echo -e "${GREEN}+ OS: ${NAME:-Linux} ${VERSION_ID:-unknown}${NC}"
|
|
fi
|
|
|
|
# Check if running in container
|
|
if [ -f /.dockerenv ] || grep -qa container /proc/1/environ 2>/dev/null; then
|
|
echo -e "${YELLOW}! Container environment detected${NC}"
|
|
fi
|
|
|
|
if [ "$EUID" -ne 0 ]; then
|
|
echo -e "${YELLOW}! Running without root privileges${NC}"
|
|
echo -e "${YELLOW} - Dedicated service user will NOT be created${NC}"
|
|
echo -e "${YELLOW} - Systemd service will NOT be configured${NC}"
|
|
echo -e "${YELLOW} - For full deployment, run: sudo bash start.sh${NC}"
|
|
echo
|
|
echo -e "${YELLOW}Press Enter to continue or Ctrl+C to exit${NC}"
|
|
read -p ""
|
|
else
|
|
echo -e "${GREEN}+ Running with root privileges${NC}"
|
|
fi
|
|
echo
|
|
}
|
|
|
|
# ===============================================
|
|
# Step 1: Check Python
|
|
# ===============================================
|
|
check_python() {
|
|
echo -e "${BLUE}[1/7] Checking Python environment...${NC}"
|
|
|
|
if ! command -v python3 &> /dev/null; then
|
|
echo -e "${RED}X Python3 not found${NC}"
|
|
echo "Please install Python $PYTHON_VERSION or higher"
|
|
exit 1
|
|
fi
|
|
|
|
PYTHON_VER=$(python3 --version | cut -d' ' -f2)
|
|
echo -e "${GREEN}+ Python version: $PYTHON_VER${NC}"
|
|
|
|
# Check venv module
|
|
if ! python3 -m venv --help &> /dev/null; then
|
|
echo -e "${YELLOW}! python3-venv not found${NC}"
|
|
|
|
if [ "$EUID" -eq 0 ]; then
|
|
echo -e "${BLUE} Installing python3-venv...${NC}"
|
|
|
|
# Detect package manager and install
|
|
if command -v apt &> /dev/null; then
|
|
# Debian/Ubuntu
|
|
apt update && apt install -y python3-venv python3-pip || {
|
|
echo -e "${RED}X Failed to install python3-venv${NC}"
|
|
echo "Please run: apt install python3-venv python3-pip"
|
|
exit 1
|
|
}
|
|
elif command -v yum &> /dev/null; then
|
|
# RHEL/CentOS
|
|
yum install -y python3-venv python3-pip || {
|
|
echo -e "${RED}X Failed to install python3-venv${NC}"
|
|
echo "Please run: yum install python3-venv python3-pip"
|
|
exit 1
|
|
}
|
|
elif command -v dnf &> /dev/null; then
|
|
# Fedora
|
|
dnf install -y python3-venv python3-pip || {
|
|
echo -e "${RED}X Failed to install python3-venv${NC}"
|
|
echo "Please run: dnf install python3-venv python3-pip"
|
|
exit 1
|
|
}
|
|
else
|
|
echo -e "${RED}X Cannot determine package manager${NC}"
|
|
echo "Please install python3-venv manually"
|
|
exit 1
|
|
fi
|
|
echo -e "${GREEN}+ python3-venv installed${NC}"
|
|
else
|
|
echo -e "${RED}X python3-venv is required but not installed${NC}"
|
|
echo
|
|
echo "Please run one of the following commands with sudo:"
|
|
echo " Debian/Ubuntu: sudo apt install python3-venv python3-pip"
|
|
echo " RHEL/CentOS: sudo yum install python3-venv python3-pip"
|
|
echo " Fedora: sudo dnf install python3-venv python3-pip"
|
|
echo
|
|
echo "Then run this script again"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Check pip
|
|
if ! command -v pip3 &> /dev/null && ! python3 -m pip --version &> /dev/null; then
|
|
echo -e "${YELLOW}! pip not found, attempting to install...${NC}"
|
|
python3 -m ensurepip --upgrade 2>/dev/null || {
|
|
if [ "$EUID" -eq 0 ]; then
|
|
if command -v apt &> /dev/null; then
|
|
apt install -y python3-pip
|
|
elif command -v yum &> /dev/null; then
|
|
yum install -y python3-pip
|
|
elif command -v dnf &> /dev/null; then
|
|
dnf install -y python3-pip
|
|
fi
|
|
fi
|
|
}
|
|
fi
|
|
echo -e "${GREEN}+ pip available${NC}"
|
|
echo
|
|
}
|
|
|
|
# ===============================================
|
|
# Step 2: Create virtual environment
|
|
# ===============================================
|
|
create_venv() {
|
|
echo -e "${BLUE}[2/7] Creating Python virtual environment...${NC}"
|
|
|
|
if [[ ! -d "$VENV_NAME" ]]; then
|
|
python3 -m venv "$VENV_NAME"
|
|
echo -e "${GREEN}+ Virtual environment created${NC}"
|
|
else
|
|
echo -e "${YELLOW}! Virtual environment already exists, skipping${NC}"
|
|
fi
|
|
|
|
# Activate virtual environment
|
|
source "$VENV_NAME/bin/activate"
|
|
echo -e "${GREEN}+ Virtual environment activated${NC}"
|
|
echo
|
|
}
|
|
|
|
# ===============================================
|
|
# Step 3: Install dependencies
|
|
# ===============================================
|
|
install_dependencies() {
|
|
echo -e "${BLUE}[3/7] Installing Python dependencies...${NC}"
|
|
|
|
# Upgrade pip
|
|
python -m pip install --upgrade pip
|
|
|
|
# Install requirements.txt
|
|
if [[ -f "requirements.txt" ]]; then
|
|
pip install -r requirements.txt
|
|
echo
|
|
# Verify installation
|
|
if ! python -c "import fastapi" 2>/dev/null; then
|
|
echo -e "${RED}X Dependencies installation failed${NC}"
|
|
exit 1
|
|
fi
|
|
echo -e "${GREEN}+ Dependencies installed successfully${NC}"
|
|
else
|
|
echo -e "${YELLOW}! requirements.txt not found, installing core dependencies${NC}"
|
|
pip install fastapi uvicorn httpx python-dotenv
|
|
echo
|
|
# Verify installation
|
|
if ! python -c "import fastapi" 2>/dev/null; then
|
|
echo -e "${RED}X Core dependencies installation failed${NC}"
|
|
exit 1
|
|
fi
|
|
echo -e "${GREEN}+ Core dependencies installed successfully${NC}"
|
|
fi
|
|
echo
|
|
}
|
|
|
|
# ===============================================
|
|
# Step 4: Initialize project
|
|
# ===============================================
|
|
initialize_project() {
|
|
echo -e "${BLUE}[4/7] Initializing project...${NC}"
|
|
|
|
# Create necessary directories
|
|
mkdir -p static logs
|
|
echo -e "${GREEN}+ Directory structure created${NC}"
|
|
|
|
# Create .env file if not exists
|
|
if [[ ! -f ".env" ]]; then
|
|
echo -e "${YELLOW}! .env file not found, creating from template...${NC}"
|
|
|
|
if [[ -f "env.example" ]]; then
|
|
cp env.example .env
|
|
echo -e "${GREEN}+ .env file created from env.example${NC}"
|
|
else
|
|
echo -e "${YELLOW}! env.example not found, creating basic .env file...${NC}"
|
|
cat > .env << 'EOF'
|
|
# WeChat Article API Configuration
|
|
# Auto-generated by start.sh
|
|
|
|
# Authentication Info (Auto-filled after login)
|
|
WECHAT_TOKEN=
|
|
WECHAT_COOKIE=
|
|
WECHAT_FAKEID=
|
|
WECHAT_NICKNAME=
|
|
WECHAT_EXPIRE_TIME=
|
|
|
|
# Webhook Configuration
|
|
WEBHOOK_URL=
|
|
WEBHOOK_NOTIFICATION_INTERVAL=300
|
|
|
|
# Rate Limiting
|
|
RATE_LIMIT_GLOBAL=10
|
|
RATE_LIMIT_PER_IP=5
|
|
RATE_LIMIT_ARTICLE_INTERVAL=3
|
|
EOF
|
|
echo -e "${GREEN}+ Basic .env file created${NC}"
|
|
fi
|
|
|
|
echo
|
|
echo -e "${YELLOW}========================================${NC}"
|
|
echo -e "${YELLOW} First-time Setup${NC}"
|
|
echo -e "${YELLOW}========================================${NC}"
|
|
echo
|
|
echo -e "${GREEN}Next Steps:${NC}"
|
|
echo " 1. Service will start after deployment"
|
|
echo " 2. Visit: http://localhost:$SERVICE_PORT/login.html"
|
|
echo " 3. Scan QR code with WeChat"
|
|
echo " 4. Login credentials will be saved automatically"
|
|
echo
|
|
echo -e "${YELLOW}========================================${NC}"
|
|
echo
|
|
else
|
|
echo -e "${GREEN}+ .env configuration file found${NC}"
|
|
|
|
# Check if credentials are actually configured (not empty)
|
|
if grep -q "WECHAT_TOKEN=.\+" .env 2>/dev/null; then
|
|
echo -e "${GREEN}+ WeChat login credentials configured${NC}"
|
|
else
|
|
echo -e "${YELLOW}! WeChat credentials not configured yet${NC}"
|
|
echo -e "${YELLOW} Please visit http://localhost:$SERVICE_PORT/login.html to login${NC}"
|
|
fi
|
|
fi
|
|
echo
|
|
}
|
|
|
|
# ===============================================
|
|
# Step 5: Start service (non-root mode)
|
|
# ===============================================
|
|
start_service() {
|
|
echo -e "${BLUE}[5/7] Starting service...${NC}"
|
|
|
|
# If running with root, service will be started via systemd
|
|
if [ "$EUID" -eq 0 ]; then
|
|
echo -e "${YELLOW}! Service will be started via systemd (see step 7)${NC}"
|
|
echo
|
|
return
|
|
fi
|
|
|
|
# For non-root users, start service directly in foreground
|
|
echo
|
|
echo "========================================"
|
|
echo -e "${GREEN}Service Startup Information${NC}"
|
|
echo "========================================"
|
|
echo
|
|
echo -e "${BLUE}Access URLs:${NC}"
|
|
echo " - Admin Panel: http://localhost:$SERVICE_PORT/admin.html"
|
|
echo " - Login Page: http://localhost:$SERVICE_PORT/login.html"
|
|
echo " - API Docs: http://localhost:$SERVICE_PORT/api/docs"
|
|
echo " - ReDoc: http://localhost:$SERVICE_PORT/api/redoc"
|
|
echo " - Health: http://localhost:$SERVICE_PORT/api/health"
|
|
echo
|
|
echo -e "${BLUE}Core Features:${NC}"
|
|
echo " + Article Retrieval - POST /api/article"
|
|
echo " + Article List - GET /api/public/articles"
|
|
echo " + Article Search - GET /api/public/articles/search"
|
|
echo " + Account Search - GET /api/public/searchbiz"
|
|
echo " + Image Proxy - GET /api/image"
|
|
echo " + Auto Rate Limiting"
|
|
echo " + Webhook Notifications"
|
|
echo
|
|
echo -e "${YELLOW}First time? Please visit login page to scan QR code:${NC}"
|
|
echo " => http://localhost:$SERVICE_PORT/login.html"
|
|
echo
|
|
echo -e "${YELLOW}Tip: Press Ctrl+C to stop service${NC}"
|
|
echo "========================================"
|
|
echo
|
|
|
|
# Start the service
|
|
python app.py
|
|
}
|
|
|
|
# ===============================================
|
|
# Step 6: Create dedicated service user (optional)
|
|
# ===============================================
|
|
create_service_user() {
|
|
echo -e "${BLUE}[6/7] Creating dedicated service user...${NC}"
|
|
|
|
if [ "$EUID" -ne 0 ]; then
|
|
echo -e "${YELLOW}! Not running as root, skipping user creation${NC}"
|
|
echo -e "${YELLOW}! Service will run as: $REAL_USER${NC}"
|
|
DEPLOY_USER="$REAL_USER"
|
|
echo
|
|
return
|
|
fi
|
|
|
|
# Check if user already exists
|
|
if id "$DEPLOY_USER" &>/dev/null; then
|
|
echo -e "${GREEN}+ Service user already exists: $DEPLOY_USER${NC}"
|
|
else
|
|
# Try to create system user
|
|
echo -e "${BLUE} Creating user $DEPLOY_USER...${NC}"
|
|
|
|
# Try different methods depending on the system
|
|
if command -v useradd &>/dev/null; then
|
|
# Most Linux distributions
|
|
if useradd -r -s /usr/sbin/nologin -c "WeChat Article API Service" "$DEPLOY_USER" 2>/dev/null; then
|
|
echo -e "${GREEN}+ Created service user: $DEPLOY_USER${NC}"
|
|
elif useradd -r -s /bin/false -c "WeChat Article API Service" "$DEPLOY_USER" 2>/dev/null; then
|
|
echo -e "${GREEN}+ Created service user: $DEPLOY_USER${NC}"
|
|
else
|
|
echo -e "${YELLOW}! User creation failed, trying with adduser...${NC}"
|
|
if command -v adduser &>/dev/null; then
|
|
adduser --system --no-create-home --group "$DEPLOY_USER" 2>/dev/null || {
|
|
echo -e "${YELLOW}! All user creation methods failed, using current user: $REAL_USER${NC}"
|
|
DEPLOY_USER="$REAL_USER"
|
|
}
|
|
else
|
|
echo -e "${YELLOW}! Using current user: $REAL_USER${NC}"
|
|
DEPLOY_USER="$REAL_USER"
|
|
fi
|
|
fi
|
|
else
|
|
echo -e "${YELLOW}! useradd not found, using current user: $REAL_USER${NC}"
|
|
DEPLOY_USER="$REAL_USER"
|
|
fi
|
|
fi
|
|
|
|
# Set proper ownership
|
|
if [ "$DEPLOY_USER" != "$REAL_USER" ]; then
|
|
echo -e "${BLUE} Setting file ownership...${NC}"
|
|
if chown -R "$DEPLOY_USER:$DEPLOY_USER" "$INSTALL_DIR" 2>/dev/null; then
|
|
echo -e "${GREEN}+ Ownership set to: $DEPLOY_USER${NC}"
|
|
else
|
|
echo -e "${YELLOW}! Warning: Could not set ownership, trying with group only...${NC}"
|
|
if getent group "$DEPLOY_USER" &>/dev/null; then
|
|
chown -R "$DEPLOY_USER:$DEPLOY_USER" "$INSTALL_DIR" 2>/dev/null || {
|
|
echo -e "${YELLOW}! Warning: File ownership not changed${NC}"
|
|
}
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
}
|
|
|
|
# ===============================================
|
|
# Step 7: Configure systemd service (optional)
|
|
# ===============================================
|
|
configure_systemd() {
|
|
echo -e "${BLUE}[7/7] Configuring systemd service...${NC}"
|
|
|
|
if [ "$EUID" -ne 0 ]; then
|
|
echo -e "${YELLOW}! Not running as root, skipping systemd configuration${NC}"
|
|
echo -e "${YELLOW}! To configure systemd, run: sudo bash start.sh${NC}"
|
|
echo
|
|
return
|
|
fi
|
|
|
|
# Create systemd service file
|
|
cat > /etc/systemd/system/wechat-article-api.service << EOF
|
|
[Unit]
|
|
Description=WeChat Article API Service
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=$DEPLOY_USER
|
|
Group=$DEPLOY_USER
|
|
WorkingDirectory=$INSTALL_DIR
|
|
Environment="PATH=$INSTALL_DIR/$VENV_NAME/bin"
|
|
ExecStart=$INSTALL_DIR/$VENV_NAME/bin/python $INSTALL_DIR/app.py
|
|
Restart=always
|
|
RestartSec=10
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=wechat-article-api
|
|
|
|
# Security hardening
|
|
NoNewPrivileges=true
|
|
PrivateTmp=true
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
ReadWritePaths=$INSTALL_DIR/logs
|
|
ReadWritePaths=$INSTALL_DIR/.env
|
|
ReadWritePaths=$INSTALL_DIR/static/qrcodes
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
echo -e "${GREEN}+ systemd service file created${NC}"
|
|
|
|
# Reload systemd configuration
|
|
systemctl daemon-reload
|
|
|
|
# Ask if user wants to start service now
|
|
read -p "Enable and start systemd service now? (y/N): " START_SERVICE
|
|
if [[ "$START_SERVICE" =~ ^[Yy]$ ]]; then
|
|
systemctl enable wechat-article-api.service
|
|
systemctl start wechat-article-api.service
|
|
echo -e "${GREEN}+ Service started${NC}"
|
|
|
|
# Show service status
|
|
echo
|
|
echo -e "${BLUE}Service status:${NC}"
|
|
systemctl status wechat-article-api --no-pager || true
|
|
else
|
|
echo -e "${YELLOW}! Service start skipped, you can start it manually later${NC}"
|
|
fi
|
|
echo
|
|
}
|
|
|
|
# ===============================================
|
|
# Show deployment summary
|
|
# ===============================================
|
|
show_summary() {
|
|
echo
|
|
echo "========================================"
|
|
echo -e "${GREEN}Deployment completed!${NC}"
|
|
echo "========================================"
|
|
echo
|
|
echo -e "${GREEN}Deployment Information:${NC}"
|
|
echo " - Installation directory: $INSTALL_DIR"
|
|
echo " - Service port: $SERVICE_PORT"
|
|
echo " - Service user: $DEPLOY_USER"
|
|
echo " - Virtual environment: $VENV_NAME"
|
|
echo " - Log directory: $LOG_DIR"
|
|
echo
|
|
echo -e "${GREEN}Usage:${NC}"
|
|
if [ "$EUID" -ne 0 ]; then
|
|
echo " - Restart: ./start.sh"
|
|
echo " - Stop: Press Ctrl+C or use ./stop.sh"
|
|
echo " - Status: ./status.sh"
|
|
echo " - Activate venv: source venv/bin/activate"
|
|
fi
|
|
echo
|
|
|
|
if [ "$EUID" -eq 0 ]; then
|
|
echo -e "${GREEN}systemd Commands:${NC}"
|
|
echo " - Start service: systemctl start wechat-article-api"
|
|
echo " - Stop service: systemctl stop wechat-article-api"
|
|
echo " - Restart service: systemctl restart wechat-article-api"
|
|
echo " - View status: systemctl status wechat-article-api"
|
|
echo " - View logs: journalctl -u wechat-article-api -f"
|
|
echo
|
|
fi
|
|
|
|
echo -e "${GREEN}Access URLs:${NC}"
|
|
echo " - Admin Panel: http://localhost:$SERVICE_PORT/admin.html"
|
|
echo " - Login Page: http://localhost:$SERVICE_PORT/login.html"
|
|
echo " - API Documentation: http://localhost:$SERVICE_PORT/api/docs"
|
|
echo " - ReDoc: http://localhost:$SERVICE_PORT/api/redoc"
|
|
echo " - Health Check: http://localhost:$SERVICE_PORT/api/health"
|
|
echo
|
|
echo -e "${GREEN}Core Features:${NC}"
|
|
echo " + Article Retrieval - POST /api/article"
|
|
echo " + Article List - GET /api/public/articles"
|
|
echo " + Article Search - GET /api/public/articles/search"
|
|
echo " + Account Search - GET /api/public/searchbiz"
|
|
echo " + Image Proxy - GET /api/image"
|
|
echo " + Auto Rate Limiting"
|
|
echo " + Webhook Notifications"
|
|
echo
|
|
echo -e "${YELLOW}Notes:${NC}"
|
|
echo " - First-time login required via web interface"
|
|
echo " - Credentials saved in .env file"
|
|
echo " - Check port usage: netstat -tulpn | grep :$SERVICE_PORT"
|
|
echo
|
|
}
|
|
|
|
# ===============================================
|
|
# Main function
|
|
# ===============================================
|
|
main() {
|
|
show_welcome
|
|
check_permission
|
|
check_python
|
|
create_venv
|
|
install_dependencies
|
|
initialize_project
|
|
start_service # Non-root: start service directly; Root: skip (use systemd)
|
|
create_service_user # Root only: create dedicated user
|
|
configure_systemd # Root only: configure systemd service
|
|
show_summary
|
|
}
|
|
|
|
# Run main function
|
|
main
|