Add CI/CD build and deploy scripts, along with docker-compose, HAProxy config, and a certbot
Some checks failed
Build And Deploy / build-and-deploy (push) Has been cancelled

merge hook. Set up env.example generation. Add doiuse dev dependency.
This commit is contained in:
badblocks 2026-02-01 13:14:32 -08:00
parent 0350a4b8e3
commit 1714225d00
No known key found for this signature in database
11 changed files with 334 additions and 2 deletions

18
.env.example Normal file
View file

@ -0,0 +1,18 @@
CERTBOT_EMAIL=${CERTBOT_EMAIL}
CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
DOMAIN=${DOMAIN}
PUBLIC_IP=${PUBLIC_IP}
ANDROID_SMS_GATEWAY_IP=${ANDROID_SMS_GATEWAY_IP}
ANDROID_SMS_GATEWAY_URL=${ANDROID_SMS_GATEWAY_URL}
ANDROID_SMS_GATEWAY_LOGIN=${ANDROID_SMS_GATEWAY_LOGIN}
ANDROID_SMS_GATEWAY_PASSWORD=${ANDROID_SMS_GATEWAY_PASSWORD}
ANDROID_SMS_GATEWAY_RECIPIENT_PHONE=${ANDROID_SMS_GATEWAY_RECIPIENT_PHONE}
ASTRO_DB_REMOTE_URL=${ASTRO_DB_REMOTE_URL}
OTP_SUPER_SECRET_SALT=${OTP_SUPER_SECRET_SALT}
IMAGE_FILENAME=${IMAGE_FILENAME}
IMAGE_NAME=${IMAGE_NAME}
SSH_USER=${SSH_USER}
SSH_PORT=${SSH_PORT}
SSH_HOST=${SSH_HOST}
SSH_KEY="${SSH_KEY}"
SSH_KNOWN_HOST="${SSH_KNOWN_HOST}"

View file

@ -0,0 +1,59 @@
name: Build And Deploy
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Install dependencies
run: |
apt-get update && apt-get install gettext -y
- name: Check out repository
uses: actions/checkout@v4
# - name: Expose repo secrets and vars as shell variables
# env:
# SECRETS_CONTEXT: ${{ toJSON(secrets) }}
# VARS_CONTEXT: ${{ toJSON(vars) }}
# run: |
# # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable
# # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
# # # EOF randomness is to account for empty secrets and vars
# EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
# to_envs() { jq -r "to_entries[] | \"\(.key)<<$EOF\n\(.value)\n$EOF\n\""; }
# echo "$VARS_CONTEXT" | to_envs >> $GITHUB_ENV
# echo "$SECRETS_CONTEXT" | to_envs >> $GITHUB_ENV
- name: Substitute environment variables in .env.example and write to .env
env:
CERTBOT_EMAIL: ${{secrets.CERTBOT_EMAIL}}
CLOUDFLARE_API_TOKEN: ${{secrets.CLOUDFLARE_API_TOKEN}}
DOMAIN: ${{secrets.DOMAIN}}
PUBLIC_IP: ${{secrets.PUBLIC_IP}}
ANDROID_SMS_GATEWAY_IP: ${{secrets.ANDROID_SMS_GATEWAY_IP}}
ANDROID_SMS_GATEWAY_URL: ${{secrets.ANDROID_SMS_GATEWAY_URL}}
ANDROID_SMS_GATEWAY_LOGIN: ${{secrets.ANDROID_SMS_GATEWAY_LOGIN}}
ANDROID_SMS_GATEWAY_PASSWORD: ${{secrets.ANDROID_SMS_GATEWAY_PASSWORD}}
ANDROID_SMS_GATEWAY_RECIPIENT_PHONE: ${{secrets.ANDROID_SMS_GATEWAY_RECIPIENT_PHONE}}
ASTRO_DB_REMOTE_URL: ${{secrets.ASTRO_DB_REMOTE_URL}}
OTP_SUPER_SECRET_SALT: ${{secrets.OTP_SUPER_SECRET_SALT}}
IMAGE_FILENAME: ${{secrets.IMAGE_FILENAME}}
IMAGE_NAME: ${{secrets.IMAGE_NAME}}
SSH_USER: ${{secrets.SSH_USER}}
SSH_PORT: ${{secrets.SSH_PORT}}
SSH_HOST: ${{secrets.SSH_HOST}}
SSH_KEY: ${{secrets.SSH_KEY}}
SSH_KNOWN_HOST: ${{secrets.SSH_KNOWN_HOST}}
run: |
envsubst < .env.example > .env
- name: Run build script
run: |
cd cicd/scripts
chmod +x ./build.sh
./build.sh
- name: Run deploy script
run: |
cd cicd/scripts
chmod +x ./deploy.sh
./deploy.sh

BIN
bun.lockb

Binary file not shown.

29
cicd/scripts/build.sh Executable file
View file

@ -0,0 +1,29 @@
#!/bin/bash
#######################
# VARIABLES #
#######################
ROOT_DIR=$(dirname $(dirname $(dirname $(realpath $0))))
GIT_REF=${GIT_REF:-main}
### NO EDITS BELOW THIS LINE ###
cd ${ROOT_DIR}
source .env
git checkout ${GIT_REF}
GIT_SHA=$(git rev-parse --short HEAD)
if [[ "${GIT_REF}" =~ ^refs/tags/v([0-9]+\.[0-9]+\.[0-9]+)(-.*)?$ ]]; then
VERSION="${BASH_REMATCH[1]}"
if [[ -n "${BASH_REMATCH[2]}" ]]; then
VERSION="${VERSION}${BASH_REMATCH[2]}"
fi
echo "Using git tag version: ${VERSION}"
else
VERSION=$(node -p "require('./package.json').version || '0.0.0'")
GIT_SHA_SHORT="${GIT_SHA:0:7}"
VERSION="${VERSION}-${GIT_SHA_SHORT}"
echo "Using package.json + SHA version: ${VERSION}"
fi
docker build -t ${IMAGE_NAME}:latest -t ${IMAGE_NAME}:v${VERSION} --build-arg VERSION=${VERSION} .
docker save -o ${IMAGE_FILENAME} ${IMAGE_NAME}:latest

42
cicd/scripts/deploy.sh Executable file
View file

@ -0,0 +1,42 @@
#!/bin/bash
#######################
# VARIABLES #
#######################
ROOT_DIR=$(dirname $(dirname $(dirname $(realpath $0))))
### NO EDITS BELOW THIS LINE ###
cd ${ROOT_DIR}
source .env
mkdir -p ${HOME}/.ssh
chmod 700 ${HOME}/.ssh
echo "${SSH_KEY}" > ${HOME}/.ssh/id_ed25519-${SSH_HOST//./_}
echo "${SSH_KNOWN_HOST}" > ${HOME}/.ssh/known_hosts-${SSH_HOST//./_}
chmod -R 600 ${HOME}/.ssh/
chmod 700 ${HOME}/.ssh
grep -q "Host ${SSH_HOST}" ${HOME}/.ssh/config || cat >> ${HOME}/.ssh/config <<EOF
Host ${SSH_HOST}
HostName ${SSH_HOST}
User ${SSH_USER}
Port ${SSH_PORT}
IdentityFile ${HOME}/.ssh/id_ed25519-${SSH_HOST//./_}
UserKnownHostsFile ${HOME}/.ssh/known_hosts-${SSH_HOST//./_}
StrictHostKeyChecking yes
ControlMaster auto
ControlPath ~/.ssh/control-%C
ControlPersist yes
ConnectionAttempts 3
ConnectTimeout 10
ServerAliveInterval 10
EOF
DOCKER_HOST=ssh://${SSH_HOST} docker load -i ${IMAGE_FILENAME}
ssh ${SSH_HOST} "mkdir -p /srv/${IMAGE_NAME#*/}/"
ssh ${SSH_HOST} "cd /srv/${IMAGE_NAME#*/}/ && docker compose down"
scp .env ${SSH_HOST}:/srv/${IMAGE_NAME#*/}/.env
cd deploy
scp -r . ${SSH_HOST}:/srv/${IMAGE_NAME#*/}/
ssh ${SSH_HOST} "cd /srv/${IMAGE_NAME#*/}/ && docker compose up -d"

View file

@ -0,0 +1,4 @@
#!/bin/bash
cat /etc/letsencrypt/live/badblocks.dev/fullchain.pem /etc/letsencrypt/live/badblocks.dev/privkey.pem > /etc/letsencrypt/fullcert.pem
chmod 755 /etc/letsencrypt/
chmod 644 /etc/letsencrypt/fullcert.pem

84
deploy/docker-compose.yml Normal file
View file

@ -0,0 +1,84 @@
services:
badblocks-personal-site:
image: ${IMAGE_NAME}:latest
restart: always
container_name: badblocks-personal-site
ports:
- "4321:4321"
networks:
- proxynet
env_file:
- .env
# healthcheck:
# test:
# [
# "CMD",
# "curl",
# "-f",
# "-s",
# "--max-time",
# "5",
# "http://localhost:4321/health",
# ]
# interval: 30s
# timeout: 15s
# retries: 3
# start_period: 120s
# wireguard:
# image: qmcgaw/gluetun
# cap_add:
# - NET_ADMIN
# container_name: wireguard
# environment:
# - VPN_SERVICE_PROVIDER=custom
# - VPN_TYPE=wireguard
# - HTTPPROXY=on
# expose:
# - "8888"
# env_file:
# - .env
# devices:
# - /dev/net/tun:/dev/net/tun
# restart: unless-stopped
# networks:
# - proxynet
# healthcheck:
# test: ss["CMD", "ping", "-c", "1", "-W", "3", "$$ANDROID_SMS_GATEWAY_IP"]
# interval: 30s
# timeout: 15s
# retries: 3
# start_period: 60s
certbot:
image: serversideup/certbot-dns-cloudflare
volumes:
- ./certs:/etc/letsencrypt
environment:
CLOUDFLARE_API_TOKEN: "${CLOUDFLARE_API_TOKEN}"
CERTBOT_EMAIL: "${CERTBOT_EMAIL}"
CERTBOT_DOMAINS: "${DOMAIN}"
haproxy:
image: haproxy:3.2
stop_signal: SIGTERM
container_name: haproxy
env_file:
- .env
command: ["haproxy", "-f", "/usr/local/etc/haproxy"]
ports:
- "${PUBLIC_IP}:80:80"
- "${PUBLIC_IP}:443:443"
- "${PUBLIC_IP}:8404:8404"
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
- ./certs:/certs:ro
restart: always
networks:
- proxynet
# healthcheck:
# test: ["CMD", "haproxy", "-c", "-f", "/usr/local/etc/haproxy"]
# interval: 30s
# timeout: 10s
# retries: 3
networks:
proxynet:
name: proxynet
driver: bridge

43
deploy/haproxy.cfg Normal file
View file

@ -0,0 +1,43 @@
global
daemon
log stdout format raw local0 info
maxconn 2000
defaults
mode http
log global
timeout connect 5s
timeout client 30s
timeout server 30s
timeout check 5s
retries 3
option httplog
option dontlognull
option redispatch
frontend http
bind :80
mode http
http-request redirect scheme https unless { ssl_fc }
frontend https
bind :443 ssl crt /certs/fullcert.pem
http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;"
default_backend main
backend main
balance leastconn
option httpchk GET /
http-check expect status 200
server badblocks-personal-site badblocks-personal-site:4321 check resolvers docker resolve-prefer ipv4 init-addr none
resolvers docker
nameserver dns1 127.0.0.11:53
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold valid 10s
hold obsolete 30s

View file

@ -37,6 +37,7 @@
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.6", "@types/bun": "^1.3.6",
"@types/validator": "^13.15.10", "@types/validator": "^13.15.10",
"doiuse": "^6.0.6",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"prettier-plugin-astro": "^0.14.1" "prettier-plugin-astro": "^0.14.1"
} }

View file

@ -469,7 +469,8 @@
<audio controls="" <audio controls=""
><source ><source
src="https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.aac" src="https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.aac"
/>audio</audio> />audio</audio
>
</div> </div>
<footer><p><a href="#top">Top</a></p></footer> <footer><p><a href="#top">Top</a></p></footer>
</article> </article>
@ -705,7 +706,11 @@
</p> </p>
<p> <p>
<label for="idt">Datetime input</label> <label for="idt">Datetime input</label>
<input type="datetime" id="idt" value="1970-01-01T00:00:00Z" /> <input
type="datetime-local"
id="idt"
value="1970-01-01T00:00:00Z"
/>
</p> </p>
<p> <p>
<label for="idtl">Datetime-local input</label> <label for="idtl">Datetime-local input</label>

47
utils/generate-env-example.sh Executable file
View file

@ -0,0 +1,47 @@
#!/bin/bash
cd $(dirname $(dirname $(realpath $0)))
# Path to the original .env file
ENV_FILE=".env"
# Path to the new .env.example file
EXAMPLE_FILE=".env.example"
# Check if the .env file exists
if [ ! -f "$ENV_FILE" ]; then
echo "The file $ENV_FILE does not exist."
exit 1
fi
# Create or empty the .env.example file
> "$EXAMPLE_FILE"
SKIP_NEXT=false
# Read each line in .env
while IFS= read -r line; do
# Skip the current line if the previous line is part of a multiline/quoted string
if [[ $SKIP_NEXT == true ]]; then
if [[ $line == *'"'* ]]; then
SKIP_NEXT=false
fi
continue
# Copy comments and empty lines verbatim
elif [[ $line == \#* ]] || [[ -z $line ]]; then
echo "$line" >> "$EXAMPLE_FILE"
continue
# Check if the line is a multiline/quoted string
elif [[ $line == *'="'* ]]; then
if [[ $line != *'"' ]]; then
SKIP_NEXT=true
fi
LINE=${line%%=*}
echo "$LINE=\"\${$LINE}\"" >> "$EXAMPLE_FILE"
# For all other lines, copy only the key (everything before the '=') if present
elif [[ $line == *'='* ]]; then
LINE=${line%%=*}
echo "$LINE=\${$LINE}" >> "$EXAMPLE_FILE"
fi
done < "$ENV_FILE"
echo ".env.example file created successfully."