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
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:
parent
0350a4b8e3
commit
1714225d00
11 changed files with 334 additions and 2 deletions
18
.env.example
Normal file
18
.env.example
Normal 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}"
|
||||||
59
.forgejo/workflows/build-and-deploy.yml
Normal file
59
.forgejo/workflows/build-and-deploy.yml
Normal 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
BIN
bun.lockb
Binary file not shown.
29
cicd/scripts/build.sh
Executable file
29
cicd/scripts/build.sh
Executable 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
42
cicd/scripts/deploy.sh
Executable 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"
|
||||||
4
deploy/certs/renewal-hooks/deploy/merge.sh
Normal file
4
deploy/certs/renewal-hooks/deploy/merge.sh
Normal 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
84
deploy/docker-compose.yml
Normal 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
43
deploy/haproxy.cfg
Normal 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
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
47
utils/generate-env-example.sh
Executable 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."
|
||||||
Loading…
Add table
Add a link
Reference in a new issue