From e945f486658e174368e313355ef44c007467e301 Mon Sep 17 00:00:00 2001 From: prichier Date: Mon, 19 Oct 2020 01:01:59 +0200 Subject: [PATCH 01/15] Add dockerfile and compose for production --- .gitlab-ci.yml | 123 --------------------------- Dockerfile | 11 +-- docker/production/Dockerfile | 62 ++++++++++++++ docker/production/README.md | 50 +++++++++++ docker/production/docker-compose.yml | 44 ++++++++++ docker/production/env | 10 +++ docker/production/prod.secret.exs | 49 +++++++++++ docker/production/start.sh | 3 + 8 files changed, 219 insertions(+), 133 deletions(-) delete mode 100644 .gitlab-ci.yml mode change 100644 => 120000 Dockerfile create mode 100644 docker/production/Dockerfile create mode 100644 docker/production/README.md create mode 100644 docker/production/docker-compose.yml create mode 100644 docker/production/env create mode 100644 docker/production/prod.secret.exs create mode 100755 docker/production/start.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 4a6efdf2f..000000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,123 +0,0 @@ -image: tcitworld/mobilizon-ci - -stages: - - check - - test - - deploy - -variables: - MIX_ENV: "test" - # DB Variables for Postgres / Postgis - POSTGRES_DB: mobilizon_test - POSTGRES_USER: postgres - POSTGRES_PASSWORD: "" - POSTGRES_HOST: postgres - # DB Variables for Mobilizon - MOBILIZON_DATABASE_USERNAME: $POSTGRES_USER - MOBILIZON_DATABASE_PASSWORD: $POSTGRES_PASSWORD - MOBILIZON_DATABASE_DBNAME: $POSTGRES_DB - MOBILIZON_DATABASE_HOST: $POSTGRES_HOST - GEOLITE_CITIES_PATH: "/usr/share/GeoIP/GeoLite2-City.mmdb" - MOBILIZON_INSTANCE_REGISTRATIONS_OPEN: "true" - -cache: - key: ${CI_COMMIT_REF_SLUG} - paths: - - ~/.cache/Cypress - - _build/ - - deps/ - - js/node_modules - - cache/Cypress - -lint: - stage: check - script: - - export EXITVALUE=0 - - mix deps.get - - mix credo --strict -a || export EXITVALUE=1 - - mix format --check-formatted --dry-run || export EXITVALUE=1 - - cd js - - yarn install - #- yarn run lint || export EXITVALUE=1 - - yarn run prettier --ignore-path="src/i18n/*" -c . || export EXITVALUE=1 - - yarn run build - - cd ../ - - exit $EXITVALUE - artifacts: - expire_in: 1 day - when: on_success - paths: - - priv/static - -deps: - stage: check - script: - - export EXITVALUE=0 - - mix deps.get - - mix hex.outdated || export EXITVALUE=1 - - cd js - - yarn outdated || export EXITVALUE=1 - - exit $EXITVALUE - allow_failure: true - -exunit: - stage: test - services: - - name: mdillon/postgis:11 - alias: postgres - before_script: - - cd js - - yarn install - - yarn run build - - cd ../ - - mix deps.get - - MIX_ENV=test mix ecto.create - - MIX_ENV=test mix ecto.migrate - dependencies: - - lint - script: - - mix coveralls -# cypress: -# stage: test -# services: -# - name: mdillon/postgis:11 -# alias: postgres -# script: -# - mix deps.get -# - cd js -# - yarn install -# - npx cypress install # just to be sure -# - yarn run build -# - cd ../ -# - MIX_ENV=e2e mix ecto.create -# - MIX_ENV=e2e mix ecto.migrate -# - MIX_ENV=e2e mix run priv/repo/e2e.seed.exs -# - MIX_ENV=e2e mix phx.server & -# - cd js -# - npx wait-on http://localhost:4000 -# - if [ -z "$CYPRESS_KEY" ]; then npx cypress run; else npx cypress run --record --parallel --key $CYPRESS_KEY; fi -# artifacts: -# expire_in: 2 day -# paths: -# - js/tests/e2e/screenshots/**/*.png -# - js/tests/e2e/videos/**/*.mp4 - -# pages: -# stage: deploy -# script: -# # - mkdir public -# # Mobilizon documentation is now on https://framagit.org/framasoft/joinmobilizon/documentation -# # Mix docs disabled because of https://github.com/elixir-lang/ex_doc/issues/1172 -# # - mix deps.get -# # - mix docs -# # - mv doc public/backend -# #- cd js -# #- yarn install -# #- yarn run styleguide:build -# #- mv styleguide ../public/frontend -# only: -# - master -# artifacts: -# expire_in: 1 hour -# paths: -# - public diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 9c73fff55..000000000 --- a/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM bitwalker/alpine-elixir:latest - -RUN apk add inotify-tools postgresql-client yarn file -RUN apk add --no-cache make gcc libc-dev argon2 imagemagick - -RUN mix local.hex --force && mix local.rebar --force - -WORKDIR /app - -EXPOSE 4000 diff --git a/Dockerfile b/Dockerfile new file mode 120000 index 000000000..27f3d3ac7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1 @@ +./docker/production/Dockerfile \ No newline at end of file diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile new file mode 100644 index 000000000..259efd41d --- /dev/null +++ b/docker/production/Dockerfile @@ -0,0 +1,62 @@ +FROM elixir:slim + +# Install dependencies, NodeJS, YARN & clean apt +RUN apt update \ + && apt -y dist-upgrade \ + && apt -y install build-essential \ + curl \ + wget \ + unzip \ + vim \ + openssl \ + git \ + cmake \ + imagemagick \ + webp \ + gifsicle \ + jpegoptim \ + optipng \ + pngquant \ + postgresql-client \ + && curl -sL https://deb.nodesource.com/setup_12.x | bash - \ + && apt -y install nodejs \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ + && apt -y update && apt -y install yarn \ + && apt -y clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Add mobilizon user +RUN groupadd -r mobilizon \ + && useradd -r -g mobilizon -m mobilizon + +USER mobilizon + +# ENV +ENV MIX_ENV=prod + +# PORT +EXPOSE 4000 + +# Copy repo +COPY . /app +WORKDIR /app + +# Compile dependencies, Mobilizon and build front-end +RUN mix local.hex --force && mix local.rebar --force \ + && HEX_HTTP_CONCURRENCY=4 HEX_HTTP_TIMEOUT=60 mix do deps.get, compile \ + && cd js && NODE_BUILD_MEMORY=2024 yarn install && NODE_BUILD_MEMORY=2024 yarn run build \ + # free space + && rm -rf js doc deps docs support \ + && rm -rf /home/mobilizon/.cache/* \ + # copy config secret env based file + && cp docker/production/prod.secret config/ \ + # set start script mod + && chmod +x /app/docker/production/start.sh + +CMD /app/docker/production/start.sh + +## start.sh: +# !/bin/bash +# mix ecto.migrate +# mix phx.server diff --git a/docker/production/README.md b/docker/production/README.md new file mode 100644 index 000000000..6b9c9c87c --- /dev/null +++ b/docker/production/README.md @@ -0,0 +1,50 @@ +# Build and deploy Mobilizon with docker + +You will need to : +- build the image +- adapte env file +- run docker-compose + +## Build the image + + docker build -t mymobilizon -f docker/prod/Dockerfile . + +## Adapt env file + + cp env .env + +- Edit .env content with your params. +- Edit docker-compose file with your params (environment section for mobilizon & posgres). + +You can generate `MOBILIZON_INSTANCE_SECRET_KEY_BASE` and `MOBILIZON_INSTANCE_SECRET_KEY` with: + + gpg --gen-random --armor 1 50 + +## run docker-compose + + docker-compose -f docker-compose-simple.yml up + # set user for volumes + sudo chown 999:999 db public wal public/upload + # in another shell + docker-compose -f docker-compose-simple.yml exec -u 0 mobilizon bash + su - mobilizon + # backup secret + mv config/prod.secret.exs config/prod.secret.exs.env + # run config generation + MIX_ENV=prod mix mobilizon.instance gen -f + # reply anything (not used after) except for : + # - What is the name of your database? [mobilizon_prod] + # - What is the user used to connect to your database? [mobilizon] + # - What is the password used to connect to your database? [autogenerated] + # get secret env based bak + mv config/prod.secret.exs.env config/prod.secret.exs + # run the db init script as root + exit + psql -U postgres -p 5432 -h postgres -f setup_db.psql + # delete db init sript + rm setup_db.psql + # create an admin with mobilizon user + su - mobilizon + cd /app + MIX_ENV=prod mix mobilizon.users.new pascoual@tedomum.fr --password mobilizon + # exit with ctrl+d (twice times) diff --git a/docker/production/docker-compose.yml b/docker/production/docker-compose.yml new file mode 100644 index 000000000..db38e50c9 --- /dev/null +++ b/docker/production/docker-compose.yml @@ -0,0 +1,44 @@ +version: "2.1" + +services: + mobilizon: + image: mobilizon + environment: + - MOBILIZON_INSTANCE_NAME="My Mobilizon Instance" + - MOBILIZON_INSTANCE_HOST=mobilizon.lan + - MOBILIZON_INSTANCE_EMAIL=noreply@mobilizon.lan + - MOBILIZON_INSTANCE_REGISTRATIONS_OPEN=true + - MOBILIZON_DATABASE_USERNAME + - MOBILIZON_DATABASE_PASSWORD + - MOBILIZON_DATABASE_DBNAME=mobilizon_prod + - MOBILIZON_DATABASE_HOST=postgres + - MOBILIZON_INSTANCE_SECRET_KEY_BASE + - MOBILIZON_INSTANCE_SECRET_KEY + - MOBILIZON_ADMIN_EMAIL=your@email.com + - MOBILIZON_SMPT_SERVER=yoursmtpserver + - MOBILIZON_SMPT_MOBILIZON_SMPT_HOSTNAME=your.smtp.domain + - MOBILIZON_SMPT_PORT=25 + - MOBILIZON_SMPT_USERNAME + - MOBILIZON_SMPT_PASSWORD + - MOBILIZON_SMPT_SSL=false + volumes: + - ./public/upload:/app/upload + ports: + - "4000:4000" + depends_on: + - postgres + + postgres: + image: postgis/postgis + volumes: + - ./db:/var/lib/postgresql/data + - ./wal:/wal + - ./postgresql.conf:/var/lib/postgresql/data/postgresql.conf + environment: + - POSTGRES_PASSWORD + - PGDATA=/var/lib/postgresql/data/pgdata + +networks: + default: + ipam: + driver: default diff --git a/docker/production/env b/docker/production/env new file mode 100644 index 000000000..e1a7972d9 --- /dev/null +++ b/docker/production/env @@ -0,0 +1,10 @@ +# You need to: +# cp env .env +# edite .env with your settings +MOBILIZON_DATABASE_PASSWORD=postgres +MOBILIZON_DATABASE_USERNAME=postgres +MOBILIZON_INSTANCE_SECRET_KEY_BASE=MmU1NWQyYWQtM2MzZC00ZTU5LTg0MmItMmY5NDZlMmNhNmEwCg +MOBILIZON_INSTANCE_SECRET_KEY=NjJhMGU5MDctZGNkOC00NGM0LWI5OWItZDEyY2FkNjRlODYyCg +MOBILIZON_SMPT_USERNAME=username +MOBILIZON_SMPT_PASSWORD=password +POSTGRES_PASSWORD=postgres \ No newline at end of file diff --git a/docker/production/prod.secret.exs b/docker/production/prod.secret.exs new file mode 100644 index 000000000..96cc31781 --- /dev/null +++ b/docker/production/prod.secret.exs @@ -0,0 +1,49 @@ +# Mobilizon instance configuration + +import Config + +config :mobilizon, Mobilizon.Web.Endpoint, + url: [host: System.get_env("MOBILIZON_INSTANCE_HOST", "mobilizon.lan")], + http: [port: 4000], + secret_key_base: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY_BASE", "ZcvexeC7cnwtKR8ADMBDwrYu2aYHUyjrOu4yA181Z112HNu/I5jyRleo4hoxOMqQ") + +config :mobilizon, Mobilizon.Web.Auth.Guardian, + secret_key: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY", "KsdUIvp6hQ7b97yxUZcDQyGH0g4LS3fF0OvIsIATpkKzd1MDvSS4KexWXsjXeMQZ") + +config :mobilizon, :instance, + name: System.get_env("MOBILIZON_INSTANCE_NAME", "Mobilizon"), + description: "Change this to a proper description of your instance", + hostname: System.get_env("MOBILIZON_INSTANCE_HOST", "mobilizon.lan"), + registrations_open: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_OPEN", "false"), + demo: false, + allow_relay: true, + federating: true, + email_from: System.get_env("MOBILIZON_INSTANCE_EMAIL", "noreply@mobilizon.lan"), + email_reply_to: System.get_env("MOBILIZON_INSTANCE_EMAIL", "noreply@mobilizon.lan") + +config :mobilizon, Mobilizon.Storage.Repo, + adapter: Ecto.Adapters.Postgres, + username: System.get_env("MOBILIZON_DATABASE_USERNAME", "username"), + password: System.get_env("MOBILIZON_DATABASE_PASSWORD", "password"), + database: System.get_env("MOBILIZON_DATABASE_DBNAME", "mobilizon"), + hostname: System.get_env("MOBILIZON_DATABASE_HOST", "postgres"), + port: "5432", + pool_size: 10 + +config :mobilizon, Mobilizon.Web.Email.Mailer, + adapter: Bamboo.SMTPAdapter, + server: System.get_env("MOBILIZON_SMPT_SERVER", "localhost"), + hostname: System.get_env("MOBILIZON_SMPT_HOSTNAME", "localhost"), + port: System.get_env("MOBILIZON_SMPT_PORT", "25"), + username: System.get_env("MOBILIZON_SMPT_USERNAME", nil), + password: System.get_env("MOBILIZON_SMPT_PASSWORD", nil), + # can be `:always` or `:never` + tls: :if_available, + allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"], + # can be `true` + ssl: System.get_env("MOBILIZON_SMPT_SSL", "false"), + retries: 1, + # can be `true` + no_mx_lookups: false, + # can be `:always`. If your smtp relay requires authentication set it to `:always`. + auth: :if_available diff --git a/docker/production/start.sh b/docker/production/start.sh new file mode 100755 index 000000000..25b32dd12 --- /dev/null +++ b/docker/production/start.sh @@ -0,0 +1,3 @@ +#!/bin/bash +mix ecto.migrate +mix phx.server \ No newline at end of file From 1acff2dd8a8af10a7cfa778940d8ce13d2d99d93 Mon Sep 17 00:00:00 2001 From: prichier Date: Thu, 22 Oct 2020 15:19:01 +0200 Subject: [PATCH 02/15] Mutli-stage build for production --- docker-compose.yml | 72 +++++++--------- docker/production/Dockerfile | 85 +++++++------------ docker/production/README.md | 73 ++++++++-------- docker/production/docker-compose.yml | 37 ++++---- docker/production/env | 35 ++++++-- .../{prod.secret.exs => releases.exs} | 23 +++-- docker/production/start.sh | 3 - lib/mobilizon/cli.ex | 11 +++ 8 files changed, 167 insertions(+), 172 deletions(-) rename docker/production/{prod.secret.exs => releases.exs} (63%) delete mode 100755 docker/production/start.sh create mode 100644 lib/mobilizon/cli.ex diff --git a/docker-compose.yml b/docker-compose.yml index 3df763f40..25d62b4fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,45 +1,39 @@ -version: '3' +version: "3" services: - postgres: - container_name: mobilizon_db - restart: unless-stopped - image: mdillon/postgis:11 + mobilizon: + image: mobilizon environment: - POSTGRES_PASSWORD: postgres - POSTGRES_DB: mobilizon_dev + - MOBILIZON_INSTANCE_NAME + - MOBILIZON_INSTANCE_HOST + - MOBILIZON_INSTANCE_EMAIL + - MOBILIZON_REPLY_EMAIL + - MOBILIZON_INSTANCE_REGISTRATIONS_OPEN=true + - MOBILIZON_DATABASE_USERNAME=${POSTGRES_USER} + - MOBILIZON_DATABASE_PASSWORD=${POSTGRES_PASSWORD} + - MOBILIZON_DATABASE_DBNAME=${POSTGRES_DB} + - MOBILIZON_DATABASE_HOST=db + - MOBILIZON_INSTANCE_SECRET_KEY_BASE + - MOBILIZON_INSTANCE_SECRET_KEY + - MOBILIZON_SMTP_SERVER=yoursmtpserver + - MOBILIZON_SMTP_HOSTNAME=your.smtp.domain + - MOBILIZON_SMTP_USERNAME + - MOBILIZON_SMTP_PASSWORD volumes: - - pgdata:/var/lib/postgresql/data - api: - container_name: mobilizon_api - restart: unless-stopped - build: . - volumes: - - '.:/app' + - /tmp/public/upload:/app/upload ports: - - "4000:4000" - depends_on: - - postgres + - "4000:4000" + + db: + image: postgis/postgis + volumes: + - /tmp/db:/var/lib/postgresql/data environment: - MIX_ENV: "dev" - DOCKER: "true" - MOBILIZON_INSTANCE_NAME: My Mobilizon Instance - MOBILIZON_INSTANCE_HOST: mobilizon.me - MOBILIZON_INSTANCE_EMAIL: noreply@mobilizon.me - MOBILIZON_INSTANCE_REGISTRATIONS_OPEN: "true" - MOBILIZON_DATABASE_PASSWORD: postgres - MOBILIZON_DATABASE_USERNAME: postgres - MOBILIZON_DATABASE_DBNAME: mobilizon_dev - MOBILIZON_DATABASE_HOST: postgres - command: > - sh -c "cd js && - yarn install && - cd ../ && - mix deps.get && - mix compile && - mix ecto.create && - mix ecto.migrate && - mix phx.server" -volumes: - pgdata: - .: + - POSTGRES_USER + - POSTGRES_PASSWORD + - POSTGRES_DB + +networks: + default: + ipam: + driver: default diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index 259efd41d..71abc377b 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -1,62 +1,41 @@ -FROM elixir:slim +# First build the application assets +FROM node:alpine as assets -# Install dependencies, NodeJS, YARN & clean apt -RUN apt update \ - && apt -y dist-upgrade \ - && apt -y install build-essential \ - curl \ - wget \ - unzip \ - vim \ - openssl \ - git \ - cmake \ - imagemagick \ - webp \ - gifsicle \ - jpegoptim \ - optipng \ - pngquant \ - postgresql-client \ - && curl -sL https://deb.nodesource.com/setup_12.x | bash - \ - && apt -y install nodejs \ - && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ - && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ - && apt -y update && apt -y install yarn \ - && apt -y clean \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +RUN apk add --no-cache python build-base -# Add mobilizon user -RUN groupadd -r mobilizon \ - && useradd -r -g mobilizon -m mobilizon +COPY js . +RUN yarn install \ + && yarn run build -USER mobilizon +# Then, build the application binary +FROM elixir:alpine AS builder -# ENV +RUN apk add --no-cache build-base git cmake + +COPY mix.exs mix.lock ./ ENV MIX_ENV=prod +RUN mix local.hex --force \ + && mix local.rebar --force \ + && mix deps.get -# PORT +COPY lib ./lib +COPY priv ./priv +COPY config ./config +COPY docker/production/releases.exs ./config/ +COPY --from=assets ./priv/static ./priv/static + +RUN mix phx.digest \ + && mix release + +# Finally setup the app +FROM alpine + +RUN apk add --no-cache openssl ncurses-libs + +USER nobody EXPOSE 4000 -# Copy repo -COPY . /app -WORKDIR /app +COPY --from=builder --chown=nobody:nobody _build/prod/rel/mobilizon ./ -# Compile dependencies, Mobilizon and build front-end -RUN mix local.hex --force && mix local.rebar --force \ - && HEX_HTTP_CONCURRENCY=4 HEX_HTTP_TIMEOUT=60 mix do deps.get, compile \ - && cd js && NODE_BUILD_MEMORY=2024 yarn install && NODE_BUILD_MEMORY=2024 yarn run build \ - # free space - && rm -rf js doc deps docs support \ - && rm -rf /home/mobilizon/.cache/* \ - # copy config secret env based file - && cp docker/production/prod.secret config/ \ - # set start script mod - && chmod +x /app/docker/production/start.sh - -CMD /app/docker/production/start.sh - -## start.sh: -# !/bin/bash -# mix ecto.migrate -# mix phx.server +ENTRYPOINT ["/bin/mobilizon"] +CMD ["start"] diff --git a/docker/production/README.md b/docker/production/README.md index 6b9c9c87c..68055e518 100644 --- a/docker/production/README.md +++ b/docker/production/README.md @@ -2,49 +2,54 @@ You will need to : - build the image -- adapte env file -- run docker-compose +- tune the environment file +- use docker-compose to run the service ## Build the image - docker build -t mymobilizon -f docker/prod/Dockerfile . + git clone https://forge.tedomum.net/tedomum/mobilizon + cd mobilizon + docker build -t mobilizon -f docker/production/Dockerfile . -## Adapt env file +## Update the env file - cp env .env + cd docker/production/ + cp env.example .env -- Edit .env content with your params. -- Edit docker-compose file with your params (environment section for mobilizon & posgres). +Edit the `.env` content with your own settings. You can generate `MOBILIZON_INSTANCE_SECRET_KEY_BASE` and `MOBILIZON_INSTANCE_SECRET_KEY` with: gpg --gen-random --armor 1 50 -## run docker-compose +## Run the service - docker-compose -f docker-compose-simple.yml up - # set user for volumes - sudo chown 999:999 db public wal public/upload - # in another shell - docker-compose -f docker-compose-simple.yml exec -u 0 mobilizon bash - su - mobilizon - # backup secret - mv config/prod.secret.exs config/prod.secret.exs.env - # run config generation - MIX_ENV=prod mix mobilizon.instance gen -f - # reply anything (not used after) except for : - # - What is the name of your database? [mobilizon_prod] - # - What is the user used to connect to your database? [mobilizon] - # - What is the password used to connect to your database? [autogenerated] - # get secret env based bak - mv config/prod.secret.exs.env config/prod.secret.exs - # run the db init script as root - exit - psql -U postgres -p 5432 -h postgres -f setup_db.psql - # delete db init sript - rm setup_db.psql - # create an admin with mobilizon user - su - mobilizon - cd /app - MIX_ENV=prod mix mobilizon.users.new pascoual@tedomum.fr --password mobilizon - # exit with ctrl+d (twice times) +Start by initializing and running the database: + + docker-compose up -d db + +Instanciate required Postgres extensions: + + docker-compose exec db psql -U + # CREATE EXTENSION pg_trgm; + # CREATE EXTENSION unaccent; + + +Then run migrations: + + docker-compose run --rm mobilizon eval Mobilizon.Cli.migrate + +Finally, run the application: + + docker-compose up -d mobilizon + +## Update the service + +Pull the latest image, then run the migrations: + + docker-compose pull mobilizon + docker-compose run --rm mobilizon eval Mobilizon.Cli.migrate + +Finally, update the service: + + docker-compose up -d mobilizon diff --git a/docker/production/docker-compose.yml b/docker/production/docker-compose.yml index db38e50c9..c3b521624 100644 --- a/docker/production/docker-compose.yml +++ b/docker/production/docker-compose.yml @@ -1,42 +1,37 @@ -version: "2.1" +version: "3" services: mobilizon: image: mobilizon environment: - - MOBILIZON_INSTANCE_NAME="My Mobilizon Instance" - - MOBILIZON_INSTANCE_HOST=mobilizon.lan - - MOBILIZON_INSTANCE_EMAIL=noreply@mobilizon.lan + - MOBILIZON_INSTANCE_NAME + - MOBILIZON_INSTANCE_HOST + - MOBILIZON_INSTANCE_EMAIL + - MOBILIZON_REPLY_EMAIL - MOBILIZON_INSTANCE_REGISTRATIONS_OPEN=true - - MOBILIZON_DATABASE_USERNAME - - MOBILIZON_DATABASE_PASSWORD - - MOBILIZON_DATABASE_DBNAME=mobilizon_prod - - MOBILIZON_DATABASE_HOST=postgres + - MOBILIZON_DATABASE_USERNAME=${POSTGRES_USER} + - MOBILIZON_DATABASE_PASSWORD=${POSTGRES_PASSWORD} + - MOBILIZON_DATABASE_DBNAME=${POSTGRES_DB} + - MOBILIZON_DATABASE_HOST=db - MOBILIZON_INSTANCE_SECRET_KEY_BASE - MOBILIZON_INSTANCE_SECRET_KEY - - MOBILIZON_ADMIN_EMAIL=your@email.com - - MOBILIZON_SMPT_SERVER=yoursmtpserver - - MOBILIZON_SMPT_MOBILIZON_SMPT_HOSTNAME=your.smtp.domain - - MOBILIZON_SMPT_PORT=25 - - MOBILIZON_SMPT_USERNAME - - MOBILIZON_SMPT_PASSWORD - - MOBILIZON_SMPT_SSL=false + - MOBILIZON_SMTP_SERVER=yoursmtpserver + - MOBILIZON_SMTP_HOSTNAME=your.smtp.domain + - MOBILIZON_SMTP_USERNAME + - MOBILIZON_SMTP_PASSWORD volumes: - ./public/upload:/app/upload ports: - "4000:4000" - depends_on: - - postgres - postgres: + db: image: postgis/postgis volumes: - ./db:/var/lib/postgresql/data - - ./wal:/wal - - ./postgresql.conf:/var/lib/postgresql/data/postgresql.conf environment: + - POSTGRES_USER - POSTGRES_PASSWORD - - PGDATA=/var/lib/postgresql/data/pgdata + - POSTGRES_DB networks: default: diff --git a/docker/production/env b/docker/production/env index e1a7972d9..3b003dd38 100644 --- a/docker/production/env +++ b/docker/production/env @@ -1,10 +1,27 @@ -# You need to: -# cp env .env -# edite .env with your settings -MOBILIZON_DATABASE_PASSWORD=postgres -MOBILIZON_DATABASE_USERNAME=postgres -MOBILIZON_INSTANCE_SECRET_KEY_BASE=MmU1NWQyYWQtM2MzZC00ZTU5LTg0MmItMmY5NDZlMmNhNmEwCg -MOBILIZON_INSTANCE_SECRET_KEY=NjJhMGU5MDctZGNkOC00NGM0LWI5OWItZDEyY2FkNjRlODYyCg -MOBILIZON_SMPT_USERNAME=username +# Copy this file to env, then update it with your own settings + +# Database settings +POSTGRES_USER=mobilizon +POSTGRES_PASSWORD=changethis +POSTGRES_DB=mobilizon + +# Application config +MOBILIZON_INSTANCE_SECRET_KEY_BASE=changethis +MOBILIZON_INSTANCE_SECRET_KEY=changethis +MOBILIZON_SMTP_USERNAME=username +MOBILIZON_SMPT_PASSWORD=password +POSTGRES_PASSWORD=postgres + +# Instance configuration +MOBILIZON_INSTANCE_NAME=My Mobilizon Instance +MOBILIZON_INSTANCE_HOST=mobilizon.lan +MOBILIZON_INSTANCE_SECRET_KEY_BASE=changethis +MOBILIZON_INSTANCE_SECRET_KEY=changethis +MOBILIZON_INSTANCE_EMAIL=noreply@mobilizon.lan +MOBILIZON_REPLY_EMAIL=contact@mobilizon.lan + +# Email settings +MOBILIZON_SMPT_SERVER=localhost +MOBILIZON_SMPT_HOSTNAME=localhost +MOBILIZON_SMPT_USERNAME=noreply@mobilizon.lan MOBILIZON_SMPT_PASSWORD=password -POSTGRES_PASSWORD=postgres \ No newline at end of file diff --git a/docker/production/prod.secret.exs b/docker/production/releases.exs similarity index 63% rename from docker/production/prod.secret.exs rename to docker/production/releases.exs index 96cc31781..64a6ef993 100644 --- a/docker/production/prod.secret.exs +++ b/docker/production/releases.exs @@ -3,12 +3,13 @@ import Config config :mobilizon, Mobilizon.Web.Endpoint, + server: true, url: [host: System.get_env("MOBILIZON_INSTANCE_HOST", "mobilizon.lan")], http: [port: 4000], - secret_key_base: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY_BASE", "ZcvexeC7cnwtKR8ADMBDwrYu2aYHUyjrOu4yA181Z112HNu/I5jyRleo4hoxOMqQ") + secret_key_base: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY_BASE", "changethis") config :mobilizon, Mobilizon.Web.Auth.Guardian, - secret_key: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY", "KsdUIvp6hQ7b97yxUZcDQyGH0g4LS3fF0OvIsIATpkKzd1MDvSS4KexWXsjXeMQZ") + secret_key: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY", "changethis") config :mobilizon, :instance, name: System.get_env("MOBILIZON_INSTANCE_NAME", "Mobilizon"), @@ -19,7 +20,7 @@ config :mobilizon, :instance, allow_relay: true, federating: true, email_from: System.get_env("MOBILIZON_INSTANCE_EMAIL", "noreply@mobilizon.lan"), - email_reply_to: System.get_env("MOBILIZON_INSTANCE_EMAIL", "noreply@mobilizon.lan") + email_reply_to: System.get_env("MOBILIZON_REPLY_EMAIL", "noreply@mobilizon.lan") config :mobilizon, Mobilizon.Storage.Repo, adapter: Ecto.Adapters.Postgres, @@ -32,18 +33,14 @@ config :mobilizon, Mobilizon.Storage.Repo, config :mobilizon, Mobilizon.Web.Email.Mailer, adapter: Bamboo.SMTPAdapter, - server: System.get_env("MOBILIZON_SMPT_SERVER", "localhost"), - hostname: System.get_env("MOBILIZON_SMPT_HOSTNAME", "localhost"), - port: System.get_env("MOBILIZON_SMPT_PORT", "25"), - username: System.get_env("MOBILIZON_SMPT_USERNAME", nil), - password: System.get_env("MOBILIZON_SMPT_PASSWORD", nil), - # can be `:always` or `:never` + server: System.get_env("MOBILIZON_SMTP_SERVER", "localhost"), + hostname: System.get_env("MOBILIZON_SMTP_HOSTNAME", "localhost"), + port: System.get_env("MOBILIZON_SMTP_PORT", "25"), + username: System.get_env("MOBILIZON_SMTP_USERNAME", nil), + password: System.get_env("MOBILIZON_SMTP_PASSWORD", nil), tls: :if_available, allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"], - # can be `true` - ssl: System.get_env("MOBILIZON_SMPT_SSL", "false"), + ssl: System.get_env("MOBILIZON_SMTP_SSL", "false"), retries: 1, - # can be `true` no_mx_lookups: false, - # can be `:always`. If your smtp relay requires authentication set it to `:always`. auth: :if_available diff --git a/docker/production/start.sh b/docker/production/start.sh deleted file mode 100755 index 25b32dd12..000000000 --- a/docker/production/start.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -mix ecto.migrate -mix phx.server \ No newline at end of file diff --git a/lib/mobilizon/cli.ex b/lib/mobilizon/cli.ex new file mode 100644 index 000000000..88f5bda2d --- /dev/null +++ b/lib/mobilizon/cli.ex @@ -0,0 +1,11 @@ +defmodule Mobilizon.Cli do + @app :mobilizon + + def migrate do + Application.load(@app) + + for repo <- Application.fetch_env!(@app, :ecto_repos) do + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) + end + end +end \ No newline at end of file From 751b72a434dd511f55633f5f7fa68ea9a367daba Mon Sep 17 00:00:00 2001 From: prichier Date: Tue, 27 Oct 2020 16:40:14 +0100 Subject: [PATCH 03/15] Fix Mix.env use in prod app & MR threads --- docker/production/README.md | 5 +++-- docker/production/docker-compose.yml | 11 +++++++---- docker/production/env | 20 +++++++------------- lib/federation/activity_pub/federator.ex | 2 +- lib/mobilizon.ex | 2 +- lib/mobilizon/cli.ex | 4 ++-- lib/web/router.ex | 2 +- 7 files changed, 22 insertions(+), 24 deletions(-) diff --git a/docker/production/README.md b/docker/production/README.md index 68055e518..12bc88510 100644 --- a/docker/production/README.md +++ b/docker/production/README.md @@ -17,6 +17,7 @@ You will need to : cp env.example .env Edit the `.env` content with your own settings. +More settings can be added in .env, see all in docker-compose.yml file. You can generate `MOBILIZON_INSTANCE_SECRET_KEY_BASE` and `MOBILIZON_INSTANCE_SECRET_KEY` with: @@ -37,7 +38,7 @@ Instanciate required Postgres extensions: Then run migrations: - docker-compose run --rm mobilizon eval Mobilizon.Cli.migrate + docker-compose run --rm mobilizon eval Mobilizon.CLI.migrate Finally, run the application: @@ -48,7 +49,7 @@ Finally, run the application: Pull the latest image, then run the migrations: docker-compose pull mobilizon - docker-compose run --rm mobilizon eval Mobilizon.Cli.migrate + docker-compose run --rm mobilizon eval Mobilizon.CLI.migrate Finally, update the service: diff --git a/docker/production/docker-compose.yml b/docker/production/docker-compose.yml index c3b521624..029a82871 100644 --- a/docker/production/docker-compose.yml +++ b/docker/production/docker-compose.yml @@ -8,19 +8,22 @@ services: - MOBILIZON_INSTANCE_HOST - MOBILIZON_INSTANCE_EMAIL - MOBILIZON_REPLY_EMAIL - - MOBILIZON_INSTANCE_REGISTRATIONS_OPEN=true + - MOBILIZON_ADMIN_EMAIL + - MOBILIZON_INSTANCE_REGISTRATIONS_OPEN - MOBILIZON_DATABASE_USERNAME=${POSTGRES_USER} - MOBILIZON_DATABASE_PASSWORD=${POSTGRES_PASSWORD} - MOBILIZON_DATABASE_DBNAME=${POSTGRES_DB} - MOBILIZON_DATABASE_HOST=db - MOBILIZON_INSTANCE_SECRET_KEY_BASE - MOBILIZON_INSTANCE_SECRET_KEY - - MOBILIZON_SMTP_SERVER=yoursmtpserver - - MOBILIZON_SMTP_HOSTNAME=your.smtp.domain + - MOBILIZON_SMTP_SERVER + - MOBILIZON_SMTP_HOSTNAME + - MOBILIZON_SMTP_PORT + - MOBILIZON_SMTP_SSL - MOBILIZON_SMTP_USERNAME - MOBILIZON_SMTP_PASSWORD volumes: - - ./public/upload:/app/upload + - ./public/uploads:/app/uploads ports: - "4000:4000" diff --git a/docker/production/env b/docker/production/env index 3b003dd38..6b08ab62b 100644 --- a/docker/production/env +++ b/docker/production/env @@ -1,27 +1,21 @@ -# Copy this file to env, then update it with your own settings +# Copy this file to .env, then update it with your own settings + +hostname=mobilizon.lan # Database settings POSTGRES_USER=mobilizon POSTGRES_PASSWORD=changethis POSTGRES_DB=mobilizon -# Application config -MOBILIZON_INSTANCE_SECRET_KEY_BASE=changethis -MOBILIZON_INSTANCE_SECRET_KEY=changethis -MOBILIZON_SMTP_USERNAME=username -MOBILIZON_SMPT_PASSWORD=password -POSTGRES_PASSWORD=postgres - # Instance configuration MOBILIZON_INSTANCE_NAME=My Mobilizon Instance -MOBILIZON_INSTANCE_HOST=mobilizon.lan MOBILIZON_INSTANCE_SECRET_KEY_BASE=changethis MOBILIZON_INSTANCE_SECRET_KEY=changethis MOBILIZON_INSTANCE_EMAIL=noreply@mobilizon.lan MOBILIZON_REPLY_EMAIL=contact@mobilizon.lan # Email settings -MOBILIZON_SMPT_SERVER=localhost -MOBILIZON_SMPT_HOSTNAME=localhost -MOBILIZON_SMPT_USERNAME=noreply@mobilizon.lan -MOBILIZON_SMPT_PASSWORD=password +MOBILIZON_SMTP_SERVER=localhost +MOBILIZON_SMTP_HOSTNAME=localhost +MOBILIZON_SMTP_USERNAME=noreply@mobilizon.lan +MOBILIZON_SMTP_PASSWORD=password diff --git a/lib/federation/activity_pub/federator.ex b/lib/federation/activity_pub/federator.ex index 74b947afe..d9027084f 100644 --- a/lib/federation/activity_pub/federator.ex +++ b/lib/federation/activity_pub/federator.ex @@ -79,7 +79,7 @@ defmodule Mobilizon.Federation.ActivityPub.Federator do def enqueue(type, payload, priority \\ 1) do Logger.debug("enqueue something with type #{inspect(type)}") - if Mix.env() == :test do + if Application.fetch_env!(:mobilizon, :env) == :test do handle(type, payload) else GenServer.cast(__MODULE__, {:enqueue, type, payload, priority}) diff --git a/lib/mobilizon.ex b/lib/mobilizon.ex index 496deb893..378584b51 100644 --- a/lib/mobilizon.ex +++ b/lib/mobilizon.ex @@ -21,7 +21,7 @@ defmodule Mobilizon do @name Mix.Project.config()[:name] @version Mix.Project.config()[:version] - @env Mix.env() + @env Application.fetch_env!(:mobilizon, :env) @spec named_version :: String.t() def named_version, do: "#{@name} #{@version}" diff --git a/lib/mobilizon/cli.ex b/lib/mobilizon/cli.ex index 88f5bda2d..969c125f0 100644 --- a/lib/mobilizon/cli.ex +++ b/lib/mobilizon/cli.ex @@ -1,4 +1,4 @@ -defmodule Mobilizon.Cli do +defmodule Mobilizon.CLI do @app :mobilizon def migrate do @@ -8,4 +8,4 @@ defmodule Mobilizon.Cli do {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) end end -end \ No newline at end of file +end diff --git a/lib/web/router.ex b/lib/web/router.ex index 37d5e4ab4..80f75d63f 100644 --- a/lib/web/router.ex +++ b/lib/web/router.ex @@ -169,7 +169,7 @@ defmodule Mobilizon.Web.Router do get("/:sig/:url/:filename", MediaProxyController, :remote) end - if Mix.env() in [:dev, :e2e] do + if Application.fetch_env!(:mobilizon, :env) in [:dev, :e2e] do # If using Phoenix forward("/sent_emails", Bamboo.SentEmailViewerPlug) end From fccdadb95a4e58651f80c89dd1c10d1b7fcb7fe9 Mon Sep 17 00:00:00 2001 From: prichier Date: Tue, 27 Oct 2020 19:09:10 +0100 Subject: [PATCH 04/15] Fix MR649 threads about env.template, pg port --- docker/production/README.md | 2 +- docker/production/{env => env.template} | 0 docker/production/releases.exs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename docker/production/{env => env.template} (100%) diff --git a/docker/production/README.md b/docker/production/README.md index 12bc88510..7054146de 100644 --- a/docker/production/README.md +++ b/docker/production/README.md @@ -14,7 +14,7 @@ You will need to : ## Update the env file cd docker/production/ - cp env.example .env + cp env.template .env Edit the `.env` content with your own settings. More settings can be added in .env, see all in docker-compose.yml file. diff --git a/docker/production/env b/docker/production/env.template similarity index 100% rename from docker/production/env rename to docker/production/env.template diff --git a/docker/production/releases.exs b/docker/production/releases.exs index 64a6ef993..412132672 100644 --- a/docker/production/releases.exs +++ b/docker/production/releases.exs @@ -28,7 +28,7 @@ config :mobilizon, Mobilizon.Storage.Repo, password: System.get_env("MOBILIZON_DATABASE_PASSWORD", "password"), database: System.get_env("MOBILIZON_DATABASE_DBNAME", "mobilizon"), hostname: System.get_env("MOBILIZON_DATABASE_HOST", "postgres"), - port: "5432", + port: 5432, pool_size: 10 config :mobilizon, Mobilizon.Web.Email.Mailer, From f3cfc2e2ef907b4945b22cea2a542746817612d1 Mon Sep 17 00:00:00 2001 From: prichier Date: Wed, 28 Oct 2020 18:19:24 +0100 Subject: [PATCH 05/15] Fix MOBILIZON_INSTANCE_HOST in env.template --- docker/production/env.template | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker/production/env.template b/docker/production/env.template index 6b08ab62b..6a037b6c2 100644 --- a/docker/production/env.template +++ b/docker/production/env.template @@ -1,7 +1,5 @@ # Copy this file to .env, then update it with your own settings -hostname=mobilizon.lan - # Database settings POSTGRES_USER=mobilizon POSTGRES_PASSWORD=changethis @@ -9,6 +7,7 @@ POSTGRES_DB=mobilizon # Instance configuration MOBILIZON_INSTANCE_NAME=My Mobilizon Instance +MOBILIZON_INSTANCE_HOST=mobilizon.lan MOBILIZON_INSTANCE_SECRET_KEY_BASE=changethis MOBILIZON_INSTANCE_SECRET_KEY=changethis MOBILIZON_INSTANCE_EMAIL=noreply@mobilizon.lan From 3e19fe1fe243ce1383d62b3a6685fcfe3faad502 Mon Sep 17 00:00:00 2001 From: prichier Date: Mon, 26 Oct 2020 00:12:54 +0100 Subject: [PATCH 06/15] Get back original root Dockerfile & gitlab-ci --- Dockerfile | 11 ++++- gitlab-ci.yml | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) mode change 120000 => 100644 Dockerfile create mode 100644 gitlab-ci.yml diff --git a/Dockerfile b/Dockerfile deleted file mode 120000 index 27f3d3ac7..000000000 --- a/Dockerfile +++ /dev/null @@ -1 +0,0 @@ -./docker/production/Dockerfile \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..9c73fff55 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM bitwalker/alpine-elixir:latest + +RUN apk add inotify-tools postgresql-client yarn file +RUN apk add --no-cache make gcc libc-dev argon2 imagemagick + +RUN mix local.hex --force && mix local.rebar --force + +WORKDIR /app + +EXPOSE 4000 diff --git a/gitlab-ci.yml b/gitlab-ci.yml new file mode 100644 index 000000000..4a6efdf2f --- /dev/null +++ b/gitlab-ci.yml @@ -0,0 +1,123 @@ +image: tcitworld/mobilizon-ci + +stages: + - check + - test + - deploy + +variables: + MIX_ENV: "test" + # DB Variables for Postgres / Postgis + POSTGRES_DB: mobilizon_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: "" + POSTGRES_HOST: postgres + # DB Variables for Mobilizon + MOBILIZON_DATABASE_USERNAME: $POSTGRES_USER + MOBILIZON_DATABASE_PASSWORD: $POSTGRES_PASSWORD + MOBILIZON_DATABASE_DBNAME: $POSTGRES_DB + MOBILIZON_DATABASE_HOST: $POSTGRES_HOST + GEOLITE_CITIES_PATH: "/usr/share/GeoIP/GeoLite2-City.mmdb" + MOBILIZON_INSTANCE_REGISTRATIONS_OPEN: "true" + +cache: + key: ${CI_COMMIT_REF_SLUG} + paths: + - ~/.cache/Cypress + - _build/ + - deps/ + - js/node_modules + - cache/Cypress + +lint: + stage: check + script: + - export EXITVALUE=0 + - mix deps.get + - mix credo --strict -a || export EXITVALUE=1 + - mix format --check-formatted --dry-run || export EXITVALUE=1 + - cd js + - yarn install + #- yarn run lint || export EXITVALUE=1 + - yarn run prettier --ignore-path="src/i18n/*" -c . || export EXITVALUE=1 + - yarn run build + - cd ../ + - exit $EXITVALUE + artifacts: + expire_in: 1 day + when: on_success + paths: + - priv/static + +deps: + stage: check + script: + - export EXITVALUE=0 + - mix deps.get + - mix hex.outdated || export EXITVALUE=1 + - cd js + - yarn outdated || export EXITVALUE=1 + - exit $EXITVALUE + allow_failure: true + +exunit: + stage: test + services: + - name: mdillon/postgis:11 + alias: postgres + before_script: + - cd js + - yarn install + - yarn run build + - cd ../ + - mix deps.get + - MIX_ENV=test mix ecto.create + - MIX_ENV=test mix ecto.migrate + dependencies: + - lint + script: + - mix coveralls +# cypress: +# stage: test +# services: +# - name: mdillon/postgis:11 +# alias: postgres +# script: +# - mix deps.get +# - cd js +# - yarn install +# - npx cypress install # just to be sure +# - yarn run build +# - cd ../ +# - MIX_ENV=e2e mix ecto.create +# - MIX_ENV=e2e mix ecto.migrate +# - MIX_ENV=e2e mix run priv/repo/e2e.seed.exs +# - MIX_ENV=e2e mix phx.server & +# - cd js +# - npx wait-on http://localhost:4000 +# - if [ -z "$CYPRESS_KEY" ]; then npx cypress run; else npx cypress run --record --parallel --key $CYPRESS_KEY; fi +# artifacts: +# expire_in: 2 day +# paths: +# - js/tests/e2e/screenshots/**/*.png +# - js/tests/e2e/videos/**/*.mp4 + +# pages: +# stage: deploy +# script: +# # - mkdir public +# # Mobilizon documentation is now on https://framagit.org/framasoft/joinmobilizon/documentation +# # Mix docs disabled because of https://github.com/elixir-lang/ex_doc/issues/1172 +# # - mix deps.get +# # - mix docs +# # - mv doc public/backend +# #- cd js +# #- yarn install +# #- yarn run styleguide:build +# #- mv styleguide ../public/frontend +# only: +# - master +# artifacts: +# expire_in: 1 hour +# paths: +# - public From 2012d1b011e006ade47f0c7472eb7b02b1450a9f Mon Sep 17 00:00:00 2001 From: prichier Date: Tue, 27 Oct 2020 17:43:31 +0100 Subject: [PATCH 07/15] Get back original root Dockerfile --- docker-compose.yml | 72 +++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 25d62b4fb..3df763f40 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,39 +1,45 @@ -version: "3" +version: '3' services: - mobilizon: - image: mobilizon + postgres: + container_name: mobilizon_db + restart: unless-stopped + image: mdillon/postgis:11 environment: - - MOBILIZON_INSTANCE_NAME - - MOBILIZON_INSTANCE_HOST - - MOBILIZON_INSTANCE_EMAIL - - MOBILIZON_REPLY_EMAIL - - MOBILIZON_INSTANCE_REGISTRATIONS_OPEN=true - - MOBILIZON_DATABASE_USERNAME=${POSTGRES_USER} - - MOBILIZON_DATABASE_PASSWORD=${POSTGRES_PASSWORD} - - MOBILIZON_DATABASE_DBNAME=${POSTGRES_DB} - - MOBILIZON_DATABASE_HOST=db - - MOBILIZON_INSTANCE_SECRET_KEY_BASE - - MOBILIZON_INSTANCE_SECRET_KEY - - MOBILIZON_SMTP_SERVER=yoursmtpserver - - MOBILIZON_SMTP_HOSTNAME=your.smtp.domain - - MOBILIZON_SMTP_USERNAME - - MOBILIZON_SMTP_PASSWORD + POSTGRES_PASSWORD: postgres + POSTGRES_DB: mobilizon_dev volumes: - - /tmp/public/upload:/app/upload + - pgdata:/var/lib/postgresql/data + api: + container_name: mobilizon_api + restart: unless-stopped + build: . + volumes: + - '.:/app' ports: - - "4000:4000" - - db: - image: postgis/postgis - volumes: - - /tmp/db:/var/lib/postgresql/data + - "4000:4000" + depends_on: + - postgres environment: - - POSTGRES_USER - - POSTGRES_PASSWORD - - POSTGRES_DB - -networks: - default: - ipam: - driver: default + MIX_ENV: "dev" + DOCKER: "true" + MOBILIZON_INSTANCE_NAME: My Mobilizon Instance + MOBILIZON_INSTANCE_HOST: mobilizon.me + MOBILIZON_INSTANCE_EMAIL: noreply@mobilizon.me + MOBILIZON_INSTANCE_REGISTRATIONS_OPEN: "true" + MOBILIZON_DATABASE_PASSWORD: postgres + MOBILIZON_DATABASE_USERNAME: postgres + MOBILIZON_DATABASE_DBNAME: mobilizon_dev + MOBILIZON_DATABASE_HOST: postgres + command: > + sh -c "cd js && + yarn install && + cd ../ && + mix deps.get && + mix compile && + mix ecto.create && + mix ecto.migrate && + mix phx.server" +volumes: + pgdata: + .: From ef2021ceac4e76105db79c07b38d42fd94009f19 Mon Sep 17 00:00:00 2001 From: prichier Date: Tue, 27 Oct 2020 19:11:05 +0100 Subject: [PATCH 08/15] Get back original .gitlab-ci.yml name --- gitlab-ci.yml => .gitlab-ci.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gitlab-ci.yml => .gitlab-ci.yml (100%) diff --git a/gitlab-ci.yml b/.gitlab-ci.yml similarity index 100% rename from gitlab-ci.yml rename to .gitlab-ci.yml From b17efc31ae327d71bb320f34342bb89d8ca8ad15 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 30 Oct 2020 15:13:49 +0100 Subject: [PATCH 09/15] Add .env to .gitignore Signed-off-by: Thomas Citharel --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 571bd8abf..2c09e1644 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ release/ *.mo *.po~ .weblate +docker/production/.env From af6ed8a3c2c87b2668859fc4854615b9e43ee335 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 30 Oct 2020 15:14:26 +0100 Subject: [PATCH 10/15] Make sure only "true" value is accepted to enable registrations Signed-off-by: Thomas Citharel --- docker/production/releases.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/production/releases.exs b/docker/production/releases.exs index 412132672..72d84ca4e 100644 --- a/docker/production/releases.exs +++ b/docker/production/releases.exs @@ -15,7 +15,7 @@ config :mobilizon, :instance, name: System.get_env("MOBILIZON_INSTANCE_NAME", "Mobilizon"), description: "Change this to a proper description of your instance", hostname: System.get_env("MOBILIZON_INSTANCE_HOST", "mobilizon.lan"), - registrations_open: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_OPEN", "false"), + registrations_open: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_OPEN", "false") == "true", demo: false, allow_relay: true, federating: true, From a269d77044dfde47fe9f32fc7e7a60f4a37a7a7a Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 30 Oct 2020 15:15:07 +0100 Subject: [PATCH 11/15] Fix pictures not being served by Plug.Static Signed-off-by: Thomas Citharel --- lib/web/endpoint.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/web/endpoint.ex b/lib/web/endpoint.ex index 16b27d541..8a0601be7 100644 --- a/lib/web/endpoint.ex +++ b/lib/web/endpoint.ex @@ -38,8 +38,7 @@ defmodule Mobilizon.Web.Endpoint do at: "/", from: {:mobilizon, "priv/static"}, gzip: false, - only: - ~w(index.html manifest.json service-worker.js css fonts images js favicon.ico robots.txt), + only: ~w(index.html manifest.json service-worker.js css fonts img js favicon.ico robots.txt), only_matching: ["precache-manifest"] ) From 01f746a5d2b8021df20ceee4f13c9a07e3dcec46 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 30 Oct 2020 15:16:01 +0100 Subject: [PATCH 12/15] Allow to use Mix tasks inside Releases Signed-off-by: Thomas Citharel --- lib/mix/tasks/mobilizon/actors.ex | 10 +- lib/mix/tasks/mobilizon/actors/refresh.ex | 13 +- lib/mix/tasks/mobilizon/actors/show.ex | 9 +- lib/mix/tasks/mobilizon/common.ex | 113 +++++++++++++++--- lib/mix/tasks/mobilizon/create_bot.ex | 3 +- lib/mix/tasks/mobilizon/ecto.ex | 54 +++++++++ lib/mix/tasks/mobilizon/ecto/migrate.ex | 71 +++++++++++ lib/mix/tasks/mobilizon/ecto/rollback.ex | 74 ++++++++++++ lib/mix/tasks/mobilizon/groups.ex | 3 +- lib/mix/tasks/mobilizon/groups/refresh.ex | 5 +- lib/mix/tasks/mobilizon/instance.ex | 31 +++-- .../tasks/mobilizon/move_participant_stats.ex | 68 ----------- lib/mix/tasks/mobilizon/relay.ex | 59 ++------- lib/mix/tasks/mobilizon/relay/accept.ex | 28 +++++ lib/mix/tasks/mobilizon/relay/follow.ex | 28 +++++ lib/mix/tasks/mobilizon/relay/refresh.ex | 28 +++++ lib/mix/tasks/mobilizon/relay/unfollow.ex | 28 +++++ lib/mix/tasks/mobilizon/setup_search.ex | 50 -------- lib/mix/tasks/mobilizon/site_map.ex | 6 +- lib/mix/tasks/mobilizon/toot.ex | 30 ----- lib/mix/tasks/mobilizon/users.ex | 10 +- lib/mix/tasks/mobilizon/users/delete.ex | 13 +- lib/mix/tasks/mobilizon/users/modify.ex | 21 ++-- lib/mix/tasks/mobilizon/users/new.ex | 15 +-- lib/mix/tasks/mobilizon/users/show.ex | 10 +- lib/mobilizon/cli.ex | 52 +++++++- test/tasks/actors_test.exs | 4 +- test/tasks/relay_test.exs | 7 +- test/tasks/users_test.exs | 28 +++-- 29 files changed, 570 insertions(+), 301 deletions(-) create mode 100644 lib/mix/tasks/mobilizon/ecto.ex create mode 100644 lib/mix/tasks/mobilizon/ecto/migrate.ex create mode 100644 lib/mix/tasks/mobilizon/ecto/rollback.ex delete mode 100644 lib/mix/tasks/mobilizon/move_participant_stats.ex create mode 100644 lib/mix/tasks/mobilizon/relay/accept.ex create mode 100644 lib/mix/tasks/mobilizon/relay/follow.ex create mode 100644 lib/mix/tasks/mobilizon/relay/refresh.ex create mode 100644 lib/mix/tasks/mobilizon/relay/unfollow.ex delete mode 100644 lib/mix/tasks/mobilizon/setup_search.ex delete mode 100644 lib/mix/tasks/mobilizon/toot.ex diff --git a/lib/mix/tasks/mobilizon/actors.ex b/lib/mix/tasks/mobilizon/actors.ex index 1e3bb1ce2..ccea79c73 100644 --- a/lib/mix/tasks/mobilizon/actors.ex +++ b/lib/mix/tasks/mobilizon/actors.ex @@ -6,12 +6,18 @@ defmodule Mix.Tasks.Mobilizon.Actors do use Mix.Task alias Mix.Tasks + import Mix.Tasks.Mobilizon.Common @shortdoc "Manages Mobilizon actors" @impl Mix.Task def run(_) do - Mix.shell().info("\nAvailable tasks:") - Tasks.Help.run(["--search", "mobilizon.actors."]) + shell_info("\nAvailable tasks:") + + if mix_shell?() do + Tasks.Help.run(["--search", "mobilizon.actors."]) + else + show_subtasks_for_module(__MODULE__) + end end end diff --git a/lib/mix/tasks/mobilizon/actors/refresh.ex b/lib/mix/tasks/mobilizon/actors/refresh.ex index e384be889..d01c23710 100644 --- a/lib/mix/tasks/mobilizon/actors/refresh.ex +++ b/lib/mix/tasks/mobilizon/actors/refresh.ex @@ -7,6 +7,7 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do alias Mobilizon.Federation.ActivityPub alias Mobilizon.Storage.Repo import Ecto.Query + import Mix.Tasks.Mobilizon.Common require Logger @shortdoc "Refresh an actor or all actors" @@ -26,11 +27,11 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do verbose = Keyword.get(options, :verbose, false) - Mix.Task.run("app.start") + start_mobilizon() total = count_actors() - Mix.shell().info(""" + shell_info(""" #{total} actors to process """) @@ -62,22 +63,22 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do @impl Mix.Task def run([preferred_username]) do - Mix.Task.run("app.start") + start_mobilizon() case ActivityPub.make_actor_from_nickname(preferred_username) do {:ok, %Actor{}} -> - Mix.shell().info(""" + shell_info(""" Actor #{preferred_username} refreshed """) {:actor, nil} -> - Mix.raise("Error: No such actor") + shell_error("Error: No such actor") end end @impl Mix.Task def run(_) do - Mix.raise("mobilizon.actors.refresh requires an username as argument or --all as an option") + shell_error("mobilizon.actors.refresh requires an username as argument or --all as an option") end @spec make_actor(String.t(), boolean()) :: any() diff --git a/lib/mix/tasks/mobilizon/actors/show.ex b/lib/mix/tasks/mobilizon/actors/show.ex index 8abab9e78..73f088796 100644 --- a/lib/mix/tasks/mobilizon/actors/show.ex +++ b/lib/mix/tasks/mobilizon/actors/show.ex @@ -5,16 +5,17 @@ defmodule Mix.Tasks.Mobilizon.Actors.Show do use Mix.Task alias Mobilizon.Actors alias Mobilizon.Actors.Actor + import Mix.Tasks.Mobilizon.Common @shortdoc "Show a Mobilizon user details" @impl Mix.Task def run([preferred_username]) do - Mix.Task.run("app.start") + start_mobilizon() case {:actor, Actors.get_actor_by_name_with_preload(preferred_username)} do {:actor, %Actor{} = actor} -> - Mix.shell().info(""" + shell_info(""" Informations for the actor #{actor.preferred_username}: - Type: #{actor.type} - Domain: #{if is_nil(actor.domain), do: "Local", else: actor.domain} @@ -24,11 +25,11 @@ defmodule Mix.Tasks.Mobilizon.Actors.Show do """) {:actor, nil} -> - Mix.raise("Error: No such actor") + shell_error("Error: No such actor") end end def run(_) do - Mix.raise("mobilizon.actors.show requires an username as argument") + shell_error("mobilizon.actors.show requires an username as argument") end end diff --git a/lib/mix/tasks/mobilizon/common.ex b/lib/mix/tasks/mobilizon/common.ex index 074b8d547..3f292dcc8 100644 --- a/lib/mix/tasks/mobilizon/common.ex +++ b/lib/mix/tasks/mobilizon/common.ex @@ -8,32 +8,107 @@ defmodule Mix.Tasks.Mobilizon.Common do Common functions to be reused in mix tasks """ - def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do - display = if defname || defval, do: "#{prompt} [#{defname || defval}]", else: "#{prompt}" - - Keyword.get(options, opt) || - case Mix.shell().prompt(display) do - "\n" -> - case defval do - nil -> - get_option(options, opt, prompt, defval) - - defval -> - defval - end - - opt -> - String.trim(opt) - end - end - def start_mobilizon do Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) {:ok, _} = Application.ensure_all_started(:mobilizon) end + def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do + Keyword.get(options, opt) || shell_prompt(prompt, defval, defname) + end + + def shell_prompt(prompt, defval \\ nil, defname \\ nil) do + prompt_message = "#{prompt} [#{defname || defval}] " + + input = + if mix_shell?(), + do: Mix.shell().prompt(prompt_message), + else: :io.get_line(prompt_message) + + case input do + "\n" -> + case defval do + nil -> + shell_prompt(prompt, defval, defname) + + defval -> + defval + end + + input -> + String.trim(input) + end + end + + def shell_yes?(message) do + if mix_shell?(), + do: Mix.shell().yes?("Continue?"), + else: shell_prompt(message, "Continue?") in ~w(Yn Y y) + end + + def shell_info(message) do + if mix_shell?(), + do: Mix.shell().info(message), + else: IO.puts(message) + end + + def shell_error(message) do + if mix_shell?(), + do: Mix.shell().error(message), + else: IO.puts(:stderr, message) + end + + @doc "Performs a safe check whether `Mix.shell/0` is available (does not raise if Mix is not loaded)" + def mix_shell?, do: :erlang.function_exported(Mix, :shell, 0) + def escape_sh_path(path) do ~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(') end + + @type task_module :: atom + + @doc """ + Gets the shortdoc for the given task `module`. + Returns the shortdoc or `nil`. + """ + @spec shortdoc(task_module) :: String.t() | nil + def shortdoc(module) when is_atom(module) do + case List.keyfind(module.__info__(:attributes), :shortdoc, 0) do + {:shortdoc, [shortdoc]} -> shortdoc + _ -> nil + end + end + + def show_subtasks_for_module(module_name) do + tasks = list_subtasks_for_module(module_name) + + max = Enum.reduce(tasks, 0, fn {name, _doc}, acc -> max(byte_size(name), acc) end) + + Enum.each(tasks, fn {name, doc} -> + shell_info("#{String.pad_trailing(name, max + 2)} # #{doc}") + end) + end + + @spec list_subtasks_for_module(atom()) :: list({String.t(), String.t()}) + def list_subtasks_for_module(module_name) do + Application.load(:mobilizon) + {:ok, modules} = :application.get_key(:mobilizon, :modules) + module_name = to_string(module_name) + + modules + |> Enum.filter(fn module -> + String.starts_with?(to_string(module), to_string(module_name)) && + to_string(module) != to_string(module_name) + end) + |> Enum.map(&format_module/1) + end + + defp format_module(module) do + {format_name(to_string(module)), shortdoc(module)} + end + + defp format_name("Elixir.Mix.Tasks.Mobilizon." <> task_name) do + String.downcase(task_name) + end end diff --git a/lib/mix/tasks/mobilizon/create_bot.ex b/lib/mix/tasks/mobilizon/create_bot.ex index fce2b6166..2aea6166b 100644 --- a/lib/mix/tasks/mobilizon/create_bot.ex +++ b/lib/mix/tasks/mobilizon/create_bot.ex @@ -8,12 +8,13 @@ defmodule Mix.Tasks.Mobilizon.CreateBot do alias Mobilizon.{Actors, Users} alias Mobilizon.Actors.Bot alias Mobilizon.Users.User + import Mix.Tasks.Mobilizon.Common require Logger @shortdoc "Create bot" def run([email, name, summary, type, url]) do - Mix.Task.run("app.start") + start_mobilizon() with {:ok, %User{} = user} <- Users.get_user_by_email(email, true), actor <- Actors.register_bot(%{name: name, summary: summary}), diff --git a/lib/mix/tasks/mobilizon/ecto.ex b/lib/mix/tasks/mobilizon/ecto.ex new file mode 100644 index 000000000..5ce88e5e1 --- /dev/null +++ b/lib/mix/tasks/mobilizon/ecto.ex @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-onl + +defmodule Mix.Tasks.Mobilizon.Ecto do + @moduledoc """ + Provides tools for Ecto-related tasks (such as migrations) + """ + + @doc """ + Ensures the given repository's migrations path exists on the file system. + """ + @spec ensure_migrations_path(Ecto.Repo.t(), Keyword.t()) :: String.t() + def ensure_migrations_path(repo, opts) do + path = opts[:migrations_path] || Path.join(source_repo_priv(repo), "migrations") + + path = + case Path.type(path) do + :relative -> + Path.join(Application.app_dir(:mobilizon), path) + + :absolute -> + path + end + + if not File.dir?(path) do + raise_missing_migrations(Path.relative_to_cwd(path), repo) + end + + path + end + + @doc """ + Returns the private repository path relative to the source. + """ + def source_repo_priv(repo) do + config = repo.config() + priv = config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}" + Path.join(Application.app_dir(:mobilizon), priv) + end + + defp raise_missing_migrations(path, repo) do + raise(""" + Could not find migrations directory #{inspect(path)} + for repo #{inspect(repo)}. + This may be because you are in a new project and the + migration directory has not been created yet. Creating an + empty directory at the path above will fix this error. + If you expected existing migrations to be found, please + make sure your repository has been properly configured + and the configured path exists. + """) + end +end diff --git a/lib/mix/tasks/mobilizon/ecto/migrate.ex b/lib/mix/tasks/mobilizon/ecto/migrate.ex new file mode 100644 index 000000000..3e6c8812e --- /dev/null +++ b/lib/mix/tasks/mobilizon/ecto/migrate.ex @@ -0,0 +1,71 @@ +# Portions of this file are derived from Pleroma: +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Mobilizon.Ecto.Migrate do + use Mix.Task + import Mix.Tasks.Mobilizon.Common + alias Mix.Tasks.Mobilizon.Ecto, as: EctoTask + require Logger + + @shortdoc "Wrapper on `ecto.migrate` task." + + @aliases [ + n: :step, + v: :to + ] + + @switches [ + all: :boolean, + step: :integer, + to: :integer, + quiet: :boolean, + log_sql: :boolean, + strict_version_order: :boolean, + migrations_path: :string + ] + + @repo Mobilizon.Storage.Repo + + @moduledoc """ + Changes `Logger` level to `:info` before start migration. + Changes level back when migration ends. + + ## Start migration + + mix mobilizon.ecto.migrate [OPTIONS] + + Options: + - see https://hexdocs.pm/ecto/2.0.0/Mix.Tasks.Ecto.Migrate.html + """ + + @impl true + def run(args \\ []) do + start_mobilizon() + {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases) + + if Application.get_env(:mobilizon, @repo)[:ssl] do + Application.ensure_all_started(:ssl) + end + + opts = + if opts[:to] || opts[:step] || opts[:all], + do: opts, + else: Keyword.put(opts, :all, true) + + opts = + if opts[:quiet], + do: Keyword.merge(opts, log: false, log_sql: false), + else: opts + + path = EctoTask.ensure_migrations_path(@repo, opts) + + level = Logger.level() + Logger.configure(level: :info) + + {:ok, _, _} = Ecto.Migrator.with_repo(@repo, &Ecto.Migrator.run(&1, path, :up, opts)) + + Logger.configure(level: level) + end +end diff --git a/lib/mix/tasks/mobilizon/ecto/rollback.ex b/lib/mix/tasks/mobilizon/ecto/rollback.ex new file mode 100644 index 000000000..694e948da --- /dev/null +++ b/lib/mix/tasks/mobilizon/ecto/rollback.ex @@ -0,0 +1,74 @@ +# Portions of this file are derived from Pleroma: +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Mobilizon.Ecto.Rollback do + use Mix.Task + import Mix.Tasks.Mobilizon.Common + alias Mix.Tasks.Mobilizon.Ecto, as: EctoTask + require Logger + @shortdoc "Wrapper on `ecto.rollback` task" + + @aliases [ + n: :step, + v: :to + ] + + @switches [ + all: :boolean, + step: :integer, + to: :integer, + start: :boolean, + quiet: :boolean, + log_sql: :boolean, + migrations_path: :string + ] + + @repo Mobilizon.Storage.Repo + + @moduledoc """ + Changes `Logger` level to `:info` before start rollback. + Changes level back when rollback ends. + + ## Start rollback + + mix mobilizon.ecto.rollback + + Options: + - see https://hexdocs.pm/ecto/2.0.0/Mix.Tasks.Ecto.Rollback.html + """ + + @impl true + def run(args \\ []) do + start_mobilizon() + {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases) + + if Application.get_env(:mobilizon, @repo)[:ssl] do + Application.ensure_all_started(:ssl) + end + + opts = + if opts[:to] || opts[:step] || opts[:all], + do: opts, + else: Keyword.put(opts, :step, 1) + + opts = + if opts[:quiet], + do: Keyword.merge(opts, log: false, log_sql: false), + else: opts + + path = EctoTask.ensure_migrations_path(@repo, opts) + + level = Logger.level() + Logger.configure(level: :info) + + if Mobilizon.Config.get(:env) == :test do + Logger.info("Rollback succesfully") + else + {:ok, _, _} = Ecto.Migrator.with_repo(@repo, &Ecto.Migrator.run(&1, path, :down, opts)) + end + + Logger.configure(level: level) + end +end diff --git a/lib/mix/tasks/mobilizon/groups.ex b/lib/mix/tasks/mobilizon/groups.ex index 412b38fe5..3d3d3a6f9 100644 --- a/lib/mix/tasks/mobilizon/groups.ex +++ b/lib/mix/tasks/mobilizon/groups.ex @@ -6,12 +6,13 @@ defmodule Mix.Tasks.Mobilizon.Groups do use Mix.Task alias Mix.Tasks + import Mix.Tasks.Mobilizon.Common @shortdoc "Manages Mobilizon groups" @impl Mix.Task def run(_) do - Mix.shell().info("\nAvailable tasks:") + shell_info("\nAvailable tasks:") Tasks.Help.run(["--search", "mobilizon.groups."]) end end diff --git a/lib/mix/tasks/mobilizon/groups/refresh.ex b/lib/mix/tasks/mobilizon/groups/refresh.ex index a88e0d22d..6e5caac57 100644 --- a/lib/mix/tasks/mobilizon/groups/refresh.ex +++ b/lib/mix/tasks/mobilizon/groups/refresh.ex @@ -6,12 +6,13 @@ defmodule Mix.Tasks.Mobilizon.Groups.Refresh do alias Mobilizon.Actors alias Mobilizon.Actors.Actor alias Mobilizon.Federation.ActivityPub.Refresher + import Mix.Tasks.Mobilizon.Common @shortdoc "Refresh a group private informations from an account member" @impl Mix.Task def run([group_url, on_behalf_of]) do - Mix.Task.run("app.start") + start_mobilizon() with %Actor{} = actor <- Actors.get_local_actor_by_name(on_behalf_of) do res = Refresher.fetch_group(group_url, actor) @@ -20,7 +21,7 @@ defmodule Mix.Tasks.Mobilizon.Groups.Refresh do end def run(_) do - Mix.raise( + shell_error( "mobilizon.groups.refresh requires a group URL and an actor username which is member of the group as arguments" ) end diff --git a/lib/mix/tasks/mobilizon/instance.ex b/lib/mix/tasks/mobilizon/instance.ex index a833edcdf..a5bf2d648 100644 --- a/lib/mix/tasks/mobilizon/instance.ex +++ b/lib/mix/tasks/mobilizon/instance.ex @@ -29,7 +29,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do use Mix.Task - alias Mix.Tasks.Mobilizon.Common + import Mix.Tasks.Mobilizon.Common @preferred_cli_env "prod" @@ -70,7 +70,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do if proceed? do [domain, port | _] = String.split( - Common.get_option( + get_option( options, :domain, "What domain will your instance use? (e.g mobilizon.org)" @@ -79,25 +79,24 @@ defmodule Mix.Tasks.Mobilizon.Instance do ) ++ [443] name = - Common.get_option( + get_option( options, :instance_name, "What is the name of your instance? (e.g. Mobilizon)" ) email = - Common.get_option( + get_option( options, :admin_email, "What's the address email will be send with?", "noreply@#{domain}" ) - dbhost = - Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost") + dbhost = get_option(options, :dbhost, "What is the hostname of your database?", "localhost") dbname = - Common.get_option( + get_option( options, :dbname, "What is the name of your database?", @@ -105,7 +104,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do ) dbuser = - Common.get_option( + get_option( options, :dbuser, "What is the user used to connect to your database?", @@ -113,7 +112,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do ) dbpass = - Common.get_option( + get_option( options, :dbpass, "What is the password used to connect to your database?", @@ -122,7 +121,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do ) listen_port = - Common.get_option( + get_option( options, :listen_port, "What port will the app listen to (leave it if you are using the default setup with nginx)?", @@ -160,24 +159,24 @@ defmodule Mix.Tasks.Mobilizon.Instance do database_password: dbpass ) - Mix.shell().info("Writing config to #{config_path}.") + shell_info("Writing config to #{config_path}.") File.write(config_path, result_config) - Mix.shell().info("Writing #{psql_path}.") + shell_info("Writing #{psql_path}.") File.write(psql_path, result_psql) - Mix.shell().info( + shell_info( "\n" <> """ To get started: 1. Check the contents of the generated files. - 2. Run `sudo -u postgres psql -f #{Common.escape_sh_path(psql_path)} && rm #{ - Common.escape_sh_path(psql_path) + 2. Run `sudo -u postgres psql -f #{escape_sh_path(psql_path)} && rm #{ + escape_sh_path(psql_path) }`. """ ) else - Mix.shell().error( + shell_error( "The task would have overwritten the following files:\n" <> (will_overwrite |> Enum.map(&"- #{&1}\n") |> Enum.join("")) <> "Rerun with `-f/--force` to overwrite them." diff --git a/lib/mix/tasks/mobilizon/move_participant_stats.ex b/lib/mix/tasks/mobilizon/move_participant_stats.ex deleted file mode 100644 index 5c31be348..000000000 --- a/lib/mix/tasks/mobilizon/move_participant_stats.ex +++ /dev/null @@ -1,68 +0,0 @@ -defmodule Mix.Tasks.Mobilizon.MoveParticipantStats do - @moduledoc """ - Temporary task to move participant stats in the events table - - This task will be removed in version 1.0.0-beta.3 - """ - - use Mix.Task - - import Ecto.Query - - alias Mobilizon.Events - alias Mobilizon.Events.Event - alias Mobilizon.Events.ParticipantRole - alias Mobilizon.Storage.Repo - - require Logger - - @shortdoc "Move participant stats to events table" - def run([]) do - Mix.Task.run("app.start") - - events = - Event - |> preload([e], :tags) - |> Repo.all() - - nb_events = length(events) - - IO.puts( - "\nStarting inserting participants stats into #{nb_events} events, this can take a while…\n" - ) - - insert_participants_stats_into_events(events, nb_events) - end - - defp insert_participants_stats_into_events([%Event{url: url} = event | events], nb_events) do - with roles <- ParticipantRole.__enum_map__(), - counts <- - Enum.reduce(roles, %{}, fn role, acc -> - Map.put(acc, role, count_participants(event, role)) - end), - {:ok, _} <- - Events.update_event(event, %{ - participant_stats: counts - }) do - Logger.debug("Added participants stats to event #{url}") - else - {:error, res} -> - Logger.error("Error while adding participants stats to event #{url} : #{inspect(res)}") - end - - ProgressBar.render(nb_events - length(events), nb_events) - - insert_participants_stats_into_events(events, nb_events) - end - - defp insert_participants_stats_into_events([], nb_events) do - IO.puts("\nFinished inserting participant stats for #{nb_events} events!\n") - end - - defp count_participants(%Event{id: event_id}, role) when is_atom(role) do - event_id - |> Events.count_participants_query() - |> Events.filter_role(role) - |> Repo.aggregate(:count, :id) - end -end diff --git a/lib/mix/tasks/mobilizon/relay.ex b/lib/mix/tasks/mobilizon/relay.ex index 4dc463a6a..03c612bc2 100644 --- a/lib/mix/tasks/mobilizon/relay.ex +++ b/lib/mix/tasks/mobilizon/relay.ex @@ -22,60 +22,19 @@ defmodule Mix.Tasks.Mobilizon.Relay do use Mix.Task - alias Mix.Tasks.Mobilizon.Common - - alias Mobilizon.Federation.ActivityPub.Relay + alias Mix.Tasks + import Mix.Tasks.Mobilizon.Common @shortdoc "Manages remote relays" - def run(["follow", target]) do - Common.start_mobilizon() - case Relay.follow(target) do - {:ok, _activity, _follow} -> - # put this task to sleep to allow the genserver to push out the messages - :timer.sleep(500) + @impl Mix.Task + def run(_) do + shell_info("\nAvailable tasks:") - {:error, e} -> - IO.puts(:stderr, "Error while following #{target}: #{inspect(e)}") - end - end - - def run(["unfollow", target]) do - Common.start_mobilizon() - - case Relay.unfollow(target) do - {:ok, _activity, _follow} -> - # put this task to sleep to allow the genserver to push out the messages - :timer.sleep(500) - - {:error, e} -> - IO.puts(:stderr, "Error while unfollowing #{target}: #{inspect(e)}") - end - end - - def run(["accept", target]) do - Common.start_mobilizon() - - case Relay.accept(target) do - {:ok, _activity} -> - # put this task to sleep to allow the genserver to push out the messages - :timer.sleep(500) - - {:error, e} -> - IO.puts(:stderr, "Error while accept #{target} follow: #{inspect(e)}") - end - end - - def run(["refresh", target]) do - Common.start_mobilizon() - IO.puts("Refreshing #{target}, this can take a while.") - - case Relay.refresh(target) do - :ok -> - IO.puts("Refreshed #{target}") - - err -> - IO.puts(:stderr, "Error while refreshing #{target}: #{inspect(err)}") + if mix_shell?() do + Tasks.Help.run(["--search", "mobilizon.relay."]) + else + show_subtasks_for_module(__MODULE__) end end end diff --git a/lib/mix/tasks/mobilizon/relay/accept.ex b/lib/mix/tasks/mobilizon/relay/accept.ex new file mode 100644 index 000000000..88d594ff8 --- /dev/null +++ b/lib/mix/tasks/mobilizon/relay/accept.ex @@ -0,0 +1,28 @@ +defmodule Mix.Tasks.Mobilizon.Relay.Accept do + @moduledoc """ + Task to accept an instance follow request + """ + use Mix.Task + alias Mobilizon.Federation.ActivityPub.Relay + import Mix.Tasks.Mobilizon.Common + + @shortdoc "Accept an instance follow request" + + @impl Mix.Task + def run([target]) do + start_mobilizon() + + case Relay.accept(target) do + {:ok, _activity} -> + # put this task to sleep to allow the genserver to push out the messages + :timer.sleep(500) + + {:error, e} -> + IO.puts(:stderr, "Error while accept #{target} follow: #{inspect(e)}") + end + end + + def run(_) do + shell_error("mobilizon.relay.accept requires an instance hostname as arguments") + end +end diff --git a/lib/mix/tasks/mobilizon/relay/follow.ex b/lib/mix/tasks/mobilizon/relay/follow.ex new file mode 100644 index 000000000..43c29f456 --- /dev/null +++ b/lib/mix/tasks/mobilizon/relay/follow.ex @@ -0,0 +1,28 @@ +defmodule Mix.Tasks.Mobilizon.Relay.Follow do + @moduledoc """ + Task to follow an instance + """ + use Mix.Task + alias Mobilizon.Federation.ActivityPub.Relay + import Mix.Tasks.Mobilizon.Common + + @shortdoc "Follow an instance" + + @impl Mix.Task + def run([target]) do + start_mobilizon() + + case Relay.follow(target) do + {:ok, _activity, _follow} -> + # put this task to sleep to allow the genserver to push out the messages + :timer.sleep(500) + + {:error, e} -> + IO.puts(:stderr, "Error while following #{target}: #{inspect(e)}") + end + end + + def run(_) do + shell_error("mobilizon.relay.follow requires an instance hostname as arguments") + end +end diff --git a/lib/mix/tasks/mobilizon/relay/refresh.ex b/lib/mix/tasks/mobilizon/relay/refresh.ex new file mode 100644 index 000000000..f788009b4 --- /dev/null +++ b/lib/mix/tasks/mobilizon/relay/refresh.ex @@ -0,0 +1,28 @@ +defmodule Mix.Tasks.Mobilizon.Relay.Refresh do + @moduledoc """ + Task to refresh an instance details + """ + use Mix.Task + alias Mobilizon.Federation.ActivityPub.Relay + import Mix.Tasks.Mobilizon.Common + + @shortdoc "Refresh an instance informations and crawl their outbox" + + @impl Mix.Task + def run([target]) do + start_mobilizon() + IO.puts("Refreshing #{target}, this can take a while.") + + case Relay.refresh(target) do + :ok -> + IO.puts("Refreshed #{target}") + + err -> + IO.puts(:stderr, "Error while refreshing #{target}: #{inspect(err)}") + end + end + + def run(_) do + shell_error("mobilizon.relay.refresh requires an instance hostname as arguments") + end +end diff --git a/lib/mix/tasks/mobilizon/relay/unfollow.ex b/lib/mix/tasks/mobilizon/relay/unfollow.ex new file mode 100644 index 000000000..c368c3fb5 --- /dev/null +++ b/lib/mix/tasks/mobilizon/relay/unfollow.ex @@ -0,0 +1,28 @@ +defmodule Mix.Tasks.Mobilizon.Relay.Unfollow do + @moduledoc """ + Task to unfollow an instance + """ + use Mix.Task + alias Mobilizon.Federation.ActivityPub.Relay + import Mix.Tasks.Mobilizon.Common + + @shortdoc "Unfollow an instance" + + @impl Mix.Task + def run([target]) do + start_mobilizon() + + case Relay.unfollow(target) do + {:ok, _activity, _follow} -> + # put this task to sleep to allow the genserver to push out the messages + :timer.sleep(500) + + {:error, e} -> + IO.puts(:stderr, "Error while unfollowing #{target}: #{inspect(e)}") + end + end + + def run(_) do + shell_error("mobilizon.relay.unfollow requires an instance hostname as arguments") + end +end diff --git a/lib/mix/tasks/mobilizon/setup_search.ex b/lib/mix/tasks/mobilizon/setup_search.ex deleted file mode 100644 index 4633a2a76..000000000 --- a/lib/mix/tasks/mobilizon/setup_search.ex +++ /dev/null @@ -1,50 +0,0 @@ -defmodule Mix.Tasks.Mobilizon.SetupSearch do - @moduledoc """ - Temporary task to insert search data from existing events - - This task will be removed in version 1.0.0-beta.3 - """ - - use Mix.Task - - import Ecto.Query - - alias Mobilizon.Events.Event - alias Mobilizon.Service.Workers - alias Mobilizon.Storage.Repo - - require Logger - - @shortdoc "Insert search data" - def run([]) do - Mix.Task.run("app.start") - - events = - Event - |> preload([e], :tags) - |> Repo.all() - - nb_events = length(events) - - IO.puts("\nStarting setting up search for #{nb_events} events, this can take a while…\n") - insert_search_event(events, nb_events) - end - - defp insert_search_event([%Event{url: url} = event | events], nb_events) do - case Workers.BuildSearch.insert_search_event(event) do - {:ok, _} -> - Logger.debug("Added event #{url} to the search") - - {:error, res} -> - Logger.error("Error while adding event #{url} to the search: #{inspect(res)}") - end - - ProgressBar.render(nb_events - length(events), nb_events) - - insert_search_event(events, nb_events) - end - - defp insert_search_event([], nb_events) do - IO.puts("\nFinished setting up search for #{nb_events} events!\n") - end -end diff --git a/lib/mix/tasks/mobilizon/site_map.ex b/lib/mix/tasks/mobilizon/site_map.ex index ef4162892..d35c94990 100644 --- a/lib/mix/tasks/mobilizon/site_map.ex +++ b/lib/mix/tasks/mobilizon/site_map.ex @@ -4,7 +4,7 @@ defmodule Mix.Tasks.Mobilizon.SiteMap do """ use Mix.Task - alias Mix.Tasks.Mobilizon.Common + import Mix.Tasks.Mobilizon.Common alias Mobilizon.Service.SiteMap alias Mobilizon.Web.Endpoint @@ -12,10 +12,10 @@ defmodule Mix.Tasks.Mobilizon.SiteMap do @shortdoc "Generates a new Sitemap" def run(["generate"]) do - Common.start_mobilizon() + start_mobilizon() with {:ok, :ok} <- SiteMap.generate_sitemap() do - Mix.shell().info("Sitemap saved to #{Endpoint.url()}/sitemap.xml") + shell_info("Sitemap saved to #{Endpoint.url()}/sitemap.xml") end end end diff --git a/lib/mix/tasks/mobilizon/toot.ex b/lib/mix/tasks/mobilizon/toot.ex deleted file mode 100644 index 3473d8b56..000000000 --- a/lib/mix/tasks/mobilizon/toot.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule Mix.Tasks.Mobilizon.Toot do - @moduledoc """ - Creates a bot from a source. - """ - - use Mix.Task - - alias Mobilizon.Actors - alias Mobilizon.Actors.Actor - - alias Mobilizon.GraphQL.API.Comments - - require Logger - - @shortdoc "Toot to an user" - def run([from, text]) do - Mix.Task.run("app.start") - - with {:local_actor, %Actor{} = actor} <- {:local_actor, Actors.get_local_actor_by_name(from)}, - {:ok, _, _} <- Comments.create_comment(%{actor: actor, text: text}) do - Mix.shell().info("Tooted") - else - {:local_actor, _, _} -> - Mix.shell().error("Failed to toot.\nActor #{from} doesn't exist") - - _ -> - Mix.shell().error("Failed to toot.") - end - end -end diff --git a/lib/mix/tasks/mobilizon/users.ex b/lib/mix/tasks/mobilizon/users.ex index e61a4e77a..aa3386b7f 100644 --- a/lib/mix/tasks/mobilizon/users.ex +++ b/lib/mix/tasks/mobilizon/users.ex @@ -6,12 +6,18 @@ defmodule Mix.Tasks.Mobilizon.Users do use Mix.Task alias Mix.Tasks + import Mix.Tasks.Mobilizon.Common @shortdoc "Manages Mobilizon users" @impl Mix.Task def run(_) do - Mix.shell().info("\nAvailable tasks:") - Tasks.Help.run(["--search", "mobilizon.users."]) + shell_info("\nAvailable tasks:") + + if mix_shell?() do + Tasks.Help.run(["--search", "mobilizon.users."]) + else + show_subtasks_for_module(__MODULE__) + end end end diff --git a/lib/mix/tasks/mobilizon/users/delete.ex b/lib/mix/tasks/mobilizon/users/delete.ex index d769601a0..c72cd97a8 100644 --- a/lib/mix/tasks/mobilizon/users/delete.ex +++ b/lib/mix/tasks/mobilizon/users/delete.ex @@ -5,6 +5,7 @@ defmodule Mix.Tasks.Mobilizon.Users.Delete do use Mix.Task alias Mobilizon.Users alias Mobilizon.Users.User + import Mix.Tasks.Mobilizon.Common @shortdoc "Deletes a Mobilizon user" @@ -26,25 +27,25 @@ defmodule Mix.Tasks.Mobilizon.Users.Delete do assume_yes? = Keyword.get(options, :assume_yes, false) keep_email? = Keyword.get(options, :keep_email, false) - Mix.Task.run("app.start") + start_mobilizon() with {:ok, %User{} = user} <- Users.get_user_by_email(email), - true <- assume_yes? or Mix.shell().yes?("Continue with deleting user #{user.email}?"), + true <- assume_yes? or shell_yes?("Continue with deleting user #{user.email}?"), {:ok, %User{} = user} <- Users.delete_user(user, reserve_email: keep_email?) do - Mix.shell().info(""" + shell_info(""" The user #{user.email} has been deleted """) else {:error, :user_not_found} -> - Mix.raise("Error: No such user") + shell_error("Error: No such user") _ -> - Mix.raise("User has not been deleted.") + shell_error("User has not been deleted.") end end def run(_) do - Mix.raise("mobilizon.users.delete requires an email as argument") + shell_error("mobilizon.users.delete requires an email as argument") end end diff --git a/lib/mix/tasks/mobilizon/users/modify.ex b/lib/mix/tasks/mobilizon/users/modify.ex index 66e0b26ee..dd8a4181c 100644 --- a/lib/mix/tasks/mobilizon/users/modify.ex +++ b/lib/mix/tasks/mobilizon/users/modify.ex @@ -3,6 +3,7 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do Task to modify an existing Mobilizon user """ use Mix.Task + import Mix.Tasks.Mobilizon.Common alias Mobilizon.Users alias Mobilizon.Users.User @@ -31,10 +32,10 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do new_email = Keyword.get(options, :email) if disable? && enable? do - Mix.raise("Can't use both --enabled and --disable options at the same time.") + shell_error("Can't use both --enabled and --disable options at the same time.") end - Mix.Task.run("app.start") + start_mobilizon() with {:ok, %User{} = user} <- Users.get_user_by_email(email), attrs <- %{}, @@ -53,7 +54,7 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do ), {:makes_changes, true} <- {:makes_changes, attrs != %{}}, {:ok, %User{} = user} <- Users.update_user(user, attrs) do - Mix.shell().info(""" + shell_info(""" An user has been modified with the following information: - email: #{user.email} - Role: #{user.role} @@ -61,23 +62,23 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do """) else {:makes_changes, false} -> - Mix.shell().info("No change has been made") + shell_info("No change has been made") {:error, :user_not_found} -> - Mix.raise("Error: No such user") + shell_error("Error: No such user") {:error, %Ecto.Changeset{errors: errors}} -> - Mix.shell().error(inspect(errors)) - Mix.raise("User has not been modified because of the above reason.") + shell_error(inspect(errors)) + shell_error("User has not been modified because of the above reason.") err -> - Mix.shell().error(inspect(err)) - Mix.raise("User has not been modified because of an unknown reason.") + shell_error(inspect(err)) + shell_error("User has not been modified because of an unknown reason.") end end def run(_) do - Mix.raise("mobilizon.users.new requires an email as argument") + shell_error("mobilizon.users.new requires an email as argument") end @spec process_new_value(map(), atom(), any(), any()) :: map() diff --git a/lib/mix/tasks/mobilizon/users/new.ex b/lib/mix/tasks/mobilizon/users/new.ex index d0802b329..453762798 100644 --- a/lib/mix/tasks/mobilizon/users/new.ex +++ b/lib/mix/tasks/mobilizon/users/new.ex @@ -3,6 +3,7 @@ defmodule Mix.Tasks.Mobilizon.Users.New do Task to create a new user """ use Mix.Task + import Mix.Tasks.Mobilizon.Common alias Mobilizon.Users alias Mobilizon.Users.User @@ -40,7 +41,7 @@ defmodule Mix.Tasks.Mobilizon.Users.New do :crypto.strong_rand_bytes(16) |> Base.encode64() |> binary_part(0, 16) ) - Mix.Task.run("app.start") + start_mobilizon() case Users.register(%{ email: email, @@ -51,7 +52,7 @@ defmodule Mix.Tasks.Mobilizon.Users.New do confirmation_token: nil }) do {:ok, %User{} = user} -> - Mix.shell().info(""" + shell_info(""" An user has been created with the following information: - email: #{user.email} - password: #{password} @@ -60,16 +61,16 @@ defmodule Mix.Tasks.Mobilizon.Users.New do """) {:error, %Ecto.Changeset{errors: errors}} -> - Mix.shell().error(inspect(errors)) - Mix.raise("User has not been created because of the above reason.") + shell_error(inspect(errors)) + shell_error("User has not been created because of the above reason.") err -> - Mix.shell().error(inspect(err)) - Mix.raise("User has not been created because of an unknown reason.") + shell_error(inspect(err)) + shell_error("User has not been created because of an unknown reason.") end end def run(_) do - Mix.raise("mobilizon.users.new requires an email as argument") + shell_error("mobilizon.users.new requires an email as argument") end end diff --git a/lib/mix/tasks/mobilizon/users/show.ex b/lib/mix/tasks/mobilizon/users/show.ex index 81864bfe6..3c33aaf52 100644 --- a/lib/mix/tasks/mobilizon/users/show.ex +++ b/lib/mix/tasks/mobilizon/users/show.ex @@ -4,7 +4,7 @@ defmodule Mix.Tasks.Mobilizon.Users.Show do """ use Mix.Task - + import Mix.Tasks.Mobilizon.Common alias Mobilizon.Actors.Actor alias Mobilizon.Users alias Mobilizon.Users.User @@ -13,11 +13,11 @@ defmodule Mix.Tasks.Mobilizon.Users.Show do @impl Mix.Task def run([email]) do - Mix.Task.run("app.start") + start_mobilizon() with {:ok, %User{} = user} <- Users.get_user_by_email(email), actors <- Users.get_actors_for_user(user) do - Mix.shell().info(""" + shell_info(""" Informations for the user #{user.email}: - Activated: #{user.confirmed_at} - Disabled: #{user.disabled} @@ -26,12 +26,12 @@ defmodule Mix.Tasks.Mobilizon.Users.Show do """) else {:error, :user_not_found} -> - Mix.raise("Error: No such user") + shell_error("Error: No such user") end end def run(_) do - Mix.raise("mobilizon.users.show requires an email as argument") + shell_error("mobilizon.users.show requires an email as argument") end defp display_actors([]), do: "" diff --git a/lib/mobilizon/cli.ex b/lib/mobilizon/cli.ex index 969c125f0..91d105d3d 100644 --- a/lib/mobilizon/cli.ex +++ b/lib/mobilizon/cli.ex @@ -1,11 +1,53 @@ +# Portions of this file are derived from Pleroma: +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mobilizon.CLI do - @app :mobilizon + @moduledoc """ + CLI wrapper for releases + """ + alias Mix.Tasks.Mobilizon.Ecto.{Migrate, Rollback} - def migrate do - Application.load(@app) + def run(args) do + [task | args] = String.split(args) - for repo <- Application.fetch_env!(@app, :ecto_repos) do - {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) + case task do + "migrate" -> migrate(args) + "rollback" -> rollback(args) + task -> mix_task(task, args) end end + + defp mix_task(task, args) do + Application.load(:mobilizon) + {:ok, modules} = :application.get_key(:mobilizon, :modules) + + module = + Enum.find(modules, fn module -> + module = Module.split(module) + + case module do + ["Mix", "Tasks", "Mobilizon" | rest] -> + String.downcase(Enum.join(rest, ".")) == task + + _ -> + false + end + end) + + if module do + module.run(args) + else + IO.puts("The task #{task} does not exist") + end + end + + def migrate(args) do + Migrate.run(args) + end + + def rollback(args) do + Rollback.run(args) + end end diff --git a/test/tasks/actors_test.exs b/test/tasks/actors_test.exs index a67c69468..934dd33e0 100644 --- a/test/tasks/actors_test.exs +++ b/test/tasks/actors_test.exs @@ -48,7 +48,9 @@ defmodule Mix.Tasks.Mobilizon.ActorsTest do end test "show non-existing actor" do - assert_raise Mix.Error, "Error: No such actor", fn -> Show.run([@username]) end + Show.run([@username]) + assert_received {:mix_shell, :error, [message]} + assert message =~ "Error: No such actor" end end end diff --git a/test/tasks/relay_test.exs b/test/tasks/relay_test.exs index 5e64dfdd5..87f197718 100644 --- a/test/tasks/relay_test.exs +++ b/test/tasks/relay_test.exs @@ -9,6 +9,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do alias Mobilizon.Actors alias Mobilizon.Actors.{Actor, Follower} + alias Mix.Tasks.Mobilizon.Relay.{Follow, Unfollow} alias Mobilizon.Federation.ActivityPub.Relay @@ -17,7 +18,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do use_cassette "relay/fetch_relay_follow" do target_instance = "mobilizon1.com" - Mix.Tasks.Mobilizon.Relay.run(["follow", target_instance]) + Follow.run([target_instance]) local_actor = Relay.get_actor() assert local_actor.url =~ "/relay" @@ -35,7 +36,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do use_cassette "relay/fetch_relay_unfollow" do target_instance = "mobilizon1.com" - Mix.Tasks.Mobilizon.Relay.run(["follow", target_instance]) + Follow.run([target_instance]) %Actor{} = local_actor = Relay.get_actor() @@ -44,7 +45,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do assert %Follower{} = Actors.is_following(local_actor, target_actor) - Mix.Tasks.Mobilizon.Relay.run(["unfollow", target_instance]) + Unfollow.run([target_instance]) refute Actors.is_following(local_actor, target_actor) end diff --git a/test/tasks/users_test.exs b/test/tasks/users_test.exs index 10d88ba59..4c9cbc625 100644 --- a/test/tasks/users_test.exs +++ b/test/tasks/users_test.exs @@ -42,9 +42,15 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do test "create with already used email" do insert(:user, email: @email) - assert_raise Mix.Error, "User has not been created because of the above reason.", fn -> - New.run([@email]) - end + New.run([@email]) + # Debug message + assert_received {:mix_shell, :error, [message]} + + assert message =~ + "[email: {\"This email is already used.\", [constraint: :unique, constraint_name: \"users_email_index\"]}]" + + assert_received {:mix_shell, :error, [message]} + assert message =~ "User has not been created because of the above reason." end end @@ -62,7 +68,9 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do end test "delete non-existing user" do - assert_raise Mix.Error, "Error: No such user", fn -> Delete.run([@email, "-y"]) end + Delete.run([@email, "-y"]) + assert_received {:mix_shell, :error, [message]} + assert message =~ "Error: No such user" end end @@ -87,7 +95,9 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do end test "show non-existing user" do - assert_raise Mix.Error, "Error: No such user", fn -> Show.run([@email]) end + Show.run([@email]) + assert_received {:mix_shell, :error, [message]} + assert message =~ "Error: No such user" end end @@ -160,11 +170,9 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do end test "enable and disable at the same time" do - assert_raise Mix.Error, - "Can't use both --enabled and --disable options at the same time.", - fn -> - Modify.run([@email, "--disable", "--enable"]) - end + Modify.run([@email, "--disable", "--enable"]) + assert_received {:mix_shell, :error, [message]} + assert message =~ "Can't use both --enabled and --disable options at the same time." end end end From 005470ba5b609d56c9331f651266a668f92776a3 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 30 Oct 2020 15:16:33 +0100 Subject: [PATCH 13/15] Introduce the mobilizon_ctl wrapper to easily call tasks inside releases Signed-off-by: Thomas Citharel --- docker/production/Dockerfile | 12 ++++---- docker/production/docker-compose.yml | 2 +- docker/production/docker-entrypoint.sh | 9 ++++++ rel/overlays/bin/mobilizon_ctl | 41 ++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 7 deletions(-) create mode 100755 docker/production/docker-entrypoint.sh create mode 100755 rel/overlays/bin/mobilizon_ctl diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index 71abc377b..81408ef81 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -5,7 +5,7 @@ RUN apk add --no-cache python build-base COPY js . RUN yarn install \ - && yarn run build + && yarn run build # Then, build the application binary FROM elixir:alpine AS builder @@ -15,8 +15,8 @@ RUN apk add --no-cache build-base git cmake COPY mix.exs mix.lock ./ ENV MIX_ENV=prod RUN mix local.hex --force \ - && mix local.rebar --force \ - && mix deps.get + && mix local.rebar --force \ + && mix deps.get COPY lib ./lib COPY priv ./priv @@ -25,7 +25,7 @@ COPY docker/production/releases.exs ./config/ COPY --from=assets ./priv/static ./priv/static RUN mix phx.digest \ - && mix release + && mix release # Finally setup the app FROM alpine @@ -36,6 +36,6 @@ USER nobody EXPOSE 4000 COPY --from=builder --chown=nobody:nobody _build/prod/rel/mobilizon ./ +COPY docker/production/docker-entrypoint.sh ./ -ENTRYPOINT ["/bin/mobilizon"] -CMD ["start"] +ENTRYPOINT ["./docker-entrypoint.sh"] diff --git a/docker/production/docker-compose.yml b/docker/production/docker-compose.yml index 029a82871..30c3cec88 100644 --- a/docker/production/docker-compose.yml +++ b/docker/production/docker-compose.yml @@ -25,7 +25,7 @@ services: volumes: - ./public/uploads:/app/uploads ports: - - "4000:4000" + - "4000:4000" db: image: postgis/postgis diff --git a/docker/production/docker-entrypoint.sh b/docker/production/docker-entrypoint.sh new file mode 100755 index 000000000..9e118d45a --- /dev/null +++ b/docker/production/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +set -e + +echo "-- Running migrations..." +/bin/mobilizon_ctl migrate + +echo "-- Starting!" +exec /bin/mobilizon start \ No newline at end of file diff --git a/rel/overlays/bin/mobilizon_ctl b/rel/overlays/bin/mobilizon_ctl new file mode 100755 index 000000000..458de578c --- /dev/null +++ b/rel/overlays/bin/mobilizon_ctl @@ -0,0 +1,41 @@ +#!/bin/sh +# Portions of this file are derived from Pleroma: +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +if [ -z "$1" ] || [ "$1" = "help" ]; then + echo "Usage: $(basename "$0") COMMAND [ARGS] + + The known commands are: + + migrate + Execute database migrations (needs to be done after updates) + + rollback [VERSION] + Rollback database migrations (needs to be done before downgrading) + + and any mix tasks under Mobilizon namespace, for example \`mix mobilizon.user.show COMMAND\` is + equivalent to \`$(basename "$0") user.show COMMAND\` + + By default mobilizon_ctl will try calling into a running instance to execute non migration-related commands, + if for some reason this is undesired, set MOBILIZON_CTL_RPC_DISABLED environment variable. + +" +else + SCRIPT=$(readlink -f "$0") + SCRIPTPATH=$(dirname "$SCRIPT") + + FULL_ARGS="$*" + + ACTION="$1" + if [ $# -gt 0 ]; then + shift + fi + + if [ "$ACTION" = "migrate" ] || [ "$ACTION" = "rollback" ] || [ "$ACTION" = "create" ] || [ "$MOBILIZON_CTL_RPC_DISABLED" = true ]; then + "$SCRIPTPATH"/mobilizon eval 'Mobilizon.CLI.run("'"$FULL_ARGS"'")' + else + "$SCRIPTPATH"/mobilizon rpc 'Mobilizon.CLI.run("'"$FULL_ARGS"'")' + fi +fi \ No newline at end of file From 320c17a29d06a7772cf2344a4597f69368cbcea1 Mon Sep 17 00:00:00 2001 From: prichier Date: Sat, 31 Oct 2020 02:39:32 +0100 Subject: [PATCH 14/15] Fix overlay & enoent error on image uploads --- docker/production/Dockerfile | 3 ++- docker/production/README.md | 20 ++++++++++---------- docker/production/releases.exs | 5 +++++ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index 81408ef81..d4c54e2f3 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -21,6 +21,7 @@ RUN mix local.hex --force \ COPY lib ./lib COPY priv ./priv COPY config ./config +COPY rel ./rel COPY docker/production/releases.exs ./config/ COPY --from=assets ./priv/static ./priv/static @@ -30,7 +31,7 @@ RUN mix phx.digest \ # Finally setup the app FROM alpine -RUN apk add --no-cache openssl ncurses-libs +RUN apk add --no-cache openssl ncurses-libs file USER nobody EXPOSE 4000 diff --git a/docker/production/README.md b/docker/production/README.md index 7054146de..2864f43a1 100644 --- a/docker/production/README.md +++ b/docker/production/README.md @@ -35,22 +35,22 @@ Instanciate required Postgres extensions: # CREATE EXTENSION pg_trgm; # CREATE EXTENSION unaccent; - -Then run migrations: - - docker-compose run --rm mobilizon eval Mobilizon.CLI.migrate - Finally, run the application: docker-compose up -d mobilizon +A migration will be automatically run before starting Mobilizon (can be run even if no migration is needed without incidence). + +## Run a mobilizon_ctl command + + docker-compose exec mobilizon mobilizon_ctl [options] + ## Update the service -Pull the latest image, then run the migrations: +Pull the latest image, then update the service: docker-compose pull mobilizon - docker-compose run --rm mobilizon eval Mobilizon.CLI.migrate - -Finally, update the service: - docker-compose up -d mobilizon + +Migration is automatic. + diff --git a/docker/production/releases.exs b/docker/production/releases.exs index 72d84ca4e..d3284e0f9 100644 --- a/docker/production/releases.exs +++ b/docker/production/releases.exs @@ -22,6 +22,11 @@ config :mobilizon, :instance, email_from: System.get_env("MOBILIZON_INSTANCE_EMAIL", "noreply@mobilizon.lan"), email_reply_to: System.get_env("MOBILIZON_REPLY_EMAIL", "noreply@mobilizon.lan") + +config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, + uploads: System.get_env("MOBILIZON_UPLOADS", "/app/uploads") + + config :mobilizon, Mobilizon.Storage.Repo, adapter: Ecto.Adapters.Postgres, username: System.get_env("MOBILIZON_DATABASE_USERNAME", "username"), From 7c7df4eaed187ba108a0ae635808fe2a97428558 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Sat, 31 Oct 2020 12:12:43 +0100 Subject: [PATCH 15/15] Remove README.md (see Documentation) and move to framasoft/mobilizon image Signed-off-by: Thomas Citharel --- docker/production/README.md | 56 ---------------------------- docker/production/docker-compose.yml | 2 +- 2 files changed, 1 insertion(+), 57 deletions(-) delete mode 100644 docker/production/README.md diff --git a/docker/production/README.md b/docker/production/README.md deleted file mode 100644 index 2864f43a1..000000000 --- a/docker/production/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Build and deploy Mobilizon with docker - -You will need to : -- build the image -- tune the environment file -- use docker-compose to run the service - -## Build the image - - git clone https://forge.tedomum.net/tedomum/mobilizon - cd mobilizon - docker build -t mobilizon -f docker/production/Dockerfile . - -## Update the env file - - cd docker/production/ - cp env.template .env - -Edit the `.env` content with your own settings. -More settings can be added in .env, see all in docker-compose.yml file. - -You can generate `MOBILIZON_INSTANCE_SECRET_KEY_BASE` and `MOBILIZON_INSTANCE_SECRET_KEY` with: - - gpg --gen-random --armor 1 50 - -## Run the service - -Start by initializing and running the database: - - docker-compose up -d db - -Instanciate required Postgres extensions: - - docker-compose exec db psql -U - # CREATE EXTENSION pg_trgm; - # CREATE EXTENSION unaccent; - -Finally, run the application: - - docker-compose up -d mobilizon - -A migration will be automatically run before starting Mobilizon (can be run even if no migration is needed without incidence). - -## Run a mobilizon_ctl command - - docker-compose exec mobilizon mobilizon_ctl [options] - -## Update the service - -Pull the latest image, then update the service: - - docker-compose pull mobilizon - docker-compose up -d mobilizon - -Migration is automatic. - diff --git a/docker/production/docker-compose.yml b/docker/production/docker-compose.yml index 30c3cec88..7fe4824d9 100644 --- a/docker/production/docker-compose.yml +++ b/docker/production/docker-compose.yml @@ -2,7 +2,7 @@ version: "3" services: mobilizon: - image: mobilizon + image: framasoft/mobilizon environment: - MOBILIZON_INSTANCE_NAME - MOBILIZON_INSTANCE_HOST