本文从 Obsidian 撰写发布
Running Page 完整部署教程(Docker + Keep 数据同步)
本教程基于 Ubuntu/Debian 系统,使用 Docker 部署 Running Page,实现 Keep 运动数据的自动同步与可视化展示。最后更新:2026-07-08
目录
- 环境准备
- 部署 Running Page
- 配置 Keep 数据同步
- 前端样式自定义
- 设置定时自动同步
- 常用命令速查
- 常见问题排查
1. 环境准备
1.1 系统要求
- Ubuntu 20.04+ / Debian 11+
- Docker 20.10+
- 至少 2 GB 内存
- 开放端口 8085(或自定义)
1.2 安装 Docker
sudo apt update && sudo apt upgrade -y
curl -fsSL https://get.docker.com | bash
sudo systemctl start docker
sudo systemctl enable docker
docker --version1.3 安装 Python3 和 pip
sudo apt install -y python3 python3-pip python3-venv2. 部署 Running Page
2.1 克隆官方项目
mkdir -p /wwwroot/running_page_official
cd /wwwroot/running_page_official
git clone https://github.com/yihong0618/running_page.git
cd running_page2.2 修改 Dockerfile(修复 Node.js 版本兼容性)
nano Dockerfile
# 找到 FROM node:18 AS frontend-build,改为 FROM node:20 AS frontend-build
# 保存:Ctrl+X,按 Y,按 Enter2.3 构建 Docker 镜像
docker build -t running-page-official .2.4 运行容器
mkdir -p data
docker run -d \
--name running-page-official \
-p 8085:80 \
-v $(pwd)/data:/data \
-e KEEP_PHONE="你的Keep手机号" \
-e KEEP_PASSWORD="你的Keep密码" \
-e RUNNER_NAME="你的名字" \
--restart unless-stopped \
running-page-official2.5 查看容器状态
docker ps | grep running-page-official
docker logs -f running-page-official2.6 访问页面
浏览器打开:http://你的服务器IP:8085
3. 配置 Keep 数据同步
3.1 安装项目依赖
cd /wwwroot/running_page_official/running_page
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt3.2 执行数据同步
python run_page/keep_sync.py 你的Keep手机号 你的Keep密码3.3 复制数据到容器
docker cp src/static/activities.json running-page-official:/usr/share/nginx/html/assets/activities-DQ6mP_-C.json
docker restart running-page-official3.4 创建一键更新脚本
cat > update.sh << 'EOF'
#!/bin/bash
cd /wwwroot/running_page_official/running_page
source venv/bin/activate
python run_page/keep_sync.py 你的Keep手机号 你的Keep密码
docker cp src/static/activities.json running-page-official:/usr/share/nginx/html/assets/activities-DQ6mP_-C.json
deactivate
echo "✅ 数据更新完成!刷新页面即可查看。"
EOF
chmod +x update.sh4. 前端样式自定义
4.1 修改"夜晚跑步"显示问题
问题原因:RICH_TITLE = true 导致页面优先使用 run.name,时间判断逻辑把所有运动归类为"夜晚跑步"。
解决方案:
cd /wwwroot/running_page_official/running_page修改 src/utils/const.ts:
nano src/utils/const.ts
# 将 const RICH_TITLE = true; 改为 const RICH_TITLE = false;修改 src/utils/utils.ts:
nano src/utils/utils.ts找到 getActivitySport 函数,替换为:
const getActivitySport = (act: Activity): string => {
const type = act.type.toLowerCase();
const subtype = act.subtype?.toLowerCase() || '';
if (type === 'run') {
if (subtype === 'generic' || subtype === '') {
const runDistance = act.distance / 1000;
if (runDistance > 20 && runDistance < 40) return RUN_TITLES.HALF_MARATHON_RUN_TITLE;
else if (runDistance >= 40) return RUN_TITLES.FULL_MARATHON_RUN_TITLE;
return ACTIVITY_TYPES.RUN_GENERIC_TITLE;
} else if (subtype === 'trail') {
return ACTIVITY_TYPES.RUN_TRAIL_TITLE;
} else if (subtype === 'treadmill') {
return ACTIVITY_TYPES.RUN_TREADMILL_TITLE;
}
return ACTIVITY_TYPES.RUN_GENERIC_TITLE;
} else if (type === 'walk' || type === 'walking') {
return ACTIVITY_TYPES.WALKING_TITLE;
} else if (type === 'ride' || type === 'cycling') {
return ACTIVITY_TYPES.CYCLING_TITLE;
} else if (type === 'hiking') {
return ACTIVITY_TYPES.HIKING_TITLE;
} else if (type.includes('skiing')) {
return ACTIVITY_TYPES.SKIING_TITLE;
}
return '';
};找到 titleForRun 函数,替换为:
const titleForRun = (run: Activity): string => {
const activitySport = getActivitySport(run);
const runHour = +run.start_date_local.slice(11, 13);
let timePrefix = '';
if (runHour >= 0 && runHour <= 5) {
timePrefix = '凌晨';
} else if (runHour >= 6 && runHour <= 8) {
timePrefix = '清晨';
} else if (runHour >= 9 && runHour <= 11) {
timePrefix = '上午';
} else if (runHour >= 12 && runHour <= 13) {
timePrefix = '午间';
} else if (runHour >= 14 && runHour <= 16) {
timePrefix = '午后';
} else if (runHour >= 17 && runHour <= 19) {
timePrefix = '傍晚';
} else {
timePrefix = '夜晚';
}
if (activitySport && activitySport !== '') {
return `${timePrefix}${activitySport}`;
}
const runDistance = run.distance / 1000;
if (runDistance > 20 && runDistance < 40) {
return RUN_TITLES.HALF_MARATHON_RUN_TITLE;
}
if (runDistance >= 40) {
return RUN_TITLES.FULL_MARATHON_RUN_TITLE;
}
return `${timePrefix}运动`;
};4.2 修改导航和 Logo
nano src/hooks/useSiteMetadata.ts将内容替换为:
const data: ISiteMetadataResult = {
siteTitle: '我的运动',
siteUrl: 'https://你的域名.com',
logo: 'https://你的图片地址.com/logo.png',
description: '个人运动记录',
navLinks: [
{ name: '总览', url: '/summary' },
{ name: '博客', url: 'https://你的博客地址.com' },
{ name: '关于', url: 'https://你的关于页面.com' },
],
};4.3 重新构建前端
cd /wwwroot/running_page_official/running_page
rm -rf dist/
docker run --rm -v $(pwd):/app -w /app node:20 sh -c "npm config set registry https://registry.npmmirror.com && npm install && npm run build" && docker cp dist/. running-page-official:/usr/share/nginx/html/ && docker restart running-page-official5. 设置定时自动同步
5.1 编辑 crontab
crontab -e5.2 添加定时任务
在文件末尾添加:
# 每天凌晨 3 点自动同步 Keep 数据
0 3 * * * cd /wwwroot/running_page_official/running_page && ./update.sh >> /var/log/running_page.log 2>&15.3 查看定时任务
crontab -l5.4 查看同步日志
tail -50 /var/log/running_page.log6. 常用命令速查
| 操作 | 命令 | |
|---|---|---|
| 查看容器状态 | `docker ps \ | grep running-page-official` |
| 查看容器日志 | docker logs -f running-page-official | |
| 重启容器 | docker restart running-page-official | |
| 停止容器 | docker stop running-page-official | |
| 启动容器 | docker start running-page-official | |
| 手动同步数据 | cd /wwwroot/running_page_official/running_page && ./update.sh | |
| 进入容器 | docker exec -it running-page-official /bin/sh | |
| 查看数据文件 | cat /wwwroot/running_page_official/running_page/src/static/activities.json |
7. 常见问题排查
7.1 页面显示 Nginx 默认页
- 检查容器是否正常运行:
docker ps | grep running-page-official - 检查端口是否被占用:
netstat -tlnp | grep 8085
7.2 数据同步失败
- 检查 Keep 账号密码是否正确
- 检查网络是否能访问 Keep API
- 查看同步日志:
tail -50 /var/log/running_page.log
7.3 页面显示"夜晚跑步"
- 确认
RICH_TITLE = false已设置 - 确认
getActivitySport函数已修改 - 重新构建前端后强制刷新浏览器(Ctrl+F 5)
7.4 构建前端失败
- 检查 Node.js 版本是否为 20+
- 使用国内镜像源:
npm config set registry https://registry.npmmirror.com - 删除
node_modules和dist目录后重新构建