Skip to content

Commit

Permalink
Self hosted improvements (#603)
Browse files Browse the repository at this point in the history
* Introduce SELFHOST config. Enable cron by default for self hosters

* Fix cron, disable custom domain for selfhost

* Add github links

* Add notice about google search console on self hosted

* Enfore BASE_URL

* Set all selfhost users' trial expiry to 100 years from now

* Fix admin user creation
  • Loading branch information
ukutaht committed Jan 15, 2021
1 parent 563c8d9 commit e873d79
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 86 deletions.
1 change: 1 addition & 0 deletions .env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ DISABLE_SUBSCRIPTION=false
ENVIRONMENT=dev
MAILER_ADAPTER=Bamboo.LocalAdapter
LOG_LEVEL=debug
SELFHOST=false
3 changes: 2 additions & 1 deletion .env.test
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/plausible_test
CLICKHOUSE_DATABASE_URL=http://127.0.0.1:8123/plausible_test
SECRET_KEY_BASE=/njrhntbycvastyvtk1zycwfm981vpo/0xrvwjjvemdakc/vsvbrevlwsc6u8rcg
BASE_URL=http://localhost:8000
CRON_ENABLED=false
LOG_LEVEL=warn
ENVIRONMENT=test
MAILER_ADAPTER=Bamboo.TestAdapter
DISABLE_SUBSCRIPTION=false
ADMIN_USER_EMAIL=admin@email.com
ADMIN_USER_PWD=fakepassword
SELFHOST=false
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Changelog
All notable changes to this project will be documented in this file.

## [1.1.2] - Unreleased
## [1.2] - Unreleased

### Added
- Ability to add event metadata plausible/analytics#381
Expand All @@ -25,6 +25,8 @@ All notable changes to this project will be documented in this file.
- Improve onboarding UX and design plausible/analytics#441
- Allows outbound link tracking script to use new tab redirection plausible/analytics#494
- "This Month" view is now Month-to-date for the current month plausible/analytics#491
- Background jobs are enabled by default for self-hosted installations plausible/analytics#603
- All new users on self-hosted installations have a never-ending trial plausible/analytics#603

### Fixed
- Do not error when activating an already activated account plausible/analytics#370
Expand Down
4 changes: 4 additions & 0 deletions assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,7 @@ blockquote {
.dark .fullwidth-shadow::before {
box-shadow: 0 4px 2px -2px rgba(200, 200, 200, 0.1);
}

iframe[hidden] {
display: none;
}
79 changes: 47 additions & 32 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@ end

port = System.get_env("PORT") || 8000

base_url =
System.get_env("BASE_URL", "http://localhost:8000")
|> URI.parse()
base_url = System.get_env("BASE_URL")

secret_key_base = System.get_env("SECRET_KEY_BASE")
if !base_url do
raise "BASE_URL configuration option is required. See https://plausible.io/docs/self-hosting-configuration#server"
end

base_url = URI.parse(base_url)

if base_url.scheme not in ["http", "https"] do
raise "BASE_URL must start with `http` or `https`. Currently configured as `#{
System.get_env("BASE_URL")
}`"
end

secret_key_base = System.fetch_env!("SECRET_KEY_BASE")

db_url =
System.get_env(
Expand Down Expand Up @@ -48,10 +58,12 @@ custom_domain_server_user = System.get_env("CUSTOM_DOMAIN_SERVER_USER")
custom_domain_server_password = System.get_env("CUSTOM_DOMAIN_SERVER_PASSWORD")
geolite2_country_db = System.get_env("GEOLITE2_COUNTRY_DB")
disable_auth = String.to_existing_atom(System.get_env("DISABLE_AUTH", "false"))
disable_registration = String.to_existing_atom(System.get_env("DISABLE_REGISTRATION", "false"))
hcaptcha_sitekey = System.get_env("HCAPTCHA_SITEKEY")
hcaptcha_secret = System.get_env("HCAPTCHA_SECRET")
log_level = String.to_existing_atom(System.get_env("LOG_LEVEL", "warn"))
appsignal_api_key = System.get_env("APPSIGNAL_API_KEY")
is_selfhost = String.to_existing_atom(System.get_env("SELFHOST", "true"))

{user_agent_cache_limit, ""} = Integer.parse(System.get_env("USER_AGENT_CACHE_LIMIT", "1000"))

Expand All @@ -64,16 +76,12 @@ config :plausible,
admin_pwd: admin_pwd,
environment: env,
mailer_email: mailer_email,
admin_emails: admin_emails
admin_emails: admin_emails,
is_selfhost: is_selfhost

config :plausible, :selfhost,
disable_authentication: disable_auth,
disable_subscription: String.to_existing_atom(System.get_env("DISABLE_SUBSCRIPTION", "true")),
disable_registration:
if(!disable_auth,
do: String.to_existing_atom(System.get_env("DISABLE_REGISTRATION", "false")),
else: false
)
disable_registration: if(!disable_auth, do: disable_registration, else: false)

config :plausible, PlausibleWeb.Endpoint,
url: [host: base_url.host, scheme: base_url.scheme, port: base_url.port],
Expand Down Expand Up @@ -146,49 +154,56 @@ config :plausible, :custom_domain_server,
config :plausible, PlausibleWeb.Firewall,
blocklist: System.get_env("IP_BLOCKLIST", "") |> String.split(",") |> Enum.map(&String.trim/1)

if config_env() !== :test do
if config_env() == :prod do
base_cron = [
# Daily at midnight
{"0 0 * * *", Plausible.Workers.RotateSalts}
]

extra_cron = [
# hourly
{"0 * * * *", Plausible.Workers.SendSiteSetupEmails},
{"0 0 * * *", Plausible.Workers.RotateSalts},
#  hourly
{"0 * * * *", Plausible.Workers.ScheduleEmailReports},
# hourly
{"0 * * * *", Plausible.Workers.SendSiteSetupEmails},
# Daily at midnight
{"0 0 * * *", Plausible.Workers.FetchTweets},
# Daily at midday
{"0 12 * * *", Plausible.Workers.SendTrialNotifications},
# Daily at midday
{"0 12 * * *", Plausible.Workers.SendCheckStatsEmails},
# Every 10 minutes
{"*/10 * * * *", Plausible.Workers.ProvisionSslCertificates},
# Every 15 minutes
{"*/15 * * * *", Plausible.Workers.SpikeNotifier},
# Every day at midnight
{"0 0 * * *", Plausible.Workers.CleanEmailVerificationCodes}
]

base_queues = [rotate_salts: 1]
extra_cron = [
# Daily at midday
{"0 12 * * *", Plausible.Workers.SendTrialNotifications},
# Every 10 minutes
{"*/10 * * * *", Plausible.Workers.ProvisionSslCertificates}
]

extra_queues = [
provision_ssl_certificates: 1,
fetch_tweets: 1,
check_stats_emails: 1,
site_setup_emails: 1,
trial_notification_emails: 1,
base_queues = [
rotate_salts: 1,
schedule_email_reports: 1,
send_email_reports: 1,
spike_notifications: 1,
clean_email_verification_codes: 1
fetch_tweets: 1,
clean_email_verification_codes: 1,
check_stats_emails: 1,
site_setup_emails: 1
]

extra_queues = [
provision_ssl_certificates: 1,
trial_notification_emails: 1
]

config :plausible, Oban,
repo: Plausible.Repo,
queues: if(cron_enabled, do: base_queues ++ extra_queues, else: base_queues),
crontab: if(cron_enabled, do: base_cron ++ extra_cron, else: base_cron)
queues: if(is_selfhost, do: base_queues, else: base_queues ++ extra_queues),
crontab: if(is_selfhost, do: base_cron, else: base_cron ++ extra_cron)
else
config :plausible, Oban,
repo: Plausible.Repo,
queues: false,
crontab: false
end

config :plausible, :hcaptcha,
Expand Down
5 changes: 0 additions & 5 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,3 @@ config :geolix,

config :plausible,
session_timeout: 0

config :plausible, Oban,
repo: Plausible.Repo,
queues: false,
crontab: false
4 changes: 2 additions & 2 deletions lib/plausible/auth/auth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ defmodule Plausible.Auth do
end
end

def create_user(name, email) do
def create_user(name, email, pwd) do
%Auth.User{}
|> Auth.User.new(%{name: name, email: email})
|> Auth.User.new(%{name: name, email: email, password: pwd, password_confirmation: pwd})
|> Repo.insert()
end

Expand Down
10 changes: 9 additions & 1 deletion lib/plausible/auth/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule Plausible.Auth.User do
|> validate_length(:password, min: 6, message: "has to be at least 6 characters")
|> validate_confirmation(:password)
|> hash_password()
|> change(trial_expiry_date: Timex.today() |> Timex.shift(days: 30))
|> change(trial_expiry_date: trial_expiry())
|> unique_constraint(:email)
end

Expand All @@ -62,4 +62,12 @@ defmodule Plausible.Auth.User do
end

def hash_password(changeset), do: changeset

defp trial_expiry() do
if Application.get_env(:plausible, :is_selfhost) do
Timex.today() |> Timex.shift(years: 100)
else
Timex.today() |> Timex.shift(days: 30)
end
end
end
11 changes: 1 addition & 10 deletions lib/plausible_release.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,7 @@ defmodule Plausible.Release do

case Plausible.Auth.find_user_by(email: admin_email) do
nil ->
{: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()

{:ok, _} = Plausible.Auth.create_user(admin_user, admin_email, admin_pwd)
IO.puts("Admin user created successful!")

_ ->
Expand Down
2 changes: 1 addition & 1 deletion lib/plausible_web/templates/auth/user_settings.html.eex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<%= if !Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_subscription) do %>
<%= if !Application.get_env(:plausible, :is_selfhost) do %>
<div class="max-w-2xl mx-auto bg-white dark:bg-gray-800 shadow-md rounded rounded-t-none border-t-2 border-orange-200 dark:border-orange-200 px-8 pt-6 pb-8 mt-24 ">
<div class="flex justify-between">
<h2 class="text-xl font-black dark:text-gray-100">Subscription Plan</h2>
Expand Down
14 changes: 12 additions & 2 deletions lib/plausible_web/templates/layout/_footer.html.eex
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@
Plausible Analytics
</h4>
<p class="mt-4 text-gray-400 text-base leading-6">
Made and hosted in the EU <span class="text-lg">🇪🇺</span><br />
<%= if !Application.get_env(:plausible, :is_selfhost) do %>
Made and hosted in the EU <span class="text-lg">🇪🇺</span><br />
<% end %>
100% self-funded and independent <br />
Built by <a class="text-gray-300 hover:text-white" href="https://twitter.com/ukutaht">@ukutaht</a> and <a class="text-gray-300 hover:text-white" href="https://twitter.com/markosaric">@markosaric</a>
Built by <a class="text-gray-100 hover:text-white" href="https://twitter.com/ukutaht">@ukutaht</a> and <a class="text-gray-100 hover:text-white" href="https://twitter.com/markosaric">@markosaric</a>
</p>
<%= if Application.get_env(:plausible, :is_selfhost) do %>
<div class="mt-4">
<a href="https://github.com/sponsors/plausible" class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-gray-50 bg-gray-700 hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500">
<svg class="-ml-1 mr-2 w-5 h-5 text-pink-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path></svg>
Sponsor @plausible
</a>
</div>
<% end %>
</div>
<div class="grid grid-cols-2 gap-8 xl:col-span-2">
<div class="md:grid md:grid-cols-2 md:gap-8">
Expand Down
10 changes: 9 additions & 1 deletion lib/plausible_web/templates/layout/app.html.eex
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,15 @@
<%= cond do %>
<% @conn.assigns[:current_user] -> %>
<ul class="flex">
<%= if !Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_subscription) && @conn.assigns[:current_user].subscription == nil do %>
<%= if Application.get_env(:plausible, :is_selfhost) do %>
<li class="mr-6 hidden sm:block">
<%= link(to: "https://github.com/plausible/analytics", class: "font-bold rounded m-1 ml-0 p-1 hover:bg-gray-200 dark:hover:bg-gray-900 dark:text-gray-100", style: "line-height: 40px;", target: "_blank") do %>
<svg class="w-4 h-4 inline -mt-1 mr-px" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub icon</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
Repo
<% end %>
</li>
<% end %>
<%= if !Application.get_env(:plausible, :is_selfhost) && @conn.assigns[:current_user].subscription == nil do %>
<li class="mr-6 hidden sm:block">
<%= link(trial_notificaton(@conn.assigns[:current_user]), to: "/settings", class: "font-bold text-orange-900 dark:text-yellow-900 rounded p-2 bg-orange-200 dark:bg-yellow-100", style: "line-height: 40px;") %>
</li>
Expand Down
66 changes: 37 additions & 29 deletions lib/plausible_web/templates/site/settings_search_console.html.eex
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,50 @@
<% end %>
</header>

<%= if @site.google_auth do %>
<div class="py-2"></div>
<span class="text-gray-700 dark:text-gray-300">Linked Google account: <b><%= @site.google_auth.email %></b></span>
<%= if Keyword.get(Application.get_env(:plausible, :google), :client_id) do %>
<%= if @site.google_auth do %>
<div class="py-2"></div>
<span class="text-gray-700 dark:text-gray-300">Linked Google account: <b><%= @site.google_auth.email %></b></span>

<%= link("Unlink Google account", to: "/#{URI.encode_www_form(@site.domain)}/settings/google", class: "inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150", method: "delete") %>
<%= link("Unlink Google account", to: "/#{URI.encode_www_form(@site.domain)}/settings/google", class: "inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150", method: "delete") %>

<%= case @search_console_domains do %>
<% {:ok, domains} -> %>
<%= if @site.google_auth.property && !(@site.google_auth.property in domains) do %>
<p class="text-gray-700 dark:text-gray-300 mt-6 font-bold">
NB: Your Google account does not have access to your currently configured property, <%= @site.google_auth.property %>. Please select a verified property from the list below.
</p>
<% else %>
<p class="text-gray-700 dark:text-gray-300 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", class: "text-indigo-500") %> on Search Console first.
</p>
<% end %>
<%= case @search_console_domains do %>
<% {:ok, domains} -> %>
<%= if @site.google_auth.property && !(@site.google_auth.property in domains) do %>
<p class="text-gray-700 dark:text-gray-300 mt-6 font-bold">
NB: Your Google account does not have access to your currently configured property, <%= @site.google_auth.property %>. Please select a verified property from the list below.
</p>
<% else %>
<p class="text-gray-700 dark:text-gray-300 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", class: "text-indigo-500") %> on Search Console first.
</p>
<% end %>

<%= form_for Plausible.Site.GoogleAuth.changeset(@site.google_auth), "/#{URI.encode_www_form(@site.domain)}/settings/google", [class: "max-w-xs"], fn f -> %>
<div class="my-6">
<div class="inline-block relative w-full">
<%= select f, :property, domains, prompt: "(Choose property)", class: "mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md" %>
<%= form_for Plausible.Site.GoogleAuth.changeset(@site.google_auth), "/#{URI.encode_www_form(@site.domain)}/settings/google", [class: "max-w-xs"], fn f -> %>
<div class="my-6">
<div class="inline-block relative w-full">
<%= select f, :property, domains, prompt: "(Choose property)", class: "mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md" %>
</div>
</div>
</div>

<%= submit "Save", class: "button" %>
<% end %>
<%= submit "Save", class: "button" %>
<% end %>
<% {:error, error} -> %>
<p class="text-gray-700 dark:text-gray-300 mt-6">The following error happened when fetching your Google Search Console domains.</p>
<p class="text-red-700 font-medium mt-3"><%= error %></p>
<% end %>
<% else %>
<%= button("Continue with Google", to: Plausible.Google.Api.authorize_url(@site.id), class: "button mt-8") %>

<div class="text-gray-700 dark:text-gray-300 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") %>
</div>
<% end %>
<% else %>
<%= button("Continue with Google", to: Plausible.Google.Api.authorize_url(@site.id), class: "button mt-8") %>

<div class="text-gray-700 dark:text-gray-300 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") %>
</div>
<% end %>
<% else %>
<div class="my-8 text-center text-lg">
<svg class="block mx-auto mb-4 w-6 h-6 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
An extra step is needed to set up your Plausible Analytics Self Hosted for the Google Search Console integration.
Find instructions <%= link("here", to: "https://plausible.io/docs/self-hosting-configuration#google-search-integration", class: "text-indigo-500") %>
</div>
<% end %>
</div>
10 changes: 9 additions & 1 deletion lib/plausible_web/views/layout_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ defmodule PlausibleWeb.LayoutView do
[key: "Goals", value: "goals"],
[key: "Search Console", value: "search-console"],
[key: "Email reports", value: "email-reports"],
[key: "Custom domain", value: "custom-domain"],
if is_selfhost() do
[key: "Custom domain", value: "custom-domain"]
else
nil
end,
[key: "Danger zone", value: "danger-zone"]
]
end
Expand Down Expand Up @@ -57,4 +61,8 @@ defmodule PlausibleWeb.LayoutView do
def is_current_tab(conn, tab) do
List.last(conn.path_info) == tab
end

defp is_selfhost() do
Application.get_env(:plausible, :is_selfhost)
end
end

0 comments on commit e873d79

Please sign in to comment.