diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b540b79 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/* +!build/.gitkeep diff --git a/.helpers/render.sh b/.helpers/render.sh new file mode 100644 index 0000000..79dcf0d --- /dev/null +++ b/.helpers/render.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# +set -o errexit +set -o nounset +set -o pipefail + +directory=$1 + +export $(xargs < ./config/production.cfg) +mkdir -p build/${directory} + +for i in $(find "./templates/${directory}" -mindepth 1 -type f); do + file=$(basename ${i}) + envsubst < "./templates/${directory}/${file}" > "./build/${directory}/${file}" +done diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2a5e32b --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +all: compose traefik gitea + +compose: + bash ./.helpers/render.sh compose + +traefik: compose + bash ./.helpers/render.sh traefik + +gitea: compose + bash ./.helpers/render.sh gitea diff --git a/build/.gitkeep b/build/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/templates/compose/docker-compose.yaml b/templates/compose/docker-compose.yaml new file mode 100644 index 0000000..499cf40 --- /dev/null +++ b/templates/compose/docker-compose.yaml @@ -0,0 +1,94 @@ +--- +version: "3.8" + +networks: + forge: + name: "forge-flow" + ipam: + driver: "default" + config: + - subnet: "${NETWORK_FORGE_FLOW_SUBNET}" + +volumes: + traefik-shared: + name: "traefik-config-shared-volume" + +services: + # -- Traffic flow -- + traefik: + container_name: "traffic-flow" + build: + args: + TRAEFIK_VERSION: "${TRAEFIK_VERSION}" + context: "./traefik" + networks: + forge: + ipv4_address: "${TRAEFIK_CONTAINER_IPV4_ADDRESS}" + ports: + - target: 80 + published: 80 + protocol: "tcp" + mode: "host" + - target: 443 + published: 443 + protocol: "tcp" + mode: "host" + - target: ${TRAEFIK_EXTERNAL_SSH_PORT} + published: ${TRAEFIK_EXTERNAL_SSH_PORT} + protocol: "tcp" + mode: "host" + restart: "always" + volumes: + - type: "volume" + source: "traefik-shared" + target: "${TRAEFIK_SHARED_MOUNT_POINT}" + - type: "bind" + source: "/etc/timezone" + target: "/etc/timezone" + read_only: true + - type: "bind" + source: "/etc/localtime" + target: "/etc/localtime" + read_only: true + # Traefik TLS volume + - type: "bind" + source: "${TRAEFIK_TLS_HOST_DIR}" + target: "${TRAEFIK_TLS_CONTAINER_DIR}" + # -- Code flow -- + gitea: + container_name: "code-flow" + build: + args: + FLOW_GID: "${FLOW_GID}" + FLOW_UID: "${FLOW_UID}" + GITEA_HOME: "${GITEA_HOME}" + GITEA_WORK_DIR: "${GITEA_WORK_DIR}" + GITEA_CUSTOM: "${GITEA_CUSTOM}" + GITEA_APP_INI: "${GITEA_APP_INI}" + GITEA_BIN: "${GITEA_BIN}" + GITEA_DATA_CONTAINER_DIR: "${GITEA_DATA_CONTAINER_DIR}" + GITEA_TMP: "${GITEA_TMP}" + context: "./gitea" + expose: + - "${GITEA_SSH_PORT}" + - "${GITEA_HTTP_PORT}" + networks: + forge: + ipv4_address: "${GITEA_CONTAINER_IPV4_ADDRESS}" + restart: "always" + volumes: + - type: "volume" + source: "traefik-shared" + target: "${TRAEFIK_SHARED_MOUNT_POINT}" + - type: "bind" + source: "/etc/timezone" + target: "/etc/timezone" + read_only: true + - type: "bind" + source: "/etc/localtime" + target: "/etc/localtime" + read_only: true + # Gitea data volume + - type: "bind" + source: "${GITEA_DATA_HOST_DIR}" + target: "${GITEA_DATA_CONTAINER_DIR}" diff --git a/templates/gitea/Dockerfile b/templates/gitea/Dockerfile new file mode 100644 index 0000000..539ba6d --- /dev/null +++ b/templates/gitea/Dockerfile @@ -0,0 +1,54 @@ +# This is a custom made Dockerfile for Gitea which is inspired from +# the official Dockerfile.rootless from https://github.com/go-gitea/gitea/ +FROM alpine:3.15.0 + +ARG FLOW_UID +ARG FLOW_GID +ARG GITEA_HOME +ARG GITEA_WORK_DIR +ARG GITEA_CUSTOM +ARG GITEA_APP_INI +ARG GITEA_BIN +ARG GITEA_DATA_CONTAINER_DIR +ARG GITEA_TMP + +RUN apk --no-cache add \ + bash \ + ca-certificates \ + gettext \ + git \ + curl \ + gnupg \ + openssh-keygen + +RUN addgroup -S -g ${FLOW_GID} flow && \ + adduser -S -H -D -h ${GITEA_HOME} -s /bin/bash -u ${FLOW_UID} -G flow git && \ + mkdir -p ${GITEA_DATA_CONTAINER_DIR} ${GITEA_TMP} && \ + chown git ${GITEA_DATA_CONTAINER_DIR} && chmod 0700 ${GITEA_DATA_CONTAINER_DIR} && \ + chown git ${GITEA_TMP} && chmod 0700 ${GITEA_TMP} + +ADD --chown=root:root gitea ${GITEA_BIN} +ADD app.ini ${GITEA_APP_INI} +ADD entrypoint.sh /usr/local/bin/entrypoint.sh +ADD --chown=${FLOW_UID}:${FLOW_GID} dynamic_git.yaml ${GITEA_TMP}/ + +RUN chown -R ${FLOW_UID}:${FLOW_UID} ${GITEA_APP_INI} && \ + chmod 0400 ${GITEA_APP_INI} && \ + chmod a+x ${GITEA_BIN} && \ + chmod a+rx /usr/local/bin/entrypoint.sh + +ENV GITEA_WORK_DIR=${GITEA_WORK_DIR} \ + GITEA_CUSTOM=${GITEA_CUSTOM} \ + GITEA_APP_INI=${GITEA_APP_INI} \ + GITEA_BIN=${GITEA_BIN} \ + HOME=${GITEA_HOME} + +USER ${FLOW_UID}:${FLOW_GID} + +WORKDIR /flow/gitea/data + +VOLUME ["/flow/gitea/data"] + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] + +CMD [] diff --git a/templates/gitea/app.ini b/templates/gitea/app.ini new file mode 100644 index 0000000..1df0f40 --- /dev/null +++ b/templates/gitea/app.ini @@ -0,0 +1,105 @@ +APP_NAME = ${GITEA_APP_NAME} +RUN_USER = git +RUN_MODE = ${GITEA_RUN_MODE} + +[repository] +ROOT = ${GITEA_DATA_CONTAINER_DIR}/git/repositories +DEFAULT_BRANCH = main + +[repository.local] +LOCAL_COPY_PATH = ${GITEA_TMP}/local-repo + +[repository.upload] +TEMP_PATH = ${GITEA_TMP}/uploads + +[repository.signing] +; Gitea will sign initial commits only if the user has a public key. +INITIAL_COMMIT = pubkey + +[ui] +DEFAULT_THEME = arc-green + +[server] +APP_DATA_PATH = ${GITEA_DATA_CONTAINER_DIR}/git +DOMAIN = ${GITEA_DOMAIN} +HTTP_ADDR = ${GITEA_CONTAINER_IPV4_ADDRESS} +HTTP_PORT = ${GITEA_HTTP_PORT} +ROOT_URL = https://${GITEA_DOMAIN} +DISABLE_SSH = false +START_SSH_SERVER = true +SSH_DOMAIN = ${GITEA_DOMAIN} +SSH_PORT = ${TRAEFIK_EXTERNAL_SSH_PORT} +SSH_LISTEN_HOST = ${GITEA_CONTAINER_IPV4_ADDRESS} +SSH_LISTEN_PORT = ${GITEA_SSH_PORT} +BUILTIN_SSH_SERVER_USER = git +LFS_START_SERVER = false +LFS_CONTENT_PATH = ${GITEA_DATA_CONTAINER_DIR}/git/lfs + +[ssh.minimum_key_sizes] +ED25519 = 256 +ECDSA = 256 +RSA = 4096 +DSA = -1 + +[database] +DB_TYPE = sqlite3 +PATH = ${GITEA_DATA_CONTAINER_DIR}/database/gitea.db +HOST = localhost:3306 +NAME = gitea +USER = gitea +PASSWD = + +[indexer] +ISSUE_INDEXER_PATH = ${GITEA_DATA_CONTAINER_DIR}/indexers/issues.bleve + +[session] +PROVIDER_CONFIG = ${GITEA_DATA_CONTAINER_DIR}/sessions + +[queue] +DATADIR = ${GITEA_DATA_CONTAINER_DIR}/queues + +[admin] +DISABLE_REGULAR_ORG_CREATION = true +DEFAULT_EMAIL_NOTIFICATION = disabled + +[security] +INSTALL_LOCK = true +SECRET_KEY = ${GITEA_SECRET_KEY} +INTERNAL_TOKEN = ${GITEA_INTERNAL_TOKEN} +LOGIN_REMEMBER_DAYS = 1 +MIN_PASSWORD_LENGTH = 12 +PASSWORD_COMPLEXITY = lower,upper,digit + +[service] +DISABLE_REGISTRATION = true + +[service.explore] +REQUIRE_SIGNIN_VIEW = false + +[picture] +AVATAR_UPLOAD_PATH = ${GITEA_DATA_CONTAINER_DIR}/avatars +REPOSITORY_AVATAR_UPLOAD_PATH = ${GITEA_DATA_CONTAINER_DIR}/repo-avatars + +[attachment] +ENABLED = true +PATH = ${GITEA_DATA_CONTAINER_DIR}/attachments + +[log] +ROOT_PATH = ${GITEA_DATA_CONTAINER_DIR}/log +MODE = console +LEVEL = ${GITEA_LOG_LEVEL} + +[log.console] +STDERR = false + +[i18n] +LANGS = en-US +NAMES = English + +[other] +SHOW_FOOTER_BRANDING = true +SHOW_FOOTER_VERSION = false +SHOW_FOOTER_TEMPLATE_LOAD_TIME = false + +[oauth2] +ENABLE = false diff --git a/templates/gitea/dynamic_git.yaml b/templates/gitea/dynamic_git.yaml new file mode 100644 index 0000000..be25e9f --- /dev/null +++ b/templates/gitea/dynamic_git.yaml @@ -0,0 +1,28 @@ +--- +http: + routers: + gitea: + entryPoints: + - "https" + rule: "Host(`${GITEA_DOMAIN}`)" + service: "git" + tls: + certResolver: resolver + services: + git: + loadBalancer: + servers: + - url: "http://${GITEA_CONTAINER_IPV4_ADDRESS}:${GITEA_HTTP_PORT}/" + +tcp: + routers: + gitSSH: + entryPoints: + - "gitSSH" + rule: "HostSNI(`*`)" + service: "gitSSH" + services: + gitSSH: + loadBalancer: + servers: + - address: "${GITEA_CONTAINER_IPV4_ADDRESS}:${GITEA_SSH_PORT}" diff --git a/templates/gitea/entrypoint.sh b/templates/gitea/entrypoint.sh new file mode 100644 index 0000000..978049c --- /dev/null +++ b/templates/gitea/entrypoint.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Create the home directory. +if ! [ -d ${GITEA_HOME} ]; then + mkdir -p ${GITEA_HOME} + chmod 0700 ${GITEA_HOME} +fi + +# Create the custom directory. +if ! [ -d ${GITEA_CUSTOM} ]; then + mkdir -p ${GITEA_CUSTOM} + chmod 0500 ${GITEA_CUSTOM} +fi + +# Move the dynamic Traefik config to the shared volume. +if [ -f /flow/gitea/tmp/dynamic_git.yaml ]; then + mv /flow/gitea/tmp/dynamic_git.yaml ${TRAEFIK_SHARED_MOUNT_POINT}/dynamic/dynamic_git.yaml +fi + +if [ $# -gt 0 ]; then + exec "$@" +else + exec ${GITEA_BIN} -c ${GITEA_APP_INI} web +fi diff --git a/templates/traefik/Dockerfile b/templates/traefik/Dockerfile new file mode 100644 index 0000000..0d75454 --- /dev/null +++ b/templates/traefik/Dockerfile @@ -0,0 +1,11 @@ +FROM traefik:${TRAEFIK_VERSION} + +ADD traefik.yaml /flow/traefik/ + +ADD entrypoint.sh / + +ADD dynamic_dashboard.yaml /tmp/ + +RUN chmod +x /entrypoint.sh + +CMD ["--configfile=/flow/traefik/traefik.yaml"] diff --git a/templates/traefik/dynamic_dashboard.yaml b/templates/traefik/dynamic_dashboard.yaml new file mode 100644 index 0000000..90afb4d --- /dev/null +++ b/templates/traefik/dynamic_dashboard.yaml @@ -0,0 +1,10 @@ +--- +http: + routers: + dashboard: + entryPoints: + - "https" + rule: "Host(`${ROOT_DOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" + service: "api@internal" + tls: + certResolver: resolver diff --git a/templates/traefik/entrypoint.sh b/templates/traefik/entrypoint.sh new file mode 100644 index 0000000..1e986c6 --- /dev/null +++ b/templates/traefik/entrypoint.sh @@ -0,0 +1,28 @@ +#!/bin/sh +set -e + +# Create the dynamic config directory in the shared volume. +mkdir -p ${TRAEFIK_SHARED_MOUNT_POINT}/dynamic +chgrp ${FLOW_GID} ${TRAEFIK_SHARED_MOUNT_POINT}/dynamic +chmod a-rwx,u+rwx,g+rwx ${TRAEFIK_SHARED_MOUNT_POINT}/dynamic + +# Move the dashboard config to the new directory. +if [ -f /tmp/dynamic_dashboard.yaml ]; then + mv /tmp/dynamic_dashboard.yaml ${TRAEFIK_SHARED_MOUNT_POINT}/dynamic/dynamic_dashboard.yaml +fi + +# first arg is `-f` or `--some-option` +if [ "${1#-}" != "$1" ]; then + set -- traefik "$@" +fi + +# if our command is a valid Traefik subcommand, let's invoke it through Traefik instead +# (this allows for "docker run traefik version", etc) +if traefik "$1" --help >/dev/null 2>&1 +then + set -- traefik "$@" +else + echo "= '$1' is not a Traefik command: assuming shell execution." 1>&2 +fi + +exec "$@" diff --git a/templates/traefik/traefik.yaml b/templates/traefik/traefik.yaml new file mode 100644 index 0000000..339f751 --- /dev/null +++ b/templates/traefik/traefik.yaml @@ -0,0 +1,35 @@ +--- +global: + checkNewVersion: ${TRAEFIK_CHECK_NEW_VERSION} + sendAnonymousUsage: ${TRAEFIK_SEND_ANONYMOUS_USAGE} +api: + insecure: false + dashboard: true + debug: false +entryPoints: + http: + address: "${TRAEFIK_CONTAINER_IP}:80" + http: + redirections: + entryPoint: + to: "https" + scheme: "https" + permanent: true + https: + address: "${TRAEFIK_CONTAINER_IPV4_ADDRESS}:443" + gitSSH: + address: "${TRAEFIK_CONTAINER_IPV4_ADDRESS}:${TRAEFIK_EXTERNAL_SSH_PORT}" +providers: + file: + watch: true + directory: "${TRAEFIK_SHARED_MOUNT_POINT}/dynamic" +certificatesResolvers: + resolver: + acme: + caServer: "${TRAEFIK_ACME_CA_SERVER}" + email: "${TRAEFIK_ACME_EMAIL}" + storage: "${TRAEFIK_TLS_CONTAINER_DIR}/acme.json" + keyType: "RSA4096" + tlsChallenge: {} +log: + level: "${TRAEFIK_LOG_LEVEL}"