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 2656e95..0cc7456 100755 Binary files a/bun.lockb and b/bun.lockb differ 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."