Private Github Readme Stats server and uploading to S3 with Ansible Semaphore

Author: Ally
Published:

Summary:

Containerise a private Github Readme Stats server and upload stats card image to S3 using an Ansible playbook on a schedule with Semaphore

Table of Contents

  1. Containerising the server
  2. Running the server
  3. Appliance
  4. Playbook
  5. Semaphore

For some time I’ve had a github-readme-stats stats card in my Github readme profile, and wanted it to include all of my private contributions.

Example:

stats

Containerising the server

Out-of-the box it’s geared towards being hosted on Vercel, etc., but they document running the server by other methods.

# using alpine doesn't include git
FROM node:18 AS builder
WORKDIR /usr/src
RUN git clone https://github.com/anuraghazra/github-readme-stats.git app
WORKDIR /usr/src/app
# Checkout to specific version
RUN git checkout 30db7790d65053750c0b47e80100f8ddecdbd551
# Install express
RUN npm i --save --package-lock-only express
# Run `ci` install with express included in package file
RUN NODE_ENV=production npm ci --ignore-scripts

FROM node:18-alpine
RUN apk add --no-cache tini
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app ./
EXPOSE 9000

LABEL org.opencontainers.image.source=https://github.com/alistaircol/grs
LABEL org.opencontainers.image.description="Github Readme Stats"
LABEL org.opencontainers.image.licenses=MIT

# Use tini as the entrypoint to handle signals correctly
ENTRYPOINT ["/sbin/tini", "node", "express.js"]

Create a PAT for pushing/pulling this image, and authenticate.

Using this taskfile to build, tag and push the image.

---
version: 3

dotenv:
- .env

vars:
  TAG_DATE:
    sh:
      date +"%Y-%m-%d-%H%M%S"
  IMAGE_NAME: github-readme-stats
  REGISTRY_LOCAL: ac93.uk
  REGISTRY_REMOTE: ghcr.io
  REGISTRY_REMOTE_USER: alistaircol

tasks:
  docker:login:
    silent: true
    cmds:
    - echo "$CR_PAT" | docker login ghcr.io -u {{.REGISTRY_REMOTE_USER}} --password-stdin

  docker:image:build:
    cmds:
    - >-
      docker
      buildx
      build
      --platform linux/amd64,linux/arm64
      --tag {{.REGISTRY_LOCAL}}/{{.IMAGE_NAME}}:{{.TAG_DATE}}
      --tag {{.REGISTRY_LOCAL}}/{{.IMAGE_NAME}}:latest
      --tag {{.REGISTRY_REMOTE}}/{{.REGISTRY_REMOTE_USER}}/{{.IMAGE_NAME}}:{{.TAG_DATE}}
      --tag {{.REGISTRY_REMOTE}}/{{.REGISTRY_REMOTE_USER}}/{{.IMAGE_NAME}}:latest
      .      

  docker:image:push:
    cmds:
    - >-
      docker
      push
      --all-tags
      {{.REGISTRY_REMOTE}}/{{.REGISTRY_REMOTE_USER}}/{{.IMAGE_NAME}}      

ghcr

Running the server

Create a .env file containing the second PAT for fetching private contributions.

PAT_1=ghp_blah

It can be run locally:

docker run \
    --rm \
    -it \
    --env-file=$(pwd)/.env \
    -p '9000:9000' \
    ac93.uk/github-readme-stats

Using the remote image:

docker run \
    --rm \
    -it \
    --env-file=$(pwd)/.env \
    -p '9000:9000' \
    ghcr.io/alistaircol/github-readme-stats

server

Appliance

I will be running this GRS server image on an Ubuntu server VM under Proxmox.

It will obviously need docker and to be aware of the container registry PAT.

This VM will be the target of the playbook so will need some AWS dependencies. A truncated setup role could look like this:

---
- name: Install required system packages
  become: true
  block:
  - name: Update apt caches
    ansible.builtin.apt:
      update_cache: true

  - name: Install required system packages
    ansible.builtin.package:
      name:
      - python3
      - python3-pip
      state: present

  - name: Install aws dependencies for ansible playbooks
    ansible.builtin.pip:
      name:
      - botocore
      - boto3

Playbook

The playbook will look relatively simple to:

---
- name: Create a private Github readme stats and upload them
  hosts:
  - grs
  gather_facts: false

  tasks:
  - name: Create a temporary directory
    ansible.builtin.tempfile:
      state: directory
      suffix: grs
    register: tmpdir

  - name: Download Github readme stats image
    ansible.builtin.get_url:
      url: http://192.168.1.97:9000/?username=alistaircol&count_private=true&show_icons=true&custom_title=Ally+on+GitHub&disable_animations=true&title_color=58a6ff&icon_color=ffffff&text_color=ffffff&bg_color=0D1117&border_color=30363D&hide=issues,contribs,stars&show=prs_merged,reviews
      dest: "{{ tmpdir.path }}/github-readme-stats.svg"
    register: downloaded_image

  - name: Upload the image to S3
    amazon.aws.aws_s3:
      access_key: "{{ lookup('ansible.builtin.vars', 'AWS_ACCESS_KEY_ID') }}"
      secret_key: "{{ lookup('ansible.builtin.vars', 'AWS_ACCESS_KEY') }}"
      region: "{{ lookup('ansible.builtin.vars', 'AWS_REGION') }}"
      bucket: static.ac93.uk
      object: github-readme-stats.svg
      src: "{{ downloaded_image.dest }}"
      mode: put

Semaphore

Create a project in semaphore then follow these instructions.

Key Store

Create a new key entry so that the workers can connect to the target.

key store

Inventory

Create a new static-yaml inventory.

---
all:
  vars:
    ansible_host_key_checking: false
  hosts:
    grs:
      ansible_host: 192.168.1.97
      ansible_user: ally

inventory

Repository

Add the git repository where the playbook lives. You might need to create a new key in the store.

Environment

Create a new environment, we will add our AWS credentials here so the playbook can read them and pass to the target.

Add the AWS credentials to the Extra variables section.

environment

Task Template

Create a new task template.

template

You can see the run history:

template

You can interrogate previous runs:

template

Use git feature to 'hide' local changes of a Dockerfile which installs xdebug
To bottom
To top