diff --git a/config/prod.exs b/config/prod.exs index 50536850..134f655e 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -10,62 +10,9 @@ use Mix.Config # which you should run after static files are built and # before starting your production server. config :tesla_mate, TeslaMateWeb.Endpoint, - http: [:inet6, port: System.get_env("PORT") || 4000], - url: [host: "example.com", port: 80], - cache_static_manifest: "priv/static/cache_manifest.json" + cache_static_manifest: "priv/static/cache_manifest.json", + root: ".", + server: true, + version: Application.spec(:tesla_mate, :vsn) -# Do not print debug messages in production config :logger, level: :info - -# ## SSL Support -# -# To get SSL working, you will need to add the `https` key -# to the previous section and set your `:url` port to 443: -# -# config :tesla_mate, TeslaMateWeb.Endpoint, -# ... -# url: [host: "example.com", port: 443], -# https: [ -# :inet6, -# port: 443, -# cipher_suite: :strong, -# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), -# certfile: System.get_env("SOME_APP_SSL_CERT_PATH") -# ] -# -# The `cipher_suite` is set to `:strong` to support only the -# latest and more secure SSL ciphers. This means old browsers -# and clients may not be supported. You can set it to -# `:compatible` for wider support. -# -# `:keyfile` and `:certfile` expect an absolute path to the key -# and cert in disk or a relative path inside priv, for example -# "priv/ssl/server.key". For all supported SSL configuration -# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 -# -# We also recommend setting `force_ssl` in your endpoint, ensuring -# no data is ever sent via http, always redirecting to https: -# -# config :tesla_mate, TeslaMateWeb.Endpoint, -# force_ssl: [hsts: true] -# -# Check `Plug.SSL` for all available options in `force_ssl`. - -# ## Using releases (distillery) -# -# If you are doing OTP releases, you need to instruct Phoenix -# to start the server for all endpoints: -# -# config :phoenix, :serve_endpoints, true -# -# Alternatively, you can configure exactly which server to -# start per endpoint: -# -# config :tesla_mate, TeslaMateWeb.Endpoint, server: true -# -# Note you can't rely on `System.get_env/1` when using releases. -# See the releases documentation accordingly. - -# Finally import the config/prod.secret.exs which should be versioned -# separately. -import_config "prod.secret.exs" diff --git a/lib/tesla_mate/api.ex b/lib/tesla_mate/api.ex index 49bf4d6e..b4aa171d 100644 --- a/lib/tesla_mate/api.ex +++ b/lib/tesla_mate/api.ex @@ -2,12 +2,10 @@ defmodule TeslaMate.Api do use GenServer require Logger - import Core.Dependency, only: [call: 3] + alias TeslaApi.{Auth, Error, Vehicle} - defstruct auth: nil, - deps: %{} - + defstruct auth: nil alias __MODULE__, as: State @name __MODULE__ diff --git a/lib/tesla_mate/release_tasks.ex b/lib/tesla_mate/release_tasks.ex new file mode 100644 index 00000000..a278202e --- /dev/null +++ b/lib/tesla_mate/release_tasks.ex @@ -0,0 +1,69 @@ +defmodule TeslaMate.ReleaseTasks do + @start_apps [:crypto, :ssl, :postgrex, :ecto, :ecto_sql] + + def migrate(_argv) do + start_services() + run_migrations() + stop_services() + end + + def seed(_argv) do + start_services() + run_migrations() + run_seeds() + stop_services() + end + + defp repos, do: Application.get_env(:tesla_mate, :ecto_repos, []) + + defp start_services do + IO.puts("Starting dependencies..") + Enum.each(@start_apps, &Application.ensure_all_started/1) + + IO.puts("Starting repos..") + Enum.each(repos(), & &1.start_link(pool_size: 2)) + end + + defp stop_services do + IO.puts("Success!") + :init.stop() + end + + defp run_migrations do + Enum.each(repos(), &run_migrations_for/1) + end + + defp run_migrations_for(repo) do + app = Keyword.get(repo.config, :otp_app) + IO.puts("Running migrations for #{app}") + migrations_path = priv_path_for(repo, "migrations") + Ecto.Migrator.run(repo, migrations_path, :up, all: true) + end + + defp run_seeds do + Enum.each(repos(), &run_seeds_for/1) + end + + defp run_seeds_for(repo) do + seed_script = priv_path_for(repo, "seeds.exs") + + if File.exists?(seed_script) do + IO.puts("Running seed script..") + Code.eval_file(seed_script) + end + end + + defp priv_path_for(repo, filename) do + app = Keyword.get(repo.config, :otp_app) + + repo_underscore = + repo + |> Module.split() + |> List.last() + |> Macro.underscore() + + priv_dir = "#{:code.priv_dir(app)}" + + Path.join([priv_dir, repo_underscore, filename]) + end +end diff --git a/mix.exs b/mix.exs index f7f1700f..7a6bbdad 100644 --- a/mix.exs +++ b/mix.exs @@ -48,7 +48,8 @@ defmodule TeslaMate.MixProject do # {:tesla_api, git: "https://github.com/adriankumpf/tesla_api", branch: "ak/error-handling"}, {:tesla_api, path: "/Users/adrian/dev/projects/tesla_api"}, {:gen_state_machine, "~> 2.0"}, - {:ecto_enum, "~> 1.2"} + {:ecto_enum, "~> 1.2"}, + {:distillery, "~> 2.0"} ] end diff --git a/mix.lock b/mix.lock index a7828423..9eadb500 100644 --- a/mix.lock +++ b/mix.lock @@ -1,10 +1,12 @@ %{ + "artificery": {:hex, :artificery, "0.4.1", "90b1fcedb9034853b36dbea2174f9fc1172bd50b0686a723178103def5c9c1c8", [:mix], [], "hexpm"}, "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm"}, "db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, "decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"}, + "distillery": {:hex, :distillery, "2.0.12", "6e78fe042df82610ac3fa50bd7d2d8190ad287d120d3cd1682d83a44e8b34dfb", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"}, "ecto": {:hex, :ecto, "3.0.8", "9eb6a1fcfc593e6619d45ef51afe607f1554c21ca188a1cd48eecc27223567f1", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, "ecto_enum": {:hex, :ecto_enum, "1.2.0", "9ead3ee04efc4cb68a50560a9d9ebb665dd697f957f1c3df8e81bf863cf7a4e9", [:mix], [{:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"}, "ecto_sql": {:hex, :ecto_sql, "3.0.5", "7e44172b4f7aca4469f38d7f6a3da394dbf43a1bcf0ca975e958cb957becd74e", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/rel/config.exs b/rel/config.exs new file mode 100644 index 00000000..5c171380 --- /dev/null +++ b/rel/config.exs @@ -0,0 +1,42 @@ +# `MyPlugin` is the name of the plugin module. +~w(rel plugins *.exs) +|> Path.join() +|> Path.wildcard() +|> Enum.map(&Code.eval_file(&1)) + +use Mix.Releases.Config, + default_release: :default, + default_environment: :prod + +# For a full list of config options for both releases +# and environments, visit https://hexdocs.pm/distillery/config/distillery.html + +environment :prod do + set(include_erts: true) + set(include_src: false) + set(cookie: "${ERLANG_COOKIE}") + set(pre_start_hooks: "rel/pre_start_hooks") + set(vm_args: "rel/vm.args") +end + +release :tesla_mate do + set(version: current_version(:tesla_mate)) + + set( + applications: [ + :runtime_tools + ] + ) + + set( + config_providers: [ + {Mix.Releases.Config.Providers.Elixir, ["${RELEASE_ROOT_DIR}/etc/config.exs"]} + ] + ) + + set( + overlays: [ + {:copy, "rel/config/config.exs", "etc/config.exs"} + ] + ) +end diff --git a/rel/config/config.exs b/rel/config/config.exs new file mode 100644 index 00000000..b97954d4 --- /dev/null +++ b/rel/config/config.exs @@ -0,0 +1,54 @@ +use Mix.Config + +defmodule Env do + def get(key), do: System.get_env(key) + def get_boolean(key), do: key |> get() |> parse_boolean() + def get_integer(key, default \\ 0), do: key |> get() |> parse_integer(default) + def exists?(key), do: get(key) not in [nil, ""] + + defp parse_boolean("true"), do: true + defp parse_boolean("1"), do: true + defp parse_boolean(_), do: false + + defp parse_integer(value, _) when is_bitstring(value), do: String.to_integer(value) + defp parse_integer(_, default), do: default +end + +config :tesla_mate, TeslaMate.Repo, + username: Env.get("DATABASE_USER"), + password: Env.get("DATABASE_PASS"), + database: Env.get("DATABASE_NAME"), + hostname: Env.get("DATABASE_HOST"), + pool_size: 15 + +config :tesla_mate, TeslaMateWeb.Endpoint, + http: [:inet6, port: Env.get("PORT") || 4000], + url: [host: Env.get("VIRTUAL_HOST"), port: 80], + secret_key_base: Env.get("SECRET_KEY_BASE") + +config :tesla_mate, :tesla_auth, + username: Env.get("TESLA_USERNAME"), + password: Env.get("TESLA_PASSWORD") + +config :tesla_mate, :mqtt, + host: Env.get("MQTT_HOST"), + username: Env.get("MQTT_USERNAME"), + password: Env.get("MQTT_PASSWORD") + +config :logger, + backends: [LoggerTelegramBackend, :console], + level: :info, + handle_otp_reports: true, + handle_sasl_reports: false, + compile_time_purge_level: :info + +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +config :logger, :telegram, + level: :warn, + chat_id: Env.get("CHAT_ID"), + token: Env.get("TOKEN") + +IO.puts("Configured") diff --git a/rel/plugins/.gitignore b/rel/plugins/.gitignore new file mode 100644 index 00000000..4fa3b5c2 --- /dev/null +++ b/rel/plugins/.gitignore @@ -0,0 +1,3 @@ +*.* +!*.exs +!.gitignore \ No newline at end of file diff --git a/rel/pre_start_hooks/migrate b/rel/pre_start_hooks/migrate new file mode 100755 index 00000000..401a66f9 --- /dev/null +++ b/rel/pre_start_hooks/migrate @@ -0,0 +1,6 @@ +#!/bin/sh + +echo "Running migrations" +release_ctl eval --mfa "TeslaMate.ReleaseTasks.migrate/1" --argv -- "$@" +echo "Migrations run successfully" + diff --git a/rel/vm.args b/rel/vm.args new file mode 100644 index 00000000..93f34bb3 --- /dev/null +++ b/rel/vm.args @@ -0,0 +1,30 @@ +## This file provide the arguments provided to the VM at startup +## You can find a full list of flags and their behaviours at +## http://erlang.org/doc/man/erl.html + +## Name of the node +-name <%= release_name %>@127.0.0.1 + +## Cookie for distributed erlang +-setcookie <%= release.profile.cookie %> + +## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive +## (Disabled by default..use with caution!) +##-heart + +## Enable kernel poll and a few async threads +##+K true +##+A 5 +## For OTP21+, the +A flag is not used anymore, +## +SDio replace it to use dirty schedulers +##+SDio 5 + +## Increase number of concurrent ports/sockets +##-env ERL_MAX_PORTS 4096 + +## Tweak GC to run more often +##-env ERL_FULLSWEEP_AFTER 10 + +# Enable SMP automatically based on availability +# On OTP21+, this is not needed anymore. +-smp auto