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 # DATABASE_URL=postgresql://user:password@localhost/checkin
# CORS 允许的前端域名(逗号分隔,生产环境必须修改) # CORS 允许的前端域名(逗号分隔,生产环境必须修改)
CORS_ORIGINS=http://localhost:5173,http://localhost:3000 CORS_ORIGINS=http://localhost:3000
# 日志级别(可选:DEBUG, INFO, WARNING, ERROR # 日志级别(可选:DEBUG, INFO, WARNING, ERROR
LOG_LEVEL=INFO LOG_LEVEL=INFO
+4 -3
View File
@@ -336,9 +336,10 @@ class TaskService:
job_id = f"task_{task.id}" 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) scheduler.remove_job(job_id)
logger.debug(f"从调度器移除旧任务: {job_id}") logger.info(f"从调度器移除旧任务: {job_id}")
# 如果任务启用且有有效的 cron 表达式,添加新任务 # 如果任务启用且有有效的 cron 表达式,添加新任务
if task.is_scheduled_enabled: if task.is_scheduled_enabled:
@@ -354,7 +355,7 @@ class TaskService:
args=[task.id], args=[task.id],
replace_existing=True replace_existing=True
) )
logger.info(f"✅ 任务 {task.id}添加到调度器: {cron_str}") logger.info(f"✅ 任务 {task.id}重新加载到调度器: {cron_str}")
else: else:
logger.warning(f"任务 {task.id} 的 cron 表达式无效: {cron_str}") logger.warning(f"任务 {task.id} 的 cron 表达式无效: {cron_str}")
else: else:
+17 -7
View File
@@ -211,12 +211,22 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
"error_message": "" "error_message": ""
} }
# 情况2: 不在打卡时间范围 → 标记为时间范围外 # 情况2: 已经提交过了(重复提交)→ 视为成功,但不发送邮件
# 支持多种匹配方式:直接文本匹配、JSON Data 字段、Description 字段 # 匹配 "已被提交" 或 "已经打卡"
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 elif ("不在打卡时间范围" in response_text or
"不在打卡时间" in response_text or "不在打卡时间" in response_text):
'"Data":"不在打卡时间范围"' in response_text or
'"Description":"不在打卡时间范围"' in response_text):
logger.warning(f"⏰ 检测到'不在打卡时间范围',打卡时间不符") logger.warning(f"⏰ 检测到'不在打卡时间范围',打卡时间不符")
return { return {
"success": False, "success": False,
@@ -225,7 +235,7 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
"error_message": "不在打卡时间范围内" "error_message": "不在打卡时间范围内"
} }
# 情况3: Token 失效的特征标识 → 失败 # 情况4: Token 失效的特征标识 → 失败
elif ("登录" in response_text): elif ("登录" in response_text):
logger.warning(f"⚠️ 检测到登录失败关键字,Token 可能已失效") logger.warning(f"⚠️ 检测到登录失败关键字,Token 可能已失效")
if email: if email:
@@ -237,7 +247,7 @@ def perform_check_in(task, user_token: str) -> Dict[str, Any]:
"error_message": "Token 已失效,需要重新授权" "error_message": "Token 已失效,需要重新授权"
} }
# 情况4: 其他响应 → 需要人工确认(标记为异常) # 情况5: 其他响应 → 需要人工确认(标记为异常)
else: else:
logger.warning(f"⚠️ 未识别的响应内容,请检查: {response_text[:200]}...") logger.warning(f"⚠️ 未识别的响应内容,请检查: {response_text[:200]}...")
# 标记为未知状态,记录完整响应供后续分析 # 标记为未知状态,记录完整响应供后续分析
+327 -55
View File
@@ -3,8 +3,6 @@ from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
import time import time
import logging import logging
import configparser
from pathlib import Path
from backend.config import settings from backend.config import settings
@@ -14,79 +12,344 @@ logger = logging.getLogger(__name__)
EXPIRATION_HTML_TEMPLATE = """ EXPIRATION_HTML_TEMPLATE = """
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Token 到期通知</title> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Token 到期提醒</title>
<style> <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); }} body {{
h1 {{ color: #d9534f; }} margin: 0;
.message {{ background-color: #fff; padding: 15px; border: 1px solid #ddd; border-radius: 5px; margin-bottom: 20px; }} padding: 0;
.important {{ font-weight: bold; color: #d9534f; }} font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
.footer {{ font-size: 0.9em; color: #666; }} 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> </style>
</head> </head>
<body> <body>
<h1>注意!</h1> <div class="container">
<div class="message"> <div class="header">
<p>{name},请注意!</p> <h1>⚠️ Token 即将到期提醒</h1>
<p>您的 <span class="important">token</span> 已经到期,请尽快重新刷新您的 token,否则您的自动打卡功能将会失效。</p> </div>
<p><strong>到期时间:</strong> {exp_time}</p> <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> </div>
<p class="footer">邮件发送时间: {send_time}</p>
</body> </body>
</html> </html>
""" """
SUCCESS_HTML_TEMPLATE = """ SUCCESS_HTML_TEMPLATE = """
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>打卡成功通知</title> <title>打卡成功通知</title>
<style> <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); }} body {{
h1 {{ color: #5cb85c; }} margin: 0;
.message {{ background-color: #fff; padding: 15px; border: 1px solid #ddd; border-radius: 5px; margin-bottom: 20px; }} padding: 0;
.important {{ font-weight: bold; color: #5cb85c; }} font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
.footer {{ font-size: 0.9em; color: #666; }} 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> </style>
</head> </head>
<body> <body>
<h1>打卡成功!</h1> <div class="container">
<div class="message"> <div class="header">
<p>{name},您好!</p> <h1>✅ 打卡成功</h1>
<p>系统已于 <span class="important">{send_time}</span> 成功为您完成自动打卡。</p> </div>
<p>您无需进行任何操作,此邮件仅作通知。</p> <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> </div>
<p class="footer">感谢您的使用!</p>
</body> </body>
</html> </html>
""" """
FAILURE_HTML_TEMPLATE = """ FAILURE_HTML_TEMPLATE = """
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>打卡失败通知</title> <title>打卡失败通知</title>
<style> <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); }} body {{
h1 {{ color: #d9534f; }} margin: 0;
.message {{ background-color: #fff; padding: 15px; border: 1px solid #ddd; border-radius: 5px; margin-bottom: 20px; }} padding: 0;
.important {{ font-weight: bold; color: #d9534f; }} font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
.footer {{ font-size: 0.9em; color: #666; }} 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> </style>
</head> </head>
<body> <body>
<h1>通知:自动打卡失败!</h1> <div class="container">
<div class="message"> <div class="header">
<p>{name},您好!</p> <h1>❌ 打卡失败通知</h1>
<p>系统于 <span class="important">{send_time}</span> 尝试为您自动打卡时失败。</p> </div>
<p><strong>失败原因:</strong> 服务器返回 "需要登录",这通常意味着您的 <span class="important">Token 已失效</span>。</p> <div class="content">
<p><strong>请您立即刷新您的 Token,以确保后续打卡能够成功。</strong></p> <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> </div>
<p class="footer">感谢您的使用!</p>
</body> </body>
</html> </html>
""" """
@@ -94,28 +357,30 @@ FAILURE_HTML_TEMPLATE = """
def get_email_settings(): def get_email_settings():
""" """
config.ini 读取邮件配置 环境变量读取邮件配置
如果 SMTP_SERVER、SMTP_PORT 或 SMTP_SENDER_EMAIL 有任一为空,则禁用邮件功能
Returns: 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 return None
try: if not settings.SMTP_PORT:
config_parser = configparser.ConfigParser() logger.debug("邮件配置未完成(SMTP_PORT 为空),邮件发送功能已禁用")
config_parser.read(settings.EMAIL_CONFIG_FILE, encoding='utf-8')
if 'Email' not in config_parser:
logger.warning("config.ini 中缺少 [Email] 配置段")
return None return None
return config_parser['Email'] # 返回配置字典
return {
except Exception as e: 'smtpserver': settings.SMTP_SERVER,
logger.error(f"读取邮件配置失败: {e}") 'smtpport': settings.SMTP_PORT,
return None '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: 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["Subject"] = subject
msg.attach(MIMEText(html_content, 'html', 'utf-8')) msg.attach(MIMEText(html_content, 'html', 'utf-8'))
# 根据配置选择使用 SSL 或普通 SMTP
if email_settings.get('use_ssl', True):
with smtplib.SMTP_SSL(email_settings['smtpserver'], int(email_settings['smtpport'])) as server: with smtplib.SMTP_SSL(email_settings['smtpserver'], int(email_settings['smtpport'])) as server:
server.login(email_settings['senderemail'], email_settings['senderpassword']) server.login(email_settings['senderemail'], email_settings['senderpassword'])
server.sendmail(msg["From"], msg["To"], msg.as_string()) 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}") logger.info(f"已成功向 {to_email} 发送邮件,主题: {subject}")
return True return True
@@ -22,53 +22,32 @@
# ============================================================================== # ==============================================================================
[Unit] [Unit]
# Service description
Description=CheckIn App V2 - Backend API Service Description=CheckIn App V2 - Backend API Service
Documentation=https://github.com/your-repo/checkin-app Documentation=https://github.com/Cccc-owo/CheckInApp
# Start after network and database are available
After=network.target After=network.target
Wants=network-online.target Wants=network-online.target
[Service] [Service]
# Service type Type=forking
Type=simple
# User and Group # CHANGE THIS: Replace with your actual installation path
# IMPORTANT: Replace 'www-data' with your actual user # Example: /home/username/CheckInApp
# Create a dedicated user: sudo useradd -r -s /bin/false checkin WorkingDirectory=/path/to/CheckInApp
User=www-data
Group=www-data
# Working directory # PID file written by manage.sh
# IMPORTANT: Replace with your actual installation path PIDFile=/path/to/CheckInApp/backend.pid
WorkingDirectory=/opt/checkin-app
# Environment variables # Start backend using manage.sh script
Environment="PATH=/opt/checkin-app/venv/bin:/usr/local/bin:/usr/bin:/bin" ExecStart=/path/to/CheckInApp/manage.sh start backend
Environment="PYTHONPATH=/opt/checkin-app"
# Load environment variables from .env file (optional) # Stop backend using manage.sh script
EnvironmentFile=-/opt/checkin-app/.env ExecStop=/path/to/CheckInApp/manage.sh stop backend
# 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
# Restart policy # Restart policy
Restart=always Restart=always
RestartSec=10 RestartSec=10
# Kill signal # Kill settings
KillSignal=SIGTERM KillSignal=SIGTERM
KillMode=mixed KillMode=mixed
@@ -76,18 +55,10 @@ KillMode=mixed
TimeoutStartSec=60 TimeoutStartSec=60
TimeoutStopSec=30 TimeoutStopSec=30
# Resource limits (optional) # Resource limits
LimitNOFILE=65535 LimitNOFILE=65535
# LimitNPROC=4096
# MemoryLimit=2G
# CPUQuota=200%
# Security settings (optional but recommended) # Security settings
# 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
NoNewPrivileges=true NoNewPrivileges=true
# Logging # Logging
@@ -96,5 +67,4 @@ StandardError=journal
SyslogIdentifier=checkin-app SyslogIdentifier=checkin-app
[Install] [Install]
# Start on boot
WantedBy=multi-user.target 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"> <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" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14" />
</svg> </svg>
接龙 ID: {{ currentTask.thread_id }} 接龙 ID: {{ getThreadId(currentTask) }}
</span> </span>
<span :class="currentTask.is_active ? 'status-success' : 'status-info'"> <span :class="currentTask.is_active ? 'status-success' : 'status-info'">
{{ currentTask.is_active ? '启用中' : '已禁用' }} {{ currentTask.is_active ? '启用中' : '已禁用' }}
@@ -148,8 +148,8 @@
v-else v-else
class="status-error" class="status-error"
> 打卡失败</span> > 打卡失败</span>
<span :class="record.trigger_type === 'scheduler' ? 'status-info' : 'status-warning'"> <span :class="record.trigger_type === 'scheduled' ? 'status-info' : 'status-warning'">
{{ record.trigger_type === 'scheduler' ? '自动触发' : '手动触发' }} {{ record.trigger_type === 'scheduled' ? '自动触发' : '手动触发' }}
</span> </span>
</div> </div>
<div class="flex items-center text-sm text-gray-600"> <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 () => { const fetchTaskDetail = async () => {
try { 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"> <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" /> <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> </svg>
接龙ID: {{ task.thread_id || '未知' }} 接龙ID: {{ getThreadId(task) }}
</div> </div>
<div class="flex items-center text-sm text-gray-600"> <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"> <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 = {} 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 () => { const fetchTasks = async () => {
loading.value = true loading.value = true
@@ -570,12 +583,22 @@ const viewTask = (task) => {
// 编辑任务 // 编辑任务
const editTask = (task) => { const editTask = (task) => {
editingTask.value = 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, { Object.assign(taskForm, {
name: task.name, name: task.name,
thread_id: task.thread_id, thread_id: threadId,
is_active: task.is_active, is_active: task.is_active,
payload_config: task.payload_config || '{}', payload_config: task.payload_config || '{}',
cron_expression: task.cron_expression || '0 20 * * *', // 新增:加载 cron_expression cron_expression: task.cron_expression || '0 20 * * *',
}) })
showCreateDialog.value = true showCreateDialog.value = true
} }
+1
View File
@@ -13,6 +13,7 @@ export default defineConfig({
}, },
server: { server: {
host: '0.0.0.0', // Listen on all network interfaces for LAN access
port: 3000, port: 3000,
proxy: { proxy: {
'/api': { '/api': {
+60 -21
View File
@@ -87,7 +87,7 @@ if exist "%BACKEND_PID_FILE%" (
echo [WARNING] Backend is already running (PID: !PID!) echo [WARNING] Backend is already running (PID: !PID!)
exit /b 0 exit /b 0
) else ( ) 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 del "%BACKEND_PID_FILE%" >nul 2>&1
) )
) )
@@ -106,7 +106,7 @@ if not exist "sessions" mkdir sessions
echo [INFO] Starting backend service in background... 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 set VBS_FILE=%TEMP%\start_backend.vbs
echo Set WshShell = CreateObject("WScript.Shell") > "%VBS_FILE%" echo Set WshShell = CreateObject("WScript.Shell") > "%VBS_FILE%"
echo WshShell.Run """%PYTHON_EXE%"" ""%APP_DIR%run_daemon.py""", 0, False >> "%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!) echo [WARNING] Frontend is already running (PID: !PID!)
exit /b 0 exit /b 0
) else ( ) 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 del "%FRONTEND_PID_FILE%" >nul 2>&1
) )
) )
@@ -211,7 +211,7 @@ del "%VBS_FILE%" >nul 2>&1
echo [INFO] Waiting for frontend to start... echo [INFO] Waiting for frontend to start...
timeout /t 3 /nobreak >nul 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 set SERVICE_RUNNING=0
for /L %%i in (1,1,10) do ( for /L %%i in (1,1,10) do (
netstat -ano | findstr ":3000" | findstr "LISTENING" >nul 2>&1 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 ( for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":8000" ^| findstr "LISTENING"') do (
taskkill /F /PID %%a >nul 2>&1 taskkill /F /PID %%a >nul 2>&1
if "!ERRORLEVEL!"=="0" ( if "!ERRORLEVEL!"=="0" (
echo [OK] Backend stopped (PID: %%a) echo [OK] Backend stopped ^(PID: %%a^)
set BACKEND_KILLED=1 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 tasklist /FI "PID eq !PID!" 2>NUL | find /I /N "python.exe">NUL
if "!ERRORLEVEL!"=="0" ( if "!ERRORLEVEL!"=="0" (
taskkill /F /T /PID !PID! >nul 2>&1 taskkill /F /T /PID !PID! >nul 2>&1
echo [OK] Backend stopped (PID: !PID!) if "!ERRORLEVEL!"=="0" (
) else ( echo [OK] Backend stopped ^(PID: !PID!^)
echo [WARNING] Backend not running (process does not exist) set BACKEND_KILLED=1
REM Delete PID file immediately after successful kill
del "%BACKEND_PID_FILE%" >nul 2>&1
) )
) else ( ) else (
echo [WARNING] Backend not running (no process found) REM Process doesn't exist, just clean up the stale PID file
del "%BACKEND_PID_FILE%" >nul 2>&1
)
)
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 exit /b 0
REM --- Frontend Stop Logic --- 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 ( for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":3000" ^| findstr "LISTENING"') do (
taskkill /F /T /PID %%a >nul 2>&1 taskkill /F /T /PID %%a >nul 2>&1
if "!ERRORLEVEL!"=="0" ( if "!ERRORLEVEL!"=="0" (
echo [OK] Frontend stopped (PID: %%a) echo [OK] Frontend stopped ^(PID: %%a^)
set FRONTEND_KILLED=1 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" ( if "%FRONTEND_KILLED%"=="0" (
for /L %%p in (3001,1,3010) do ( for /L %%p in (3001,1,3010) do (
for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":%%p" ^| findstr "LISTENING"') 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" ( if "!ERRORLEVEL!"=="0" (
taskkill /F /T /PID %%a >nul 2>&1 taskkill /F /T /PID %%a >nul 2>&1
if "!ERRORLEVEL!"=="0" ( if "!ERRORLEVEL!"=="0" (
echo [OK] Frontend stopped (PID: %%a, Port: %%p) echo [OK] Frontend stopped ^(PID: %%a, Port: %%p^)
set FRONTEND_KILLED=1 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 tasklist /FI "PID eq !PID!" 2>NUL | find /I /N "node.exe">NUL
if "!ERRORLEVEL!"=="0" ( if "!ERRORLEVEL!"=="0" (
taskkill /F /T /PID !PID! >nul 2>&1 taskkill /F /T /PID !PID! >nul 2>&1
echo [OK] Frontend stopped (PID: !PID!) if "!ERRORLEVEL!"=="0" (
) else ( echo [OK] Frontend stopped ^(PID: !PID!^)
echo [WARNING] Frontend not running (process does not exist) set FRONTEND_KILLED=1
REM Delete PID file immediately after successful kill
del "%FRONTEND_PID_FILE%" >nul 2>&1
) )
) else ( ) else (
echo [WARNING] Frontend not running (no process found) REM Process doesn't exist, just clean up the stale PID file
del "%FRONTEND_PID_FILE%" >nul 2>&1
)
)
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 exit /b 0
REM ============================================ REM ============================================
@@ -373,6 +399,19 @@ echo [INFO] Restarting %TARGET%...
echo. echo.
call :stop call :stop
timeout /t 2 /nobreak >nul 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 call :start
goto end goto end
@@ -586,7 +625,7 @@ echo build - Build frontend for production
echo. echo.
echo Targets: echo Targets:
echo backend - Backend API service (default port: 8000) 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 all - Both services (default)
echo. echo.
echo Examples: echo Examples:
+627 -510
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)
# }