diff options
Diffstat (limited to 'build')
-rw-r--r-- | build/db-init/Dockerfile | 41 | ||||
-rw-r--r-- | build/db-init/README.md | 52 | ||||
-rw-r--r-- | build/db-init/base.sql | 86 | ||||
-rwxr-xr-x | build/db-init/db-init | 168 | ||||
-rw-r--r-- | build/db-init/ext.sql | 28 | ||||
-rw-r--r-- | build/db-init/rev.sql | 41 | ||||
-rw-r--r-- | build/init/Dockerfile | 22 | ||||
-rwxr-xr-x | build/init/init | 4 | ||||
-rwxr-xr-x | build/init/stamp.sh | 14 | ||||
-rw-r--r-- | build/nginx/Dockerfile | 30 | ||||
-rwxr-xr-x | build/nginx/entrypoint.sh | 8 | ||||
-rw-r--r-- | build/nginx/nginx.api.conf | 3 | ||||
-rw-r--r-- | build/nginx/nginx.api.server.conf | 14 | ||||
-rw-r--r-- | build/nginx/nginx.conf | 54 | ||||
-rw-r--r-- | build/php/Dockerfile | 17 | ||||
-rw-r--r-- | build/postgres/Dockerfile | 24 | ||||
-rw-r--r-- | build/postgrest/Dockerfile | 30 | ||||
-rwxr-xr-x | build/postgrest/entrypoint.sh | 23 |
18 files changed, 659 insertions, 0 deletions
diff --git a/build/db-init/Dockerfile b/build/db-init/Dockerfile new file mode 100644 index 0000000..c5a4b59 --- /dev/null +++ b/build/db-init/Dockerfile @@ -0,0 +1,41 @@ +### CRIMSON --- A simple PHP framework. +### Copyright © 2024 Freya Murphy <contact@freyacat.org> +### +### This file is part of CRIMSON. +### +### CRIMSON is free software; you can redistribute it and/or modify it +### under the terms of the GNU General Public License as published by +### the Free Software Foundation; either version 3 of the License, or (at +### your option) any later version. +### +### CRIMSON is distributed in the hope that it will be useful, but +### WITHOUT ANY WARRANTY; without even the implied warranty of +### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +### GNU General Public License for more details. +### +### You should have received a copy of the GNU General Public License +### along with CRIMSON. If not, see <http://www.gnu.org/licenses/>. +FROM alpine:latest + +# install packages +RUN apk add --no-cache postgresql16-client tini shadow sed +RUN rm -fr /var/cache/apk/* + +# setup main user +RUN adduser -D db-init +RUN groupmod --gid 1000 db-init +RUN usermod --uid 1000 db-init + +# copy scripts +COPY ./db-init /usr/local/bin/db-init +COPY ./rev.sql /var/lib/rev.sql +COPY ./ext.sql /var/lib/ext.sql +COPY ./base.sql /var/lib/base.sql + +# remove build packages +RUN apk del shadow + +# do the +USER db-init +ENTRYPOINT ["/sbin/tini", "--"] +CMD ["/usr/local/bin/db-init"] diff --git a/build/db-init/README.md b/build/db-init/README.md new file mode 100644 index 0000000..942090a --- /dev/null +++ b/build/db-init/README.md @@ -0,0 +1,52 @@ +## db-init + +This script setups of the databse with the requrired baseline, runs migrations, +and loads the api schema (if enabled). + +#### Migration script + +All migrations scrips MUST do ALL of the following: + + - Placed in src/db/migrations + - Named with its migration number (0 indexed), and have four + numbers of padding. i.e. `0000.sql`, `0030.sql`, or `9999.sql` + - In numerical order with all other migrations (cannot go from migration + 0 to 2). + - A postgres transaction. `BEGIN TRANSACTION ... COMMIT TRANSACTION`. + - End with the following before COMMIT, where <rev> is the NEXT + revision number. (i.e. in `0000.sql` <rev> MUST be 1). + +``` +UPDATE sys.database_info SET curr_revision = <rev> WHERE name = current_database(); +``` + +Example `0000.sql`: +```sql +BEGIN TRANSACTION; + +CREATE SCHEMA website; + +UPDATE sys.database_info SET curr_revision = 1 WHERE name = current_database(); + +COMMIT TRANSACTION; +``` + +Migrations will ONLY EVER be ONCE, and will ALLWAYS be run in order. This means +that you can assume all previous migrations have run successfully in any current +migration, and there is NO other possible state in the database. + +### API + +Once all migrations have been completed, the api will be initalized (if enabled. + +If you opt to use postgrest which is builtin into crimson, you must create a +sql file loads the api schema that MUST do ALL of the following: + + - Placed in src/db/rest and named `rest.sql` + - A postgres transaction. `BEGIN TRANSACTION ... COMMIT TRANSACTION`. + +Within that transaction you can setup postgres with the api schema you want. +See https://docs.postgrest.org/en/v12/. (crimson currently uses postgres 12). + +NOTE: If you want to load any sql file though an absolute path, src/db will be +mounted as READ ONLY to /db. (i.e. src/db/rest/rest.sql => /db/rest/rest.sql). diff --git a/build/db-init/base.sql b/build/db-init/base.sql new file mode 100644 index 0000000..784eaaa --- /dev/null +++ b/build/db-init/base.sql @@ -0,0 +1,86 @@ +--- CRIMSON --- A simple PHP framework. +--- Copyright © 2024 Freya Murphy <contact@freyacat.org> +--- +--- This file is part of CRIMSON. +--- +--- CRIMSON is free software; you can redistribute it and/or modify it +--- under the terms of the GNU General Public License as published by +--- the Free Software Foundation; either version 3 of the License, or (at +--- your option) any later version. +--- +--- CRIMSON is distributed in the hope that it will be useful, but +--- WITHOUT ANY WARRANTY; without even the implied warranty of +--- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--- GNU General Public License for more details. +--- +--- You should have received a copy of the GNU General Public License +--- along with CRIMSON. If not, see <http://www.gnu.org/licenses/>. + +-- Set's up crimsons baseline database. Can be run multiple times. + +BEGIN TRANSACTION; +SET search_path = public; +SET client_min_messages TO WARNING; + +-- Migration Start + +CREATE SCHEMA IF NOT EXISTS sys; + +-- sys + +ALTER SCHEMA sys OWNER TO POSTGRES_USER; + +-- sys.*/* + +DROP DOMAIN IF EXISTS sys."*/*" CASCADE; +CREATE DOMAIN sys."*/*" AS BYTEA; + +-- sys.database_info + +CREATE TABLE IF NOT EXISTS sys.database_info ( + name TEXT DEFAULT ''::text NOT NULL, + jwt_secret TEXT DEFAULT ''::text NOT NULL, + curr_revision INTEGER DEFAULT 0 NOT NULL +); + +ALTER TABLE sys.database_info + DROP CONSTRAINT IF EXISTS database_info_pkey; + +ALTER TABLE sys.database_info + ADD CONSTRAINT database_info_pkey PRIMARY KEY (name); + +ALTER TABLE sys.database_info OWNER TO POSTGRES_USER; + +INSERT INTO sys.database_info + (name, curr_revision) VALUES (current_database(), 0) + ON CONFLICT DO NOTHING; + +-- sys.JWT + +DROP TYPE IF EXISTS sys.JWT CASCADE; +CREATE TYPE sys.JWT AS ( + token TEXT +); + +-- authenticator +DO +$$ +BEGIN + IF NOT EXISTS (SELECT * FROM pg_user WHERE usename = 'authenticator') + THEN + CREATE ROLE authenticator + LOGIN NOINHERIT NOCREATEDB NOCREATEROLE NOSUPERUSER; + END IF; +END +$$; + +ALTER ROLE authenticator WITH PASSWORD 'postgrest'; + +-- anonymous + +DROP ROLE IF EXISTS anonymous; +CREATE ROLE anonymous NOLOGIN; + +-- Migration End; + +COMMIT TRANSACTION; diff --git a/build/db-init/db-init b/build/db-init/db-init new file mode 100755 index 0000000..5b62927 --- /dev/null +++ b/build/db-init/db-init @@ -0,0 +1,168 @@ +#!/bin/sh + +errors=$(mktemp) + +step() { + printf '\x1b[34;1m>> %s\x1b[0m\n' "$*" +} + +error() { + { + printf '\x1b[31;1merror: \x1b[0m%s\n' "$*"; + grep -v 'current transaction is aborted' < "$errors"; + printf "\x1b[31m;1error: \x1b[0mAborting migrations, fix file(s) then restart process."; + } 1>&2; +} + +try() { + "$@" 2> "$errors"; + count=$(grep -c 'ERROR' < "$errors") + if [ "$count" -eq 0 ]; then + return 0; + else + return 1; + fi +} + +export PGPASSWORD="$POSTGRES_PASSWORD" + +psql() { + /usr/bin/psql \ + -h postgres \ + -p 5432 \ + -d "$POSTGRES_DB" \ + -U "$POSTGRES_USER" \ + "$@" +} + +pg_isready() { + /usr/bin/pg_isready \ + -h postgres \ + -p 5432 \ + -d "$POSTGRES_DB" \ + -U "$POSTGRES_USER" +} + +curr_revision() { + sed "s/POSTGRES_USER/$POSTGRES_USER/g" /var/lib/rev.sql > /tmp/rev.sql + psql -qtAX -f /tmp/rev.sql; +} + +wait_until_ready() { + step 'Checking if the database is ready...'; + while true; do + pg_isready; + code=$?; + if [ $code -eq 0 ]; then + break; + fi + sleep 3; + done +} + +run_hook() { + hook="$1" + if [ ! -f "$hook" ]; then + printf '\x1b[31;1merror: \x1b[0m%s\n' "required hook not found: '$hook'"; + return 1; + fi + if ! try psql -f "$hook"; then + error "An error occoured during a hook ($hook)" + return 1; + fi +} + +run_baseline() { + sed "s/POSTGRES_USER/$POSTGRES_USER/g" /var/lib/base.sql > /tmp/base.sql + run_hook /tmp/base.sql +} + +run_migrations() { + i="$1" + while true; do + name=$(printf "%04d" "$i"); + file="/db/migrations/$name.sql" + if [ -f "$file" ]; then + if try psql -f "$file"; then + i=$((i+1)); + continue; + else + error "An error occoured during a migration (rev $name)" + return 1; + fi + else + return 0; + fi + done +} + +update_jwt() { + if try psql -c "UPDATE sys.database_info SET jwt_secret = '$API_SECRET' WHERE name = current_database();"; then + return 0; + else + error "Could not update JWT" + return 1; + fi +} + +init_api () { + step 'Initalizing the api'; + # reinit the api schema for + # postgrest + if ! run_hook "/db/rest/rest.sql"; then + return 1; + fi + + step 'Updating JWT secret'; + # make sure postgres has the corrent + # jwt secret + if ! update_jwt; then + return 1; + fi +} + +init_db () { + # reomve ready status + # so php ignores requests + rm -f /var/run/crimson/db_ready + + step 'Waiting for database'; + # make sure the database is running + # before we run any requests + wait_until_ready; + step 'Database ready'; + + step 'Loading extensions'; + # Make sure extensions are loaded + if ! run_hook "/var/lib/ext.sql"; then + return 1; + fi + + step 'Checking baseline' + # make sure baseline sys schema exists + if ! run_baseline; then + return 1; + fi + + step 'Peforming migrations'; + # get the current revision + REV=$(curr_revision); + step "Database at revision: $REV" + # run each migration that is + # higher than our current revision + if ! run_migrations "$REV"; then + return 1; + fi + + if [ "$API_ENABLED" = "true" ]; then + if ! init_api; then + return 1; + fi + fi + + step 'Database is initialized' + # database is ready + touch /var/run/crimson/db_ready +} + +init_db diff --git a/build/db-init/ext.sql b/build/db-init/ext.sql new file mode 100644 index 0000000..5ec4fea --- /dev/null +++ b/build/db-init/ext.sql @@ -0,0 +1,28 @@ +--- CRIMSON --- A simple PHP framework. +--- Copyright © 2024 Freya Murphy <contact@freyacat.org> +--- +--- This file is part of CRIMSON. +--- +--- CRIMSON is free software; you can redistribute it and/or modify it +--- under the terms of the GNU General Public License as published by +--- the Free Software Foundation; either version 3 of the License, or (at +--- your option) any later version. +--- +--- CRIMSON is distributed in the hope that it will be useful, but +--- WITHOUT ANY WARRANTY; without even the implied warranty of +--- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--- GNU General Public License for more details. +--- +--- You should have received a copy of the GNU General Public License +--- along with CRIMSON. If not, see <http://www.gnu.org/licenses/>. + +-- Loads required postgres extensions. + +BEGIN TRANSACTION; +SET search_path = public; +SET client_min_messages TO WARNING; + +CREATE EXTENSION IF NOT EXISTS pgcrypto; +CREATE EXTENSION IF NOT EXISTS pgjwt; + +COMMIT TRANSACTION; diff --git a/build/db-init/rev.sql b/build/db-init/rev.sql new file mode 100644 index 0000000..d8443c3 --- /dev/null +++ b/build/db-init/rev.sql @@ -0,0 +1,41 @@ +--- CRIMSON --- A simple PHP framework. +--- Copyright © 2024 Freya Murphy <contact@freyacat.org> +--- +--- This file is part of CRIMSON. +--- +--- CRIMSON is free software; you can redistribute it and/or modify it +--- under the terms of the GNU General Public License as published by +--- the Free Software Foundation; either version 3 of the License, or (at +--- your option) any later version. +--- +--- CRIMSON is distributed in the hope that it will be useful, but +--- WITHOUT ANY WARRANTY; without even the implied warranty of +--- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--- GNU General Public License for more details. +--- +--- You should have received a copy of the GNU General Public License +--- along with CRIMSON. If not, see <http://www.gnu.org/licenses/>. + +-- Gets the current databse revision. + +CREATE OR REPLACE FUNCTION curr_revision() +RETURNS INTEGER +LANGUAGE plpgsql VOLATILE +AS $BODY$ +DECLARE + _revision INTEGER; +BEGIN + BEGIN + SELECT curr_revision INTO _revision + FROM sys.database_info + WHERE name = current_database(); + RETURN _revision; + EXCEPTION WHEN OTHERS THEN + RETURN 0; + END; +END +$BODY$; + +GRANT EXECUTE ON FUNCTION curr_revision() TO POSTGRES_USER; + +SELECT curr_revision(); diff --git a/build/init/Dockerfile b/build/init/Dockerfile new file mode 100644 index 0000000..cd2af92 --- /dev/null +++ b/build/init/Dockerfile @@ -0,0 +1,22 @@ +FROM alpine:latest + +# install packages +RUN apk add --no-cache tini shadow coreutils findutils +RUN rm -fr /var/cache/apk/* + +# setup main user +RUN adduser -D init +RUN groupmod --gid 1000 init +RUN usermod --uid 1000 init + +# copy scripts +COPY ./init /usr/local/bin/init +COPY ./stamp.sh /usr/local/bin/stamp.sh + +# remove build packages +RUN apk del shadow + +# do the +USER init +ENTRYPOINT ["/sbin/tini", "--"] +CMD ["/usr/local/bin/init"] diff --git a/build/init/init b/build/init/init new file mode 100755 index 0000000..a4de21f --- /dev/null +++ b/build/init/init @@ -0,0 +1,4 @@ +#!/bin/sh + +# stamp assets +/usr/local/bin/stamp.sh diff --git a/build/init/stamp.sh b/build/init/stamp.sh new file mode 100755 index 0000000..6f71038 --- /dev/null +++ b/build/init/stamp.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +out="/var/run/crimson/stamp.php" +public="/opt/site/public" +files=$(find "$public" -type f -printf %P\\n) + +printf "<?php\n\$__stamps = array();\n" > "$out" +for file in $files; do + stamp=$(date +%s -r "$public/$file") + echo "\$__stamps['public/$file'] = $stamp;" >> "$out"; +done +echo "define('FILE_TIMES', \$__stamps);" >> "$out" +echo "unset(\$__stamps);" >> "$out" + diff --git a/build/nginx/Dockerfile b/build/nginx/Dockerfile new file mode 100644 index 0000000..f74d555 --- /dev/null +++ b/build/nginx/Dockerfile @@ -0,0 +1,30 @@ +FROM alpine:latest + +# install packages +RUN apk add --no-cache nginx shadow curl tini +RUN rm -fr /var/cache/apk/* + +# update nginx user +RUN groupmod --gid 1000 nginx +RUN usermod --uid 1000 nginx + +# remove build packages +RUN apk del shadow + +# make log syms +RUN ln -sf /dev/stdout /var/log/nginx/access.log && \ + ln -sf /dev/stderr /var/log/nginx/error.log + +# copy configs +RUN mkdir -p /etc/nginx +COPY ./*.conf /etc/nginx/ +RUN chown -R nginx:nginx /etc/nginx + +# copy entrypoint +COPY ./entrypoint.sh /usr/local/bin/entrypoint +RUN chmod +x /usr/local/bin/entrypoint + +# do the +USER nginx +ENTRYPOINT ["/sbin/tini", "--"] +CMD ["/usr/local/bin/entrypoint"] diff --git a/build/nginx/entrypoint.sh b/build/nginx/entrypoint.sh new file mode 100755 index 0000000..6dc7eec --- /dev/null +++ b/build/nginx/entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +if [ ! "$API_ENABLED" = "true" ]; then + echo "" > /etc/nginx/nginx.api.conf + echo "" > /etc/nginx/nginx.api.server.conf +fi + +exec -a /usr/sbin/nginx /usr/sbin/nginx -c /etc/nginx/nginx.conf diff --git a/build/nginx/nginx.api.conf b/build/nginx/nginx.api.conf new file mode 100644 index 0000000..52190c8 --- /dev/null +++ b/build/nginx/nginx.api.conf @@ -0,0 +1,3 @@ +upstream postgrest { + server rest:3000; +} diff --git a/build/nginx/nginx.api.server.conf b/build/nginx/nginx.api.server.conf new file mode 100644 index 0000000..5dd88a4 --- /dev/null +++ b/build/nginx/nginx.api.server.conf @@ -0,0 +1,14 @@ +location /api/ { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header Accept-Encoding ""; + proxy_redirect off; + + default_type application/json; + add_header 'Access-Control-Allow-Origin' '*'; + add_header Content-Location /api/$upstream_http_content_location; + proxy_set_header Connection ""; + proxy_http_version 1.1; + + proxy_pass http://postgrest/; +} diff --git a/build/nginx/nginx.conf b/build/nginx/nginx.conf new file mode 100644 index 0000000..d3dc0ae --- /dev/null +++ b/build/nginx/nginx.conf @@ -0,0 +1,54 @@ +worker_processes 4; +daemon off; +pid /tmp/nginx.pid; +error_log /var/log/nginx/error.log; + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 70; + server_tokens off; + client_max_body_size 2m; + + access_log /var/log/nginx/access.log; + + include "nginx.api.conf"; + + server { + listen 8080; + root /opt/site; + + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml image/x-icon; + + include "nginx.api.server.conf"; + + location /favicon.ico { + add_header Cache-Control "public, max-age=31536000, immutable"; + root /opt/site/public; + } + + location /public { + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + location / { + add_header Content-Security-Policy "base-uri 'none'"; + root /opt/crimson; + include fastcgi_params; + fastcgi_pass php:9000; + fastcgi_param SCRIPT_FILENAME $document_root/index.php; + } + } +} diff --git a/build/php/Dockerfile b/build/php/Dockerfile new file mode 100644 index 0000000..5f4bdd5 --- /dev/null +++ b/build/php/Dockerfile @@ -0,0 +1,17 @@ +FROM php:fpm-alpine + +# install packages +RUN apk add --no-cache postgresql-dev runuser shadow +RUN rm -fr /var/cache/apk/* + +# update php user +RUN groupmod --gid 1000 www-data +RUN usermod --uid 1000 www-data + +# install php packages +RUN docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql +RUN docker-php-ext-install pdo pdo_pgsql + +# remove build packages +RUN apk del shadow +USER www-data diff --git a/build/postgres/Dockerfile b/build/postgres/Dockerfile new file mode 100644 index 0000000..8223800 --- /dev/null +++ b/build/postgres/Dockerfile @@ -0,0 +1,24 @@ +FROM postgres:16-alpine + +# install packages +RUN apk add --no-cache make git shadow +RUN rm -fr /var/cache/apk/* + +# install pgjwt +RUN git clone https://github.com/michelp/pgjwt.git /tmp/pgjwt +WORKDIR /tmp/pgjwt +RUN make install + +# update postgres user +RUN groupmod --gid 1000 postgres +RUN usermod --uid 1000 postgres + +# remove build packages +RUN apk del make git shadow + +# set perms +RUN chown -R postgres:postgres /var/run/postgresql + +# fix workdir +WORKDIR / +USER postgres diff --git a/build/postgrest/Dockerfile b/build/postgrest/Dockerfile new file mode 100644 index 0000000..747052f --- /dev/null +++ b/build/postgrest/Dockerfile @@ -0,0 +1,30 @@ +FROM alpine:latest + +# install packages +RUN apk add --no-cache tini wget curl shadow +RUN rm -fr /var/cache/apk/* + +# setup main user +RUN adduser -D postgrest +RUN groupmod --gid 1000 postgrest +RUN usermod --uid 1000 postgrest + +# install postgrest +RUN wget "https://github.com/PostgREST/postgrest/releases/download/v12.2.3/postgrest-v12.2.3-linux-static-x64.tar.xz" -O /tmp/postgrest.tar.xz +RUN tar xJf /tmp/postgrest.tar.xz -C /usr/local/bin +RUN rm /tmp/postgrest.tar.xz + +# copy scripts +COPY ./entrypoint.sh /usr/local/bin/entrypoint.sh + +# remove build packages +RUN apk del shadow + +# make the dirs +RUN mkdir -p /etc/postgrest.d && \ + chown postgrest:postgrest /etc/postgrest.d + +# do the +USER postgrest +ENTRYPOINT ["/sbin/tini", "--"] +CMD ["/usr/local/bin/entrypoint.sh"] diff --git a/build/postgrest/entrypoint.sh b/build/postgrest/entrypoint.sh new file mode 100755 index 0000000..6a944f2 --- /dev/null +++ b/build/postgrest/entrypoint.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +config=/etc/postgrest.d/postgrest.conf + +JWT_SECRET="$API_SECRET" + +PGRST_DB_URI="postgres://authenticator:postgrest@postgres:5432/$POSTGRES_DB" +PGRST_ROLE="$API_ROLE" +PGRST_SCHEMA="$API_SCHEMA" + +rm -fr "$config" +touch "$config" +{ + printf 'db-uri = "%s"\n' "$PGRST_DB_URI"; + printf 'db-anon-role = "%s"\n' "$PGRST_ROLE"; + printf 'db-schemas = "%s"\n' "$PGRST_SCHEMA"; + printf 'jwt-secret = "%s"\n' "$JWT_SECRET"; + printf 'jwt-secret-is-base64 = false\n'; + printf 'server-host = "*"\n'; + printf 'server-port = 3000\n'; +} >> $config + +exec -a /usr/local/bin/postgrest /usr/local/bin/postgrest "$config" |