惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 【当耐特】
Latest news
Latest news
IT之家
IT之家
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
L
LangChain Blog
腾讯CDC
J
Java Code Geeks
GbyAI
GbyAI
美团技术团队
V
Visual Studio Blog
Apple Machine Learning Research
Apple Machine Learning Research
Recorded Future
Recorded Future
U
Unit 42
Jina AI
Jina AI
月光博客
月光博客
罗磊的独立博客
I
InfoQ
有赞技术团队
有赞技术团队
B
Blog RSS Feed
The Register - Security
The Register - Security
WordPress大学
WordPress大学
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
MongoDB | Blog
MongoDB | Blog
NISL@THU
NISL@THU
S
Security Archives - TechRepublic
雷峰网
雷峰网
O
OpenAI News
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Y
Y Combinator Blog
G
GRAHAM CLULEY
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LINUX DO - 热门话题
H
Help Net Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
S
Securelist
P
Proofpoint News Feed
C
Cybersecurity and Infrastructure Security Agency CISA
博客园 - 叶小钗
Security Latest
Security Latest
A
About on SuperTechFans
G
Google Developers Blog
T
Troy Hunt's Blog
小众软件
小众软件
H
Hacker News: Front Page
C
Cisco Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
L
LINUX DO - 最新话题
大猫的无限游戏
大猫的无限游戏
Webroot Blog
Webroot Blog

Razeen`s Blog

Reflections on Management Training: A Technologist's Perspective on the Path to Leadership UPS Multi-Device Protection: Safeguarding Your NAS and Linux Server From Media Center to AI Assistant: My List of 50 Homelab Services Discussing the Reduction of SSL Certificate Validity to 47 Days One Year Review and Cost Analysis of Tesla Model Y Replacing Disqus Comments with Self-hosted Waline Use Prometheus and Grafana to Set Up Your Certificate Monitoring Dashboard Server Migration: Lessons Learned from Swapping Linux Disk Boards Improve Information Sources with RSS (RSShub + Reeder 5) Razeen`s Blog App Share | AppCleaner - The Uninstaller for Mac Gallery Goods Apps Links
Deploy Your Own Running Page
2025-04-10 · via Razeen`s Blog

I’ve been following the Running Page project for a long time, and today I finally deployed it on my blog. Running Page is an open-source project that visualizes workout data, supports summarizing running records, generates heatmaps, and integrates with MapBox to display routes. This post will share how to automate the build and deployment of static pages using GitLab CI/CD and integrate Apple Shortcuts for one-click deployment.

Preparation

The official documentation supports automatic deployment via Vercel (recommended) and GitHub Pages, but I prefer hosting it on my own server. Since I already have a GitLab setup, I decided to give it a try.

The required environment includes:

  • GitLab CE Repository: I previously set up a private GitLab instance at home to host my code and automate blog deployments.
  • GitLab Runner: Install a GitLab Runner to execute workflows.
  • Server: I purchased a small server from Racknerd to host my blog.
  • Running Data: The official documentation supports importing data from various platforms. I primarily use Keep, so I just need to prepare my account credentials.

image-20250412092600972

Building the Page

Importing the Repository

GitLab has a handy feature to directly import projects from GitHub. Using this, I imported the Running Page repository into my private repository.

I encountered two import failures, likely due to network issues. Retrying resolved the problem.

image-20250412165736786

Custom Modifications

Running Page supports customizing site titles and other information. The official documentation provides detailed instructions, so I won’t elaborate here.

However, I recommend making modifications on a separate branch to facilitate future maintenance and updates.

Recently, ChatGPT’s image generation feature has been amazing. I created a new logo following the original style 😄
logo

If you want to host the page in a subdirectory (e.g., /running/), set the PATH_PREFIX environment variable or directly modify vite.config.ts to base: process.env.PATH_PREFIX || '/running/'.

Building and Deploying

Next, automate the build process using CI/CD. The official repository’s Dockerfile can be used to build the static page. The steps are straightforward:

graph LR
    B[Build with Docker] --> C[Copy static files from Docker]
    C --> D[Save build artifacts]
    D --> E[Push files to server with rsync]

For security, add your credentials and server private key to the project’s Settings > CI/CD > Variables. I added three variables:

  • Keep login phone number: KEEP_LOGIN_PHONE
  • Keep login password: KEEP_LOGIN_PASSWORD
  • Blog server private key: BLOG_SSH_PRIVATE_KEY (Generate a dedicated private key for deployment and configure the public key on the server).

When adding variables, enable Masked and avoid enabling Protect variable unless your branch is protected; otherwise, the values won’t be accessible during builds.

Here’s the .gitlab-ci.yml configuration:

image: docker:latest

stages:
  - build
  - deploy

build:
  stage: build
  tags:
    - linux01
  script:
    - docker build --build-arg app=Keep --build-arg keep_phone_number=$KEEP_LOGIN_PHONE --build-arg keep_password=$KEEP_LOGIN_PASSWORD --build-arg YOUR_NAME="Razeen" -t running-page:$CI_COMMIT_SHA .
    - docker create --name temp_container running-page:$CI_COMMIT_SHA
    - docker cp temp_container:/usr/share/nginx/html ./dist
    - docker rm temp_container
    - mkdir -p artifacts
    - cp -r dist/* artifacts/
  artifacts:
    paths:
      - artifacts/
    expire_in: 1 week
  allow_failure: false

deploy:
  stage: deploy
  tags:
    - homelab01
  script:
    - mkdir -p ~/.ssh
    - echo "$BLOG_SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - echo -e "Host *\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
    - chmod 600 ~/.ssh/config
    - rsync -avz -e 'ssh -p 2222' --delete artifacts/ [email protected]:/www/running/
  dependencies:
    - build
  allow_failure: false

Configure the server’s nginx path:

location /running {
    root                        /www/;
    index                       index.html index.htm;
}

Triggering with Shortcuts

The above CI/CD configuration is ready, but it lacks a trigger. To simplify updates, I used Apple’s Shortcuts app.

In the project settings under Settings > CI/CD > Pipeline trigger tokens, add a trigger token. Use the token to trigger the workflow via a POST request, e.g.:

# https://git.isw.app/ => GitLab URL
# 25 => Project ID
# REF_NAME => Branch or tag (I trigger a specific branch)
# glptt-xxxxx => Trigger token
https://git.razeen.app/api/v4/projects/25/ref/REF_NAME/trigger/pipeline?token=glptt-xxxxx

Use the “Get Contents of URL” action in Shortcuts to trigger the workflow with a POST request. Add a “Show Alert” action to display the response. Assign a logo to the shortcut, save it, and add it to your home screen for one-click triggering.

image-20250412165609885

Add trigger conditions to the CI/CD configuration to handle web and trigger-based executions:

workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "trigger"
      when: always
    - if: $CI_PIPELINE_SOURCE == "web"
      when: always

Automatic Code Syncing

The previous steps automate builds and deployments, but I also want to keep my local repository in sync with GitHub. GitLab’s “Mirroring repositories” feature can sync with remote repositories, but the CE version only supports push mirroring. To enable pull mirroring, use CI.

sync-upstream:
  stage: sync
  tags:
    - m1max
  script:
    - git config --global user.name "GitLab CI"
    - git config --global user.email "[email protected]"
    - git clone https://oauth2:${REPO_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git repo
    - cd repo
    - git checkout master
    - git remote add upstream https://github.com/yihong0618/running_page.git
    - git fetch upstream
    - git merge upstream/master -m "Merge upstream changes"
    - git push https://oauth2:${REPO_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git master

To ensure continuous updates, schedule a job in the background. Add conditions to distinguish between build and sync scenarios:

rules:
  - if: $CI_PIPELINE_SOURCE == "schedule" && $SYNC_ONLY == "true"
    when: always
  - if: $CI_PIPELINE_SOURCE == "web" && $CI_JOB_NAME == "sync-upstream"
    when: always

image-20250412165248283

Conclusion

With this setup, I can now update my Running Page after every run with a single click. For better health and a more vibrant heatmap, let’s keep running!

Full Configuration

As usual, here’s the complete .gitlab-ci.yml configuration. To prevent multiple builds and concurrent executions, I added a resource_group.

image: docker:latest

stages:
  - sync
  - build
  - deploy

variables:
  GIT_STRATEGY: clone

# 简化触发器配置
workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "trigger"
      when: always
    - if: $CI_PIPELINE_SOURCE == "web"
      when: always
    - if: $CI_PIPELINE_SOURCE == "schedule"
      when: always

.build_template: &build_template
  resource_group: running-page-pipeline
  interruptible: false

deploy:
  <<: *build_template
  stage: deploy
  tags:
    - homelab01
  script:
    - mkdir -p ~/.ssh
    - echo "$BLOG_SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ls -la ~/.ssh
    - echo -e "Host *\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
    - chmod 600 ~/.ssh/config
    - rsync -avz -e 'ssh -p 2222' --delete artifacts/ [email protected]:/www/running/
  dependencies:
    - build
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule" && $SYNC_ONLY == "true"
      when: never
    - if: $CI_PIPELINE_SOURCE == "web" && $CI_JOB_NAME == "sync-upstream"
      when: never
    - when: always
  allow_failure: false

build:
  <<: *build_template
  stage: build
  tags:
    - linux01
  script:
    - docker build --build-arg app=Keep --build-arg keep_phone_number=$KEEP_LOGIN_PHONE --build-arg keep_password=$KEEP_LOGIN_PASSWORD --build-arg YOUR_NAME="Razeen" -t running-page:$CI_COMMIT_SHA .
    - docker create --name temp_container running-page:$CI_COMMIT_SHA
    - docker cp temp_container:/usr/share/nginx/html ./dist
    - docker rm temp_container
    - mkdir -p artifacts
    - cp -r dist/* artifacts/
  artifacts:
    paths:
      - artifacts/
    expire_in: 1 week
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - activities/
      - assets/
      - GPX_OUT/
      - TCX_OUT/
      - FIT_OUT/
      - Workouts/
      - run_page/data.db
      - src/static/activities.json
      - imported.json
    policy: pull-push
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule" && $SYNC_ONLY == "true"
      when: never
    - if: $CI_PIPELINE_SOURCE == "web" && $CI_JOB_NAME == "sync-upstream"
      when: never
    - when: always
  allow_failure: false
  
sync-upstream:
  stage: sync
  resource_group: repo-sync
  tags:
    - m1max
  script:
    - git config --global user.name "GitLab CI"
    - git config --global user.email "[email protected]"
    - git clone https://oauth2:${REPO_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git repo
    - cd repo
    - git checkout master
    - git remote add upstream https://github.com/yihong0618/running_page.git
    - git fetch upstream
    - git merge upstream/master -m "Merge upstream changes"
    - git push https://oauth2:${REPO_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git master
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule" && $SYNC_ONLY == "true"
      when: always
    - if: $CI_PIPELINE_SOURCE == "web" && $CI_JOB_NAME == "sync-upstream"
      when: always
  allow_failure: true

The configuration uses three runner tags: homelab01 and linux01 run on my home server, while m1max runs on my local machine. Ensure the necessary tools like Git are installed on the runners.