diff --git a/.gitignore b/.gitignore index 94315ed..233f5d2 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ server-*.tar /tmp/ .env +.env.prod diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5003311 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,74 @@ +ARG ELIXIR_VERSION=1.14.0 +ARG OTP_VERSION=25.2.1 +ARG DEBIAN_VERSION=bullseye-20230109-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 + +# Compile the release +COPY lib lib + +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 \ + && 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/chessh ./ +COPY --from=builder --chown=nobody:root /app/priv /app/priv + +USER nobody + +CMD /app/bin/chessh eval "Chessh.Release.migrate" && /app/bin/chessh start diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 index a2d358b..aad5f57 --- a/build.sh +++ b/build.sh @@ -1,11 +1,16 @@ -#!/usr/bin/env bash -# Initial setup -mix deps.get --only prod -MIX_ENV=prod mix compile +#!/bin/bash -# Remove the existing release directory and build the release -rm -rf "_build" -MIX_ENV=prod mix release +env_file=.env.prod +started_in=$PWD -# for auto DB migration upon deploy -MIX_ENV=prod mix ecto.migrate +export $(cat $env_file | xargs) + +cd front +docker build \ + --build-arg REACT_APP_GITHUB_OAUTH=${REACT_APP_GITHUB_OAUTH} \ + --build-arg REACT_APP_SSH_SERVER=${REACT_APP_SSH_SERVER} \ + --build-arg REACT_APP_SSH_PORT=${REACT_APP_SSH_PORT} \ + . -t chessh/frontend + +cd $started_in +docker build . -t chessh/server diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..cc23dde --- /dev/null +++ b/deploy.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +datestamp=$(date +%Y%m%d-%H%M) +env_file=.env.prod +project_name=chessh +port=8080 +host=127.0.0.1 + +container_names=("chessh-database" "chessh-server" "chessh-frontend") + +for name in ${container_names[@]}; do + docker stop $name + docker rm $name +done + +# Create network for chessh +docker network ls | grep -q $project_name || docker network create --driver bridge $project_name + +# Start postgres container +# Firstly create pg volume if it does not exist +docker volume ls | grep -q $project_name-pgdata || docker volume create $project_name-pgdata + +# Then run the pg container +docker run \ + -d \ + --restart unless-stopped \ + --env-file $env_file \ + --network $project_name \ + --name $project_name-database \ + --net-alias database \ + --volume $project_name-pgdata:/var/lib/postgresql/data/ \ + postgres + +# Start backend container +# Check if running; if so, stop, and rename +docker run \ + -d \ + --restart unless-stopped \ + --env-file $env_file \ + --network $project_name \ + --name $project_name-server \ + --net-alias server \ + chessh/server + +# Start frontend container +# Check if running; if so, stop, and rename +docker run \ + -d \ + --restart unless-stopped \ + --env-file $env_file \ + --network $project_name \ + --name $project_name-frontend \ + --publish "${host}:${port}:80/tcp" \ + --net-alias frontend \ + chessh/frontend diff --git a/front/.dockerignore b/front/.dockerignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/front/.dockerignore @@ -0,0 +1 @@ +node_modules diff --git a/front/Dockerfile b/front/Dockerfile new file mode 100644 index 0000000..b8de217 --- /dev/null +++ b/front/Dockerfile @@ -0,0 +1,28 @@ +# Builder +FROM node:16-alpine as build + +WORKDIR /usr/app + +COPY package-lock.json package.json ./ +RUN npm ci + +COPY . /usr/app + +ARG REACT_APP_GITHUB_OAUTH +ARG REACT_APP_SSH_SERVER +ARG REACT_APP_SSH_PORT +ENV REACT_APP_GITHUB_OAUTH $REACT_APP_GITHUB_OAUTH +ENV REACT_APP_SSH_SERVER $REACT_APP_SSH_SERVER +ENV REACT_APP_SSH_PORT $REACT_APP_SSH_PORT +RUN npm run build + +# Runner +FROM nginx:alpine + +EXPOSE 80 +COPY nginx.conf /etc/nginx/conf.d/ +COPY --from=build /usr/app/build /usr/share/nginx/html + +RUN chown -R nginx:nginx /usr/share/nginx/html + +ENTRYPOINT ["nginx", "-g", "daemon off;"] diff --git a/front/nginx.conf b/front/nginx.conf new file mode 100644 index 0000000..5423b15 --- /dev/null +++ b/front/nginx.conf @@ -0,0 +1,13 @@ +server { + listen 80; + + location / { + root /usr/share/nginx/html/; + index index.html index.htm index.nginx-debian.html; + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_pass http://server:8080; + } +} diff --git a/front/src/hooks/useLocalStorage.js b/front/src/hooks/useLocalStorage.js index 4e8684a..f00a11b 100644 --- a/front/src/hooks/useLocalStorage.js +++ b/front/src/hooks/useLocalStorage.js @@ -13,8 +13,10 @@ const useStorage = (storage, keyPrefix) => (storageKey, fallbackState) => { if (storedString !== null) parsedObject = JSON.parse(storedString); + // eslint-disable-next-line react-hooks/rules-of-hooks const [value, setValue] = useState(parsedObject ?? fallbackState); + // eslint-disable-next-line react-hooks/rules-of-hooks useEffect(() => { storage.setItem(keyPrefix + storageKey, JSON.stringify(value)); }, [value, storageKey]); @@ -22,6 +24,7 @@ const useStorage = (storage, keyPrefix) => (storageKey, fallbackState) => { return [value, setValue]; }; +// eslint-disable-next-line react-hooks/rules-of-hooks export const useLocalStorage = useStorage( window.localStorage, STORAGE_KEYS_PREFIX diff --git a/front/src/index.css b/front/src/index.css index 837ad6f..40d45cb 100644 --- a/front/src/index.css +++ b/front/src/index.css @@ -174,7 +174,8 @@ textarea { border-radius: 4px; border: 1px solid var(--primary-text-color); } -input,textarea: focus { +input:focus, +textarea:focus { border: 1px solid var(--gold-color); } diff --git a/lib/chessh/release.ex b/lib/chessh/release.ex new file mode 100644 index 0000000..bae0c08 --- /dev/null +++ b/lib/chessh/release.ex @@ -0,0 +1,19 @@ +defmodule Chessh.Release do + @app :chessh + + def migrate do + for repo <- repos() do + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) + end + end + + def rollback(repo, version) do + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) + end + + defp repos do + Application.load(@app) + Application.fetch_env!(@app, :ecto_repos) + end +end + diff --git a/priv/make_keys.sh b/priv/make_keys.sh index bd65aec..89464a8 100755 --- a/priv/make_keys.sh +++ b/priv/make_keys.sh @@ -1,9 +1,12 @@ #!/bin/sh -mkdir keys -chmod 700 keys -cd keys +if [ ! -d "keys" ] +then + mkdir keys + chmod 700 keys + cd 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 \ No newline at end of file + 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