Improve overall configuration and support
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
d2a4d2c0db
commit
29cf798956
22
.env.production.sample
Normal file
22
.env.production.sample
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Settings
|
||||||
|
MOBILIZON_INSTANCE_NAME="<%= instance_name %>"
|
||||||
|
MOBILIZON_INSTANCE_HOST="<%= instance_domain %>"
|
||||||
|
MOBILIZON_INSTANCE_EMAIL="<%= instance_email %>"
|
||||||
|
MOBILIZON_INSTANCE_REGISTRATIONS_OPEN=true
|
||||||
|
|
||||||
|
# API
|
||||||
|
GRAPHQL_API_ENDPOINT="https://<%= instance_domain %>"
|
||||||
|
GRAPHQL_API_FULL_PATH=""
|
||||||
|
|
||||||
|
# APP
|
||||||
|
MIX_ENV=prod
|
||||||
|
PORT=4002
|
||||||
|
MOBILIZON_LOGLEVEL="info"
|
||||||
|
MOBILIZON_SECRET="<%= instance_secret %>"
|
||||||
|
|
||||||
|
# Database
|
||||||
|
MOBILIZON_DATABASE_USERNAME="mobilizon"
|
||||||
|
MOBILIZON_DATABASE_PASSWORD="<%= database_password %>"
|
||||||
|
MOBILIZON_DATABASE_DBNAME="mobilizon_prod"
|
||||||
|
MOBILIZON_DATABASE_HOST="localhost"
|
||||||
|
MOBILIZON_DATABASE_PORT=5432
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -15,6 +15,10 @@ erl_crash.dump
|
|||||||
# variables.
|
# variables.
|
||||||
/config/*.secret.exs
|
/config/*.secret.exs
|
||||||
|
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
setup_db.psql
|
||||||
|
|
||||||
.elixir_ls
|
.elixir_ls
|
||||||
/doc
|
/doc
|
||||||
priv/static/*
|
priv/static/*
|
||||||
|
@ -63,7 +63,4 @@ config :geolix,
|
|||||||
config :arc,
|
config :arc,
|
||||||
storage: Arc.Storage.Local
|
storage: Arc.Storage.Local
|
||||||
|
|
||||||
config :email_checker,
|
|
||||||
validations: [EmailChecker.Check.Format]
|
|
||||||
|
|
||||||
config :phoenix, :format_encoders, json: Jason
|
config :phoenix, :format_encoders, json: Jason
|
||||||
|
@ -19,8 +19,20 @@ config :mobilizon, MobilizonWeb.Endpoint,
|
|||||||
host: System.get_env("MOBILIZON_HOST") || "example.com",
|
host: System.get_env("MOBILIZON_HOST") || "example.com",
|
||||||
port: 80
|
port: 80
|
||||||
],
|
],
|
||||||
|
secret_key_base:
|
||||||
|
System.get_env("MOBILIZON_SECRET") || "ThisShouldBeAVeryStrongStringPleaseReplaceMe",
|
||||||
cache_static_manifest: "priv/static/cache_manifest.json"
|
cache_static_manifest: "priv/static/cache_manifest.json"
|
||||||
|
|
||||||
|
# Configure your database
|
||||||
|
config :mobilison, Mobilizon.Repo,
|
||||||
|
adapter: Ecto.Adapters.Postgres,
|
||||||
|
username: System.get_env("MOBILIZON_DATABASE_USERNAME") || "mobilizon",
|
||||||
|
password: System.get_env("MOBILIZON_DATABASE_PASSWORD") || "mobilizon",
|
||||||
|
database: System.get_env("MOBILIZON_DATABASE_DBNAME") || "mobilizon_prod",
|
||||||
|
hostname: System.get_env("MOBILIZON_DATABASE_HOST") || "localhost",
|
||||||
|
port: System.get_env("MOBILIZON_DATABASE_PORT") || "5432",
|
||||||
|
pool_size: 15
|
||||||
|
|
||||||
config :mobilizon, Mobilizon.Mailer,
|
config :mobilizon, Mobilizon.Mailer,
|
||||||
adapter: Bamboo.SMTPAdapter,
|
adapter: Bamboo.SMTPAdapter,
|
||||||
server: "localhost",
|
server: "localhost",
|
||||||
@ -80,7 +92,3 @@ config :logger, level: System.get_env("MOBILIZON_LOGLEVEL") |> String.to_atom()
|
|||||||
#
|
#
|
||||||
# config :mobilizon, MobilizonWeb.Endpoint, server: true
|
# config :mobilizon, MobilizonWeb.Endpoint, server: true
|
||||||
#
|
#
|
||||||
|
|
||||||
# Finally import the config/prod.secret.exs
|
|
||||||
# which should be versioned separately.
|
|
||||||
import_config "prod.secret.exs"
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
API_HOST=mobilizon.tld
|
|
||||||
API_ORIGIN=https://mobilizon.tld
|
|
||||||
API_PATH=/api/v1
|
|
@ -1,3 +1,26 @@
|
|||||||
export const API_HOST = process.env.API_HOST;
|
/**
|
||||||
export const API_ORIGIN = process.env.API_ORIGIN;
|
* Host of the instance
|
||||||
export const API_PATH = process.env.API_PATH;
|
*
|
||||||
|
* Required
|
||||||
|
*
|
||||||
|
* Example: framameet.org
|
||||||
|
*/
|
||||||
|
export const MOBILIZON_INSTANCE_HOST = process.env.MOBILIZON_INSTANCE_HOST;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL on which the API is. "/api" will be added at the end
|
||||||
|
*
|
||||||
|
* Required
|
||||||
|
*
|
||||||
|
* Example: https://framameet.org
|
||||||
|
*/
|
||||||
|
export const GRAPHQL_API_ENDPOINT = process.env.GRAPHQL_API_ENDPOINT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL with path on which the API is. Replaces GRAPHQL_API_ENDPOINT if used
|
||||||
|
*
|
||||||
|
* Optional
|
||||||
|
*
|
||||||
|
* Example: https://framameet.org/api
|
||||||
|
*/
|
||||||
|
export const GRAPHQL_API_FULL_PATH = process.env.GRAPHQL_API_FULL_PATH;
|
||||||
|
@ -5,12 +5,14 @@ import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemo
|
|||||||
import { createLink } from 'apollo-absinthe-upload-link';
|
import { createLink } from 'apollo-absinthe-upload-link';
|
||||||
import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client';
|
import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client';
|
||||||
import { AUTH_TOKEN } from './constants';
|
import { AUTH_TOKEN } from './constants';
|
||||||
|
import { GRAPHQL_API_ENDPOINT, GRAPHQL_API_FULL_PATH } from './api/_entrypoint';
|
||||||
|
|
||||||
// Install the vue plugin
|
// Install the vue plugin
|
||||||
Vue.use(VueApollo);
|
Vue.use(VueApollo);
|
||||||
|
|
||||||
// Http endpoint
|
// Http endpoint
|
||||||
const httpEndpoint = process.env.VUE_APP_GRAPHQL_HTTP || 'http://localhost:4000/api';
|
const httpServer = GRAPHQL_API_ENDPOINT || 'http://localhost:4000';
|
||||||
|
const httpEndpoint = GRAPHQL_API_FULL_PATH || `${httpServer}/api`;
|
||||||
|
|
||||||
const fragmentMatcher = new IntrospectionFragmentMatcher({
|
const fragmentMatcher = new IntrospectionFragmentMatcher({
|
||||||
introspectionQueryResultData: {
|
introspectionQueryResultData: {
|
||||||
|
121
lib/mix/tasks/generate_config.ex
Normal file
121
lib/mix/tasks/generate_config.ex
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
defmodule Mix.Tasks.GenerateConfig do
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
Generate a new config
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
``mix generate_config``
|
||||||
|
|
||||||
|
This mix task is interactive, and will overwrite the environment file present at ``.env.production``.
|
||||||
|
|
||||||
|
Inspired from Pleroma own generate_config task
|
||||||
|
"""
|
||||||
|
def run(_) do
|
||||||
|
IO.puts("Answer a few questions to generate a new config\n")
|
||||||
|
|
||||||
|
override =
|
||||||
|
if File.exists?(".env.production") do
|
||||||
|
confirm("You already have an .env.production file, do you want to override it?")
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if override == true do
|
||||||
|
IO.puts("\n--- THIS WILL OVERWRITE YOUR .env.production file! ---\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
if override != false do
|
||||||
|
domain = string_required("What is your domain name? (e.g. framameet.org): ")
|
||||||
|
name = string_required("What is the name of your instance? (e.g. Framameet): ")
|
||||||
|
email = email("What's your admin email address: ")
|
||||||
|
|
||||||
|
if confirm("Is everything okay?") do
|
||||||
|
do_generate(domain, name, email)
|
||||||
|
else
|
||||||
|
IO.puts("\nYou cancelled installation\n")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
IO.puts("\nYou cancelled installation\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_generate(domain, name, email) do
|
||||||
|
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||||
|
|
||||||
|
# Try to avoid issues with some special caracters using url_encode64()
|
||||||
|
dbpass = :crypto.strong_rand_bytes(64) |> Base.url_encode64() |> binary_part(0, 64)
|
||||||
|
|
||||||
|
resultSql = EEx.eval_file("support/postgresql/setup_db.psql", database_password: dbpass)
|
||||||
|
|
||||||
|
result =
|
||||||
|
EEx.eval_file(
|
||||||
|
".env.production.sample",
|
||||||
|
instance_domain: domain,
|
||||||
|
instance_name: name,
|
||||||
|
instance_email: email,
|
||||||
|
instance_secret: secret,
|
||||||
|
database_password: dbpass
|
||||||
|
)
|
||||||
|
|
||||||
|
IO.puts("\nWriting config to .env.production.\n\nCheck it and configure your database.")
|
||||||
|
|
||||||
|
File.write(".env.production", result)
|
||||||
|
|
||||||
|
IO.puts("""
|
||||||
|
\nWriting setup_db.psql, please run it as postgres superuser, i.e.: sudo su postgres -c 'psql -f setup_db.psql'\n
|
||||||
|
You may delete the setup_db.psql file once it has been executed.
|
||||||
|
""")
|
||||||
|
|
||||||
|
File.write("setup_db.psql", resultSql)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Taken from ex_prompt
|
||||||
|
@spec confirm(String.t()) :: boolean()
|
||||||
|
defp confirm(prompt) do
|
||||||
|
answer =
|
||||||
|
String.trim(prompt)
|
||||||
|
|> Kernel.<>(" [Yn] ")
|
||||||
|
|> string()
|
||||||
|
|> String.downcase()
|
||||||
|
|
||||||
|
cond do
|
||||||
|
answer in ~w(yes y) -> true
|
||||||
|
answer in ~w(no n) -> false
|
||||||
|
true -> confirm(prompt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Taken from ex_prompt
|
||||||
|
@spec string(String.t()) :: String.t()
|
||||||
|
defp string(prompt) do
|
||||||
|
case IO.gets(prompt) do
|
||||||
|
:eof -> ""
|
||||||
|
{:error, _reason} -> ""
|
||||||
|
str -> String.trim_trailing(str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Taken from ex_prompt
|
||||||
|
@spec string_required(String.t()) :: String.t()
|
||||||
|
defp string_required(prompt) do
|
||||||
|
case string(prompt) do
|
||||||
|
"" -> string_required(prompt)
|
||||||
|
str -> str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec email(String.t(), boolean()) :: String.t()
|
||||||
|
defp email(prompt, required \\ true) do
|
||||||
|
email_value =
|
||||||
|
case required do
|
||||||
|
true -> string_required(prompt)
|
||||||
|
_ -> string(prompt)
|
||||||
|
end
|
||||||
|
|
||||||
|
case Mobilizon.Service.EmailChecker.valid?(email_value) do
|
||||||
|
false -> email(prompt, required)
|
||||||
|
_ -> email_value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -5,6 +5,7 @@ defmodule Mobilizon.Actors.User do
|
|||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Mobilizon.Actors.{Actor, User}
|
alias Mobilizon.Actors.{Actor, User}
|
||||||
|
alias Mobilizon.Service.EmailChecker
|
||||||
|
|
||||||
schema "users" do
|
schema "users" do
|
||||||
field(:email, :string)
|
field(:email, :string)
|
||||||
|
@ -27,12 +27,19 @@ defmodule MobilizonWeb.Schema do
|
|||||||
field(:summary, :string, description: "The actor's summary")
|
field(:summary, :string, description: "The actor's summary")
|
||||||
field(:preferred_username, :string, description: "The actor's preferred username")
|
field(:preferred_username, :string, description: "The actor's preferred username")
|
||||||
field(:keys, :string, description: "The actors RSA Keys")
|
field(:keys, :string, description: "The actors RSA Keys")
|
||||||
field(:manually_approves_followers, :boolean, description: "Whether the actors manually approves followers")
|
|
||||||
|
field(:manually_approves_followers, :boolean,
|
||||||
|
description: "Whether the actors manually approves followers"
|
||||||
|
)
|
||||||
|
|
||||||
field(:suspended, :boolean, description: "If the actor is suspended")
|
field(:suspended, :boolean, description: "If the actor is suspended")
|
||||||
field(:avatar_url, :string, description: "The actor's avatar url")
|
field(:avatar_url, :string, description: "The actor's avatar url")
|
||||||
field(:banner_url, :string, description: "The actor's banner url")
|
field(:banner_url, :string, description: "The actor's banner url")
|
||||||
# field(:followers, list_of(:follower))
|
# field(:followers, list_of(:follower))
|
||||||
field(:organized_events, list_of(:event), resolve: dataloader(Events), description: "A list of the events this actor has organized")
|
field(:organized_events, list_of(:event),
|
||||||
|
resolve: dataloader(Events),
|
||||||
|
description: "A list of the events this actor has organized"
|
||||||
|
)
|
||||||
|
|
||||||
# field(:memberships, list_of(:member))
|
# field(:memberships, list_of(:member))
|
||||||
field(:user, :user, description: "The user this actor is associated to")
|
field(:user, :user, description: "The user this actor is associated to")
|
||||||
@ -52,13 +59,29 @@ defmodule MobilizonWeb.Schema do
|
|||||||
field(:id, non_null(:id), description: "The user's ID")
|
field(:id, non_null(:id), description: "The user's ID")
|
||||||
field(:email, non_null(:string), description: "The user's email")
|
field(:email, non_null(:string), description: "The user's email")
|
||||||
# , resolve: dataloader(:actors))
|
# , resolve: dataloader(:actors))
|
||||||
field(:actors, non_null(list_of(:actor)), description: "The user's list of actors (identities)")
|
field(:actors, non_null(list_of(:actor)),
|
||||||
|
description: "The user's list of actors (identities)"
|
||||||
|
)
|
||||||
|
|
||||||
field(:default_actor_id, non_null(:integer), description: "The user's default actor")
|
field(:default_actor_id, non_null(:integer), description: "The user's default actor")
|
||||||
field(:confirmed_at, :datetime, description: "The datetime when the user was confirmed/activated")
|
|
||||||
field(:confirmation_sent_at, :datetime, description: "The datetime the last activation/confirmation token was sent")
|
field(:confirmed_at, :datetime,
|
||||||
|
description: "The datetime when the user was confirmed/activated"
|
||||||
|
)
|
||||||
|
|
||||||
|
field(:confirmation_sent_at, :datetime,
|
||||||
|
description: "The datetime the last activation/confirmation token was sent"
|
||||||
|
)
|
||||||
|
|
||||||
field(:confirmation_token, :string, description: "The account activation/confirmation token")
|
field(:confirmation_token, :string, description: "The account activation/confirmation token")
|
||||||
field(:reset_password_sent_at, :datetime, description: "The datetime last reset password email was sent")
|
|
||||||
field(:reset_password_token, :string, description: "The token sent when requesting password token")
|
field(:reset_password_sent_at, :datetime,
|
||||||
|
description: "The datetime last reset password email was sent"
|
||||||
|
)
|
||||||
|
|
||||||
|
field(:reset_password_token, :string,
|
||||||
|
description: "The token sent when requesting password token"
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@desc "A JWT and the associated user ID"
|
@desc "A JWT and the associated user ID"
|
||||||
|
15
lib/service/email_checker.ex
Normal file
15
lib/service/email_checker.ex
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
defmodule Mobilizon.Service.EmailChecker do
|
||||||
|
@moduledoc """
|
||||||
|
Provides a function to test emails against a "not so bad" regex
|
||||||
|
"""
|
||||||
|
|
||||||
|
@email_regex ~r/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns whether the email is valid
|
||||||
|
"""
|
||||||
|
@spec valid?(String.t()) :: boolean()
|
||||||
|
def valid?(email) do
|
||||||
|
email =~ @email_regex
|
||||||
|
end
|
||||||
|
end
|
1
mix.exs
1
mix.exs
@ -87,7 +87,6 @@ defmodule Mobilizon.Mixfile do
|
|||||||
{:dataloader, "~> 1.0"},
|
{:dataloader, "~> 1.0"},
|
||||||
{:arc, "~> 0.11.0"},
|
{:arc, "~> 0.11.0"},
|
||||||
{:arc_ecto, "~> 0.11.0"},
|
{:arc_ecto, "~> 0.11.0"},
|
||||||
{:email_checker, "~> 0.1.2"},
|
|
||||||
{:plug_cowboy, "~> 1.0"},
|
{:plug_cowboy, "~> 1.0"},
|
||||||
# Dev and test dependencies
|
# Dev and test dependencies
|
||||||
{:phoenix_live_reload, "~> 1.0", only: :dev},
|
{:phoenix_live_reload, "~> 1.0", only: :dev},
|
||||||
|
27
support/systemd/mobilizon.service
Normal file
27
support/systemd/mobilizon.service
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Mobilizon Service
|
||||||
|
After=network.target postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=mobilizon
|
||||||
|
WorkingDirectory=/home/mobilizon/mobilizon
|
||||||
|
ExecStart=/usr/local/bin/mix phx.server
|
||||||
|
ExecReload=/bin/kill $MAINPID
|
||||||
|
KillMode=process
|
||||||
|
Restart=on-failure
|
||||||
|
EnvironmentFile=/var/www/mobilizon/.env
|
||||||
|
|
||||||
|
; Some security directives.
|
||||||
|
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
|
||||||
|
PrivateTmp=true
|
||||||
|
; Mount /usr, /boot, and /etc as read-only for processes invoked by this service.
|
||||||
|
ProtectSystem=full
|
||||||
|
; Sets up a new /dev mount for the process and only adds API pseudo devices like /dev/null, /dev/zero or /dev/random but not physical devices. Disabled by default because it may not work on devices like the Raspberry Pi.
|
||||||
|
PrivateDevices=false
|
||||||
|
; Ensures that the service process and all its children can never gain new privileges through execve().
|
||||||
|
NoNewPrivileges=true
|
||||||
|
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
Alias=mobilizon.service
|
Loading…
Reference in New Issue
Block a user