refactor: see details below

- Fix emailing.
- Updated manage.sh to enhance command handling and service management for backend and frontend.
- Introduced utility functions for better code organization and readability.
- Added support for checking Node.js version and ensuring the virtual environment is set up.
- Implemented improved logging with color-coded output for better visibility.
- Created a new nginx.conf.example file for easy Nginx configuration setup for the application.
This commit is contained in:
2026-01-02 01:57:25 +08:00
parent fdc725b893
commit 5430dc03f4
15 changed files with 1257 additions and 2023 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
# DATABASE_URL=postgresql://user:password@localhost/checkin
# CORS 允许的前端域名(逗号分隔,生产环境必须修改)
CORS_ORIGINS=http://localhost:5173,http://localhost:3000
CORS_ORIGINS=http://localhost:3000
# 日志级别(可选:DEBUG, INFO, WARNING, ERROR
LOG_LEVEL=INFO
+4 -3
View File
@@ -336,9 +336,10 @@ class TaskService:
job_id = f"task_{task.id}"
# 先移除旧的任务(如果存在)
if scheduler.get_job(job_id):
existing_job = scheduler.get_job(job_id)
if existing_job:
scheduler.remove_job(job_id)
logger.debug(f"从调度器移除旧任务: {job_id}")
logger.info(f"从调度器移除旧任务: {job_id}")
# 如果任务启用且有有效的 cron 表达式,添加新任务
if task.is_scheduled_enabled:
@@ -354,7 +355,7 @@ class TaskService:
args=[task.id],
replace_existing=True
)
logger.info(f"✅ 任务 {task.id}添加到调度器: {cron_str}")
logger.info(f"✅ 任务 {task.id}重新加载到调度器: {cron_str}")
else:
logger.warning(f"任务 {task.id} 的 cron 表达式无效: {cron_str}")
else:
+17 -7
View File
@@ -211,12 +211,22 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
"error_message": ""
}
# 情况2: 不在打卡时间范围 → 标记为时间范围外
# 支持多种匹配方式:直接文本匹配、JSON Data 字段、Description 字段
# 情况2: 已经提交过了(重复提交)→ 视为成功,但不发送邮件
# 匹配 "已被提交" 或 "已经打卡"
elif ("已被提交" in response_text or "已经打卡" in response_text or
"重复提交" in response_text):
logger.info(f"✅ 检测到'已被提交',本次打卡已完成(重复提交,不发送邮件)")
return {
"success": True,
"status": "success",
"response_text": response_text,
"error_message": ""
}
# 情况3: 不在打卡时间范围 → 标记为时间范围外
# 匹配 Data 或 Description 中的内容
elif ("不在打卡时间范围" in response_text or
"不在打卡时间" in response_text or
'"Data":"不在打卡时间范围"' in response_text or
'"Description":"不在打卡时间范围"' in response_text):
"不在打卡时间" in response_text):
logger.warning(f"⏰ 检测到'不在打卡时间范围',打卡时间不符")
return {
"success": False,
@@ -225,7 +235,7 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
"error_message": "不在打卡时间范围内"
}
# 情况3: Token 失效的特征标识 → 失败
# 情况4: Token 失效的特征标识 → 失败
elif ("登录" in response_text):
logger.warning(f"⚠️ 检测到登录失败关键字,Token 可能已失效")
if email:
@@ -237,7 +247,7 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
"error_message": "Token 已失效,需要重新授权"
}
# 情况4: 其他响应 → 需要人工确认(标记为异常)
# 情况5: 其他响应 → 需要人工确认(标记为异常)
else:
logger.warning(f"⚠️ 未识别的响应内容,请检查: {response_text[:200]}...")
# 标记为未知状态,记录完整响应供后续分析
+331 -59
View File
@@ -3,8 +3,6 @@ from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import time
import logging
import configparser
from pathlib import Path
from backend.config import settings
@@ -14,79 +12,344 @@ logger = logging.getLogger(__name__)
EXPIRATION_HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="zh">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Token 到期通知</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Token 到期提醒</title>
<style>
body {{ font-family: Arial, sans-serif; background-color: #f4f4f4; color: #333; margin: 20px; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); }}
h1 {{ color: #d9534f; }}
.message {{ background-color: #fff; padding: 15px; border: 1px solid #ddd; border-radius: 5px; margin-bottom: 20px; }}
.important {{ font-weight: bold; color: #d9534f; }}
.footer {{ font-size: 0.9em; color: #666; }}
body {{
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background-color: #f5f5f5;
line-height: 1.6;
}}
.container {{
max-width: 600px;
margin: 40px auto;
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
}}
.header {{
background: linear-gradient(135deg, #f44336 0%, #e91e63 100%);
color: white;
padding: 30px 20px;
text-align: center;
}}
.header h1 {{
margin: 0;
font-size: 24px;
font-weight: 600;
}}
.content {{
padding: 30px 40px;
}}
.alert-box {{
background-color: #fff3e0;
border-left: 4px solid #ff9800;
padding: 16px;
margin: 20px 0;
border-radius: 4px;
}}
.info-item {{
margin: 16px 0;
padding: 12px;
background-color: #f9f9f9;
border-radius: 6px;
}}
.info-item strong {{
color: #333;
display: inline-block;
min-width: 100px;
}}
.highlight {{
color: #f44336;
font-weight: 600;
}}
.action-button {{
display: inline-block;
margin: 20px 0;
padding: 12px 32px;
background: linear-gradient(135deg, #f44336 0%, #e91e63 100%);
color: white;
text-decoration: none;
border-radius: 6px;
font-weight: 500;
}}
.footer {{
background-color: #fafafa;
padding: 20px;
text-align: center;
color: #999;
font-size: 12px;
border-top: 1px solid #eeeeee;
}}
</style>
</head>
<body>
<h1>注意!</h1>
<div class="message">
<p>{name},请注意!</p>
<p>您的 <span class="important">token</span> 已经到期,请尽快重新刷新您的 token,否则您的自动打卡功能将会失效。</p>
<p><strong>到期时间:</strong> {exp_time}</p>
<div class="container">
<div class="header">
<h1>⚠️ Token 即将到期提醒</h1>
</div>
<div class="content">
<p>您好,</p>
<div class="alert-box">
<p>您的接龙打卡系统 <span class="highlight">Token 即将到期</span>,为避免影响自动打卡功能,请尽快刷新您的 Token。</p>
</div>
<div class="info-item">
<strong>到期时间:</strong><span class="highlight">{exp_time}</span>
</div>
<div class="info-item">
<strong>通知时间:</strong>{send_time}
</div>
<p style="margin-top: 20px; color: #666;">
请登录系统,前往 <strong>用户设置</strong> 页面刷新您的 Token,以确保自动打卡功能正常运行。
</p>
</div>
<div class="footer">
<p>此邮件由接龙自动打卡系统自动发送,请勿直接回复</p>
<p>CheckIn App V2 © 2026</p>
</div>
</div>
<p class="footer">邮件发送时间: {send_time}</p>
</body>
</html>
"""
SUCCESS_HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="zh">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>打卡成功通知</title>
<style>
body {{ font-family: Arial, sans-serif; background-color: #f4f4f4; color: #333; margin: 20px; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); }}
h1 {{ color: #5cb85c; }}
.message {{ background-color: #fff; padding: 15px; border: 1px solid #ddd; border-radius: 5px; margin-bottom: 20px; }}
.important {{ font-weight: bold; color: #5cb85c; }}
.footer {{ font-size: 0.9em; color: #666; }}
body {{
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background-color: #f5f5f5;
line-height: 1.6;
}}
.container {{
max-width: 600px;
margin: 40px auto;
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
}}
.header {{
background: linear-gradient(135deg, #4caf50 0%, #66bb6a 100%);
color: white;
padding: 30px 20px;
text-align: center;
}}
.header h1 {{
margin: 0;
font-size: 24px;
font-weight: 600;
}}
.content {{
padding: 30px 40px;
}}
.success-icon {{
text-align: center;
font-size: 64px;
margin: 20px 0;
}}
.success-box {{
background-color: #e8f5e9;
border-left: 4px solid #4caf50;
padding: 16px;
margin: 20px 0;
border-radius: 4px;
}}
.info-item {{
margin: 16px 0;
padding: 12px;
background-color: #f9f9f9;
border-radius: 6px;
}}
.info-item strong {{
color: #333;
display: inline-block;
min-width: 100px;
}}
.highlight {{
color: #4caf50;
font-weight: 600;
}}
.footer {{
background-color: #fafafa;
padding: 20px;
text-align: center;
color: #999;
font-size: 12px;
border-top: 1px solid #eeeeee;
}}
</style>
</head>
<body>
<h1>打卡成功!</h1>
<div class="message">
<p>{name},您好!</p>
<p>系统已于 <span class="important">{send_time}</span> 成功为您完成自动打卡。</p>
<p>您无需进行任何操作,此邮件仅作通知。</p>
<div class="container">
<div class="header">
<h1>✅ 打卡成功</h1>
</div>
<div class="content">
<div class="success-icon">🎉</div>
<p>您好,</p>
<div class="success-box">
<p><strong>自动打卡已成功完成!</strong></p>
</div>
<div class="info-item">
<strong>打卡时间:</strong><span class="highlight">{send_time}</span>
</div>
<div class="info-item">
<strong>打卡状态:</strong><span class="highlight">成功 ✓</span>
</div>
<p style="margin-top: 20px; color: #666;">
您无需进行任何操作,系统已自动为您完成打卡。此邮件仅作通知。
</p>
</div>
<div class="footer">
<p>此邮件由接龙自动打卡系统自动发送,请勿直接回复</p>
<p>CheckIn App V2 © 2026</p>
</div>
</div>
<p class="footer">感谢您的使用!</p>
</body>
</html>
"""
FAILURE_HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="zh">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>打卡失败通知</title>
<style>
body {{ font-family: Arial, sans-serif; background-color: #f4f4f4; color: #333; margin: 20px; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); }}
h1 {{ color: #d9534f; }}
.message {{ background-color: #fff; padding: 15px; border: 1px solid #ddd; border-radius: 5px; margin-bottom: 20px; }}
.important {{ font-weight: bold; color: #d9534f; }}
.footer {{ font-size: 0.9em; color: #666; }}
body {{
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background-color: #f5f5f5;
line-height: 1.6;
}}
.container {{
max-width: 600px;
margin: 40px auto;
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
}}
.header {{
background: linear-gradient(135deg, #f44336 0%, #e91e63 100%);
color: white;
padding: 30px 20px;
text-align: center;
}}
.header h1 {{
margin: 0;
font-size: 24px;
font-weight: 600;
}}
.content {{
padding: 30px 40px;
}}
.error-icon {{
text-align: center;
font-size: 64px;
margin: 20px 0;
}}
.error-box {{
background-color: #ffebee;
border-left: 4px solid #f44336;
padding: 16px;
margin: 20px 0;
border-radius: 4px;
}}
.info-item {{
margin: 16px 0;
padding: 12px;
background-color: #f9f9f9;
border-radius: 6px;
}}
.info-item strong {{
color: #333;
display: inline-block;
min-width: 100px;
}}
.highlight {{
color: #f44336;
font-weight: 600;
}}
.action-box {{
background-color: #fff3e0;
padding: 16px;
margin: 20px 0;
border-radius: 6px;
border: 1px solid #ffb74d;
}}
.action-box h3 {{
margin: 0 0 12px 0;
color: #ff6f00;
font-size: 16px;
}}
.action-box ul {{
margin: 8px 0;
padding-left: 20px;
}}
.action-box li {{
margin: 6px 0;
color: #666;
}}
.footer {{
background-color: #fafafa;
padding: 20px;
text-align: center;
color: #999;
font-size: 12px;
border-top: 1px solid #eeeeee;
}}
</style>
</head>
<body>
<h1>通知:自动打卡失败!</h1>
<div class="message">
<p>{name},您好!</p>
<p>系统于 <span class="important">{send_time}</span> 尝试为您自动打卡时失败。</p>
<p><strong>失败原因:</strong> 服务器返回 "需要登录",这通常意味着您的 <span class="important">Token 已失效</span>。</p>
<p><strong>请您立即刷新您的 Token,以确保后续打卡能够成功。</strong></p>
<div class="container">
<div class="header">
<h1>❌ 打卡失败通知</h1>
</div>
<div class="content">
<div class="error-icon">⚠️</div>
<p>您好,</p>
<div class="error-box">
<p><strong>自动打卡失败,需要您的关注!</strong></p>
</div>
<div class="info-item">
<strong>失败时间:</strong>{send_time}
</div>
<div class="info-item">
<strong>失败原因:</strong><span class="highlight">Token 已失效(需要登录)</span>
</div>
<div class="action-box">
<h3>📋 需要您执行以下操作:</h3>
<ul>
<li>登录接龙自动打卡系统</li>
<li>刷新您的 Authorization Token</li>
<li>确认 Token 更新成功</li>
</ul>
</div>
<p style="margin-top: 20px; color: #666;">
Token 失效是正常现象,通常在一段时间后会自动过期。刷新 Token 后,系统将恢复自动打卡功能。
</p>
</div>
<div class="footer">
<p>此邮件由接龙自动打卡系统自动发送,请勿直接回复</p>
<p>CheckIn App V2 © 2026</p>
</div>
</div>
<p class="footer">感谢您的使用!</p>
</body>
</html>
"""
@@ -94,29 +357,31 @@ FAILURE_HTML_TEMPLATE = """
def get_email_settings():
"""
config.ini 读取邮件配置
环境变量读取邮件配置
如果 SMTP_SERVER、SMTP_PORT 或 SMTP_SENDER_EMAIL 有任一为空,则禁用邮件功能
Returns:
dict: 邮件配置,如果配置文件不存在则返回 None
dict: 邮件配置,如果配置不完整则返回 None
"""
if not settings.EMAIL_CONFIG_FILE.exists():
logger.warning("找不到 config.ini,无法发送邮件")
# 检查必要的邮件配置是否存在
if not settings.SMTP_SERVER or not settings.SMTP_SENDER_EMAIL:
logger.debug("邮件配置未完成(SMTP_SERVER 或 SMTP_SENDER_EMAIL 为空),邮件发送功能已禁用")
return None
try:
config_parser = configparser.ConfigParser()
config_parser.read(settings.EMAIL_CONFIG_FILE, encoding='utf-8')
if 'Email' not in config_parser:
logger.warning("config.ini 中缺少 [Email] 配置段")
return None
return config_parser['Email']
except Exception as e:
logger.error(f"读取邮件配置失败: {e}")
if not settings.SMTP_PORT:
logger.debug("邮件配置未完成(SMTP_PORT 为空),邮件发送功能已禁用")
return None
# 返回配置字典
return {
'smtpserver': settings.SMTP_SERVER,
'smtpport': settings.SMTP_PORT,
'senderemail': settings.SMTP_SENDER_EMAIL,
'senderpassword': settings.SMTP_SENDER_PASSWORD,
'use_ssl': settings.SMTP_USE_SSL
}
def _send_email(to_email: str, subject: str, html_content: str, email_settings: dict) -> bool:
"""
@@ -138,9 +403,16 @@ def _send_email(to_email: str, subject: str, html_content: str, email_settings:
msg["Subject"] = subject
msg.attach(MIMEText(html_content, 'html', 'utf-8'))
with smtplib.SMTP_SSL(email_settings['smtpserver'], int(email_settings['smtpport'])) as server:
server.login(email_settings['senderemail'], email_settings['senderpassword'])
server.sendmail(msg["From"], msg["To"], msg.as_string())
# 根据配置选择使用 SSL 或普通 SMTP
if email_settings.get('use_ssl', True):
with smtplib.SMTP_SSL(email_settings['smtpserver'], int(email_settings['smtpport'])) as server:
server.login(email_settings['senderemail'], email_settings['senderpassword'])
server.sendmail(msg["From"], msg["To"], msg.as_string())
else:
with smtplib.SMTP(email_settings['smtpserver'], int(email_settings['smtpport'])) as server:
server.starttls()
server.login(email_settings['senderemail'], email_settings['senderpassword'])
server.sendmail(msg["From"], msg["To"], msg.as_string())
logger.info(f"已成功向 {to_email} 发送邮件,主题: {subject}")
return True
@@ -22,53 +22,32 @@
# ==============================================================================
[Unit]
# Service description
Description=CheckIn App V2 - Backend API Service
Documentation=https://github.com/your-repo/checkin-app
# Start after network and database are available
Documentation=https://github.com/Cccc-owo/CheckInApp
After=network.target
Wants=network-online.target
[Service]
# Service type
Type=simple
Type=forking
# User and Group
# IMPORTANT: Replace 'www-data' with your actual user
# Create a dedicated user: sudo useradd -r -s /bin/false checkin
User=www-data
Group=www-data
# CHANGE THIS: Replace with your actual installation path
# Example: /home/username/CheckInApp
WorkingDirectory=/path/to/CheckInApp
# Working directory
# IMPORTANT: Replace with your actual installation path
WorkingDirectory=/opt/checkin-app
# PID file written by manage.sh
PIDFile=/path/to/CheckInApp/backend.pid
# Environment variables
Environment="PATH=/opt/checkin-app/venv/bin:/usr/local/bin:/usr/bin:/bin"
Environment="PYTHONPATH=/opt/checkin-app"
# Start backend using manage.sh script
ExecStart=/path/to/CheckInApp/manage.sh start backend
# Load environment variables from .env file (optional)
EnvironmentFile=-/opt/checkin-app/.env
# Command to start the service
# Using uvicorn directly for production
ExecStart=/opt/checkin-app/venv/bin/python /opt/checkin-app/run_daemon.py
# Alternative: Using uvicorn directly with more control
# ExecStart=/opt/checkin-app/venv/bin/uvicorn backend.main:app \
# --host 0.0.0.0 \
# --port 8000 \
# --workers 4 \
# --log-level info \
# --access-log \
# --proxy-headers
# Stop backend using manage.sh script
ExecStop=/path/to/CheckInApp/manage.sh stop backend
# Restart policy
Restart=always
RestartSec=10
# Kill signal
# Kill settings
KillSignal=SIGTERM
KillMode=mixed
@@ -76,18 +55,10 @@ KillMode=mixed
TimeoutStartSec=60
TimeoutStopSec=30
# Resource limits (optional)
# Resource limits
LimitNOFILE=65535
# LimitNPROC=4096
# MemoryLimit=2G
# CPUQuota=200%
# Security settings (optional but recommended)
# Restrict access to the filesystem
# ReadWritePaths=/opt/checkin-app/data /opt/checkin-app/logs /opt/checkin-app/sessions
# ReadOnlyPaths=/opt/checkin-app
# Prevent privilege escalation
# Security settings
NoNewPrivileges=true
# Logging
@@ -96,5 +67,4 @@ StandardError=journal
SyslogIdentifier=checkin-app
[Install]
# Start on boot
WantedBy=multi-user.target
-474
View File
@@ -1,474 +0,0 @@
# CheckIn App V2 - Deployment Guide
This guide explains how to deploy CheckIn App V2 to a production server using Nginx and systemd.
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Server Setup](#server-setup)
3. [Application Setup](#application-setup)
4. [Nginx Configuration](#nginx-configuration)
5. [Systemd Service Setup](#systemd-service-setup)
6. [SSL/TLS Certificate](#ssltls-certificate)
7. [Monitoring and Logs](#monitoring-and-logs)
8. [Troubleshooting](#troubleshooting)
---
## Prerequisites
- **Operating System**: Ubuntu 20.04+ or similar Linux distribution
- **Python**: 3.9 or higher
- **Node.js**: 16+ (for building frontend)
- **Nginx**: 1.18 or higher
- **Domain name** (optional but recommended for SSL)
### Install Required Packages
```bash
# Update system
sudo apt update && sudo apt upgrade -y
# Install Python and tools
sudo apt install -y python3 python3-pip python3-venv
# Install Node.js (using NodeSource)
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs
# Install Nginx
sudo apt install -y nginx
# Install other dependencies
sudo apt install -y git curl wget
```
---
## Server Setup
### 1. Create Application User
```bash
# Create a dedicated user for the application
sudo useradd -r -m -s /bin/bash checkin
sudo usermod -aG www-data checkin
```
### 2. Create Application Directory
```bash
# Create directory structure
sudo mkdir -p /opt/checkin-app
sudo chown -R checkin:www-data /opt/checkin-app
# Create required subdirectories
sudo -u checkin mkdir -p /opt/checkin-app/{data,logs,sessions}
```
---
## Application Setup
### 1. Clone Repository
```bash
# Switch to application user
sudo su - checkin
# Clone the repository
cd /opt/checkin-app
git clone https://github.com/your-repo/checkin-app.git .
# Or upload your files using scp/rsync
```
### 2. Setup Backend
```bash
# Create virtual environment
python3 -m venv venv
# Activate virtual environment
source venv/bin/activate
# Install Python dependencies
pip install -r backend/requirements.txt
# Create .env file
cp .env.example .env
# Edit .env and configure your settings
nano .env
```
**Important Environment Variables:**
```env
# Database
DATABASE_URL=sqlite:///./data/checkin.db
# Security
SECRET_KEY=your-secret-key-here-change-this
ALLOWED_ORIGINS=https://your-domain.com
# QQ Login (if applicable)
QQ_APPID=your-qq-appid
QQ_APPSECRET=your-qq-appsecret
# Email notifications (optional)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-app-password
ADMIN_EMAIL=admin@your-domain.com
```
### 3. Initialize Database
```bash
# Run database migrations if needed
# Example:
# alembic upgrade head
# Or run initialization script
python backend/scripts/create_admin.py
```
### 4. Build Frontend
```bash
# Install frontend dependencies
cd frontend
npm install
# Build for production
npm run build
# Verify build output
ls -lh dist/
```
### 5. Set Permissions
```bash
# Exit from checkin user
exit
# Set proper permissions
sudo chown -R checkin:www-data /opt/checkin-app
sudo chmod -R 755 /opt/checkin-app
sudo chmod -R 775 /opt/checkin-app/{data,logs,sessions}
```
---
## Nginx Configuration
### 1. Copy Configuration
```bash
# Copy example configuration
sudo cp /opt/checkin-app/deployment/nginx.conf.example /etc/nginx/sites-available/checkin-app
# Edit configuration
sudo nano /etc/nginx/sites-available/checkin-app
```
### 2. Update Configuration
Replace the following placeholders:
- `your-domain.com` → Your actual domain name
- `/opt/checkin-app` → Your installation path (if different)
### 3. Enable Site
```bash
# Create symbolic link
sudo ln -s /etc/nginx/sites-available/checkin-app /etc/nginx/sites-enabled/
# Remove default site (optional)
sudo rm /etc/nginx/sites-enabled/default
# Test Nginx configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
```
---
## Systemd Service Setup
### 1. Copy Service File
```bash
# Copy example service file
sudo cp /opt/checkin-app/deployment/checkin-app.service.example /etc/systemd/system/checkin-app.service
# Edit service file
sudo nano /etc/systemd/system/checkin-app.service
```
### 2. Update Service File
Replace placeholders:
- `User=www-data``User=checkin` (if using dedicated user)
- `WorkingDirectory=/opt/checkin-app` → Your installation path
- Adjust paths in `ExecStart` if needed
### 3. Enable and Start Service
```bash
# Reload systemd
sudo systemctl daemon-reload
# Enable service (start on boot)
sudo systemctl enable checkin-app.service
# Start service
sudo systemctl start checkin-app.service
# Check status
sudo systemctl status checkin-app.service
# View logs
sudo journalctl -u checkin-app -f
```
---
## SSL/TLS Certificate
### Using Let's Encrypt (Recommended)
```bash
# Install Certbot
sudo apt install -y certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
# Follow the prompts to configure SSL
# Test auto-renewal
sudo certbot renew --dry-run
```
The Certbot will automatically update your Nginx configuration with SSL settings.
### Manual Certificate Setup
If you have your own SSL certificate:
```bash
# Copy certificate files
sudo mkdir -p /etc/nginx/ssl
sudo cp your-cert.crt /etc/nginx/ssl/
sudo cp your-key.key /etc/nginx/ssl/
# Set permissions
sudo chmod 600 /etc/nginx/ssl/your-key.key
# Update Nginx configuration with certificate paths
```
---
## Monitoring and Logs
### Service Logs
```bash
# View service logs
sudo journalctl -u checkin-app -f
# View last 100 lines
sudo journalctl -u checkin-app -n 100
# View logs since yesterday
sudo journalctl -u checkin-app --since yesterday
```
### Application Logs
```bash
# Backend logs
tail -f /opt/checkin-app/logs/backend.log
# Nginx access logs
sudo tail -f /var/log/nginx/checkin-app-access.log
# Nginx error logs
sudo tail -f /var/log/nginx/checkin-app-error.log
```
### Service Status
```bash
# Check service status
sudo systemctl status checkin-app
# Check if port is listening
sudo netstat -tlnp | grep :8000
# Check process
ps aux | grep python
```
---
## Troubleshooting
### Service Won't Start
```bash
# Check service logs
sudo journalctl -u checkin-app -xe
# Check if port is already in use
sudo lsof -i :8000
# Verify permissions
ls -la /opt/checkin-app/
# Test manual start
sudo -u checkin /opt/checkin-app/venv/bin/python /opt/checkin-app/run_daemon.py
```
### Nginx Errors
```bash
# Test Nginx configuration
sudo nginx -t
# Check error logs
sudo tail -f /var/log/nginx/error.log
# Verify backend is running
curl http://localhost:8000/health
```
### Database Issues
```bash
# Check database file permissions
ls -la /opt/checkin-app/data/
# Check if database is locked
fuser /opt/checkin-app/data/checkin.db
# Backup database
cp /opt/checkin-app/data/checkin.db /opt/checkin-app/data/checkin.db.backup
```
### Frontend Not Loading
```bash
# Verify build exists
ls -la /opt/checkin-app/frontend/dist/
# Check Nginx configuration for root path
grep -n "root" /etc/nginx/sites-available/checkin-app
# Clear browser cache or test with curl
curl -I https://your-domain.com/
```
---
## Updating the Application
### Update Backend
```bash
# Switch to application user
sudo su - checkin
cd /opt/checkin-app
# Pull latest changes
git pull
# Activate virtual environment
source venv/bin/activate
# Update dependencies
pip install -r backend/requirements.txt
# Run migrations if needed
# alembic upgrade head
# Exit and restart service
exit
sudo systemctl restart checkin-app
```
### Update Frontend
```bash
sudo su - checkin
cd /opt/checkin-app/frontend
# Pull latest changes
git pull
# Install dependencies
npm install
# Build
npm run build
# Exit
exit
# No need to restart - Nginx serves static files
```
---
## Security Recommendations
1. **Firewall**: Use `ufw` to restrict access
```bash
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
```
2. **Regular Updates**: Keep system and packages updated
```bash
sudo apt update && sudo apt upgrade
```
3. **Backup**: Regular backups of database and configuration
```bash
# Create backup script
sudo nano /opt/checkin-app/backup.sh
```
4. **Monitoring**: Consider using monitoring tools like Prometheus, Grafana, or Uptime Kuma
5. **Rate Limiting**: Configure Nginx rate limiting for API endpoints
---
## Additional Resources
- [Nginx Documentation](https://nginx.org/en/docs/)
- [Systemd Documentation](https://www.freedesktop.org/software/systemd/man/)
- [Let's Encrypt](https://letsencrypt.org/)
- [FastAPI Deployment](https://fastapi.tiangolo.com/deployment/)
---
## Support
For issues or questions, please:
- Check the logs first
- Review this guide carefully
- Open an issue on GitHub
- Contact system administrator
-307
View File
@@ -1,307 +0,0 @@
# Deployment Files
This directory contains configuration files and scripts for deploying CheckIn App V2 to a production server.
## Files
- **`nginx.conf.example`** - Nginx reverse proxy configuration
- **`checkin-app.service.example`** - Systemd service file
- **`deploy.sh`** - Automated deployment script
- **`DEPLOYMENT.md`** - Comprehensive deployment guide
## Quick Start
### Option 1: Automated Deployment (Recommended)
```bash
# Make script executable
chmod +x deployment/deploy.sh
# Run installation
sudo deployment/deploy.sh install
```
### Option 2: Manual Deployment
Follow the step-by-step guide in [DEPLOYMENT.md](./DEPLOYMENT.md).
## Deployment Script Usage
The `deploy.sh` script provides three main commands:
### 1. Install (First-time deployment)
```bash
sudo deployment/deploy.sh install
```
This will:
- Check system dependencies
- Create application user
- Setup virtual environment
- Install Python dependencies
- Build frontend
- Configure systemd service
- Configure Nginx
- Start all services
### 2. Update (Update existing installation)
```bash
sudo deployment/deploy.sh update
```
This will:
- Backup database
- Pull latest changes (if using git)
- Update Python dependencies
- Rebuild frontend
- Restart services
### 3. Rollback (Revert to previous version)
```bash
sudo deployment/deploy.sh rollback
```
This will:
- Stop services
- Restore database from latest backup
- Restart services
## Configuration Files
### Nginx Configuration
Edit `/etc/nginx/sites-available/checkin-app` and update:
- `server_name` - Your domain name
- `ssl_certificate` and `ssl_certificate_key` - SSL certificate paths
- `root` - Frontend build directory path (usually `/opt/checkin-app/frontend/dist`)
### Systemd Service
Edit `/etc/systemd/system/checkin-app.service` and update:
- `User` and `Group` - Application user (default: `checkin`)
- `WorkingDirectory` - Application directory (default: `/opt/checkin-app`)
- `ExecStart` - Path to Python executable and run script
### Environment Variables
Create and configure `.env` file in the application root:
```bash
sudo nano /opt/checkin-app/.env
```
Required variables:
```env
# Database
DATABASE_URL=sqlite:///./data/checkin.db
# Security
SECRET_KEY=your-secret-key-here
ALLOWED_ORIGINS=https://your-domain.com
# QQ Login
QQ_APPID=your-appid
QQ_APPSECRET=your-appsecret
```
## SSL Certificate Setup
### Using Let's Encrypt (Recommended)
```bash
# Install Certbot
sudo apt install certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d your-domain.com
# Auto-renewal is configured automatically
```
### Manual Certificate
If you have your own SSL certificate:
1. Copy certificate files to `/etc/nginx/ssl/`
2. Update Nginx configuration with correct paths
3. Reload Nginx: `sudo systemctl reload nginx`
## Service Management
### Start Service
```bash
sudo systemctl start checkin-app
```
### Stop Service
```bash
sudo systemctl stop checkin-app
```
### Restart Service
```bash
sudo systemctl restart checkin-app
```
### Check Status
```bash
sudo systemctl status checkin-app
```
### View Logs
```bash
# Application logs
sudo journalctl -u checkin-app -f
# Nginx access logs
sudo tail -f /var/log/nginx/checkin-app-access.log
# Nginx error logs
sudo tail -f /var/log/nginx/checkin-app-error.log
```
## Directory Structure
After deployment, the application structure should look like:
```
/opt/checkin-app/
├── backend/ # Backend Python code
│ ├── api/
│ ├── models/
│ ├── services/
│ └── ...
├── frontend/ # Frontend source code
│ ├── src/
│ ├── dist/ # Built static files (served by Nginx)
│ └── ...
├── venv/ # Python virtual environment
├── data/ # SQLite database
├── logs/ # Application logs
├── sessions/ # Session data
├── deployment/ # Deployment files (this directory)
├── .env # Environment variables
└── run_daemon.py # Application entry point
```
## Troubleshooting
### Service won't start
```bash
# Check logs
sudo journalctl -u checkin-app -xe
# Verify configuration
sudo -u checkin /opt/checkin-app/venv/bin/python /opt/checkin-app/run_daemon.py
```
### Nginx configuration errors
```bash
# Test configuration
sudo nginx -t
# Check error logs
sudo tail -f /var/log/nginx/error.log
```
### Database locked
```bash
# Check what's using the database
sudo fuser /opt/checkin-app/data/checkin.db
# Kill the process if needed
sudo fuser -k /opt/checkin-app/data/checkin.db
```
### Permission issues
```bash
# Fix ownership
sudo chown -R checkin:www-data /opt/checkin-app
# Fix permissions
sudo chmod -R 755 /opt/checkin-app
sudo chmod -R 775 /opt/checkin-app/{data,logs,sessions}
```
## Security Best Practices
1. **Keep system updated**
```bash
sudo apt update && sudo apt upgrade
```
2. **Use firewall**
```bash
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
```
3. **Regular backups**
```bash
# Backup database
sudo -u checkin cp /opt/checkin-app/data/checkin.db /backup/checkin-$(date +%Y%m%d).db
```
4. **Monitor logs**
```bash
# Setup log rotation
sudo nano /etc/logrotate.d/checkin-app
```
5. **Use strong passwords** and **secure SECRET_KEY**
## Performance Tuning
### Nginx
- Enable gzip compression (already configured)
- Configure caching headers (already configured)
- Adjust worker processes based on CPU cores
### Backend
- Increase uvicorn workers in service file:
```
ExecStart=/opt/checkin-app/venv/bin/uvicorn backend.main:app --workers 4
```
- Consider using Gunicorn with uvicorn workers for production
### Database
- For high traffic, consider switching to PostgreSQL
- Regular VACUUM for SQLite
## Monitoring
Consider setting up monitoring tools:
- **Uptime monitoring**: Uptime Kuma, UptimeRobot
- **Log aggregation**: Loki, ELK Stack
- **Metrics**: Prometheus + Grafana
- **Error tracking**: Sentry
## Support
For detailed deployment instructions, see [DEPLOYMENT.md](./DEPLOYMENT.md).
For issues or questions:
- Check application logs
- Review troubleshooting section
- Open an issue on GitHub
-358
View File
@@ -1,358 +0,0 @@
#!/bin/bash
# ==============================================================================
# CheckIn App V2 - Quick Deployment Script
# ==============================================================================
#
# This script automates the deployment process for CheckIn App V2
#
# Usage:
# sudo ./deploy.sh [install|update|rollback]
#
# ==============================================================================
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Configuration
APP_NAME="checkin-app"
APP_USER="checkin"
APP_DIR="/opt/checkin-app"
SERVICE_NAME="checkin-app.service"
NGINX_CONFIG="checkin-app"
# Functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
check_root() {
if [ "$EUID" -ne 0 ]; then
log_error "This script must be run as root (use sudo)"
exit 1
fi
}
check_dependencies() {
log_info "Checking dependencies..."
local missing_deps=()
# Check Python
if ! command -v python3 &> /dev/null; then
missing_deps+=("python3")
fi
# Check Node.js
if ! command -v node &> /dev/null; then
missing_deps+=("nodejs")
fi
# Check Nginx
if ! command -v nginx &> /dev/null; then
missing_deps+=("nginx")
fi
if [ ${#missing_deps[@]} -ne 0 ]; then
log_error "Missing dependencies: ${missing_deps[*]}"
log_info "Please install them first:"
log_info " sudo apt install -y python3 python3-pip python3-venv nodejs nginx"
exit 1
fi
log_info "All dependencies are installed"
}
create_user() {
if id "$APP_USER" &>/dev/null; then
log_info "User $APP_USER already exists"
else
log_info "Creating user $APP_USER..."
useradd -r -m -s /bin/bash "$APP_USER"
usermod -aG www-data "$APP_USER"
log_info "User $APP_USER created"
fi
}
create_directories() {
log_info "Creating application directories..."
mkdir -p "$APP_DIR"
chown -R "$APP_USER:www-data" "$APP_DIR"
sudo -u "$APP_USER" mkdir -p "$APP_DIR"/{data,logs,sessions}
log_info "Directories created"
}
setup_backend() {
log_info "Setting up backend..."
cd "$APP_DIR"
# Create virtual environment
if [ ! -d "venv" ]; then
log_info "Creating virtual environment..."
sudo -u "$APP_USER" python3 -m venv venv
fi
# Install dependencies
log_info "Installing Python dependencies..."
sudo -u "$APP_USER" bash -c "source venv/bin/activate && pip install --upgrade pip && pip install -r backend/requirements.txt"
# Create .env if not exists
if [ ! -f ".env" ]; then
log_warn ".env file not found, please create one from .env.example"
if [ -f ".env.example" ]; then
sudo -u "$APP_USER" cp .env.example .env
log_info "Created .env from .env.example - please configure it"
fi
fi
log_info "Backend setup complete"
}
build_frontend() {
log_info "Building frontend..."
cd "$APP_DIR/frontend"
# Install dependencies
if [ ! -d "node_modules" ]; then
log_info "Installing Node.js dependencies..."
sudo -u "$APP_USER" npm install
fi
# Build
log_info "Building frontend for production..."
sudo -u "$APP_USER" npm run build
if [ -d "dist" ]; then
log_info "Frontend built successfully"
else
log_error "Frontend build failed - dist directory not found"
exit 1
fi
}
setup_systemd() {
log_info "Setting up systemd service..."
if [ -f "$APP_DIR/deployment/checkin-app.service.example" ]; then
# Copy service file
cp "$APP_DIR/deployment/checkin-app.service.example" "/etc/systemd/system/$SERVICE_NAME"
# Reload systemd
systemctl daemon-reload
# Enable service
systemctl enable "$SERVICE_NAME"
log_info "Systemd service configured"
else
log_error "Service file not found: $APP_DIR/deployment/checkin-app.service.example"
exit 1
fi
}
setup_nginx() {
log_info "Setting up Nginx configuration..."
if [ -f "$APP_DIR/deployment/nginx.conf.example" ]; then
# Copy Nginx config
cp "$APP_DIR/deployment/nginx.conf.example" "/etc/nginx/sites-available/$NGINX_CONFIG"
# Create symlink
if [ ! -L "/etc/nginx/sites-enabled/$NGINX_CONFIG" ]; then
ln -s "/etc/nginx/sites-available/$NGINX_CONFIG" "/etc/nginx/sites-enabled/$NGINX_CONFIG"
fi
# Test Nginx config
if nginx -t; then
log_info "Nginx configuration is valid"
else
log_error "Nginx configuration test failed"
exit 1
fi
log_warn "Please edit /etc/nginx/sites-available/$NGINX_CONFIG and configure your domain"
else
log_error "Nginx config file not found: $APP_DIR/deployment/nginx.conf.example"
exit 1
fi
}
start_services() {
log_info "Starting services..."
# Start application
systemctl start "$SERVICE_NAME"
# Reload Nginx
systemctl reload nginx
# Check status
sleep 2
if systemctl is-active --quiet "$SERVICE_NAME"; then
log_info "Application service started successfully"
else
log_error "Application service failed to start"
systemctl status "$SERVICE_NAME"
exit 1
fi
log_info "All services started"
}
install() {
log_info "Starting installation..."
check_root
check_dependencies
create_user
create_directories
setup_backend
build_frontend
setup_systemd
setup_nginx
# Set permissions
chown -R "$APP_USER:www-data" "$APP_DIR"
chmod -R 755 "$APP_DIR"
chmod -R 775 "$APP_DIR"/{data,logs,sessions}
start_services
echo ""
log_info "================================================"
log_info "Installation complete!"
log_info "================================================"
echo ""
log_info "Next steps:"
log_info "1. Configure .env file: sudo nano $APP_DIR/.env"
log_info "2. Configure Nginx: sudo nano /etc/nginx/sites-available/$NGINX_CONFIG"
log_info "3. Set up SSL certificate: sudo certbot --nginx -d your-domain.com"
log_info "4. Restart services: sudo systemctl restart $SERVICE_NAME nginx"
echo ""
log_info "Useful commands:"
log_info " Status: sudo systemctl status $SERVICE_NAME"
log_info " Logs: sudo journalctl -u $SERVICE_NAME -f"
log_info " Restart: sudo systemctl restart $SERVICE_NAME"
echo ""
}
update() {
log_info "Updating application..."
check_root
cd "$APP_DIR"
# Backup database
if [ -f "data/checkin.db" ]; then
log_info "Backing up database..."
sudo -u "$APP_USER" cp data/checkin.db "data/checkin.db.backup.$(date +%Y%m%d_%H%M%S)"
fi
# Pull latest changes (if using git)
if [ -d ".git" ]; then
log_info "Pulling latest changes..."
sudo -u "$APP_USER" git pull
fi
# Update backend
log_info "Updating backend dependencies..."
sudo -u "$APP_USER" bash -c "source venv/bin/activate && pip install -r backend/requirements.txt"
# Rebuild frontend
build_frontend
# Restart service
log_info "Restarting service..."
systemctl restart "$SERVICE_NAME"
# Check status
sleep 2
if systemctl is-active --quiet "$SERVICE_NAME"; then
log_info "Update completed successfully"
else
log_error "Service failed to start after update"
systemctl status "$SERVICE_NAME"
exit 1
fi
}
rollback() {
log_info "Rolling back to previous version..."
check_root
cd "$APP_DIR"
# Find latest backup
LATEST_BACKUP=$(ls -t data/checkin.db.backup.* 2>/dev/null | head -n 1)
if [ -z "$LATEST_BACKUP" ]; then
log_error "No database backup found"
exit 1
fi
log_info "Found backup: $LATEST_BACKUP"
# Stop service
systemctl stop "$SERVICE_NAME"
# Restore database
log_info "Restoring database..."
sudo -u "$APP_USER" cp "$LATEST_BACKUP" data/checkin.db
# Rollback git (if using git)
if [ -d ".git" ]; then
log_warn "Please manually rollback git to the desired commit"
log_info "Example: git reset --hard <commit-hash>"
fi
# Start service
systemctl start "$SERVICE_NAME"
log_info "Rollback completed"
}
# Main
case "${1:-}" in
install)
install
;;
update)
update
;;
rollback)
rollback
;;
*)
echo "CheckIn App V2 - Deployment Script"
echo ""
echo "Usage: $0 {install|update|rollback}"
echo ""
echo "Commands:"
echo " install - Full installation (first time)"
echo " update - Update existing installation"
echo " rollback - Rollback to previous version"
echo ""
exit 1
;;
esac
exit 0
-216
View File
@@ -1,216 +0,0 @@
# ==============================================================================
# CheckIn App V2 - Nginx Configuration Example
# ==============================================================================
#
# Usage:
# 1. Copy this file: sudo cp nginx.conf.example /etc/nginx/sites-available/checkin-app
# 2. Edit the file and replace placeholders with your actual values
# 3. Create symlink: sudo ln -s /etc/nginx/sites-available/checkin-app /etc/nginx/sites-enabled/
# 4. Test config: sudo nginx -t
# 5. Reload Nginx: sudo systemctl reload nginx
#
# ==============================================================================
# Upstream backend API server
upstream checkin_backend {
# Backend FastAPI server running on port 8000
server 127.0.0.1:8000;
# Optional: Add more backend servers for load balancing
# server 127.0.0.1:8001;
# server 127.0.0.1:8002;
# Keep alive connections
keepalive 32;
}
# HTTP Server - Redirect to HTTPS (optional)
server {
listen 80;
listen [::]:80;
server_name your-domain.com www.your-domain.com;
# Redirect all HTTP traffic to HTTPS
return 301 https://$server_name$request_uri;
}
# HTTPS Server
server {
# SSL Configuration
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your-domain.com www.your-domain.com;
# SSL Certificate (Let's Encrypt recommended)
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
# SSL Configuration (Modern)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Root directory for frontend static files
root /opt/checkin-app/frontend/dist;
index index.html;
# Access and Error Logs
access_log /var/log/nginx/checkin-app-access.log;
error_log /var/log/nginx/checkin-app-error.log;
# Gzip Compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
# Client body size (for file uploads)
client_max_body_size 10M;
# ==========================================
# API Proxy Configuration
# ==========================================
location /api/ {
# Proxy to backend
proxy_pass http://checkin_backend;
# Proxy headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (if needed)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffering
proxy_buffering off;
proxy_request_buffering off;
}
# API Documentation
location /docs {
proxy_pass http://checkin_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /redoc {
proxy_pass http://checkin_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /openapi.json {
proxy_pass http://checkin_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# ==========================================
# Frontend Static Files
# ==========================================
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# Frontend routes (SPA)
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# Favicon
location = /favicon.ico {
log_not_found off;
access_log off;
}
# Robots.txt
location = /robots.txt {
log_not_found off;
access_log off;
}
# Health check endpoint
location /health {
proxy_pass http://checkin_backend;
access_log off;
}
}
# ==============================================================================
# Alternative: HTTP-only configuration (for development/internal use)
# ==============================================================================
# Uncomment below if you don't need HTTPS
# server {
# listen 80;
# listen [::]:80;
# server_name your-domain.com;
#
# root /opt/checkin-app/frontend/dist;
# index index.html;
#
# access_log /var/log/nginx/checkin-app-access.log;
# error_log /var/log/nginx/checkin-app-error.log;
#
# gzip on;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
#
# client_max_body_size 10M;
#
# # API Proxy
# location /api/ {
# proxy_pass http://127.0.0.1:8000;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_http_version 1.1;
# proxy_buffering off;
# }
#
# # API Documentation
# location ~ ^/(docs|redoc|openapi.json) {
# proxy_pass http://127.0.0.1:8000;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# }
#
# # Static files
# location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
# expires 1y;
# add_header Cache-Control "public";
# }
#
# # Frontend routes
# location / {
# try_files $uri $uri/ /index.html;
# }
# }
+16 -3
View File
@@ -23,7 +23,7 @@
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14" />
</svg>
接龙 ID: {{ currentTask.thread_id }}
接龙 ID: {{ getThreadId(currentTask) }}
</span>
<span :class="currentTask.is_active ? 'status-success' : 'status-info'">
{{ currentTask.is_active ? '启用中' : '已禁用' }}
@@ -148,8 +148,8 @@
v-else
class="status-error"
> 打卡失败</span>
<span :class="record.trigger_type === 'scheduler' ? 'status-info' : 'status-warning'">
{{ record.trigger_type === 'scheduler' ? '自动触发' : '手动触发' }}
<span :class="record.trigger_type === 'scheduled' ? 'status-info' : 'status-warning'">
{{ record.trigger_type === 'scheduled' ? '自动触发' : '手动触发' }}
</span>
</div>
<div class="flex items-center text-sm text-gray-600">
@@ -239,6 +239,19 @@ const recordStats = computed(() => {
}
})
// 从 payload_config 中提取 ThreadId
const getThreadId = (task) => {
if (!task || !task.payload_config) return '未知'
try {
const payload = JSON.parse(task.payload_config)
return payload.ThreadId || '未知'
} catch (e) {
console.error('解析 payload_config 失败:', e)
return '未知'
}
}
// 获取任务详情
const fetchTaskDetail = async () => {
try {
+26 -3
View File
@@ -110,7 +110,7 @@
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
接龙ID: {{ task.thread_id || '未知' }}
接龙ID: {{ getThreadId(task) }}
</div>
<div class="flex items-center text-sm text-gray-600">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -550,6 +550,19 @@ const handleModeChange = (mode) => {
templateTaskForm.field_values = {}
}
// 从 payload_config 中提取 ThreadId
const getThreadId = (task) => {
if (!task.payload_config) return '未知'
try {
const payload = JSON.parse(task.payload_config)
return payload.ThreadId || '未知'
} catch (e) {
console.error('解析 payload_config 失败:', e)
return '未知'
}
}
// 加载任务列表
const fetchTasks = async () => {
loading.value = true
@@ -570,12 +583,22 @@ const viewTask = (task) => {
// 编辑任务
const editTask = (task) => {
editingTask.value = task
// 从 payload_config 中提取 thread_id
let threadId = ''
try {
const payload = JSON.parse(task.payload_config || '{}')
threadId = payload.ThreadId || ''
} catch (e) {
console.error('解析 payload_config 失败:', e)
}
Object.assign(taskForm, {
name: task.name,
thread_id: task.thread_id,
thread_id: threadId,
is_active: task.is_active,
payload_config: task.payload_config || '{}',
cron_expression: task.cron_expression || '0 20 * * *', // 新增:加载 cron_expression
cron_expression: task.cron_expression || '0 20 * * *',
})
showCreateDialog.value = true
}
+1
View File
@@ -13,6 +13,7 @@ export default defineConfig({
},
server: {
host: '0.0.0.0', // Listen on all network interfaces for LAN access
port: 3000,
proxy: {
'/api': {
+60 -21
View File
@@ -87,7 +87,7 @@ if exist "%BACKEND_PID_FILE%" (
echo [WARNING] Backend is already running (PID: !PID!)
exit /b 0
) else (
echo [INFO] Backend PID file exists but process not running, cleaning up...
REM Silently clean up stale PID file (don't show message)
del "%BACKEND_PID_FILE%" >nul 2>&1
)
)
@@ -106,7 +106,7 @@ if not exist "sessions" mkdir sessions
echo [INFO] Starting backend service in background...
REM Create a VBS script to run Python invisibly
REM Create a VBS script to run Python invisibly (using venv's python.exe directly)
set VBS_FILE=%TEMP%\start_backend.vbs
echo Set WshShell = CreateObject("WScript.Shell") > "%VBS_FILE%"
echo WshShell.Run """%PYTHON_EXE%"" ""%APP_DIR%run_daemon.py""", 0, False >> "%VBS_FILE%"
@@ -167,7 +167,7 @@ if exist "%FRONTEND_PID_FILE%" (
echo [WARNING] Frontend is already running (PID: !PID!)
exit /b 0
) else (
echo [INFO] Frontend PID file exists but process not running, cleaning up...
REM Silently clean up stale PID file (don't show message)
del "%FRONTEND_PID_FILE%" >nul 2>&1
)
)
@@ -211,7 +211,7 @@ del "%VBS_FILE%" >nul 2>&1
echo [INFO] Waiting for frontend to start...
timeout /t 3 /nobreak >nul
REM Check if port 3000 is listening (Vite configured port)
REM Check if port 5000 is listening (Vite configured port)
set SERVICE_RUNNING=0
for /L %%i in (1,1,10) do (
netstat -ano | findstr ":3000" | findstr "LISTENING" >nul 2>&1
@@ -289,8 +289,12 @@ set BACKEND_KILLED=0
for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":8000" ^| findstr "LISTENING"') do (
taskkill /F /PID %%a >nul 2>&1
if "!ERRORLEVEL!"=="0" (
echo [OK] Backend stopped (PID: %%a)
echo [OK] Backend stopped ^(PID: %%a^)
set BACKEND_KILLED=1
REM Delete PID file immediately after successful kill
if exist "%BACKEND_PID_FILE%" (
del "%BACKEND_PID_FILE%" >nul 2>&1
)
)
)
@@ -301,17 +305,24 @@ if "%BACKEND_KILLED%"=="0" (
tasklist /FI "PID eq !PID!" 2>NUL | find /I /N "python.exe">NUL
if "!ERRORLEVEL!"=="0" (
taskkill /F /T /PID !PID! >nul 2>&1
echo [OK] Backend stopped (PID: !PID!)
if "!ERRORLEVEL!"=="0" (
echo [OK] Backend stopped ^(PID: !PID!^)
set BACKEND_KILLED=1
REM Delete PID file immediately after successful kill
del "%BACKEND_PID_FILE%" >nul 2>&1
)
) else (
echo [WARNING] Backend not running (process does not exist)
REM Process doesn't exist, just clean up the stale PID file
del "%BACKEND_PID_FILE%" >nul 2>&1
)
) else (
echo [WARNING] Backend not running (no process found)
)
REM Only show warning if nothing was stopped
if "%BACKEND_KILLED%"=="0" (
echo [WARNING] Backend not running
)
)
REM Clean up PID file
del "%BACKEND_PID_FILE%" >nul 2>&1
exit /b 0
REM --- Frontend Stop Logic ---
@@ -323,12 +334,16 @@ set FRONTEND_KILLED=0
for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":3000" ^| findstr "LISTENING"') do (
taskkill /F /T /PID %%a >nul 2>&1
if "!ERRORLEVEL!"=="0" (
echo [OK] Frontend stopped (PID: %%a)
echo [OK] Frontend stopped ^(PID: %%a^)
set FRONTEND_KILLED=1
REM Delete PID file immediately after successful kill
if exist "%FRONTEND_PID_FILE%" (
del "%FRONTEND_PID_FILE%" >nul 2>&1
)
)
)
REM Also check ports 3001-3010 (Vite fallback ports)
REM Also check ports 5001-5010 (Vite fallback ports)
if "%FRONTEND_KILLED%"=="0" (
for /L %%p in (3001,1,3010) do (
for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":%%p" ^| findstr "LISTENING"') do (
@@ -337,8 +352,12 @@ if "%FRONTEND_KILLED%"=="0" (
if "!ERRORLEVEL!"=="0" (
taskkill /F /T /PID %%a >nul 2>&1
if "!ERRORLEVEL!"=="0" (
echo [OK] Frontend stopped (PID: %%a, Port: %%p)
echo [OK] Frontend stopped ^(PID: %%a, Port: %%p^)
set FRONTEND_KILLED=1
REM Delete PID file immediately after successful kill
if exist "%FRONTEND_PID_FILE%" (
del "%FRONTEND_PID_FILE%" >nul 2>&1
)
)
)
)
@@ -352,17 +371,24 @@ if "%FRONTEND_KILLED%"=="0" (
tasklist /FI "PID eq !PID!" 2>NUL | find /I /N "node.exe">NUL
if "!ERRORLEVEL!"=="0" (
taskkill /F /T /PID !PID! >nul 2>&1
echo [OK] Frontend stopped (PID: !PID!)
if "!ERRORLEVEL!"=="0" (
echo [OK] Frontend stopped ^(PID: !PID!^)
set FRONTEND_KILLED=1
REM Delete PID file immediately after successful kill
del "%FRONTEND_PID_FILE%" >nul 2>&1
)
) else (
echo [WARNING] Frontend not running (process does not exist)
REM Process doesn't exist, just clean up the stale PID file
del "%FRONTEND_PID_FILE%" >nul 2>&1
)
) else (
echo [WARNING] Frontend not running (no process found)
)
REM Only show warning if nothing was stopped
if "%FRONTEND_KILLED%"=="0" (
echo [WARNING] Frontend not running
)
)
REM Clean up PID file
del "%FRONTEND_PID_FILE%" >nul 2>&1
exit /b 0
REM ============================================
@@ -373,6 +399,19 @@ echo [INFO] Restarting %TARGET%...
echo.
call :stop
timeout /t 2 /nobreak >nul
REM Force cleanup of any remaining PID files before starting
if "%TARGET%"=="backend" (
del "%BACKEND_PID_FILE%" >nul 2>&1
)
if "%TARGET%"=="frontend" (
del "%FRONTEND_PID_FILE%" >nul 2>&1
)
if "%TARGET%"=="all" (
del "%BACKEND_PID_FILE%" >nul 2>&1
del "%FRONTEND_PID_FILE%" >nul 2>&1
)
call :start
goto end
@@ -586,7 +625,7 @@ echo build - Build frontend for production
echo.
echo Targets:
echo backend - Backend API service (default port: 8000)
echo frontend - Frontend dev server (default port: 3000)
echo frontend - Frontend dev server (default port: 5000)
echo all - Both services (default)
echo.
echo Examples:
+644 -527
View File
File diff suppressed because it is too large Load Diff
+143
View File
@@ -0,0 +1,143 @@
# ==============================================================================
# CheckIn App V2 - Nginx Configuration Example (HTTP Only)
# ==============================================================================
#
# Usage:
# 1. Copy this file: sudo cp nginx.conf.example /etc/nginx/sites-available/checkin-app
# 2. Edit the file and replace placeholders with your actual values
# 3. Create symlink: sudo ln -s /etc/nginx/sites-available/checkin-app /etc/nginx/sites-enabled/
# 4. Test config: sudo nginx -t
# 5. Reload Nginx: sudo systemctl reload nginx
#
# ==============================================================================
# HTTP Server - Production Configuration
server {
listen 80;
listen [::]:80;
server_name your-domain.com; # CHANGE THIS: Replace with your domain or IP
# CHANGE THIS: Replace with your actual frontend build path
# Example: /home/username/CheckInApp/frontend/dist
root /path/to/CheckInApp/frontend/dist;
index index.html;
# Access and Error Logs
access_log /var/log/nginx/checkin-app-access.log;
error_log /var/log/nginx/checkin-app-error.log;
# Gzip Compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript
application/x-javascript application/xml+rss
application/json application/javascript;
# Client body size (for file uploads)
client_max_body_size 10M;
# ==========================================
# API Proxy Configuration
# ==========================================
location /api/ {
# Proxy to backend FastAPI server
proxy_pass http://127.0.0.1:8000;
# Proxy headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# HTTP version and buffering
proxy_http_version 1.1;
proxy_buffering off;
proxy_request_buffering off;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# API Documentation endpoints
location ~ ^/(docs|redoc|openapi.json|health) {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# ==========================================
# Frontend Static Files
# ==========================================
# Cache static assets (JS, CSS, images, fonts)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# Frontend routes (SPA - Single Page Application)
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# Favicon
location = /favicon.ico {
log_not_found off;
access_log off;
}
# Robots.txt
location = /robots.txt {
log_not_found off;
access_log off;
}
}
# ==============================================================================
# HTTPS Configuration (Currently Commented Out)
# ==============================================================================
# Uncomment and configure below when you need HTTPS with SSL certificate
#
# server {
# listen 80;
# listen [::]:80;
# server_name your-domain.com;
#
# # Redirect HTTP to HTTPS
# return 301 https://$server_name$request_uri;
# }
#
# server {
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# server_name your-domain.com;
#
# # SSL Certificate (use Let's Encrypt: certbot --nginx)
# ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
#
# # SSL Configuration
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
# ssl_prefer_server_ciphers off;
# ssl_session_cache shared:SSL:10m;
# ssl_session_timeout 10m;
#
# # Security Headers
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# add_header X-Frame-Options "SAMEORIGIN" always;
# add_header X-Content-Type-Options "nosniff" always;
#
# # CHANGE THIS: Replace with your actual frontend build path
# root /path/to/CheckInApp/frontend/dist;
# index index.html;
#
# # ... (rest of the configuration same as HTTP version above)
# }