2024-07-04 06:40:30 +00:00
|
|
|
#!/bin/bash
|
|
|
|
set -e
|
|
|
|
|
|
|
|
test_ca_list=()
|
|
|
|
case "$1" in
|
|
|
|
--test)
|
|
|
|
ln -sf test.env .env
|
|
|
|
test_ca_list=(test/certs/cur-root.crt test/certs/pebble.minica.pem)
|
|
|
|
;;
|
|
|
|
--production)
|
|
|
|
ln -sf production.env .env
|
|
|
|
;;
|
|
|
|
*)
|
|
|
|
echo "usage: $0 --test|--production" >&2
|
|
|
|
exit 1
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
. .env
|
|
|
|
|
|
|
|
subdomains=('' mail git forum)
|
|
|
|
|
|
|
|
function fatal()
|
|
|
|
{
|
|
|
|
echo "fatal: $*" >&2
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
|
|
|
function random_passwd()
|
|
|
|
{
|
|
|
|
tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 16
|
|
|
|
}
|
|
|
|
|
|
|
|
function wait_for_server()
|
|
|
|
{
|
|
|
|
local i
|
|
|
|
echo "waiting for server to start up..."
|
|
|
|
for i in {1..30}; do
|
|
|
|
sleep 1
|
|
|
|
if curl "http://$BASE_DOMAIN_NAME/" >/dev/null; then
|
|
|
|
echo "server up"
|
|
|
|
return
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
fatal "server failed to start up after $i attempts"
|
|
|
|
}
|
|
|
|
|
|
|
|
function retry_if_failed()
|
|
|
|
{
|
|
|
|
local retry error_args=": $*"
|
|
|
|
if [[ "$1" == "-q" ]]; then
|
|
|
|
error_args=""
|
|
|
|
shift
|
|
|
|
fi
|
|
|
|
for retry in {1..3}; do
|
|
|
|
if "$@"; then
|
|
|
|
return
|
|
|
|
fi
|
|
|
|
echo "command failed (retry $retry)$error_args" >&2
|
|
|
|
sleep 2
|
|
|
|
done
|
|
|
|
fatal "command failed$error_args"
|
|
|
|
}
|
|
|
|
|
|
|
|
function forgejo()
|
|
|
|
{
|
|
|
|
docker run --rm codeberg.org/forgejo/forgejo:7 forgejo "$@"
|
|
|
|
}
|
|
|
|
|
2024-07-05 05:03:06 +00:00
|
|
|
function write_config()
|
|
|
|
{
|
|
|
|
local src="" dest="" vars="" new_vars=() mode="644" owner="root:root"
|
|
|
|
while (($#)); do
|
|
|
|
case "$1" in
|
|
|
|
--src)
|
|
|
|
src="$2"
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
--mode)
|
|
|
|
mode="$2"
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
--owner)
|
|
|
|
owner="$2"
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
--dest)
|
|
|
|
dest="$2"
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
--var)
|
|
|
|
[[ "$2" =~ ^([A-Za-z0-9_]+)= ]] || fatal "invalid --var argument"
|
|
|
|
vars+="\$${BASH_REMATCH[1]} "
|
|
|
|
new_vars+=("$2")
|
|
|
|
shift 2
|
|
|
|
;;
|
|
|
|
*)
|
|
|
|
fatal "write_config: unrecognized argument: $1"
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
done
|
|
|
|
: "${src:?missing --src argument}"
|
|
|
|
local dest_dir temp
|
|
|
|
dest_dir="$(dirname "${dest:?missing --dest argument}")"
|
|
|
|
temp="$(umask 577 && mktemp -p "$dest_dir")"
|
|
|
|
# printf '%q ' env "${new_vars[@]}" envsubst "$vars";
|
|
|
|
# echo "<" "$src" ">" "$temp"
|
|
|
|
env "${new_vars[@]}" envsubst "$vars" < "$src" > "$temp" || { rm -f "$temp"; exit 1; }
|
|
|
|
chmod "$mode" "$temp" || { rm -f "$temp"; exit 1; }
|
|
|
|
chown "$owner" "$temp" || { rm -f "$temp"; exit 1; }
|
2024-07-12 12:26:50 +00:00
|
|
|
if [[ ! -f "$dest" ]] && mv -v -T "$temp" "$dest"; then
|
2024-07-05 05:03:06 +00:00
|
|
|
return 0
|
|
|
|
fi
|
|
|
|
if diff -u --label="expanded $src" "$temp" "$dest"; then
|
|
|
|
rm -f "$temp"
|
|
|
|
return 0
|
|
|
|
else
|
|
|
|
rm -f "$temp"
|
|
|
|
fatal "config file doesn't match generated config for $dest expanded from $src"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2024-07-04 06:40:30 +00:00
|
|
|
if [[ "$(id -u)" != 0 ]]; then
|
|
|
|
fatal "must be ran as root"
|
|
|
|
fi
|
|
|
|
|
2024-07-14 10:27:19 +00:00
|
|
|
apt-get remove -y -q docker.io docker-doc docker-compose podman-docker containerd runc
|
|
|
|
|
2024-07-04 06:40:30 +00:00
|
|
|
mkdir -p /var/lib/stalwart-mail
|
|
|
|
apt-get update -y -q
|
2024-07-14 10:27:19 +00:00
|
|
|
apt-get install ca-certificates curl jq gettext-base diffutils -y -q
|
2024-07-04 06:40:30 +00:00
|
|
|
# force using overlay2 driver so btrfs snapshots will snapshot the entire system and not miss all the docker stuff
|
|
|
|
mkdir -p /etc/docker
|
2024-07-05 05:03:06 +00:00
|
|
|
write_config --src templates/etc/docker/daemon.json --dest /etc/docker/daemon.json
|
2024-07-14 10:27:19 +00:00
|
|
|
write_config --src templates/etc/apt/sources.list.d/docker.list \
|
|
|
|
--dest /etc/apt/sources.list.d/docker.list \
|
|
|
|
--var dpkg_arch="$(dpkg --print-architecture)" \
|
|
|
|
--var VERSION_CODENAME="$(. /etc/os-release && echo "$VERSION_CODENAME")"
|
|
|
|
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
|
|
|
|
chmod a+r /etc/apt/keyrings/docker.asc
|
|
|
|
apt-get update -y -q
|
|
|
|
apt-get install certbot docker-ce docker-ce-cli containerd.io docker-compose-plugin sudo openssl crudini git ssl-cert -y -q
|
2024-07-04 06:40:30 +00:00
|
|
|
if ((${#test_ca_list[@]})); then
|
|
|
|
install -m 644 "${test_ca_list[0]}" /usr/local/share/ca-certificates/test-root.crt
|
|
|
|
install -m 644 "${test_ca_list[1]}" /usr/local/share/ca-certificates/test-root2.crt
|
|
|
|
update-ca-certificates
|
|
|
|
fi
|
|
|
|
addgroup --gid=1000 git || true
|
|
|
|
[[ "$(getent group 1000)" =~ ^'git:x:1000:' ]] ||
|
|
|
|
fatal "some other group has gid 1000, which is needed for the git group"
|
|
|
|
adduser --system --shell=/bin/bash --gecos 'Git Version Control' --uid=1000 --ingroup=git --disabled-password --home=/var/lib/forgejo git || true
|
|
|
|
[[ "$(getent passwd 1000)" == 'git:x:1000:1000:Git Version Control,,,:/var/lib/forgejo:/bin/bash' ]] ||
|
|
|
|
fatal "some other user has gid 1000, which is needed for the git user"
|
|
|
|
[[ -f ~git/.ssh/id_ed25519 ]] || sudo -u git ssh-keygen -f ~git/.ssh/id_ed25519 -t ed25519 -C "Forgejo Host Key" -N ""
|
|
|
|
[[ -f ~git/.ssh/authorized_keys ]] || sudo -u git cat ~git/.ssh/id_ed25519.pub | sudo -u git tee ~git/.ssh/authorized_keys
|
|
|
|
sudo -u git chmod 600 ~git/.ssh/authorized_keys
|
2024-07-05 05:03:06 +00:00
|
|
|
write_config --src templates/usr/local/bin/gitea --dest /usr/local/bin/gitea --mode 755
|
2024-07-04 06:40:30 +00:00
|
|
|
mkdir -p /etc/forgejo
|
|
|
|
rm -rf /var/www/html
|
|
|
|
mkdir -p /var/www/html
|
|
|
|
chown git:git /var/www/html
|
|
|
|
chmod 775 /var/www/html
|
|
|
|
(cd /var/www/html && sudo -u git git init --no-initial-branch)
|
|
|
|
(cd /var/www/html && sudo -u git git remote add -t heads/rendered --mirror=fetch origin /data/git/repositories/libre-chip/website.git)
|
|
|
|
chown root:git /etc/forgejo
|
|
|
|
chmod 770 /etc/forgejo
|
|
|
|
if [[ ! -f /etc/forgejo/app.ini ]]; then
|
2024-07-05 05:03:06 +00:00
|
|
|
write_config --src templates/etc/forgejo/app.ini \
|
|
|
|
--dest /etc/forgejo/app.ini --mode 640 --owner root:git \
|
|
|
|
--var BASE_DOMAIN_NAME="$BASE_DOMAIN_NAME" \
|
|
|
|
--var SECRET_KEY="$(forgejo generate secret SECRET_KEY)" \
|
|
|
|
--var INTERNAL_TOKEN="$(forgejo generate secret INTERNAL_TOKEN)" \
|
|
|
|
--var MAIL_PASSWD="$(random_passwd)" \
|
|
|
|
--var JWT_SECRET="$(forgejo generate secret JWT_SECRET)" \
|
|
|
|
--var LFS_JWT_SECRET="$(forgejo generate secret LFS_JWT_SECRET)"
|
2024-07-04 06:40:30 +00:00
|
|
|
fi
|
|
|
|
mkdir -p /var/lib/stalwart-mail/etc
|
|
|
|
mail_passwd=""
|
|
|
|
mail_passwd_hash=""
|
|
|
|
if [[ ! -f /var/lib/stalwart-mail/etc/config.toml ]]; then
|
|
|
|
mail_passwd="$(random_passwd)"
|
2024-07-05 05:03:06 +00:00
|
|
|
write_config --src templates/var/lib/stalwart-mail/cli.sh \
|
|
|
|
--dest /var/lib/stalwart-mail/cli.sh --mode 400 \
|
|
|
|
--var wd="$(pwd)" --var mail_passwd="$mail_passwd"
|
2024-07-04 06:40:30 +00:00
|
|
|
mail_passwd_hash="$(echo -n "$mail_passwd" | openssl passwd -6 -stdin)"
|
2024-07-05 05:03:06 +00:00
|
|
|
write_config --src templates/var/lib/stalwart-mail/etc/config.toml \
|
|
|
|
--dest /var/lib/stalwart-mail/etc/config.toml --mode 600 \
|
|
|
|
--var mail_passwd_hash="$mail_passwd_hash" \
|
|
|
|
--var BASE_DOMAIN_NAME="$BASE_DOMAIN_NAME"
|
2024-07-04 06:40:30 +00:00
|
|
|
fi
|
|
|
|
. /var/lib/stalwart-mail/cli.sh
|
|
|
|
if [[ ! -f /var/discourse/containers/app.yml ]]; then
|
|
|
|
if [[ ! -d /var/discourse/containers ]]; then
|
|
|
|
git clone https://github.com/discourse/discourse_docker.git /var/discourse
|
|
|
|
chmod 700 /var/discourse/containers
|
|
|
|
fi
|
|
|
|
forum_smtp_passwd="$(random_passwd)"
|
2024-07-05 05:03:06 +00:00
|
|
|
write_config --src templates/var/discourse/containers/app.yml \
|
|
|
|
--dest /var/discourse/containers/app.yml --mode 400 \
|
|
|
|
--var BASE_DOMAIN_NAME="$BASE_DOMAIN_NAME" \
|
|
|
|
--var forum_smtp_passwd="$forum_smtp_passwd" \
|
|
|
|
--var mail_passwd="$mail_passwd"
|
2024-07-04 06:40:30 +00:00
|
|
|
fi
|
|
|
|
wd="$(pwd)"
|
|
|
|
if ! [[ "$wd" =~ ^/[-/a-zA-Z0-9_]*$ ]]; then
|
|
|
|
fatal "invalid characters in current directory: $wd"
|
|
|
|
fi
|
|
|
|
nginx_container="$(docker create --rm -v /var/www/.well-known/acme-challenge:/var/www/.well-known/acme-challenge:ro -v "$wd"/http_only_nginx_templates:/etc/nginx/templates:ro -p 80:80 nginx:bookworm)"
|
|
|
|
docker start "$nginx_container"
|
|
|
|
trap 'docker stop "$nginx_container"' EXIT
|
|
|
|
echo "waiting for server to come up..."
|
|
|
|
for _ in {0..30}; do
|
|
|
|
sleep 1
|
|
|
|
if curl "http://$BASE_DOMAIN_NAME/" >/dev/null; then
|
|
|
|
break
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
echo "server up"
|
|
|
|
certbot_args=(certonly -n --email "postmaster@$BASE_DOMAIN_NAME" "--server=$ACME_SERVER_URL" --cert-name server --agree-tos --webroot --webroot-path /var/www)
|
2024-07-14 10:27:19 +00:00
|
|
|
certbot_args+=(--disable-hook-validation --post-hook "cd '$wd' && docker compose -p server restart")
|
2024-07-04 06:40:30 +00:00
|
|
|
for subdomain in "${subdomains[@]}"; do
|
|
|
|
if [[ -n "$subdomain" ]]; then
|
|
|
|
subdomain+=.
|
|
|
|
fi
|
|
|
|
certbot_args+=(-d "$subdomain$BASE_DOMAIN_NAME")
|
|
|
|
certbot_args+=(-d "$subdomain$ALT_BASE_DOMAIN_NAME")
|
|
|
|
done
|
|
|
|
retry_if_failed certbot "${certbot_args[@]}"
|
|
|
|
trap EXIT
|
|
|
|
docker stop "$nginx_container"
|
2024-07-14 10:27:19 +00:00
|
|
|
DOCKER_BUILDKIT=1 docker compose -p server up -d
|
2024-07-08 22:50:37 +00:00
|
|
|
sleep 10
|
2024-07-04 06:40:30 +00:00
|
|
|
if [[ -n "$mail_passwd_hash" ]]; then
|
|
|
|
forgejo_smtp_passwd="$(crudini --get /etc/forgejo/app.ini mailer PASSWD)"
|
|
|
|
stalwart-cli domain create "$BASE_DOMAIN_NAME"
|
|
|
|
curl -u "admin:$mail_passwd" "https://mail.$BASE_DOMAIN_NAME/api/dkim" --data-binary '{"id":null,"algorithm":"Ed25519","domain":"'"$BASE_DOMAIN_NAME"'","selector":null}' > /dev/null
|
|
|
|
curl -u "admin:$mail_passwd" "https://mail.$BASE_DOMAIN_NAME/api/dkim" --data-binary '{"id":null,"algorithm":"Rsa","domain":"'"$BASE_DOMAIN_NAME"'","selector":null}' > /dev/null
|
|
|
|
stalwart-cli account create -d 'Admin Account' -i true -a "postmaster@$BASE_DOMAIN_NAME" 'admin' "$mail_passwd"
|
|
|
|
stalwart-cli account create -d 'Forgejo Server' -i false -a "forgejo@$BASE_DOMAIN_NAME" 'forgejo' "$forgejo_smtp_passwd"
|
2024-07-14 10:27:19 +00:00
|
|
|
add_postmaster=(docker compose -p server exec -T -u git forgejo forgejo admin user create --admin --username postmaster --password "$mail_passwd" --email "postmaster@$BASE_DOMAIN_NAME")
|
2024-07-04 06:40:30 +00:00
|
|
|
retry_if_failed -q "${add_postmaster[@]}"
|
|
|
|
forum_smtp_passwd="$(sed 's/^ *DISCOURSE_SMTP_PASSWORD: "*\([^"]*\)"$/\1/p; d' < /var/discourse/containers/app.yml)"
|
|
|
|
[[ -n "$forum_smtp_passwd" ]] || fatal "can't parse smtp password out of /var/discourse/containers/app.yml"
|
|
|
|
stalwart-cli account create -d 'Forum Replies' -i false -a "@$BASE_DOMAIN_NAME" 'forum' "$forum_smtp_passwd"
|
|
|
|
stalwart-cli account create -d 'Forum Notifications' -i false -a "forum-noreply@$BASE_DOMAIN_NAME" 'forum-noreply' "$forum_smtp_passwd"
|
|
|
|
forgejo_api=(retry_if_failed -q curl --fail-with-body -u "postmaster:$mail_passwd" -H 'Accept: application/json' -H 'Content-Type: application/json')
|
|
|
|
"${forgejo_api[@]}" -X 'POST' "https://git.$BASE_DOMAIN_NAME/api/v1/orgs" -d '{"username": "libre-chip"}' > /dev/null
|
|
|
|
"${forgejo_api[@]}" -X 'POST' "https://git.$BASE_DOMAIN_NAME/api/v1/orgs/libre-chip/repos" -d '{"name": "website"}' > /dev/null
|
2024-07-05 05:03:06 +00:00
|
|
|
post_receive_hook="$(jq -csR '{content:.}' < website_git_post_receive_hook.sh)"
|
2024-07-04 06:40:30 +00:00
|
|
|
"${forgejo_api[@]}" -X 'PATCH' "https://git.$BASE_DOMAIN_NAME/api/v1/repos/libre-chip/website/hooks/git/post-receive" -d "$post_receive_hook" > /dev/null
|
|
|
|
fi
|
|
|
|
(
|
|
|
|
cd /var/discourse
|
|
|
|
# must run after starting mail server since it validates POP3
|
|
|
|
./launcher bootstrap app
|
|
|
|
./launcher start app
|
|
|
|
)
|