From 5eb8929929d82cd974f6705aa1c0f14ed786a514 Mon Sep 17 00:00:00 2001 From: Chandra Tungathurthi <tckb@users.noreply.github.com> Date: Tue, 26 May 2020 15:09:34 +0200 Subject: [PATCH] Support for docker based self-hosting (#64) * first commit with test and compile job Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * adding 'prepare' stage Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * updated ci script to include "test" compile phase Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * adding environment variables for connecting to postgresql Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * updated ci config for postgres Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * using non-alpine version of elixir Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * re-using the 'compile' artifacts and added explict env variables for testing Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * removing redundant deps fetching from common code Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * formatting using mix.format -- beware no-code changes! Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * added release config Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * adding consistent env variable for Database Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * more cleaning up of environment variables Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Adding releases config for enabling releases Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * cleaning up env configs Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Cleaned up config and prepared config for releases Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * updated CI script with new config for test Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Added Dockerfile for creating production docker image Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Adding "docker" build job yay! Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * using non-slim version of debian and installing webpack Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Adding overlays for migrations on releases Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * restricting the docker built to master branch only Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * typo fix Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * adding "Hosting.md" to explain hosting instructions Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * removed the default comments Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Added documentation related to env variables * updated documentation and fixed typo Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * updated documentation * Bumping up elixir version as `overlays` are only supported in latest version read release notes: https://github.com/elixir-lang/elixir/releases/tag/v1.10.0 Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Adding tarball assembly during release Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * updated HOSTING.md Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Added support for db migration Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * minor corrections Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * initializing admin user Admin user has been added in the "migration" phase. A default user is automatically created in the process. One can provide the related env variables, else a new one will be automatically created for you. Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Initial base domain update - phase#1 These changes are only meant for correct operating it under self-hosting. There are many other cosmetic changes, that require updates to email, site and other places where the original website and author is used. Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Using dedicated config variable `base_domain` instead Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * adding base_domain to releases config Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * removing the dedicated config "base_domain", relying on endpoint host Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Removed the usage of "Mix" in code! It is bad practice to use "mix" module inside the code as in actual release this module is unavailable. Replacing this with a config environment variable Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Added support for SMTP via Bamboo Smtp Adapter Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Capturing SMTP errors via Sentry Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Minor updates Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * Adding junit formatter -- useful for generating test reports Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * adding documentation for default user * Resolve "Gitlab Adoption: Add supported services in "Security & Compliance"" * bumping up the debian version to fix issues fixing some vulnerabilities identified by the scanning tools * More updates for self-hosting Changes in most of the places to suit self-hosting. Although, there are some which have been left-off. Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * quick-dirty-fix! * bumping up the db connect timeout Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * bumping up the db connect timeout Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * bumping up the db connect timeout Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * bumping up timeout - skipping MRs :-/ * removing restrictions on watching for changes this stuff isn't working * Update HOSTING.md * renamed the module name * reverting formatting-whitespace changes Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * reverting the name to release Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * adding docker-compose.yml and related instructions Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * using `plausible_url` instead of assuming `https` this is because, it is much to test in local dev machines and in most cases there's already a layer above which is capable for `https` termination and http -> https upgrade Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * WIP: merging changes from upstream Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * wip: more changes * Pushing in changes from upstream Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * changes to ci for testing Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * cleaning up and finishing clickhouse integration Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me> * updating readme with hosting details --- .gitlab-ci.yml | 125 ++++++++++ .gitlab/build-scripts/docker-entrypoint.sh | 17 ++ .gitlab/build-scripts/docker.gitlab.sh | 37 +++ Dockerfile | 82 ++++++ HOSTING.md | 152 ++++++++++++ README.md | 4 +- config/config.exs | 96 ++++++- config/dev.exs | 25 +- config/prod.exs | 106 +------- config/releases.exs | 128 ++++++++++ config/test.exs | 27 +- docker-compose.yml | 79 ++++++ lib/mix/tasks/hydrate_clickhouse.ex | 1 + lib/mix/tasks/send_check_stats_emails.ex | 2 +- lib/mix/tasks/send_email_reports.ex | 3 +- lib/mix/tasks/send_site_setup_emails.ex | 6 +- lib/mix/tasks/send_trial_notifications.ex | 6 +- lib/plausible/google/api.ex | 2 +- lib/plausible/mailer.ex | 14 ++ lib/plausible_release.ex | 234 ++++++++++++++++++ .../controllers/auth_controller.ex | 4 +- .../controllers/billing_controller.ex | 4 + .../controllers/page_controller.ex | 2 +- .../controllers/stats_controller.ex | 7 +- lib/plausible_web/email.ex | 28 ++- lib/plausible_web/endpoint.ex | 10 +- lib/plausible_web/router.ex | 2 +- .../email/check_stats_email.html.eex | 4 +- .../email/create_site_email.html.eex | 4 +- .../email/site_setup_help_email.html.eex | 4 +- .../email/site_setup_success_email.html.eex | 7 +- .../email/trial_one_week_reminder.html.eex | 4 +- .../templates/email/trial_over_email.html.eex | 4 +- .../email/trial_upgrade_email.html.eex | 4 +- .../templates/email/welcome_email.html.eex | 4 +- .../templates/layout/_footer.html.eex | 4 +- .../templates/layout/_tracking.html.eex | 4 +- .../templates/page/privacy.html.eex | 2 +- .../templates/page/terms.html.eex | 2 +- .../site/custom_domain_dns_setup.html.eex | 4 +- .../templates/site/settings.html.eex | 6 +- lib/plausible_web/views/auth_view.ex | 12 + lib/plausible_web/views/billing_view.ex | 12 + lib/plausible_web/views/email_view.ex | 12 + lib/plausible_web/views/layout_view.ex | 12 + lib/plausible_web/views/page_view.ex | 12 + lib/plausible_web/views/site_view.ex | 25 +- lib/plausible_web/views/stats_view.ex | 12 + mix.exs | 37 ++- mix.lock | 33 ++- .../20190618165016_add_public_sites.exs | 3 +- rel/env.bat.eex | 6 + rel/env.sh.eex | 18 ++ rel/overlays/createdb.sh | 7 + rel/overlays/migrate.sh | 6 + rel/overlays/rollback.sh | 5 + rel/overlays/seed.sh | 5 + rel/vm.args.eex | 11 + test/test_helper.exs | 1 + 59 files changed, 1246 insertions(+), 243 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100755 .gitlab/build-scripts/docker-entrypoint.sh create mode 100644 .gitlab/build-scripts/docker.gitlab.sh create mode 100644 Dockerfile create mode 100644 HOSTING.md create mode 100644 config/releases.exs create mode 100644 docker-compose.yml create mode 100644 lib/plausible_release.ex create mode 100644 rel/env.bat.eex create mode 100644 rel/env.sh.eex create mode 100755 rel/overlays/createdb.sh create mode 100755 rel/overlays/migrate.sh create mode 100755 rel/overlays/rollback.sh create mode 100755 rel/overlays/seed.sh create mode 100644 rel/vm.args.eex diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..f7e6a0e8 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,125 @@ +include: + - template: Container-Scanning.gitlab-ci.yml + - template: License-Scanning.gitlab-ci.yml + - template: SAST.gitlab-ci.yml + +stages: + - prepare + - compile + - test + - build + - postbuild + +.commons: &elixir-commons + image: elixir:1.10.3 + cache: + key: ${CI_COMMIT_REF_SLUG} + paths: + - $CI_PROJECT_DIR/.mix + - $CI_PROJECT_DIR/priv/plts + - ~/.hex/ + before_script: + - mkdir -p $CI_PROJECT_DIR/priv/plts/ + - mix local.hex --force && mix local.rebar --force + - chmod +x .gitlab/build-scripts/* + - source .gitlab/build-scripts/docker.gitlab.sh + +deps: + <<: *elixir-commons + stage: prepare + variables: + MIX_HOME: $CI_PROJECT_DIR/.mix + script: + - mix deps.get + dependencies: [] + artifacts: + paths: + - mix.lock + - deps + +compile: + <<: *elixir-commons + stage: compile + script: + - mix compile + dependencies: + - deps + artifacts: + paths: + - mix.lock + - _build + - deps + + +license_scanning: + stage: compile + dependencies: + - deps + +sast: + stage: compile + +test:ex_unit: + <<: *elixir-commons + services: + - postgres + - name: yandex/clickhouse-server:20.3.9.70 + alias: clickhouse + stage: test + variables: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + DATABASE_URL: postgres://postgres:postgres@postgres:5432/plausible_test?currentSchema=default + CLICKHOUSE_DATABASE_HOST: clickhouse + CLICKHOUSE_DATABASE_NAME: plausible_test + MIX_HOME: $CI_PROJECT_DIR/.mix + before_script: + - apt update && apt install -y clickhouse-client + - clickhouse-client --host clickhouse --query "CREATE DATABASE IF NOT EXISTS plausible_test" + script: + - mix test --cover + coverage: '/\[TOTAL\]\s+(\d+\.\d+)%/' + dependencies: + - compile + artifacts: + reports: + junit: plausible-report.xml + +build:docker: + <<: *elixir-commons + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + stage: build + variables: + MIX_ENV: prod + MIX_HOME: $CI_PROJECT_DIR/.mix/ + APP_VERSION: $CI_COMMIT_SHORT_SHA + before_script: + - chmod +x .gitlab/build-scripts/* + - source .gitlab/build-scripts/docker.gitlab.sh + - docker_create_config + script: + - docker_build_image + dependencies: + - compile + only: + - master + +deploy:plausible: + stage: postbuild + script: + - "curl -X POST -F token=$PLAUSIBLE_DEPLOY_TOKEN -F ref=master -F variables[IMAGE_TAG]=${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA} $PLAUSIBLE_DEPLOY_PROJECT" + only: + - master + +container_scanning: + stage: postbuild + image: registry.gitlab.com/gitlab-org/security-products/analyzers/klar:$CS_MAJOR_VERSION + variables: + CS_MAJOR_VERSION: 2 + KLAR_TRACE: "true" + CLAIR_TRACE: "true" + CLAIR_OUTPUT: "medium" + CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE + CI_APPLICATION_TAG: ${CI_COMMIT_REF_SLUG}-$CI_COMMIT_SHORT_SHA diff --git a/.gitlab/build-scripts/docker-entrypoint.sh b/.gitlab/build-scripts/docker-entrypoint.sh new file mode 100755 index 00000000..b44a0135 --- /dev/null +++ b/.gitlab/build-scripts/docker-entrypoint.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -e + +chmod a+x /app/*.sh + +if [[ "$1" = 'run' ]]; then + exec gosu plausibleuser /app/bin/plausible start + +elif [[ "$1" = 'db' ]]; then + exec gosu plausibleuser /app/"$2".sh + else + exec "$@" + +fi + +exec "$@" + diff --git a/.gitlab/build-scripts/docker.gitlab.sh b/.gitlab/build-scripts/docker.gitlab.sh new file mode 100644 index 00000000..4cc663ca --- /dev/null +++ b/.gitlab/build-scripts/docker.gitlab.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +############################ +function docker_create_config() { +############################ + mkdir -p /kaniko/.docker/ + echo "###############" + echo "Logging into GitLab Container Registry with CI credentials for kaniko..." + echo "###############" + echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json + echo "" + +} + + +############################ +function docker_build_image() { +############################ + if [[ -f Dockerfile ]]; then + echo "###############" + echo "Building Dockerfile-based application..." + echo "###############" + + /kaniko/executor \ + --cache=true \ + --context ${CI_PROJECT_DIR} \ + --dockerfile ${CI_PROJECT_DIR}/Dockerfile \ + --destination ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA} \ + --destination ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}-latest \ + \ + "$@" + + else + echo "No Dockerfile found." + return 1 + fi +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..1b63605b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,82 @@ +# we can not use the pre-built tar because the distribution is +# platform specific, it makes sense to build it in the docker + +#### Builder +FROM elixir:1.10.3 as buildcontainer + +# preparation +ARG APP_VER=0.0.1 +ENV GOSU_VERSION 1.11 +ENV MIX_ENV=prod +ENV NODE_ENV=production +ENV APP_VERSION=$APP_VER + +RUN mkdir /app +WORKDIR /app + +# install build dependencies +RUN apt-get update && \ + apt-get install -y git build-essential nodejs yarn python npm --no-install-recommends && \ + npm install npm@latest -g && \ + npm install -g webpack + +RUN apt-get install -y --no-install-recommends ca-certificates wget \ + && apt-get install -y --install-recommends gnupg2 dirmngr + +# grab gosu for easy step-down from root +RUN set -x \ + && dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \ + && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \ + && wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc" \ + && export GNUPGHOME="$(mktemp -d)" \ + && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \ + && gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \ + && command -v gpgconf && gpgconf --kill all || : \ + && rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc \ + && chmod +x /usr/local/bin/gosu \ + && gosu --version \ + && gosu nobody true + +COPY config ./config +COPY assets ./assets +COPY priv ./priv +COPY lib ./lib +COPY mix.exs ./ +COPY mix.lock ./ +RUN mix local.hex --force && \ + mix local.rebar --force && \ + mix deps.get --only prod && \ + mix deps.compile + +RUN npm audit fix --prefix ./assets && \ + npm install --prefix ./assets && \ + npm run deploy --prefix ./assets && \ + mix phx.digest priv/static + +WORKDIR /app +COPY rel rel +RUN mix release plausible + + +# Main Docker Image +FROM debian:bullseye +LABEL maintainer="tckb <tckb@tgrthi.me>" +ENV LANG=C.UTF-8 + +RUN apt-get update && \ + apt-get install -y bash openssl --no-install-recommends&& \ + apt-get clean autoclean && \ + apt-get autoremove --yes && \ + rm -rf /var/lib/{apt,dpkg,cache,log}/ + +COPY .gitlab/build-scripts/docker-entrypoint.sh /entrypoint.sh + +RUN chmod a+x /entrypoint.sh && \ + useradd -d /app -u 1000 -s /bin/bash -m plausibleuser + +COPY --from=buildcontainer /usr/local/bin/gosu /usr/local/bin/gosu +COPY --from=buildcontainer /app/_build/prod/rel/plausible /app +RUN chown -R plausibleuser:plausibleuser /app +WORKDIR /app +ENTRYPOINT ["/entrypoint.sh"] +CMD ["run"] diff --git a/HOSTING.md b/HOSTING.md new file mode 100644 index 00000000..2bafd3f2 --- /dev/null +++ b/HOSTING.md @@ -0,0 +1,152 @@ +# Plausible Analytics +Self-hosting is possible based on the docker images and are automatically pushed into [Gitlab hosted docker](registry.gitlab.com/tckb-public/plausible) registry for all commits on `master` branch. +All `master-*` tags are considered to be stable and are persisted. Any other tag in the registry is considered to be for development purposes and/or unstable and are auto-deleted after a week. + + +### Building Docker image +Besides the GitlabCI, one can build docker image from [Dockerfile](./Dockerfile). + +#### Up and Running +The repo supplies with a [Docker Compose](./docker-compose.yml) file, this serves as a sample for running Plausible with Docker. +In this sample, the db migration is done by default on startup, so you need to clean the data up every time you run: + +First run +```bash +$ docker-compose up +``` + +subsequent runs-- +```bash +$ docker-compose down +$ docker volume rm plausible_db-data -f +$ docker-compose up +``` + +### Non-docker building +It is possible to create a release artifact by running a release. + +```elixir +MIX_ENV=prod mix release plausible +``` +the release will create the pre-packed artifact at `_build/prod/rel/plausible/bin/plausible`, the release will also create a tarball at `_build/prod/` for convenience. + +Note, that you have to feed in the related environment variables (see below `Environment Variables`) + +## Database Migration +On the initial setup, a migration step is necessary to create database and table schemas needed for initial bootup. +Normally, this done by mix aliases like `ecto.setup` defined in the `mix.exs`. As this not available in "released" artifact, [`plausible_migration.ex`](./lib/plausible_migration.ex) facilitates this process. +The overlay [scripts](./rel/overlays) take care of these. + +After the release, these are available under `_build/prod/rel/plausible` -- + + +```bash +_build/prod/rel/plausible/createdb.sh +_build/prod/rel/plausible/migrate.sh +_build/prod/rel/plausible/rollback.sh +_build/prod/rel/plausible/seed.sh +``` + +the same is available in the docker images as follows -- + +```bash +docker run plausible:master-12add db createdb +docker run plausible:master-12add db migrate +docker run plausible:master-12add db rollback +docker run plausible:master-12add db seed +``` + + +## Environment Variables +Plausible relies on the several services for operating, the expected environment variables are explaiend below. + +### Server +Following are the variables that can be used to configure the availability of the server. + +- HOST (*String*) + - The hosting address of the server. For running on local system, this can be set to **localhost**. In production systems, this can be your ingress host. +- PORT (*Number*) + - The port on which the server is available. +- SECRET_KEY_BASE (*String*) + - An internal secret key used by [Phoenix Framework](https://www.phoenixframework.org/). Follow the [instructions](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html#content) to generate one. +- ENVIRONMENT (*String*) + - The current running environment. _defaults to **prod**_ +- APP_VERSION (*String*) + - The version of the app running. _defaults to current docker tag_ + +### Default User Generation +For self-hosting, a default user is generated during the [Database Migration](#Database Migration) to access Plausible. To be noted that, a default user is a user whose trial period expires in 100 Years ;). +It is *highly* recommended that you configure these parameters. + +- ADMIN_USER_NAME + - The default ("admin") username. _if not provided, one will be generated for you_ +- ADMIN_USER_EMAIL + - The default ("admin") user email. _if not provided, one will be generated for you_ +- ADMIN_USER_PWD + - The default ("admin") user password. _if not provided, one will be generated for you_ + +### Mailer/SMTP Setup + +- MAILER_ADAPTER (*String*) + - The adapter used for sending out e-mails. Available: `Bamboo.PostmarkAdapter` / `Bamboo.SMTPAdapter` +- MAILER_EMAIL (*String*) + - The email id to use for as _from_ address of all communications from Plausible. + +In case of `Bamboo.SMTPAdapter` you need to supply the following variables: + +- SMTP_HOST_ADDR (*String*) + - The host address of your smtp server. +- SMTP_HOST_PORT (*Number*) + - The port of your smtp server. +- SMTP_USER_NAME (*String*) + - The username/email for smtp auth. +- SMTP_USER_PWD (*String*) + - The password for smtp auth. +- SMTP_HOST_SSL_ENABLED (*Boolean String*) + - If ssl is enabled for connecting to Smtp, _defaults to `false`_ +- SMTP_RETRIES (*Number*) + - Number of retries to make until mailer gives up. _defaults to `2`_ +- SMTP_MX_LOOKUPS_ENABLED (*Boolean String*) + - If MX lookups should be done before sending out emails. _defaults to `false`_ + +### Database + +Plausible uses postgresql as database for storing all the user-data. Use the following the variables to configure it. + +- DATABASE_URL (*String*) + - The repo Url as dictated [here](https://hexdocs.pm/ecto/Ecto.Repo.html#module-urls) +- DATABASE_POOL_SIZE (*Number*) + - A default pool size for connecting to the database, defaults to *10*, a higher number is recommended for a production system. +- DATABASE_TLS_ENABLED (*Boolean String*) + - A flag that says whether to connect to the database via TLS, read [here](https://www.postgresql.org/docs/10/ssl-tcp.html) + +For performance reasons, all the analytics events are stored in clickhouse: + +- CLICKHOUSE_DATABASE_HOST (*String*) +- CLICKHOUSE_DATABASE_NAME (*String*) +- CLICKHOUSE_DATABASE_USER (*String*) +- CLICKHOUSE_DATABASE_PASSWORD (*String*) +- CLICKHOUSE_DATABASE_POOLSIZE (*Number*) + - A default pool size for connecting to the database, defaults to *10*, a higher number is recommended for a production system. + +### External Services + +- [Google Client](https://developers.google.com/api-client-library) + - GOOGLE_CLIENT_ID + - GOOGLE_CLIENT_SECRET +- [Sentry](https://sentry.io/) + - SENTRY_DSN +- [Paddle](https://paddle.com/) + - PADDLE_VENDOR_AUTH_CODE +- [PostMark](https://postmarkapp.com/), only in case of `Bamboo.PostmarkAdapter` mail adapter. + - POSTMARK_API_KEY + +Apart from these, there are also the following integrations + +- [Twitter](https://developer.twitter.com/en/docs) + - TWITTER_CONSUMER_KEY + - TWITTER_CONSUMER_SECRET + - TWITTER_ACCESS_TOKEN + - TWITTER_ACCESS_TOKEN_SECRET +- [Slack](https://api.slack.com/messaging/webhooks) + - SLACK_WEBHOOK diff --git a/README.md b/README.md index a8d71988..9d34818c 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,7 @@ Interested? [Read more on our website](https://plausible.io) ### Can Plausible Analytics be self-hosted? -At the moment we don't provide support for easily self-hosting the code. Currently, the purpose of keeping the code open-source is to be transparent with the community about how we collect and process data. - -Making Plausible Analytics easy to self-host, providing full documentation and support for the process is something we want to see happening in the future. There is [a GitHub thread](https://github.com/plausible-insights/plausible/issues/26) you can join and engage with to follow our progress in making Plausible Analytics easy to self-host. +The purpose of keeping the code open-source is to be transparent with the community about how we collect and process however, we do provide an experimental [docker-based self hosting](./HOSTING.md) setup. Please note that this is still in *alpha* stage and care should be taken while using it for production system. ### Why is Plausible Analytics not free like Google Analytics? diff --git a/config/config.exs b/config/config.exs index 72c4ee3a..c09c04a2 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,19 +1,34 @@ -# This file is responsible for configuring your application -# and its dependencies with the aid of the Mix.Config module. -# -# This configuration file is loaded before any dependency and -# is restricted to this project. - -# General application configuration use Mix.Config config :plausible, - ecto_repos: [Plausible.Repo] + admin_user: System.get_env("ADMIN_USER_NAME", "admin"), + admin_email: System.get_env("ADMIN_USER_EMAIL", "admin@plausible.local"), + mailer_email: System.get_env("MAILER_EMAIL", "hello@plausible.local"), + admin_pwd: System.get_env("ADMIN_USER_PWD", "!@d3in"), + ecto_repos: [Plausible.Repo], + environment: System.get_env(Atom.to_string(Mix.env()), "dev") + +config :plausible, :clickhouse, + hostname: System.get_env("CLICKHOUSE_DATABASE_HOST", "localhost"), + database: System.get_env("CLICKHOUSE_DATABASE_NAME", "plausible_test"), + username: System.get_env("CLICKHOUSE_DATABASE_USER"), + password: System.get_env("CLICKHOUSE_DATABASE_PASSWORD"), + pool_size: 10 # Configures the endpoint config :plausible, PlausibleWeb.Endpoint, - url: [host: "localhost"], - secret_key_base: "/NJrhNtbyCVAsTyvtk1ZYCwfm981Vpo/0XrVwjJvemDaKC/vsvBRevLwsc6u8RCg", + url: [ + host: System.get_env("HOST", "localhost"), + port: String.to_integer(System.get_env("PORT", "8000")) + ], + http: [ + port: String.to_integer(System.get_env("PORT", "8000")) + ], + secret_key_base: + System.get_env( + "SECRET_KEY_BASE", + "/NJrhNtbyCVAsTyvtk1ZYCwfm981Vpo/0XrVwjJvemDaKC/vsvBRevLwsc6u8RCg" + ), render_errors: [ view: PlausibleWeb.ErrorView, accepts: ~w(html json) @@ -21,11 +36,13 @@ config :plausible, PlausibleWeb.Endpoint, pubsub: [name: Plausible.PubSub, adapter: Phoenix.PubSub.PG2] config :sentry, - dsn: "https://0350a42aa6234a2eaf1230866788598e@sentry.io/1382353", + dsn: System.get_env("SENTRY_DSN"), included_environments: [:prod, :staging], - environment_name: String.to_atom(Map.get(System.get_env(), "APP_ENV", "dev")), + environment_name: String.to_atom(Map.get(System.get_env(), "MIX_ENV", "dev")), enable_source_code_context: true, - root_source_code_path: File.cwd! + root_source_code_path: File.cwd!(), + tags: %{app_version: System.get_env("APP_VERSION", "0.0.1")}, + context_lines: 5 # Configures Elixir's Logger config :logger, :console, @@ -46,6 +63,7 @@ config :plausible, google_api: Plausible.Google.Api config :plausible, + # 30 minutes session_timeout: 1000 * 60 * 30, # 30 minutes session_length_minutes: 30 @@ -53,6 +71,58 @@ config :plausible, :paddle, vendor_id: "49430", vendor_auth_code: System.get_env("PADDLE_VENDOR_AUTH_CODE") +config :plausible, + Plausible.Repo, + pool_size: String.to_integer(System.get_env("DATABASE_POOL_SIZE", "10")), + timeout: 300_000, + connect_timeout: 300_000, + handshake_timeout: 300_000, + url: + System.get_env( + "DATABASE_URL", + "postgres://postgres:postgres@127.0.0.1:5432/plausible_test?currentSchema=default" + ), + ssl: false + +config :plausible, :google, + client_id: System.get_env("GOOGLE_CLIENT_ID"), + client_secret: System.get_env("GOOGLE_CLIENT_SECRET") + +config :plausible, :slack, webhook: System.get_env("SLACK_WEBHOOK") + +mailer_adapter = System.get_env("MAILER_ADAPTER", "Bamboo.PostmarkAdapter") + +case mailer_adapter do + "Bamboo.PostmarkAdapter" -> + config :plausible, Plausible.Mailer, + adapter: :"Elixir.#{mailer_adapter}", + api_key: System.get_env("POSTMARK_API_KEY") + + "Bamboo.SMTPAdapter" -> + config :plausible, Plausible.Mailer, + adapter: :"Elixir.#{mailer_adapter}", + server: System.fetch_env!("SMTP_HOST_ADDR"), + hostname: System.get_env("HOST", "localhost"), + port: System.fetch_env!("SMTP_HOST_PORT"), + username: System.fetch_env!("SMTP_USER_NAME"), + password: System.fetch_env!("SMTP_USER_PWD"), + tls: :if_available, + allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"], + ssl: System.get_env("SMTP_HOST_SSL_ENABLED") || true, + retries: System.get_env("SMTP_RETRIES") || 2, + no_mx_lookups: System.get_env("SMTP_MX_LOOKUPS_ENABLED") || true, + auth: :always + + _ -> + raise "Unknown mailer_adapter; expected SMTPAdapter or PostmarkAdapter" +end + +config :plausible, :twitter, + consumer_key: System.get_env("TWITTER_CONSUMER_KEY"), + consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET"), + token: System.get_env("TWITTER_ACCESS_TOKEN"), + token_secret: System.get_env("TWITTER_ACCESS_TOKEN_SECRET") + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index f8be6ec5..5da1a099 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -1,13 +1,7 @@ use Mix.Config -# For development, we disable any cache and enable -# debugging and code reloading. -# -# The watchers configuration can be used to run external -# watchers to your application. For example, we use it -# with webpack to recompile .js and .css sources. config :plausible, PlausibleWeb.Endpoint, - http: [port: 8000], + server: true, debug_errors: true, code_reloader: true, check_origin: false, @@ -35,21 +29,8 @@ config :logger, :console, format: "[$level] $message\n" config :phoenix, :stacktrace_depth, 20 config :phoenix, :plug_init_mode, :runtime -config :plausible, :clickhouse, - hostname: "localhost", - database: "plausible_dev", - pool_size: 10 - -config :plausible, Plausible.Repo, - username: "postgres", - password: "postgres", - database: "plausible_dev", - hostname: "localhost", - pool_size: 10 - -config :plausible, Plausible.Mailer, - adapter: Bamboo.LocalAdapter - if File.exists?("config/dev.secret.exs") do import_config "dev.secret.exs" end + +config :logger, level: :debug diff --git a/config/prod.exs b/config/prod.exs index 3691c77c..3ccd578d 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -1,105 +1,7 @@ use Mix.Config -# For production, don't forget to configure the url host -# to something meaningful, Phoenix uses this information -# when generating URLs. -# -# Note we also include the path to a cache manifest -# containing the digested version of static files. This -# manifest is generated by the `mix phx.digest` task, -# which you should run after static files are built and -# before starting your production server. -config :plausible, PlausibleWeb.Endpoint, - http: [:inet6, port: System.get_env("PORT") || 4000], - url: [host: System.get_env("HOST"), scheme: "https", port: 443], - cache_static_manifest: "priv/static/cache_manifest.json" +# For the actual-production deployments we will use releases, +# i.e., "releases.exs" is the _actual_ production config +# see "releases.exs" -# 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 :plausible, PlausibleWeb.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 :plausible, PlausibleWeb.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 :plausible, PlausibleWeb.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. -config :plausible, PlausibleWeb.Endpoint, - secret_key_base: System.get_env("SECRET_KEY_BASE") - -config :plausible, :clickhouse, - hostname: System.get_env("CLICKHOUSE_DATABASE_HOST"), - database: System.get_env("CLICKHOUSE_DATABASE_NAME"), - username: System.get_env("CLICKHOUSE_DATABASE_USER"), - password: System.get_env("CLICKHOUSE_DATABASE_PASSWORD"), - pool_size: 30 - -# Configure your database -config :plausible, Plausible.Repo, - adapter: Ecto.Adapters.Postgres, - url: System.get_env("AVIEN_DATABASE_URL"), - pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), - timeout: 10_000, - ssl: true - -config :plausible, :google, - client_id: System.get_env("GOOGLE_CLIENT_ID"), - client_secret: System.get_env("GOOGLE_CLIENT_SECRET") - -config :plausible, :slack, - webhook: System.get_env("SLACK_WEBHOOK") - -config :plausible, Plausible.Mailer, - adapter: Bamboo.PostmarkAdapter, - api_key: System.get_env("POSTMARK_API_KEY") - -config :plausible, :twitter, [ - consumer_key: System.get_env("TWITTER_CONSUMER_KEY"), - consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET"), - token: System.get_env("TWITTER_ACCESS_TOKEN"), - token_secret: System.get_env("TWITTER_ACCESS_TOKEN_SECRET") -] +import_config "releases.exs" diff --git a/config/releases.exs b/config/releases.exs new file mode 100644 index 00000000..cae7097e --- /dev/null +++ b/config/releases.exs @@ -0,0 +1,128 @@ +import Config + +### Mandatory params Start +# it is highly recommended to change this parameters in production systems +# params are made optional to facilitate smooth release + +port = System.get_env("PORT") || 8000 +host = System.get_env("HOST", "localhost") + +secret_key_base = + System.get_env( + "SECRET_KEY_BASE", + "/NJrhNtbyCVAsTyvtk1ZYCwfm981Vpo/0XrVwjJvemDaKC/vsvBRevLwsc6u8RCg" + ) + +db_pool_size = String.to_integer(System.get_env("DATABASE_POOL_SIZE", "10")) + +db_url = + System.get_env( + "DATABASE_URL", + "postgres://postgres:postgres@127.0.0.1:5432/plausible_test?currentSchema=default" + ) + +db_tls_enabled? = String.to_existing_atom(System.get_env("DATABASE_TLS_ENABLED", "false")) +admin_user = System.get_env("ADMIN_USER_NAME") +admin_email = System.get_env("ADMIN_USER_EMAIL") +admin_pwd = System.get_env("ADMIN_USER_PWD") +env = System.get_env("ENVIRONMENT", "prod") +mailer_adapter = System.get_env("MAILER_ADAPTER", "Bamboo.PostmarkAdapter") +mailer_email = System.get_env("MAILER_EMAIL", "hello@plausible.local") +app_version = System.get_env("APP_VERSION", "0.0.1") +ck_host = System.get_env("CLICKHOUSE_DATABASE_HOST", "localhost") +ck_db = System.get_env("CLICKHOUSE_DATABASE_NAME", "plausible_dev") +ck_db_user = System.get_env("CLICKHOUSE_DATABASE_USER") +ck_db_pwd = System.get_env("CLICKHOUSE_DATABASE_PASSWORD") +ck_db_pool = System.get_env("CLICKHOUSE_DATABASE_POOLSIZE") || 10 +### Mandatory params End + +sentry_dsn = System.get_env("SENTRY_DSN") +paddle_auth_code = System.get_env("PADDLE_VENDOR_AUTH_CODE") +google_cid = System.get_env("GOOGLE_CLIENT_ID") +google_secret = System.get_env("GOOGLE_CLIENT_SECRET") +slack_hook_url = System.get_env("SLACK_WEBHOOK") +twitter_consumer_key = System.get_env("TWITTER_CONSUMER_KEY") +twitter_consumer_secret = System.get_env("TWITTER_CONSUMER_SECRET") +twitter_token = System.get_env("TWITTER_ACCESS_TOKEN") +twitter_token_secret = System.get_env("TWITTER_ACCESS_TOKEN_SECRET") +postmark_api_key = System.get_env("POSTMARK_API_KEY") + +config :plausible, + admin_user: admin_user, + admin_email: admin_email, + admin_pwd: admin_pwd, + environment: env, + mailer_email: mailer_email + +config :plausible, PlausibleWeb.Endpoint, + url: [host: host, port: port], + http: [ + port: port + ], + secret_key_base: secret_key_base, + cache_static_manifest: "priv/static/cache_manifest.json", + check_origin: false, + load_from_system_env: true, + server: true, + code_reloader: false + +config :plausible, + Plausible.Repo, + pool_size: db_pool_size, + url: db_url, + adapter: Ecto.Adapters.Postgres, + ssl: db_tls_enabled? + +config :sentry, + dsn: sentry_dsn, + environment_name: env, + release: app_version, + tags: %{app_version: app_version} + +config :plausible, :paddle, vendor_auth_code: paddle_auth_code + +config :plausible, :google, + client_id: google_cid, + client_secret: google_secret + +config :plausible, :slack, webhook: slack_hook_url + +config :plausible, :clickhouse, + hostname: ck_host, + database: ck_db, + username: ck_db_user, + password: ck_db_pwd, + pool_size: ck_db_pool + +case mailer_adapter do + "Bamboo.PostmarkAdapter" -> + config :plausible, Plausible.Mailer, + adapter: :"Elixir.#{mailer_adapter}", + api_key: System.get_env("POSTMARK_API_KEY") + + "Bamboo.SMTPAdapter" -> + config :plausible, Plausible.Mailer, + adapter: :"Elixir.#{mailer_adapter}", + server: System.fetch_env!("SMTP_HOST_ADDR"), + hostname: System.get_env("HOST", "localhost"), + port: System.fetch_env!("SMTP_HOST_PORT"), + username: System.fetch_env!("SMTP_USER_NAME"), + password: System.fetch_env!("SMTP_USER_PWD"), + tls: :if_available, + allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"], + ssl: System.get_env("SMTP_HOST_SSL_ENABLED") || true, + retries: System.get_env("SMTP_RETRIES") || 2, + no_mx_lookups: System.get_env("SMTP_MX_LOOKUPS_ENABLED") || true, + auth: :always + + _ -> + raise "Unknown mailer_adapter; expected SMTPAdapter or PostmarkAdapter" +end + +config :plausible, :twitter, + consumer_key: twitter_consumer_key, + consumer_secret: twitter_consumer_secret, + token: twitter_token, + token_secret: twitter_token_secret + +config :logger, level: :warn diff --git a/config/test.exs b/config/test.exs index 337864fe..eae3c2c1 100644 --- a/config/test.exs +++ b/config/test.exs @@ -13,24 +13,23 @@ config :logger, level: :warn config :bcrypt_elixir, :log_rounds, 4 # Configure your database -config :plausible, Plausible.Repo, - username: "postgres", - password: "postgres", - database: "plausible_test", - hostname: "localhost", - pool: Ecto.Adapters.SQL.Sandbox - -config :plausible, :clickhouse, - hostname: "localhost", - database: "plausible_test", - pool_size: 10 +config :plausible, + Plausible.Repo, + pool: Ecto.Adapters.SQL.Sandbox -config :plausible, Plausible.Mailer, - adapter: Bamboo.TestAdapter +config :plausible, Plausible.Mailer, adapter: Bamboo.TestAdapter config :plausible, paddle_api: Plausible.PaddleApi.Mock, google_api: Plausible.Google.Api.Mock + +config :junit_formatter, + report_file: "report.xml", + report_dir: File.cwd!(), + print_report_file: true, + prepend_project_name?: true, + include_filename?: true + config :plausible, - session_timeout: 0 + session_timeout: 0 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..a27d2dd9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,79 @@ +# NOTE: +# This Docker-compose file is created as a sample and should not be used directly for production +# You can adjust the settings as-per your environment +version: "3.3" +services: + +# As it says, this service is a fake smtp server which accepts SMTP connections +# the inbox is available at localhost:8025, in actuality, you need to use a proper smtp server + fakesmtp_server: + container_name: fakesmtp_server + image: mailhog/mailhog:v1.0.0 + ports: + - 1025:1025 + - 8025:8025 + healthcheck: + test: echo | telnet 127.0.0.1 1025 + + + plausible_db: + container_name: plausible_db + image: postgres:9.4 + command: ["postgres", "-c", "log_statement=all", "-c", "log_destination=stderr"] + volumes: + - db-data:/var/lib/postgresql/data + environment: + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=plausible_db + - POSTGRES_USER=postgres + ports: + - 5432:5432 + + plausible_events_db: + container_name: plausible_events_db + image: yandex/clickhouse-server + ports: + - 8123:8123 + + plausible: + container_name: plausible + build: + context: . + dockerfile: ./Dockerfile + command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate &&/entrypoint.sh run" + environment: + - ENVIRONMENT=production + - PORT=8080 + - SECRET_KEY_BASE=iYb1mP5cnmY+gUxo7C/h6XMigossPhzwd8/ic6LFnQ9Y58Fl1xduSWaPq0fHDdbn + - SIGNING_SALT=PL/THF0VMOzuv1bOcldjDzYFBLryvXNs + - HOST=localhost + - DATABASE_URL=postgres://postgres:postgres@plausible_db:5432/plausible + - DATABASE_TLS_ENABLED=false + - ADMIN_USER_NAME=admin + - ADMIN_USER_EMAIL=admin@plausible.local + - ADMIN_USER_PWD=admin@1234! + - APP_VERSION=test + - MAILER_ADAPTER=Bamboo.SMTPAdapter + - SMTP_HOST_ADDR=fakesmtp_server + - SMTP_HOST_PORT=1025 + - SMTP_USER_NAME=fakeuser@plausible.local + - SMTP_USER_PWD=password + - SMTP_HOST_SSL_ENABLED=false + - SMTP_MX_LOOKUPS_ENABLED=false + - CLICKHOUSE_DATABASE_HOST=plausible_events_db + - CLICKHOUSE_DATABASE_NAME=plausible_events_db + depends_on: + - plausible_events_db + - plausible_db + - fakesmtp_server + ports: + - 80:8080 + links: + - plausible_db + - plausible_events_db + - fakesmtp_server + + +volumes: + db-data: + driver: local \ No newline at end of file diff --git a/lib/mix/tasks/hydrate_clickhouse.ex b/lib/mix/tasks/hydrate_clickhouse.ex index a8f9776b..ff687ac1 100644 --- a/lib/mix/tasks/hydrate_clickhouse.ex +++ b/lib/mix/tasks/hydrate_clickhouse.ex @@ -20,6 +20,7 @@ defmodule Mix.Tasks.HydrateClickhouse do hydrate_events(repo) end + def create_events() do ddl = """ CREATE TABLE IF NOT EXISTS events ( diff --git a/lib/mix/tasks/send_check_stats_emails.ex b/lib/mix/tasks/send_check_stats_emails.ex index 23063f8c..8532dd8d 100644 --- a/lib/mix/tasks/send_check_stats_emails.ex +++ b/lib/mix/tasks/send_check_stats_emails.ex @@ -39,7 +39,7 @@ defmodule Mix.Tasks.SendCheckStatsEmails do defp send_check_stats_email(_, user) do PlausibleWeb.Email.check_stats_email(user) - |> Plausible.Mailer.deliver_now() + |> Plausible.Mailer.send_email() Repo.insert_all("check_stats_emails", [%{ user_id: user.id, diff --git a/lib/mix/tasks/send_email_reports.ex b/lib/mix/tasks/send_email_reports.ex index a11d3100..3c1ab90f 100644 --- a/lib/mix/tasks/send_email_reports.ex +++ b/lib/mix/tasks/send_email_reports.ex @@ -91,7 +91,8 @@ defmodule Mix.Tasks.SendEmailReports do pages: pages, query: query, name: name - ) |> Plausible.Mailer.deliver_now() + ) + |> Plausible.Mailer.send_email() end defp weekly_report_sent(site, time) do diff --git a/lib/mix/tasks/send_site_setup_emails.ex b/lib/mix/tasks/send_site_setup_emails.ex index 6b855c6d..b9720cff 100644 --- a/lib/mix/tasks/send_site_setup_emails.ex +++ b/lib/mix/tasks/send_site_setup_emails.ex @@ -80,7 +80,7 @@ defmodule Mix.Tasks.SendSiteSetupEmails do defp send_create_site_email(_, user) do PlausibleWeb.Email.create_site_email(user) - |> Plausible.Mailer.deliver_now() + |> Plausible.Mailer.send_email() Repo.insert_all("create_site_emails", [%{ user_id: user.id, @@ -94,7 +94,7 @@ defmodule Mix.Tasks.SendSiteSetupEmails do defp send_setup_success_email(_, user, site) do PlausibleWeb.Email.site_setup_success(user, site) - |> Plausible.Mailer.deliver_now() + |> Plausible.Mailer.send_email() Repo.insert_all("setup_success_emails", [%{ site_id: site.id, @@ -108,7 +108,7 @@ defmodule Mix.Tasks.SendSiteSetupEmails do defp send_setup_help_email(_, user, site) do PlausibleWeb.Email.site_setup_help(user, site) - |> Plausible.Mailer.deliver_now() + |> Plausible.Mailer.send_email() Repo.insert_all("setup_help_emails", [%{ site_id: site.id, diff --git a/lib/mix/tasks/send_trial_notifications.ex b/lib/mix/tasks/send_trial_notifications.ex index 4f2bbc28..41b844f2 100644 --- a/lib/mix/tasks/send_trial_notifications.ex +++ b/lib/mix/tasks/send_trial_notifications.ex @@ -52,7 +52,7 @@ defmodule Mix.Tasks.SendTrialNotifications do defp send_one_week_reminder(_, user) do PlausibleWeb.Email.trial_one_week_reminder(user) - |> Plausible.Mailer.deliver_now() + |> Plausible.Mailer.send_email() end defp send_tomorrow_reminder(["--dry-run"], user) do @@ -63,7 +63,7 @@ defmodule Mix.Tasks.SendTrialNotifications do usage = Plausible.Billing.usage(user) PlausibleWeb.Email.trial_upgrade_email(user, "tomorrow", usage) - |> Plausible.Mailer.deliver_now() + |> Plausible.Mailer.send_email() end defp send_today_reminder(["--dry-run"], user) do @@ -74,7 +74,7 @@ defmodule Mix.Tasks.SendTrialNotifications do usage = Plausible.Billing.usage(user) PlausibleWeb.Email.trial_upgrade_email(user, "today", usage) - |> Plausible.Mailer.deliver_now() + |> Plausible.Mailer.send_email() end defp send_over_reminder(["--dry-run"], user) do diff --git a/lib/plausible/google/api.ex b/lib/plausible/google/api.ex index 21a7d1fc..417813ab 100644 --- a/lib/plausible/google/api.ex +++ b/lib/plausible/google/api.ex @@ -3,7 +3,7 @@ defmodule Plausible.Google.Api do @verified_permission_levels ["siteOwner", "siteFullUser", "siteRestrictedUser"] def authorize_url(site_id) do - if Mix.env() == :test do + if Application.get_env(:plausible, :environment) == "test" do "" else "https://accounts.google.com/o/oauth2/v2/auth?client_id=#{client_id()}&redirect_uri=#{redirect_uri()}&prompt=consent&response_type=code&access_type=offline&scope=#{@scope}&state=#{site_id}" diff --git a/lib/plausible/mailer.ex b/lib/plausible/mailer.ex index 60b06a7b..220a655f 100644 --- a/lib/plausible/mailer.ex +++ b/lib/plausible/mailer.ex @@ -1,3 +1,17 @@ defmodule Plausible.Mailer do use Bamboo.Mailer, otp_app: :plausible + + def send_email(email) do + try do + Plausible.Mailer.deliver_now(email) + rescue + error -> + Sentry.capture_exception(error, + stacktrace: __STACKTRACE__, + extra: %{extra: "Error while sending email"} + ) + + raise error + end + end end diff --git a/lib/plausible_release.ex b/lib/plausible_release.ex new file mode 100644 index 00000000..a018c67c --- /dev/null +++ b/lib/plausible_release.ex @@ -0,0 +1,234 @@ +defmodule Plausible.Release do + use Plausible.Repo + @app :plausible + @start_apps [ + :postgrex, + :ecto + ] + alias Mix.Tasks.HydrateClickhouse, as: Clickhouse + + def init_admin do + {admin_email, admin_user, admin_pwd} = + validate_admin( + {Application.get_env(:plausible, :admin_email), + Application.get_env(:plausible, :admin_user), + Application.get_env(:plausible, :admin_pwd)} + ) + + {:ok, admin} = Plausible.Auth.create_user(admin_user, admin_email) + # set the password + {:ok, admin} = Plausible.Auth.User.set_password(admin, admin_pwd) |> Repo.update() + # bump-up the trail period + admin + |> Ecto.Changeset.cast(%{trial_expiry_date: Timex.today() |> Timex.shift(years: 100)}, [ + :trial_expiry_date + ]) + |> Repo.update() + + IO.puts("Admin user created successful!") + end + + def migrate do + prepare() + Enum.each(repos(), &run_migrations_for/1) + init_admin() + IO.puts("Migrations successful!") + end + + def seed do + prepare() + # Run seed script + Enum.each(repos(), &run_seeds_for/1) + + # Signal shutdown + IO.puts("Success!") + end + + def createdb do + prepare() + do_create_db() + IO.puts("Creation of Db successful!") + end + + def rollback do + prepare() + + get_step = + IO.gets("Enter the number of steps: ") + |> String.trim() + |> Integer.parse() + + case get_step do + {int, _trailing} -> + Enum.each(repos(), fn repo -> run_rollbacks_for(repo, int) end) + IO.puts("Rollback successful!") + + :error -> + IO.puts("Invalid integer") + end + end + + ############################## + + defp validate_admin({nil, nil, nil}) do + random_user = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8) + random_pwd = :crypto.strong_rand_bytes(20) |> Base.encode64() |> binary_part(0, 20) + random_email = "#{random_user}@#{System.get_env("HOST")}" + IO.puts("generated admin user/password: #{random_email} / #{random_pwd}") + {random_email, random_user, random_pwd} + end + + defp validate_admin({admin_email, admin_user, admin_password}) do + {admin_email, admin_user, admin_password} + end + + defp repos do + Application.fetch_env!(@app, :ecto_repos) + end + + defp run_seeds_for(repo) do + # Run the seed script if it exists + seed_script = seeds_path(repo) + + if File.exists?(seed_script) do + IO.puts("Running seed script..") + Code.eval_file(seed_script) + end + end + + defp run_migrations_for(repo) do + app = Keyword.get(repo.config, :otp_app) + IO.puts("Running migrations for #{app}") + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) + end + + defp do_create_db do + for repo <- repos() do + :ok = ensure_repo_created(repo) + end + do_create_ch_db() + end + + + defp do_create_ch_db() do + db_to_create = Keyword.get(Application.get_env(:plausible, :clickhouse),:database) + + IO.puts("create #{inspect(db_to_create)} clickhouse database/tables if it doesn't exist") + + Clickhousex.query(:clickhouse, "CREATE DATABASE IF NOT EXISTS #{db_to_create}", []) + + tb_events = """ + CREATE TABLE IF NOT EXISTS #{db_to_create}.events ( + timestamp DateTime, + name String, + domain String, + user_id UInt64, + session_id UInt64, + hostname String, + pathname String, + referrer String, + referrer_source String, + initial_referrer String, + initial_referrer_source String, + country_code LowCardinality(FixedString(2)), + screen_size LowCardinality(String), + operating_system LowCardinality(String), + browser LowCardinality(String) + ) ENGINE = MergeTree() + PARTITION BY toYYYYMM(timestamp) + ORDER BY (name, domain, user_id, timestamp) + SETTINGS index_granularity = 8192 + """ + + Clickhousex.query(:clickhouse, tb_events, []) + + tb_sessions = """ + CREATE TABLE IF NOT EXISTS #{db_to_create}.sessions ( + session_id UInt64, + sign Int8, + domain String, + user_id UInt64, + hostname String, + timestamp DateTime, + start DateTime, + is_bounce UInt8, + entry_page String, + exit_page String, + pageviews Int32, + events Int32, + duration UInt32, + referrer String, + referrer_source String, + country_code LowCardinality(FixedString(2)), + screen_size LowCardinality(String), + operating_system LowCardinality(String), + browser LowCardinality(String) + ) ENGINE = CollapsingMergeTree(sign) + PARTITION BY toYYYYMM(start) + ORDER BY (domain, user_id, session_id, start) + SETTINGS index_granularity = 8192 + """ + + Clickhousex.query(:clickhouse, tb_sessions, []) + end + + + defp ensure_repo_created(repo) do + IO.puts("create #{inspect(repo)} database if it doesn't exist") + + case repo.__adapter__.storage_up(repo.config) do + :ok -> :ok + {:error, :already_up} -> :ok + {:error, term} -> {:error, term} + end + end + + defp run_rollbacks_for(repo, step) do + app = Keyword.get(repo.config, :otp_app) + IO.puts("Running rollbacks for #{app} (STEP=#{step})") + + {:ok, _, _} = + Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, all: false, step: step)) + end + + defp prepare do + IO.puts("Loading #{@app}..") + # Load the code for myapp, but don't start it + :ok = Application.load(@app) + + prepare_clickhouse() + + IO.puts("Starting dependencies..") + # Start apps necessary for executing migrations + Enum.each(@start_apps, &Application.ensure_all_started/1) + + + # Start the Repo(s) for myapp + IO.puts("Starting repos..") + Enum.each(repos(), & &1.start_link(pool_size: 2)) + + end + + defp prepare_clickhouse do + Application.ensure_all_started(:db_connection) + Application.ensure_all_started(:hackney) + Clickhousex.start_link([ + scheme: :http, + port: 8123, + name: :clickhouse, + database: "default", + hostname: Keyword.get(Application.get_env(:plausible,:clickhouse),:hostname) + ]) + end + + defp seeds_path(repo), do: priv_path_for(repo, "seeds.exs") + + defp priv_path_for(repo, filename) do + app = Keyword.get(repo.config, :otp_app) + IO.puts("App: #{app}") + repo_underscore = repo |> Module.split() |> List.last() |> Macro.underscore() + Path.join([priv_dir(app), repo_underscore, filename]) + end + + defp priv_dir(app), do: "#{:code.priv_dir(app)}" +end diff --git a/lib/plausible_web/controllers/auth_controller.ex b/lib/plausible_web/controllers/auth_controller.ex index c1053be4..ea3e0be0 100644 --- a/lib/plausible_web/controllers/auth_controller.ex +++ b/lib/plausible_web/controllers/auth_controller.ex @@ -21,7 +21,7 @@ defmodule PlausibleWeb.AuthController do url = PlausibleWeb.Endpoint.clean_url() <> "/claim-activation?token=#{token}" Logger.info(url) email_template = PlausibleWeb.Email.activation_email(user, url) - Plausible.Mailer.deliver_now(email_template) + Plausible.Mailer.send_email(email_template) conn |> render("register_success.html", email: user.email, layout: {PlausibleWeb.LayoutView, "focus.html"}) {:error, changeset} -> render(conn, "register_form.html", changeset: changeset, layout: {PlausibleWeb.LayoutView, "focus.html"}) @@ -34,7 +34,7 @@ defmodule PlausibleWeb.AuthController do case Auth.create_user(name, email) do {:ok, user} -> PlausibleWeb.Email.welcome_email(user) - |> Plausible.Mailer.deliver_now() + |> Plausible.Mailer.send_email() conn |> put_session(:current_user_id, user.id) diff --git a/lib/plausible_web/controllers/billing_controller.ex b/lib/plausible_web/controllers/billing_controller.ex index f63ed2a5..7fd51481 100644 --- a/lib/plausible_web/controllers/billing_controller.ex +++ b/lib/plausible_web/controllers/billing_controller.ex @@ -6,6 +6,10 @@ defmodule PlausibleWeb.BillingController do plug PlausibleWeb.RequireAccountPlug + def admin_email do + Application.get_env(:plausible, :admin_email) + end + def change_plan_form(conn, _params) do subscription = Billing.active_subscription_for(conn.assigns[:current_user].id) diff --git a/lib/plausible_web/controllers/page_controller.ex b/lib/plausible_web/controllers/page_controller.ex index 1011e685..8d7235b0 100644 --- a/lib/plausible_web/controllers/page_controller.ex +++ b/lib/plausible_web/controllers/page_controller.ex @@ -70,7 +70,7 @@ defmodule PlausibleWeb.PageController do end def submit_contact_form(conn, %{"text" => text, "email" => email}) do - PlausibleWeb.Email.feedback(email, text) |> Plausible.Mailer.deliver_now + PlausibleWeb.Email.feedback(email, text) |> Plausible.Mailer.send_email() render(conn, "contact_thanks.html") end diff --git a/lib/plausible_web/controllers/stats_controller.ex b/lib/plausible_web/controllers/stats_controller.ex index 42c5401f..71916976 100644 --- a/lib/plausible_web/controllers/stats_controller.ex +++ b/lib/plausible_web/controllers/stats_controller.ex @@ -6,6 +6,10 @@ defmodule PlausibleWeb.StatsController do plug PlausibleWeb.AuthorizeStatsPlug when action in [:stats, :csv_export] + def base_domain() do + PlausibleWeb.Endpoint.host() + end + def stats(conn, _params) do site = conn.assigns[:site] user = conn.assigns[:current_user] @@ -14,7 +18,7 @@ defmodule PlausibleWeb.StatsController do redirect(conn, to: "/billing/upgrade") else if Stats.has_pageviews?(site) do - demo = site.domain == "plausible.io" + demo = site.domain == base_domain() offer_email_report = get_session(conn, site.domain <> "_offer_email_report") conn @@ -108,4 +112,3 @@ defmodule PlausibleWeb.StatsController do end end end - diff --git a/lib/plausible_web/email.ex b/lib/plausible_web/email.ex index e955bb3c..bc2088d1 100644 --- a/lib/plausible_web/email.ex +++ b/lib/plausible_web/email.ex @@ -2,10 +2,18 @@ defmodule PlausibleWeb.Email do use Bamboo.Phoenix, view: PlausibleWeb.EmailView import Bamboo.PostmarkHelper + def mailer_email_from do + Application.get_env(:plausible, :mailer_email) + end + + def admin_email do + Application.get_env(:plausible, :admin_email) + end + def activation_email(user, link) do base_email() |> to(user.email) - |> from("Uku Taht <uku@plausible.io>") + |> from(mailer_email_from()) |> tag("activation-email") |> subject("Activate your Plausible free trial") |> render("activation_email.html", name: user.name, link: link) @@ -14,7 +22,7 @@ defmodule PlausibleWeb.Email do def welcome_email(user) do base_email() |> to(user) - |> from("Uku Taht <uku@plausible.io>") + |> from(mailer_email_from()) |> tag("welcome-email") |> subject("Welcome to Plausible") |> render("welcome_email.html", user: user) @@ -23,7 +31,7 @@ defmodule PlausibleWeb.Email do def create_site_email(user) do base_email() |> to(user) - |> from("Uku Taht <uku@plausible.io>") + |> from(mailer_email_from()) |> tag("create-site-email") |> subject("Your Plausible setup: Add your website details") |> render("create_site_email.html", user: user) @@ -32,7 +40,7 @@ defmodule PlausibleWeb.Email do def site_setup_help(user, site) do base_email() |> to(user) - |> from("Uku Taht <uku@plausible.io>") + |> from(mailer_email_from()) |> tag("help-email") |> subject("Your Plausible setup: Waiting for the first page views") |> render("site_setup_help_email.html", user: user, site: site) @@ -41,7 +49,7 @@ defmodule PlausibleWeb.Email do def site_setup_success(user, site) do base_email() |> to(user) - |> from("Uku Taht <uku@plausible.io>") + |> from(mailer_email_from()) |> tag("setup-success-email") |> subject("Plausible is now tracking your website stats") |> render("site_setup_success_email.html", user: user, site: site) @@ -50,7 +58,7 @@ defmodule PlausibleWeb.Email do def check_stats_email(user) do base_email() |> to(user) - |> from("Uku Taht <uku@plausible.io>") + |> from(mailer_email_from()) |> tag("check-stats-email") |> subject("Check your Plausible website stats") |> render("check_stats_email.html", user: user) @@ -59,7 +67,7 @@ defmodule PlausibleWeb.Email do def password_reset_email(email, reset_link) do base_email() |> to(email) - |> from("Uku Taht <uku@plausible.io>") + |> from(mailer_email_from()) |> tag("password-reset-email") |> subject("Plausible password reset") |> render("password_reset_email.html", reset_link: reset_link) @@ -68,7 +76,7 @@ defmodule PlausibleWeb.Email do def trial_one_week_reminder(user) do base_email() |> to(user) - |> from("Uku Taht <uku@plausible.io>") + |> from(mailer_email_from()) |> tag("trial-one-week-reminder") |> subject("Your Plausible trial expires next week") |> render("trial_one_week_reminder.html", user: user) @@ -77,7 +85,7 @@ defmodule PlausibleWeb.Email do def trial_upgrade_email(user, day, pageviews) do base_email() |> to(user) - |> from("Uku Taht <uku@plausible.io>") + |> from(mailer_email_from()) |> tag("trial-upgrade-email") |> subject("Your Plausible trial ends #{day}") |> render("trial_upgrade_email.html", user: user, day: day, pageviews: pageviews) @@ -86,7 +94,7 @@ defmodule PlausibleWeb.Email do def trial_over_email(user) do base_email() |> to(user) - |> from("Uku Taht <uku@plausible.io>") + |> from(mailer_email_from()) |> tag("trial-over-email") |> subject("Your Plausible trial has ended") |> render("trial_over_email.html", user: user) diff --git a/lib/plausible_web/endpoint.ex b/lib/plausible_web/endpoint.ex index 9eaf7d93..29c07e81 100644 --- a/lib/plausible_web/endpoint.ex +++ b/lib/plausible_web/endpoint.ex @@ -44,11 +44,11 @@ defmodule PlausibleWeb.Endpoint do def clean_url() do url = PlausibleWeb.Endpoint.url - - if Mix.env() == :prod do - URI.parse(url) |> Map.put(:port, nil) |> URI.to_string() - else - url + case Application.get_env(:plausible, :environment) do + # do not truncate the port in case of dev or test environment + env when env in ["dev", "test"] -> url + # in most deployments, there's a layer above the above + _ -> URI.parse(url) |> Map.put(:port, nil) |> URI.to_string() end end end diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex index efa3edbe..df72da24 100644 --- a/lib/plausible_web/router.ex +++ b/lib/plausible_web/router.ex @@ -29,7 +29,7 @@ defmodule PlausibleWeb.Router do plug :fetch_session end - if Mix.env == :dev do + if Application.get_env(:plausible, :environment) == "dev" do forward "/sent-emails", Bamboo.SentEmailViewerPlug end diff --git a/lib/plausible_web/templates/email/check_stats_email.html.eex b/lib/plausible_web/templates/email/check_stats_email.html.eex index 1b60ed9a..047383f4 100644 --- a/lib/plausible_web/templates/email/check_stats_email.html.eex +++ b/lib/plausible_web/templates/email/check_stats_email.html.eex @@ -2,7 +2,7 @@ Hey <%= user_salutation(@user) %>, <br /><br /> Plausible is tracking your website stats without compromising the user experience and the privacy of your visitors. <br /><br /> -<%= link("View your Plausible dashboard now", to: "https://plausible.io/") %> for the most valuable traffic insights at a glance. +<%= link("View your Plausible dashboard now", to: "#{plausible_url()}") %> for the most valuable traffic insights at a glance. <br /><br /> Do reply back to this email if you have any questions or need some guidance. <br /></br> @@ -11,4 +11,4 @@ Uku Taht <br /><br /> -- <br /><br /> -https://plausible.io +<%= plausible_url() %> diff --git a/lib/plausible_web/templates/email/create_site_email.html.eex b/lib/plausible_web/templates/email/create_site_email.html.eex index 8b3dc57a..11c08e6d 100644 --- a/lib/plausible_web/templates/email/create_site_email.html.eex +++ b/lib/plausible_web/templates/email/create_site_email.html.eex @@ -2,7 +2,7 @@ Hey <%= user_salutation(@user) %>, <br /><br /> You've activated your free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool. <br /><br /> -<%= link("Click here", to: "https://plausible.io/sites/new") %> to add your website URL, your timezone and install our one-line JavaScript snippet to start collecting visitor statistics. +<%= link("Click here", to: "#{plausible_url()}/sites/new") %> to add your website URL, your timezone and install our one-line JavaScript snippet to start collecting visitor statistics. <br /><br /> Do reply back to this email if you have any questions or need some guidance. <br /></br> @@ -11,4 +11,4 @@ Uku Taht <br /><br /> -- <br /><br /> -https://plausible.io +<%= plausible_url() %> diff --git a/lib/plausible_web/templates/email/site_setup_help_email.html.eex b/lib/plausible_web/templates/email/site_setup_help_email.html.eex index 3a405cbd..ceba95fc 100644 --- a/lib/plausible_web/templates/email/site_setup_help_email.html.eex +++ b/lib/plausible_web/templates/email/site_setup_help_email.html.eex @@ -4,7 +4,7 @@ Hey <%= user_salutation(@user) %>, You signed up for a free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool. <br /><br /> <% end %> -To finish your setup for <%= @site.domain %>, you need to install <%= link("this lightweight line of JavaScript code", to: "https://plausible.io/#{URI.encode_www_form(@site.domain)}/snippet") %> into your site to start collecting visitor statistics. +To finish your setup for <%= @site.domain %>, you need to install <%= link("this lightweight line of JavaScript code", to: "#{plausible_url()}/#{URI.encode_www_form(@site.domain)}/snippet") %> into your site to start collecting visitor statistics. <br /><br /> This Plausible script is 14 times smaller than Google Analytics script so you’ll have a fast loading site while getting all the important traffic insights on one single page. <br /><br /> @@ -15,4 +15,4 @@ Uku Taht <br /><br /> -- <br /><br /> -https://plausible.io +<%= plausible_url() %> diff --git a/lib/plausible_web/templates/email/site_setup_success_email.html.eex b/lib/plausible_web/templates/email/site_setup_success_email.html.eex index f7ef990b..98fd1c34 100644 --- a/lib/plausible_web/templates/email/site_setup_success_email.html.eex +++ b/lib/plausible_web/templates/email/site_setup_success_email.html.eex @@ -2,13 +2,13 @@ Hey <%= user_salutation(@user) %>, <br /><br /> Congrats! The Plausible script has been installed correctly on <%= link(@site.domain, to: "https://#{@site.domain}") %>. Your website traffic is now being tracked without compromising the user experience and the privacy of your visitors. <br /><br /> -<%= link("Check your stats", to: "https://plausible.io/#{URI.encode_www_form(@site.domain)}") %> +<%= link("Check your stats", to: "#{plausible_url()}/#{URI.encode_www_form(@site.domain)}") %> <br /><br /> <%= if Plausible.Billing.on_trial?(@user) do %> You're on a 30-day unlimited-use free trial with no obligations so do take your time to explore your simple and privacy-friendly website analytics dashboard. <br /><br /> <% end %> -PS: Plausible is fully open-source and our public roadmap is defined by the community. <%= link("Leave your feedback", to: "https://plausible.io/feedback") %> and have your say on metrics and features we should be adding next. +PS: Plausible is fully open-source and our public roadmap is defined by the community. <%= link("Leave your feedback", to: "#{plausible_url()}/feedback") %> and have your say on metrics and features we should be adding next. <br /><br /> Do reply back to this email if you have any questions. <br /></br> @@ -17,4 +17,5 @@ Uku Taht <br /><br /> -- <br /><br /> -https://plausible.io +<%= plausible_url() %> + diff --git a/lib/plausible_web/templates/email/trial_one_week_reminder.html.eex b/lib/plausible_web/templates/email/trial_one_week_reminder.html.eex index c2985cf5..16481807 100644 --- a/lib/plausible_web/templates/email/trial_one_week_reminder.html.eex +++ b/lib/plausible_web/templates/email/trial_one_week_reminder.html.eex @@ -4,10 +4,10 @@ Time flies! Your 30-day free trial of Plausible will end next week. <br /><br /> Over the last three weeks, I hope you got to experience the potential benefits of having website stats in a simple dashboard while respecting the privacy of your visitors, not annoying them with the cookie and privacy notices and still having a fast loading site. <br /><br /> -In order to continue receiving valuable website traffic insights at a glance, you’ll need to <%= link("Upgrade your account", to: "https://plausible.io/billing/upgrade") %>. +In order to continue receiving valuable website traffic insights at a glance, you’ll need to <%= link("Upgrade your account", to: "#{plausible_url()}/billing/upgrade") %>. <br /><br /> If you have any questions or feedback for me, feel free to reply to this email. <br /><br /> Thanks,<br /> Uku Taht<br /> -https://plausible.io +<%= plausible_url() %> diff --git a/lib/plausible_web/templates/email/trial_over_email.html.eex b/lib/plausible_web/templates/email/trial_over_email.html.eex index d5c97546..05a6352b 100644 --- a/lib/plausible_web/templates/email/trial_over_email.html.eex +++ b/lib/plausible_web/templates/email/trial_over_email.html.eex @@ -2,7 +2,7 @@ Hey <%= user_salutation(@user) %>, <br /><br /> Your free Plausible trial has now expired. Upgrade your account to continue receiving valuable website traffic insights at a glance while respecting the privacy of your visitors and still having a fast loading site. <br /><br /> -<%= link("Upgrade now", to: "https://plausible.io/billing/upgrade") %> +<%= link("Upgrade now", to: "#{plausible_url()}/billing/upgrade") %> <br /><br /> We will keep recording stats for another month to give you time to upgrade. @@ -14,4 +14,4 @@ Founder, Plausible Insights <br /><br /> -- <br /><br /> -https://plausible.io +<%= plausible_url() %> \ No newline at end of file diff --git a/lib/plausible_web/templates/email/trial_upgrade_email.html.eex b/lib/plausible_web/templates/email/trial_upgrade_email.html.eex index 5cf424f5..dc99f55d 100644 --- a/lib/plausible_web/templates/email/trial_upgrade_email.html.eex +++ b/lib/plausible_web/templates/email/trial_upgrade_email.html.eex @@ -6,11 +6,11 @@ In the last month, your account has used <%= PlausibleWeb.AuthView.delimit_integ Based on that we recommend you select the <%= suggested_plan_name(@pageviews) %> plan which runs at <%= suggested_plan_cost(@pageviews) %>. You can also go with yearly billing to get 33% off on your plan. <br /><br /> -<%= link("Upgrade now", to: "https://plausible.io/billing/upgrade") %> +<%= link("Upgrade now", to: "#{plausible_url()}/billing/upgrade") %> <br /><br /> Have a question, feedback or need some guidance? Just reply to this email to get in touch! <br /><br /> <br /><br /> Thanks,<br /> Uku Taht<br /> -https://plausible.io +<%= plausible_url() %> diff --git a/lib/plausible_web/templates/email/welcome_email.html.eex b/lib/plausible_web/templates/email/welcome_email.html.eex index 875eee08..88de79df 100644 --- a/lib/plausible_web/templates/email/welcome_email.html.eex +++ b/lib/plausible_web/templates/email/welcome_email.html.eex @@ -3,7 +3,7 @@ Hey <%= user_salutation(@user) %>, I'm building Plausible to provide a simple and ethical approach to tracking website visitors. I'm super excited to have you on board! <br /><br /> -To start collecting stats, you need to <%= link("add a site on Plausible", to: "https://plausible.io/sites/new") %>. +To start collecting stats, you need to <%= link("add a site on Plausible", to: "#{plausible_url()}/sites/new") %>. <br /><br /> Have a question, feedback or need some guidance? Do reply back to this email. <br /><br /> @@ -12,4 +12,4 @@ Uku Taht <br /><br /> -- <br /><br /> -https://plausible.io +<%= plausible_url() %> diff --git a/lib/plausible_web/templates/layout/_footer.html.eex b/lib/plausible_web/templates/layout/_footer.html.eex index 33138428..1c14f79c 100644 --- a/lib/plausible_web/templates/layout/_footer.html.eex +++ b/lib/plausible_web/templates/layout/_footer.html.eex @@ -6,8 +6,8 @@ </div> <ul class="text-center md:text-left my-4 md:m-0"> <li>Read our <a href="/blog/" class="light-text font-medium mr-4 underline">Blog</a></li> - <li>Study the <a href="https://docs.plausible.io" target="_blank" class="light-text font-medium mr-4 underline">Documentation</a></li> - <li>Check out the <a href="/plausible.io" class="light-text font-medium underline">Live Demo</a></li> + <li>Study the <%= link("Documentation", to: "https://docs.#{base_domain()}",target: "_blank",class: "light-text font-medium mr-4 underline") %></li> + <li>Check out the <%= link("Live Demo", to: "/plausible.io",class: "light-text font-medium underline") %></li> </ul> <ul class="text-center md:text-left my-4 md:m-0"> <li>Give us <a href="/feedback" target="_blank" class="light-text font-medium mr-4 underline">Feedback</a></li> diff --git a/lib/plausible_web/templates/layout/_tracking.html.eex b/lib/plausible_web/templates/layout/_tracking.html.eex index 03b58844..ddd28660 100644 --- a/lib/plausible_web/templates/layout/_tracking.html.eex +++ b/lib/plausible_web/templates/layout/_tracking.html.eex @@ -1,4 +1,4 @@ <%= if !@conn.assigns[:skip_plausible_tracking] do %> - <script async defer src="https://plausible.io/js/plausible.js"></script> + <script async defer src="<%="#{plausible_url()}/js/plausible.js"%>"></script> <script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script> -<% end %> +<% end %> \ No newline at end of file diff --git a/lib/plausible_web/templates/page/privacy.html.eex b/lib/plausible_web/templates/page/privacy.html.eex index cfc374f2..31db64ae 100644 --- a/lib/plausible_web/templates/page/privacy.html.eex +++ b/lib/plausible_web/templates/page/privacy.html.eex @@ -1,6 +1,6 @@ <div class="max-w-md mx-auto py-12 just-text"> <h1 class="text-xl font-black mb-4">Privacy Policy</h1> -<p>Your privacy is important to us. It is Plausible Insights' policy to respect your privacy regarding any information we may collect from you across our website, <a href="http://plausible.io">http://plausible.io</a>, and other sites we own and operate.</p> +<p>Your privacy is important to us. It is Plausible Analytics' policy to respect your privacy regarding any information we may collect from you across our website, <%= link(plausible_url(), to: plausible_url()) %>, and other sites we own and operate.</p> <p>We only ask for personal information when we truly need it to provide a service to you. We collect it by fair and lawful means, with your knowledge and consent. We also let you know why we’re collecting it and how it will be used.</p> <p>We only retain collected information for as long as necessary to provide you with your requested service. What data we store, we’ll protect within commercially acceptable means to prevent loss and theft, as well as unauthorised access, disclosure, copying, use or modification.</p> <p>We don’t share any personally identifying information publicly or with third-parties, except when required to by law.</p> diff --git a/lib/plausible_web/templates/page/terms.html.eex b/lib/plausible_web/templates/page/terms.html.eex index 611fa5c8..448957e2 100644 --- a/lib/plausible_web/templates/page/terms.html.eex +++ b/lib/plausible_web/templates/page/terms.html.eex @@ -1,7 +1,7 @@ <div class="max-w-2xl mx-auto leading-normal py-12 just-text"> <h1 class="text-2xl font-black mb-4">Terms of Service</h1> <h3 class="text-lg font-black">1. Terms</h3> - <p>By accessing the website at <a href="http://plausible.io">http://plausible.io</a>, you are agreeing to be bound by these terms of service, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws. If you do not agree with any of these terms, you are prohibited from using or accessing this site. The materials contained in this website are protected by applicable copyright and trademark law.</p> + <p>By accessing the website at <%= link(plausible_url(), to: plausible_url()) %>, you are agreeing to be bound by these terms of service, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws. If you do not agree with any of these terms, you are prohibited from using or accessing this site. The materials contained in this website are protected by applicable copyright and trademark law.</p> <h3 class="text-lg font-black">2. Use License</h3> <ol type="a" class="list-decimal pl-4"> <li>Permission is granted to temporarily download one copy of the materials (information or software) on Plausible Insights' website for personal, non-commercial transitory viewing only. This is the grant of a license, not a transfer of title, and under this license you may not: diff --git a/lib/plausible_web/templates/site/custom_domain_dns_setup.html.eex b/lib/plausible_web/templates/site/custom_domain_dns_setup.html.eex index dc30cba8..4fbfc1cc 100644 --- a/lib/plausible_web/templates/site/custom_domain_dns_setup.html.eex +++ b/lib/plausible_web/templates/site/custom_domain_dns_setup.html.eex @@ -3,8 +3,8 @@ <ol class="list-disc pl-4 my-4"> <li>Go to your DNS provider’s website</li> <li class="mt-4">Create a new CNAME record for <code><%= @site.custom_domain.domain %></code></li> - <li class="mt-4">Point the record to <code>custom.plausible.io.</code> (including the dot)</li> + <li class="mt-4">Point the record to <code>custom.<%= plausible_url() %>.</code> (including the dot)</li> </ol> <%= link("Done ->", to: "/sites/#{URI.encode_www_form(@site.domain)}/custom-domains/snippet", class: "button w-full mt-6") %> -</div> +</div> \ No newline at end of file diff --git a/lib/plausible_web/templates/site/settings.html.eex b/lib/plausible_web/templates/site/settings.html.eex index 4e6a4d5b..2fb4acf4 100644 --- a/lib/plausible_web/templates/site/settings.html.eex +++ b/lib/plausible_web/templates/site/settings.html.eex @@ -35,7 +35,7 @@ <%= if @site.public do %> Stats for <%= @site.domain %> are currently <b>public</b>. Anyone with the following link can view the stats: <div class="relative text-sm mt-4"> - <input type="text" id="public-link" value="https://plausible.io/<%= URI.encode_www_form(@site.domain) %>" class="transition bg-gray-100 appearance-none border border-transparent rounded w-full p-2 pr-16 text-gray-700 appearance-none focus:outline-none" /> + <input type="text" id="public-link" value="<%= base_domain() <> "/" <> URI.encode_www_form(@site.domain)%>" class="transition bg-gray-100 appearance-none border border-transparent rounded w-full p-2 pr-16 text-gray-700 appearance-none focus:outline-none" /> <a onclick="var input = document.getElementById('public-link'); input.focus(); input.select(); document.execCommand('copy');" href="javascript:void(0)" class="absolute right-0 text-indigo-700 font-bold p-2"> <svg class="feather-sm"><use xlink:href="#feather-copy" /></svg> </a> @@ -112,7 +112,7 @@ </p> <% else %> <p class="text-gray-700 mt-6"> - Select the Google Search Console property you would like to pull keyword data from. If you don't see your domain, <%= link("set it up and verify", to: "https://docs.plausible.io/google-search-console-integration#1-add-your-site-on-the-search-console", class: "text-indigo-500") %> on Search Console first. + Select the Google Search Console property you would like to pull keyword data from. If you don't see your domain, <%= link("set it up and verify", to: "https://docs.#{base_domain()}/google-search-console-integration#1-add-your-site-on-the-search-console", class: "text-indigo-500") %> on Search Console first. </p> <% end %> @@ -132,7 +132,7 @@ <%= button("Continue with Google", to: Plausible.Google.Api.authorize_url(@site.id), class: "button mt-4") %> <div class="text-gray-700 mt-8"> - NB: You also need to set up your site on <%= link("Google Search Console", to: "https://search.google.com/search-console/about") %> for the integration to work. <%= link("Read the docs", to: "https://docs.plausible.io/google-search-console-integration", class: "text-indigo-500") %> + NB: You also need to set up your site on <%= link("Google Search Console", to: "https://search.google.com/search-console/about") %> for the integration to work. <%= link("Read the docs", to: "https://docs.#{base_domain()}/google-search-console-integration", class: "text-indigo-500") %> </div> <% end %> </div> diff --git a/lib/plausible_web/views/auth_view.ex b/lib/plausible_web/views/auth_view.ex index 2c1d95dd..de7acdad 100644 --- a/lib/plausible_web/views/auth_view.ex +++ b/lib/plausible_web/views/auth_view.ex @@ -11,6 +11,18 @@ defmodule PlausibleWeb.AuthView do "free_10k" => "10k / free" } + def admin_email do + Application.get_env(:plausible, :admin_email) + end + + def base_domain do + PlausibleWeb.Endpoint.host() + end + + def plausible_url do + PlausibleWeb.Endpoint.clean_url() + end + def subscription_name(subscription) do @subscription_names[subscription.paddle_plan_id] end diff --git a/lib/plausible_web/views/billing_view.ex b/lib/plausible_web/views/billing_view.ex index 353f9b9a..9346899f 100644 --- a/lib/plausible_web/views/billing_view.ex +++ b/lib/plausible_web/views/billing_view.ex @@ -1,6 +1,18 @@ defmodule PlausibleWeb.BillingView do use PlausibleWeb, :view + def admin_email do + Application.get_env(:plausible, :admin_email) + end + + def base_domain do + PlausibleWeb.Endpoint.host() + end + + def plausible_url do + PlausibleWeb.Endpoint.clean_url() + end + def present_date(date) do Date.from_iso8601!(date) |> Timex.format!("{D} {Mshort} {YYYY}") diff --git a/lib/plausible_web/views/email_view.ex b/lib/plausible_web/views/email_view.ex index cd4dbea6..c4074c65 100644 --- a/lib/plausible_web/views/email_view.ex +++ b/lib/plausible_web/views/email_view.ex @@ -1,6 +1,18 @@ defmodule PlausibleWeb.EmailView do use PlausibleWeb, :view + def admin_email do + Application.get_env(:plausible, :admin_email) + end + + def plausible_url do + PlausibleWeb.Endpoint.clean_url() + end + + def base_domain() do + PlausibleWeb.Endpoint.host() + end + def user_salutation(user) do if user.name do String.split(user.name) |> List.first diff --git a/lib/plausible_web/views/layout_view.ex b/lib/plausible_web/views/layout_view.ex index d6b88480..4ec2b068 100644 --- a/lib/plausible_web/views/layout_view.ex +++ b/lib/plausible_web/views/layout_view.ex @@ -1,6 +1,18 @@ defmodule PlausibleWeb.LayoutView do use PlausibleWeb, :view + def admin_email do + Application.get_env(:plausible, :admin_email) + end + + def base_domain do + PlausibleWeb.Endpoint.host() + end + + def plausible_url do + PlausibleWeb.Endpoint.clean_url() + end + def home_dest(conn) do if conn.assigns[:current_user] do "/sites" diff --git a/lib/plausible_web/views/page_view.ex b/lib/plausible_web/views/page_view.ex index c4ace790..12119a80 100644 --- a/lib/plausible_web/views/page_view.ex +++ b/lib/plausible_web/views/page_view.ex @@ -1,3 +1,15 @@ defmodule PlausibleWeb.PageView do use PlausibleWeb, :view + + def admin_email do + Application.get_env(:plausible, :admin_email) + end + + def base_domain do + PlausibleWeb.Endpoint.host() + end + + def plausible_url do + PlausibleWeb.Endpoint.clean_url() + end end diff --git a/lib/plausible_web/views/site_view.ex b/lib/plausible_web/views/site_view.ex index f5e68ace..c000da93 100644 --- a/lib/plausible_web/views/site_view.ex +++ b/lib/plausible_web/views/site_view.ex @@ -1,7 +1,19 @@ defmodule PlausibleWeb.SiteView do use PlausibleWeb, :view - def goal_name(%Plausible.Goal{page_path: page_path}) when is_binary(page_path) do + def admin_email do + Application.get_env(:plausible, :admin_email) + end + + def plausible_url do + PlausibleWeb.Endpoint.clean_url() + end + + def base_domain() do + PlausibleWeb.Endpoint.host() + end + + def goal_name(%Plausible.Goal{page_path: page_path}) when is_binary(page_path) do "Visit " <> page_path end @@ -14,11 +26,12 @@ defmodule PlausibleWeb.SiteView do end def snippet(site) do - tracker = if site.custom_domain do - "https://" <> site.custom_domain.domain <> "/js/index.js" - else - "https://plausible.io/js/plausible.js" - end + tracker = + if site.custom_domain do + "https://" <> site.custom_domain.domain <> "/js/index.js" + else + "#{plausible_url()}/js/plausible.js" + end """ <script async defer data-domain="#{site.domain}" src="#{tracker}"></script> diff --git a/lib/plausible_web/views/stats_view.ex b/lib/plausible_web/views/stats_view.ex index 83110edf..96f7e713 100644 --- a/lib/plausible_web/views/stats_view.ex +++ b/lib/plausible_web/views/stats_view.ex @@ -1,6 +1,18 @@ defmodule PlausibleWeb.StatsView do use PlausibleWeb, :view + def admin_email do + Application.get_env(:plausible, :admin_email) + end + + def base_domain do + PlausibleWeb.Endpoint.host() + end + + def plausible_url do + PlausibleWeb.Endpoint.clean_url() + end + def large_number_format(n) do cond do n >= 1_000 && n < 1_000_000 -> diff --git a/mix.exs b/mix.exs index 692d2336..3ebce892 100644 --- a/mix.exs +++ b/mix.exs @@ -4,13 +4,23 @@ defmodule Plausible.MixProject do def project do [ app: :plausible, - version: "0.1.0", - elixir: "~> 1.5", - elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), + version: System.get_env("APP_VERSION", "0.0.1"), + elixir: "~> 1.10", + elixirc_paths: elixirc_paths(Mix.env()), + compilers: [:phoenix, :gettext] ++ Mix.compilers(), start_permanent: Mix.env() == :prod, aliases: aliases(), deps: deps(), - test_coverage: [tool: ExCoveralls] + test_coverage: [ + tool: ExCoveralls + ], + releases: [ + plausible: [ + include_executables_for: [:unix], + applications: [plausible: :permanent], + steps: [:assemble, :tar] + ] + ] ] end @@ -20,7 +30,16 @@ defmodule Plausible.MixProject do def application do [ mod: {Plausible.Application, []}, - extra_applications: [:logger, :sentry, :runtime_tools, :timex, :ua_inspector, :ref_inspector, :bamboo] + extra_applications: [ + :logger, + :sentry, + :runtime_tools, + :timex, + :ua_inspector, + :ref_inspector, + :bamboo, + :bamboo_smtp + ] ] end @@ -33,7 +52,8 @@ defmodule Plausible.MixProject do # Type `mix help deps` for examples and options. defp deps do [ - {:browser, "~> 0.4.3"}, # remove + # remove + {:browser, "~> 0.4.3"}, {:bcrypt_elixir, "~> 2.0"}, {:cors_plug, "~> 1.5"}, {:ecto_sql, "~> 3.0"}, @@ -47,17 +67,20 @@ defmodule Plausible.MixProject do {:phoenix_pubsub, "~> 1.1"}, {:plug_cowboy, "~> 2.0"}, {:postgrex, ">= 0.0.0"}, - {:poison, "~> 3.1"}, # Used in paddle_api, can remove + # Used in paddle_api, can remove + {:poison, "~> 3.1"}, {:ref_inspector, "~> 1.3"}, {:timex, "~> 3.6"}, {:ua_inspector, "~> 0.18"}, {:bamboo, "~> 1.3"}, {:bamboo_postmark, "~> 0.5"}, + {:bamboo_smtp, "~> 2.1.0"}, {:sentry, "~> 7.0"}, {:httpoison, "~> 1.4"}, {:ex_machina, "~> 2.3", only: :test}, {:excoveralls, "~> 0.10", only: :test}, {:double, "~> 0.7.0", only: :test}, + {:junit_formatter, "~> 3.1", only: [:test]}, {:joken, "~> 2.0"}, {:php_serializer, "~> 0.9.0"}, {:csv, "~> 2.3"}, diff --git a/mix.lock b/mix.lock index 6cb18970..8b0bb239 100644 --- a/mix.lock +++ b/mix.lock @@ -1,11 +1,10 @@ %{ - "bamboo": {:hex, :bamboo, "1.4.0", "7b9201c49a843e4802061cf45692405b2c00efcf1cebf8b7b64f015ead072392", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b9cad03bf38c7f37b6308876039355665b6ce09fefb46dc529cef4def912cffa"}, + "bamboo": {:hex, :bamboo, "1.5.0", "1926107d58adba6620450f254dfe8a3686637a291851fba125686fa8574842af", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d5f3d04d154e80176fd685e2531e73870d8700679f14d25a567e448abce6298d"}, "bamboo_postmark": {:hex, :bamboo_postmark, "0.6.0", "429ee3153497e2f1081f8741242450be13cdca52e2c56166e8eda5ebfcb23c0a", [:mix], [{:bamboo, ">= 1.2.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:hackney, ">= 1.6.5", [hex: :hackney, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "badb3c7677440f641d920e0900ff0cd13c01d517e76562aa5c00e560f46f36ba"}, - "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"}, + "bamboo_smtp": {:hex, :bamboo_smtp, "2.1.0", "4be58f3c51d9f7875dc169ae58a1d2f08e5b718bf3895f70d130548c0598f422", [:mix], [{:bamboo, "~> 1.2", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 0.15.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm", "0aad00ef93d0e0c83a0e1ca6998fea070c8a720a990fbda13ce834136215ee49"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.2.0", "3df902b81ce7fa8867a2ae30d20a1da6877a2c056bfb116fd0bc8a5f0190cea4", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "762be3fcb779f08207531bc6612cca480a338e4b4357abb49f5ce00240a77d1e"}, "browser": {:hex, :browser, "0.4.4", "bd6436961a6b2299c6cb38d0e49761c1161d869cd0db46369cef2bf6b77c3665", [:mix], [{:plug, "~> 1.2", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d476ca309d4a4b19742b870380390aabbcb323c1f6f8745e2da2dfd079b4f8d7"}, "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, - "clickhouse_ecto": {:git, "git@github.com:appodeal/clickhouse_ecto.git", "c4fa1c3d2b73e4be698e205ad6e9ace22ac23f7d", []}, "clickhousex": {:git, "https://github.com/atlas-forks/clickhousex.git", "e010c4eaa6cb6b659e44790a3bea2ec7703ceb31", []}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, @@ -17,50 +16,50 @@ "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"}, "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, "double": {:hex, :double, "0.7.0", "a7ee4c3488a0acc6d2ad9b69b6c7d3ddf3da2b54488d0f7c2d6ceb3a995887ca", [:mix], [], "hexpm", "f0c387a2266b4452da7bab03598feec11aef8b2acab061ea947dae81bb257329"}, - "ecto": {:hex, :ecto, "3.4.2", "6890af71025769bd27ef62b1ed1925cfe23f7f0460bcb3041da4b705215ff23e", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3959b8a83e086202a4bd86b4b5e6e71f9f1840813de14a57d502d3fc2ef7132"}, - "ecto_sql": {:hex, :ecto_sql, "3.4.2", "3d842665a81ba2137b62aa70151afe81dae44824cd09b2076a255937ab4e2dc9", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f2b064102467e1525314a464b6fea0707ff28ee132a15006727ccf51b73492ff"}, + "ecto": {:hex, :ecto, "3.4.4", "a2c881e80dc756d648197ae0d936216c0308370332c5e77a2325a10293eef845", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4bd3ad62abc3b21fb629f0f7a3dab23a192fca837d257dd08449fba7373561"}, + "ecto_sql": {:hex, :ecto_sql, "3.4.4", "d28bac2d420f708993baed522054870086fd45016a9d09bb2cd521b9c48d32ea", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb49af715dd72f213b66adfd0f668a43c17ed510b5d9ac7528569b23af57fe8"}, "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, "ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"}, "excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"}, - "extwitter": {:hex, :extwitter, "0.11.0", "9472e19f1711bc60bc7efa594353164532475d7c47ea9f1bb66d4faa889b079e", [:mix], [{:oauther, "~> 1.1", [hex: :oauther, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, - "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"}, + "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, + "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"}, "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"}, "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"}, "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"}, + "junit_formatter": {:hex, :junit_formatter, "3.1.0", "3f69c61c5413750f9c45e367d77aabbeac9b395acf478d8e70b4ee9d1989c709", [:mix], [], "hexpm", "da52401a93f711fc4f77ffabdda68f9a16fcad5d96f5fce4ae606ab1d73b72f4"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "myxql": {:hex, :myxql, "0.4.0", "d95582db9e4b4707eb3a6a7002b8869a5240247931775f82d811ad450ca06503", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.3", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "27a7ebaf7822cf7c89ea796371e70b942c8ec5e2c4eedcb8cd8e043f014014ad"}, "nanoid": {:hex, :nanoid, "2.0.2", "f3f7b4bf103ab6667f22beb00b6315825ee3f30100dd2c93d534e5c02164e857", [:mix], [], "hexpm", "3095cb1fac7bbc78843a8ccd99f1af375d0da1d3ebaa8552e846b73438c0c44f"}, "oauther": {:hex, :oauther, "1.1.1", "7d8b16167bb587ecbcddd3f8792beb9ec3e7b65c1f8ebd86b8dd25318d535752", [:mix], [], "hexpm", "9374f4302045321874cccdc57eb975893643bd69c3b22bf1312dab5f06e5788e"}, "parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm", "639b2e8749e11b87b9eb42f2ad325d161c170b39b288ac8d04c4f31f8f0823eb"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, - "phoenix": {:hex, :phoenix, "1.4.16", "2cbbe0c81e6601567c44cc380c33aa42a1372ac1426e3de3d93ac448a7ec4308", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "856cc1a032fa53822737413cf51aa60e750525d7ece7d1c0576d90d7c0f05c24"}, + "phoenix": {:hex, :phoenix, "1.4.17", "1b1bd4cff7cfc87c94deaa7d60dd8c22e04368ab95499483c50640ef3bd838d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a8e5d7a3d76d452bb5fb86e8b7bd115f737e4f8efe202a463d4aeb4a5809611"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, - "phoenix_html": {:hex, :phoenix_html, "2.14.1", "7dabafadedb552db142aacbd1f11de1c0bbaa247f90c449ca549d5e30bbc66b4", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "536d5200ad37fecfe55b3241d90b7a8c3a2ca60cd012fc065f776324fa9ab0a9"}, - "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.1", "274a4b07c4adbdd7785d45a8b0bb57634d0b4f45b18d2c508b26c0344bd59b8f", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "41b4103a2fa282cfd747d377233baf213c648fdcc7928f432937676532490eee"}, + "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.2", "38d94c30df5e2ef11000697a4fbe2b38d0fbf79239d492ff1be87bbc33bc3a84", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "a3dec3d28ddb5476c96a7c8a38ea8437923408bc88da43e5c45d97037b396280"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"}, "php_serializer": {:hex, :php_serializer, "0.9.2", "59c5fd6bd3096671fd89358fb8229341ac7423b50ad8d45a15213b02ea2edab2", [:mix], [], "hexpm", "34eb835a460944f7fc216773b363c02e7dcf8ac0390c9e9ccdbd92b31a7ca59a"}, - "plug": {:hex, :plug, "1.10.0", "6508295cbeb4c654860845fb95260737e4a8838d34d115ad76cd487584e2fc4d", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "422a9727e667be1bf5ab1de03be6fa0ad67b775b2d84ed908f3264415ef29d4a"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"}, + "plug": {:hex, :plug, "1.10.1", "c56a6d9da7042d581159bcbaef873ba9d87f15dce85420b0d287bca19f40f9bd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b5cd52259817eb8a31f2454912ba1cff4990bca7811918878091cb2ab9e52cb8"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.2.1", "fcf58aa33227a4322a050e4783ee99c63c031a2e7f9a2eb7340d55505e17f30f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b43de24460d87c0971887286e7a20d40462e48eb7235954681a20cee25ddeb6"}, "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, - "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"}, + "postgrex": {:hex, :postgrex, "0.15.4", "5d691c25fc79070705a2ff0e35ce0822b86a0ee3c6fdb7a4fb354623955e1aed", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "306515b9d975fcb2478dc337a1d27dc3bf8af7cd71017c333fe9db3a3d211b0a"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, - "ref_inspector": {:hex, :ref_inspector, "1.3.0", "a02b89647440d084f2867ecece7a99895bcd4683482397fe086508bb22a165f3", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "d2069ae6b371112ac696a3cd116fd1e08d5726249b8d1357f377e67f0716cc10"}, + "ref_inspector": {:hex, :ref_inspector, "1.3.1", "bb0489a4c4299dcd633f2b7a60c41a01f5590789d0b28225a60be484e1fbe777", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "3172eb1b08e5c69966f796e3fe0e691257546fa143a5eb0ecc18a6e39b233854"}, "sentry": {:hex, :sentry, "7.2.4", "b5bc90b594d40c2e653581e797a5fd2fdf994f2568f6bd66b7fa4971598be8d5", [:mix], [{:hackney, "~> 1.8 or 1.6.5", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "4ee4d368b5013076afcc8b73ed028bdc8ee9db84ea987e3591101e194c1fc24b"}, "siphash": {:hex, :siphash, "3.2.0", "ec03fd4066259218c85e2a4b8eec4bb9663bc02b127ea8a0836db376ba73f2ed", [:make, :mix], [], "hexpm", "ba3810701c6e95637a745e186e8a4899087c3b079ba88fb8f33df054c3b0b7c3"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, - "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"}, + "timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"}, "tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"}, "ua_inspector": {:hex, :ua_inspector, "0.20.0", "01939baf5706f7d6c2dc0affbbd7f5e14309ba43ebf8967aa6479ee2204f23bc", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.0", [hex: :poolboy, repo: "hexpm", optional: false]}, {:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "30e8623b9f55e7d58be12fc2afd50be8792ec14192c289701d3cc93ad6027f26"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, - "yamerl": {:hex, :yamerl, "0.7.0", "e51dba652dce74c20a88294130b48051ebbbb0be7d76f22de064f0f3ccf0aaf5", [:rebar3], [], "hexpm", "cb5a4481e2e2ad36db83bd9962153e1a9208e2b2484185e33fc2caac6a50b108"}, + "yamerl": {:hex, :yamerl, "0.8.0", "8214cfe16bbabe5d1d6c14a14aea11c784b9a21903dd6a7c74f8ce180adae5c7", [:rebar3], [], "hexpm", "010634477bf9c208a0767dcca89116c2442cf0b5e87f9c870f85cd1c3e0c2aab"}, } diff --git a/priv/repo/migrations/20190618165016_add_public_sites.exs b/priv/repo/migrations/20190618165016_add_public_sites.exs index a92d2acd..ccc64193 100644 --- a/priv/repo/migrations/20190618165016_add_public_sites.exs +++ b/priv/repo/migrations/20190618165016_add_public_sites.exs @@ -1,11 +1,12 @@ defmodule Plausible.Repo.Migrations.AddPublicSites do use Ecto.Migration + @host Application.get_env(:plausible, :url, :host) def change do alter table(:sites) do add :public, :boolean, null: false, default: false end - execute "update sites set public=true where domain='plausible.io'" + execute "update sites set public=true where domain='#{@host}'" end end diff --git a/rel/env.bat.eex b/rel/env.bat.eex new file mode 100644 index 00000000..22be2800 --- /dev/null +++ b/rel/env.bat.eex @@ -0,0 +1,6 @@ +@echo off +rem Set the release to work across nodes. If using the long name format like +rem the one below (my_app@127.0.0.1), you need to also uncomment the +rem RELEASE_DISTRIBUTION variable below. Must be "sname", "name" or "none". +rem set RELEASE_DISTRIBUTION=name +rem set RELEASE_NODE=<%= @release.name %>@127.0.0.1 diff --git a/rel/env.sh.eex b/rel/env.sh.eex new file mode 100644 index 00000000..382cd833 --- /dev/null +++ b/rel/env.sh.eex @@ -0,0 +1,18 @@ +#!/bin/sh + +# Sets and enables heart (recommended only in daemon mode) +# case $RELEASE_COMMAND in +# daemon*) +# HEART_COMMAND="$RELEASE_ROOT/bin/$RELEASE_NAME $RELEASE_COMMAND" +# export HEART_COMMAND +# export ELIXIR_ERL_OPTIONS="-heart" +# ;; +# *) +# ;; +# esac + +# Set the release to work across nodes. If using the long name format like +# the one below (my_app@127.0.0.1), you need to also uncomment the +# RELEASE_DISTRIBUTION variable below. Must be "sname", "name" or "none". +# export RELEASE_DISTRIBUTION=name +# export RELEASE_NODE=<%= @release.name %>@127.0.0.1 diff --git a/rel/overlays/createdb.sh b/rel/overlays/createdb.sh new file mode 100755 index 00000000..21a221f0 --- /dev/null +++ b/rel/overlays/createdb.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# Creates the database if needed + + +BIN_DIR=`dirname "$0"` + +${BIN_DIR}/bin/plausible eval Plausible.Release.createdb \ No newline at end of file diff --git a/rel/overlays/migrate.sh b/rel/overlays/migrate.sh new file mode 100755 index 00000000..d77c1ece --- /dev/null +++ b/rel/overlays/migrate.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# starts the db migration + +BIN_DIR=`dirname "$0"` + +${BIN_DIR}/bin/plausible eval Plausible.Release.migrate \ No newline at end of file diff --git a/rel/overlays/rollback.sh b/rel/overlays/rollback.sh new file mode 100755 index 00000000..db69eba6 --- /dev/null +++ b/rel/overlays/rollback.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +BIN_DIR=`dirname "$0"` + +${BIN_DIR}/bin/plausible eval Plausible.Release.rollback \ No newline at end of file diff --git a/rel/overlays/seed.sh b/rel/overlays/seed.sh new file mode 100755 index 00000000..1791f86d --- /dev/null +++ b/rel/overlays/seed.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +BIN_DIR=`dirname "$0"` + +${BIN_DIR}/bin/plausible eval Plausible.Release.seed \ No newline at end of file diff --git a/rel/vm.args.eex b/rel/vm.args.eex new file mode 100644 index 00000000..957d9832 --- /dev/null +++ b/rel/vm.args.eex @@ -0,0 +1,11 @@ +## Customize flags given to the VM: http://erlang.org/doc/man/erl.html +## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here + +## Number of dirty schedulers doing IO work (file, sockets, and others) +##+SDio 5 + +## Increase number of concurrent ports/sockets +##+Q 65536 + +## Tweak GC to run more often +##-env ERL_FULLSWEEP_AFTER 10 diff --git a/test/test_helper.exs b/test/test_helper.exs index 884d9340..3b76c7e3 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,5 @@ {:ok, _} = Application.ensure_all_started(:ex_machina) +ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter]) Plausible.Test.ClickhouseSetup.run() ExUnit.start() Application.ensure_all_started(:double) -- GitLab