diff --git a/buildscripts/build/.gitignore b/buildscripts/build/.gitignore new file mode 100644 index 0000000..0b5e2d0 --- /dev/null +++ b/buildscripts/build/.gitignore @@ -0,0 +1,2 @@ +.env +keys/ diff --git a/buildscripts/build/build.sh b/buildscripts/build/build.sh new file mode 100755 index 0000000..97b0f8a --- /dev/null +++ b/buildscripts/build/build.sh @@ -0,0 +1,179 @@ +#!/usr/bin/bash + +frontend_port=3000 +server_port=8080 +ssh_port=34355 + +frontend_node_ids=(2) +server_node_ids=(4 5 6) + +build_dir="${HOME}/build" + +#server_name="usufslc-pi-cluster.bluezone.usu.edu" +server_name="chessh.linux.usu.edu" +load_balancer_nginx_site_file="/etc/nginx/sites-enabled/${server_name}.conf" +ha_proxy_cfg="/etc/haproxy/haproxy.cfg" +ssl_cert_path="/etc/letsencrypt/live/${server_name}" +certbot_webroot_path="/var/www/html/${server_name}" +load_balancer_nginx_site=" +upstream frontend { + $(printf "server 192.168.100.%s:${frontend_port};\n" ${frontend_node_ids[@]}) +} + +upstream api { + $(printf "server 192.168.100.%s:${server_port};\n" ${server_node_ids[@]}) +} + +server { + default_type application/octet-stream; + + server_name ${server_name}; + + listen 443 ssl; + ssl_certificate ${ssl_cert_path}/fullchain.pem; + ssl_certificate_key ${ssl_cert_path}/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + + location ~ /.well-known { + allow all; + default_type "text/plain"; + alias ${certbot_webroot_path}; + } + + location /api/ { + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_pass http://api/; + } + + location / { + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_pass http://frontend/; + } +} + +server { + if (\$host = ${server_name}) { + return 301 https://\$host\$request_uri; + } + + server_name ${server_name}; + listen 80; + return 404; +} +" +# TODO: Servers should pull server ids from array +ha_proxy_cfg=" +global\n + log /dev/log local0\n + log /dev/log local1 notice\n + maxconn 2500\n + user haproxy\n + group haproxy\n + daemon\n +\n +defaults\n + log global\n + mode tcp\n + timeout connect 10s\n + timeout client 36h\n + timeout server 36h\n + option dontlognull\n +\n +listen ssh\n + bind *:${ssh_port}\n + balance leastconn\n + mode tcp\n + + server hostA 192.168.100.4:${ssh_port} check inter 10s fall 2 rise 1\n + server hostB 192.168.100.5:${ssh_port} check inter 10s fall 2 rise 1\n + server hostC 192.168.100.6:${ssh_port} check inter 10s fall 2 rise 1\n +" +echo $ha_proxy_cfg + +ssh_opts="-oStrictHostKeyChecking=no" + +function make_pi_node_conn_str() { + echo "pi$(printf "%04d" $1)@192.168.100.${1}" +} + +function copy_ssh_keys() { + if [ ! -d "${build_dir}/keys" ] + then + mkdir "${build_dir}/keys" + chmod 700 "${build_dir}/keys" + cd "${build_dir}/keys" + + ssh-keygen -N "" -b 256 -t ecdsa -f ssh_host_ecdsa_key + ssh-keygen -N "" -b 1024 -t dsa -f ssh_host_dsa_key + ssh-keygen -N "" -b 2048 -t rsa -f ssh_host_rsa_key + fi + for node_id in "${server_node_ids[@]}" + do + node_conn=$(make_pi_node_conn_str $node_id) + scp -r $ssh_opts "${build_dir}/keys" $node_conn:~ + done +} + + +function reload_nginx_confs() { + dead_files=("/etc/nginx/sites-enabled/default" "/etc/nginx/nginx.conf" "$load_balancer_nginx_site_file") + for file in "${dead_files[@]}" + do + [ -e $file ] && sudo rm $file + done + + sudo cp "${build_dir}/nginx.conf" /etc/nginx/nginx.conf + echo $load_balancer_nginx_site | sudo tee $load_balancer_nginx_site_file + + sudo systemctl restart nginx +} + +function build_frontend() { + node_id=$1 + node_conn=$(make_pi_node_conn_str $node_id) + + scp $ssh_opts "${build_dir}/.env" $node_conn:~ + scp $ssh_opts "${build_dir}/build_front.sh" $node_conn:~/ + ssh $ssh_opts $node_conn "~/build_front.sh" +} + +function build_frontend_nodes() { + for node_id in "${frontend_node_ids[@]}" + do + build_frontend $node_id + done +} + +function build_server() { + node_id=$1 + node_conn=$(make_pi_node_conn_str $node_id) + temp_file=$(mktemp) + + cp "${build_dir}/.env" $temp_file + printf "\nNODE_ID=$node_id\n" >> $temp_file + scp $ssh_opts $temp_file $node_conn:~/.env + + cp "${build_dir}/chessh.service" $temp_file + sed -i "s/\$BUILD_ENV/\/home\/pi$(printf "%04d" $1)\/.env/" $temp_file + scp -r $ssh_opts $temp_file $node_conn:~/chessh.service + + scp $ssh_opts "${build_dir}/build_server.sh" $node_conn:~/ + + ssh $ssh_opts $node_conn "~/build_server.sh" +} + +function build_server_nodes() { + copy_ssh_keys + + for node_id in "${server_node_ids[@]}" + do + build_server $node_id + done +} + +reload_nginx_confs +build_server_nodes +build_frontend_nodes diff --git a/buildscripts/build/build_front.sh b/buildscripts/build/build_front.sh new file mode 100755 index 0000000..ed3cb8b --- /dev/null +++ b/buildscripts/build/build_front.sh @@ -0,0 +1,62 @@ +#!/usr/bin/bash + +export $(cat ~/.env | xargs) + +chessh_source="https://github.com/Simponic/chessh" +chessh_path="$HOME/src/chessh" +build_output="/var/www/html/chessh_front" +nginx_site="/etc/nginx/sites-enabled/chessh_front.conf" +front_port=3000 +nginx_conf=" +server { + listen ${front_port}; + listen [::]:${front_port}; + + location / { + root ${build_output}; + index index.html; + try_files \$uri \$uri/ /index.html; + } +} +" + +# Grab deps +if [ $(which node) == "" ] +then + curl -sSL https://deb.nodesource.com/setup_16.x | sudo bash - + sudo apt install -y nodejs +fi +[ "$(which git)" != "" ] || sudo apt install -y git +[ "$(which nginx)" != "" ] || sudo apt install -y nginx + +# Checkout source +if [ ! -d $chessh_path ] +then + mkdir -p $chessh_path + cd $chessh_path + git init + git remote add origin $chessh_source + git pull origin + git checkout main + git config pull.rebase true +else + cd $chessh_path + git pull origin main +fi + +# Build +cd $chessh_path/front +npm ci +npm run build + +# Copy to nginx root +sudo rm -rf $build_output +sudo mkdir -p $build_output +sudo cp -r $chessh_path/front/build/* $build_output +sudo chown -R www-data $build_output + +# Copy nginx config +echo "$nginx_conf" | sudo tee $nginx_site + +# Restart nginx +sudo systemctl restart nginx diff --git a/buildscripts/build/build_server.sh b/buildscripts/build/build_server.sh new file mode 100755 index 0000000..0870c79 --- /dev/null +++ b/buildscripts/build/build_server.sh @@ -0,0 +1,40 @@ +#!/usr/bin/bash + +export $(cat ~/.env | xargs) + +chessh_source="https://github.com/Simponic/chessh" +chessh_path="$HOME/src/chessh" + +# Grab deps +[ "$(which git)" != "" ] || sudo apt install -y git +if [ "$(which docker)" = "" ] +then + curl -sSL https://get.docker.com | sh +fi + +# Checkout source +if [ ! -d $chessh_path ] +then + mkdir -p $chessh_path + cd $chessh_path + git init + git remote add origin $chessh_source + git pull origin + git checkout main + git config pull.rebase true +else + cd $chessh_path + git pull origin main +fi + +# Build +cd $chessh_path +[ -d "$chessh_path/priv/keys" ] && cp ~/keys/* "$chessh_path/priv/keys/" || cp -r ~/keys "$chessh_path/priv" +sudo docker build . -t chessh/server + +# Systemd service +cd $HOME +sudo mv chessh.service /etc/systemd/system/chessh.service +sudo systemctl daemon-reload +sudo systemctl enable --now chessh +sudo systemctl restart chessh diff --git a/buildscripts/build/chessh.service b/buildscripts/build/chessh.service new file mode 100644 index 0000000..3281fcc --- /dev/null +++ b/buildscripts/build/chessh.service @@ -0,0 +1,19 @@ +[Unit] +Description=CheSSH Server +After=docker.service +BindsTo=docker.service +ReloadPropagatedFrom=docker.service +WantedBy=default.target + +[Service] +Restart=always +ExecStartPre=-/usr/bin/docker stop chessh-server +ExecStartPre=-/usr/bin/docker rm chessh-server +ExecStart=/usr/bin/docker run \ + --env-file $BUILD_ENV \ + --network=host \ + --name chessh-server \ + chessh/server + +[Install] +WantedBy=multi-user.target diff --git a/buildscripts/build/nginx.conf b/buildscripts/build/nginx.conf new file mode 100644 index 0000000..baf2cb4 --- /dev/null +++ b/buildscripts/build/nginx.conf @@ -0,0 +1,28 @@ +user www-data; +worker_processes auto; +pid /run/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 768; +} + +http { + sendfile on; + tcp_nopush on; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE + ssl_prefer_server_ciphers on; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + gzip on; + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*.conf; +} diff --git a/build.sh b/buildscripts/docker/build.sh similarity index 82% rename from build.sh rename to buildscripts/docker/build.sh index 5781727..9852f20 100755 --- a/build.sh +++ b/buildscripts/docker/build.sh @@ -1,10 +1,10 @@ #!/bin/bash -env_file=.env.prod +env_file=../../.env.prod export $(cat $env_file | xargs) -docker build . -t chessh/server +docker build ../.. -t chessh/server cd front docker build \ diff --git a/deploy.sh b/buildscripts/docker/deploy.sh similarity index 98% rename from deploy.sh rename to buildscripts/docker/deploy.sh index 10f96f5..8299aea 100755 --- a/deploy.sh +++ b/buildscripts/docker/deploy.sh @@ -1,7 +1,7 @@ #!/bin/bash datestamp=$(date +%Y%m%d-%H%M) -env_file=.env.prod +env_file=../../.env.prod project_name=chessh container_names=("chessh-redis" "chessh-database" "chessh-server" "chessh-frontend")