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
|
# 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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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]}...")
|
||||||
# 标记为未知状态,记录完整响应供后续分析
|
# 标记为未知状态,记录完整响应供后续分析
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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">
|
<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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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