mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 05:56:29 +00:00
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:
+1
-1
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]}...")
|
||||
# 标记为未知状态,记录完整响应供后续分析
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
# }
|
||||
# }
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
# }
|
||||
Reference in New Issue
Block a user