diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5d37244 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,53 @@ +# This file excludes paths from the Docker build context. +# +# By default, Docker's build context includes all files (and folders) in the +# current directory. Even if a file isn't copied into the container it is still sent to +# the Docker daemon. +# +# There are multiple reasons to exclude files from the build context: +# +# 1. Prevent nested folders from being copied into the container (ex: exclude +# /assets/node_modules when copying /assets) +# 2. Reduce the size of the build context and improve build time +# 3. Avoid sending sensitive files +# +# More information on using .dockerignore is available here: +# https://docs.docker.com/engine/reference/builder/#dockerignore-file + +.dockerignore + +# Ignore git, but keep git HEAD and refs to access current commit hash +.git/objects +.git/tmp + +# Common development/test artifacts +/cover/ +/doc/ +/test/ +/tmp/ +.elixir_ls + +# Mix artifacts +/_build/ +/deps/ +*.ez + +# Generated on crash by the VM +erl_crash.dump + +# Static artifacts - These should be fetched and built inside the Docker image +/assets/node_modules/ +/priv/static/assets/ + +# Files matching config/*.secret.exs pattern are treated as secrets +/config/*.secret.exs + +# Environment files +.env* + +# Editor/IDE files +.idea/ +.vscode/ +*.swp +*.swo +*~ diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..0eb489b --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,97 @@ +name: CI - Test, Build, and Push + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + container: hexpm/elixir:1.17.3-erlang-27.1.2-debian-bookworm-20240904-slim + + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: localspot_test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - name: Install build dependencies + run: | + apt-get update -y + apt-get install -y build-essential git + + - name: Checkout repository + run: | + git clone ${{ github.server_url }}/${{ github.repository }} . + git checkout ${{ github.sha }} + + - name: Install hex and rebar + run: | + mix local.hex --force + mix local.rebar --force + + - name: Install dependencies + run: mix deps.get + + - name: Check formatting + run: mix format --check-formatted + + - name: Compile (warnings as errors) + run: mix compile --warnings-as-errors + + - name: Setup database + env: + MIX_ENV: test + DATABASE_URL: postgres://postgres:postgres@postgres:5432/localspot_test + run: mix ecto.create && mix ecto.migrate + + - name: Run tests + env: + MIX_ENV: test + DATABASE_URL: postgres://postgres:postgres@postgres:5432/localspot_test + run: mix test + + build-and-push: + needs: test + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + runs-on: ubuntu-latest + container: docker:latest + + steps: + - name: Checkout repository + run: | + git clone ${{ github.server_url }}/${{ github.repository }} . + git checkout ${{ github.sha }} + + - name: Log in to Forgejo Container Registry + run: | + echo "Logging in as: ${{ github.actor }}" + echo "${{ secrets.FORGEJO_TOKEN }}" | docker login forgejo.sivic.me -u ${{ github.actor }} --password-stdin + echo "Login exit code: $?" + + - name: Build and push Docker images + run: | + docker build \ + -t forgejo.sivic.me/${{ github.repository }}:latest \ + -t forgejo.sivic.me/${{ github.repository }}:${{ github.sha }} \ + . + docker push forgejo.sivic.me/${{ github.repository }}:latest + docker push forgejo.sivic.me/${{ github.repository }}:${{ github.sha }} + + - name: Summary + run: | + echo "✅ Successfully built and pushed:" + echo " - forgejo.sivic.me/${{ github.repository }}:latest" + echo " - forgejo.sivic.me/${{ github.repository }}:${{ github.sha }}" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bd7d9a1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,100 @@ +# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian +# instead of Alpine to avoid DNS://connection issues that some users report. +# +# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu +# https://hub.docker.com/_/ubuntu?tab=tags +# +# This file is based on these images: +# +# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image +# - https://hub.docker.com/_/debian?tab=tags&page=1&name=bookworm-slim - for the release image +# +# Usage: +# docker build -t localspot . +# docker run -p 4000:4000 localspot + +ARG ELIXIR_VERSION=1.17.3 +ARG OTP_VERSION=27.1.2 +ARG DEBIAN_VERSION=bookworm-20240904-slim + +ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" +ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" + +FROM ${BUILDER_IMAGE} as builder + +# install build dependencies +RUN apt-get update -y && apt-get install -y build-essential git \ + && apt-get clean && rm -f /var/lib/apt/lists/*_* + +# prepare build dir +WORKDIR /app + +# install hex + rebar +RUN mix local.hex --force && \ + mix local.rebar --force + +# set build ENV +ENV MIX_ENV="prod" + +# install mix dependencies +COPY mix.exs mix.lock ./ +RUN mix deps.get --only $MIX_ENV +RUN mkdir config + +# copy compile-time config files before we compile dependencies +# to ensure any relevant config change will trigger the dependencies +# to be re-compiled. +COPY config/config.exs config/${MIX_ENV}.exs config/ +RUN mix deps.compile + +COPY priv priv + +COPY lib lib + +COPY assets assets + +# compile assets +RUN mix assets.deploy + +# Compile the release +RUN mix compile + +# Changes to config/runtime.exs don't require recompiling the code +COPY config/runtime.exs config/ + +COPY rel rel +RUN mix release + +# start a new build stage so that the final image will only contain +# the compiled release and other runtime necessities +FROM ${RUNNER_IMAGE} + +RUN apt-get update -y && \ + apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \ + && apt-get clean && rm -f /var/lib/apt/lists/*_* + +# Set the locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +WORKDIR "/app" +RUN chown nobody /app + +# set runner ENV +ENV MIX_ENV="prod" + +# Only copy the final release from the build stage +COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/localspot ./ + +USER nobody + +# If using an environment that doesn't automatically reap zombie processes, it is +# advised to add an init process such as tini via `apt-get install tini` inside the +# temporary build container. +# +# See https://github.com/krallin/tini for details +# Also set PHX_SERVER=true to start the phoenix web server +CMD ["/app/bin/server"] diff --git a/lib/localspot/release.ex b/lib/localspot/release.ex new file mode 100644 index 0000000..033b213 --- /dev/null +++ b/lib/localspot/release.ex @@ -0,0 +1,30 @@ +defmodule Localspot.Release do + @moduledoc """ + Used for executing DB release tasks when run in production without Mix + installed. + """ + @app :localspot + + def migrate do + load_app() + + for repo <- repos() do + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) + end + end + + def rollback(repo, version) do + load_app() + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) + end + + defp repos do + Application.fetch_env!(@app, :ecto_repos) + end + + defp load_app do + # Many platforms require SSL when connecting to the database + Application.ensure_all_started(:ssl) + Application.ensure_loaded(@app) + end +end diff --git a/rel/overlays/bin/migrate b/rel/overlays/bin/migrate new file mode 100644 index 0000000..a881e03 --- /dev/null +++ b/rel/overlays/bin/migrate @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +exec ./localspot eval Localspot.Release.migrate diff --git a/rel/overlays/bin/migrate.bat b/rel/overlays/bin/migrate.bat new file mode 100644 index 0000000..f8797a2 --- /dev/null +++ b/rel/overlays/bin/migrate.bat @@ -0,0 +1 @@ +call "%~dp0\localspot" eval Localspot.Release.migrate diff --git a/rel/overlays/bin/server b/rel/overlays/bin/server new file mode 100644 index 0000000..c068249 --- /dev/null +++ b/rel/overlays/bin/server @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +PHX_SERVER=true exec ./localspot start diff --git a/rel/overlays/bin/server.bat b/rel/overlays/bin/server.bat new file mode 100644 index 0000000..603af7d --- /dev/null +++ b/rel/overlays/bin/server.bat @@ -0,0 +1,2 @@ +set PHX_SERVER=true +call "%~dp0\localspot" start