From 1714225d00993263f5ceec0b4dc738725e201bea Mon Sep 17 00:00:00 2001 From: badbl0cks <4161747+badbl0cks@users.noreply.github.com> Date: Sun, 1 Feb 2026 13:14:32 -0800 Subject: [PATCH] Add CI/CD build and deploy scripts, along with docker-compose, HAProxy config, and a certbot merge hook. Set up env.example generation. Add doiuse dev dependency. --- .env.example | 18 +++++ .forgejo/workflows/build-and-deploy.yml | 59 +++++++++++++++ bun.lockb | Bin 227174 -> 238804 bytes cicd/scripts/build.sh | 29 +++++++ cicd/scripts/deploy.sh | 42 +++++++++++ deploy/certs/renewal-hooks/deploy/merge.sh | 4 + deploy/docker-compose.yml | 84 +++++++++++++++++++++ deploy/haproxy.cfg | 43 +++++++++++ package.json | 1 + src/pages/css-test.astro | 9 ++- utils/generate-env-example.sh | 47 ++++++++++++ 11 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 .env.example create mode 100644 .forgejo/workflows/build-and-deploy.yml create mode 100755 cicd/scripts/build.sh create mode 100755 cicd/scripts/deploy.sh create mode 100644 deploy/certs/renewal-hooks/deploy/merge.sh create mode 100644 deploy/docker-compose.yml create mode 100644 deploy/haproxy.cfg create mode 100755 utils/generate-env-example.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..93f995b --- /dev/null +++ b/.env.example @@ -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}" diff --git a/.forgejo/workflows/build-and-deploy.yml b/.forgejo/workflows/build-and-deploy.yml new file mode 100644 index 0000000..7396a1f --- /dev/null +++ b/.forgejo/workflows/build-and-deploy.yml @@ -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 diff --git a/bun.lockb b/bun.lockb index 2656e95f5c885dacca2d56136e574d3aba4e3571..0cc7456c869a9d9b52e6ede651be4c1453e145ca 100755 GIT binary patch delta 17860 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 delta 10747 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 diff --git a/cicd/scripts/build.sh b/cicd/scripts/build.sh new file mode 100755 index 0000000..3dedac4 --- /dev/null +++ b/cicd/scripts/build.sh @@ -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 diff --git a/cicd/scripts/deploy.sh b/cicd/scripts/deploy.sh new file mode 100755 index 0000000..350fa0a --- /dev/null +++ b/cicd/scripts/deploy.sh @@ -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 < /etc/letsencrypt/fullcert.pem +chmod 755 /etc/letsencrypt/ +chmod 644 /etc/letsencrypt/fullcert.pem diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..b5dd6a4 --- /dev/null +++ b/deploy/docker-compose.yml @@ -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 diff --git a/deploy/haproxy.cfg b/deploy/haproxy.cfg new file mode 100644 index 0000000..4e7fc5e --- /dev/null +++ b/deploy/haproxy.cfg @@ -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 diff --git a/package.json b/package.json index 804ecd6..ea5cbff 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "devDependencies": { "@types/bun": "^1.3.6", "@types/validator": "^13.15.10", + "doiuse": "^6.0.6", "prettier": "^3.8.1", "prettier-plugin-astro": "^0.14.1" } diff --git a/src/pages/css-test.astro b/src/pages/css-test.astro index 44fd0fc..b68fba3 100644 --- a/src/pages/css-test.astro +++ b/src/pages/css-test.astro @@ -469,7 +469,8 @@ + />audio @@ -705,7 +706,11 @@

- +

diff --git a/utils/generate-env-example.sh b/utils/generate-env-example.sh new file mode 100755 index 0000000..e1afeec --- /dev/null +++ b/utils/generate-env-example.sh @@ -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."