Skip to main content

Laravel Forge Setup on Umami self-hosted analytics | Laravelnote

 Many of us are loooking to move away from Google Analytics where possible. Reasons for doing so include:


Data privacy concerns

Avoiding cookie warnings

Seeking a simpler UI to share with clients/team

While looking into alternatives you'll find a range of paid, privacy-focussed SaaS analytics products, as well as some self hosted open source options (some of which are shared by companies who also offer a hosted paid service).


I wanted a self hosted option and before settling on Umami.js I also checked:


Ackee

Plausible (self-hosted, SaaS available)

Pirsch (self-hosted, SaaS available)

For all I know, these might be great - but I didn't get to a feature comparison or to try any of them out. I went with Umami.js simply because of how I wanted to install it on this occasion: on an Ubuntu VM, with Postgress/MySQL, and without using docker.


One nice feature of Umami.js is easily sharing a public dashboard for a website, you can take a look at their own public dashboard (pictured below) to see if it's the kind of thing you're looking for.




Instructions | Laravelnote

These instructions are for Laravel Forge, which makes things even easier if you're using it but the installation, build instructions, and key Nginx config snippet apply to any situation.


SSH into your VM and create the database you plan to use.


mysql -u forge -p

create database umami;

Then from /home/forge/ run.


git clone https://github.com/mikecao/umami.git

cd umami

npm install

mysql -u forge -p umami < sql/schema.mysql.sql

Now create a .env file in the same folder.


DATABASE_URL=mysql://forge:PASSWORD@localhost:3306/umami

HASH_SALT=RANDOMSTRING

Replacing PASSWORD with your Forge MySql database password and RANDOMSTRING with... a random string! From your Linux server you can generate a simple one with the code below, where 32 is the string length.


cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 32

Now everything is in place to build the project, this could take a minute as it optimizes the production build. If everything went well, we can also start it to check that command runs successfully too.


npm run build | Laravelnote

npm start

If everything has worked so far, you'll see some terminal output showing that it's now running on localhost:3000 (if you're already running something on port 3000 you can change the port by adding PORT=xxx to your .env file and building again).


At this point, everything is running but we can't see the app, and it's not accessible from the outside world. We'll resolve that soon, but first, now we know the app runs correctly, we'll add it as a server Daemon (see below) and Forge will configure Supervisor to make sure it's always running.


Now we'll give it a working web address, so head over to wherever you manage your DNS and create a new A Record for the web address you want to use, stats.yourdomain.com for example.


Next we'll use Forge to create the appropriate starting point Nginx conf...




... and create a LetsEncrypt SSL certificate for us.




Almost there. Finally we will change the Nginx conf to proxy the traffic. Click the Files button at the very bottom of the page, click 'Edit Nginx configuration', and add this location block as shown below.


location / {

    proxy_pass http://localhost:3000;

    proxy_set_header Host $host;

    proxy_set_header X-Real-IP $remote_addr;

    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

}



At long last you should be able to access Umami at https://stats.yourdomain.com now. Login using admin/umami and change your password, then click 'Settings' and create a website to track, save it, and click the </> button to collect the tracking code.


Add the tracking code to your website, visit the website, and return to https://stats.yourdomain.com/realtime where you'll hopefully see your visit in the realtime stats!


And finally

When you release the tracker to your production website or app, you probably don't want your own traffic skewing the data.


On a web app you might want to add the tag in your blade template only when it's in production, and the user is logged in, and isn't an admin (the example assumes you've setup getIsAdminAttribute() on the User model).


@if( config('app.env') == 'production' && Auth::user() && !Auth::user()->is_admin )

    <script async defer data-website-id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" src="https://stats.yourdomain.com/umami.js"></script>

@endif

My blog is a static site made with Jigsaw so I'm including the tag only in the production build, and additionally I'm adding the tag dynamically only when a localStorage key called disable-tracking isn't true. This might be overkill for you on your blog, but since mine is quite new, I'm still quite a large proportion of the traffic 😂


@if ( $page->production )

    <script>

    if(!window.localStorage.getItem('disable-tracking')) {

        var script = document.createElement('script');

        script.src = "https://stats.yourdomain.com/umami.js"

        script.setAttribute('data-website-id', 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')

        document.getElementsByTagName('head')[0].appendChild(script);

    }

    </script>

@endif

Going further

The ultimate setup for me would be to use Umami across all sites by hosting that instance on a VM and proxying a sub path (rather than a sub domain) from each site that uses it siteA.com/stats siteB.com/stats through to that instance. I believe this is possible from reading some support threads on GitHub, but I couldn't get it to work immediately and since I don't actually need it at this time. I thought I'd just finish up and spend that time writing this blog post instead.

Popular posts from this blog

Laravel8 in Serializes Models trait | laravelnote

This article was originally posted, with additional formatting, on my personal blog at laravel serializes model Background  When dispatching an object onto the queue, behind the scenes Laravel is recursively serializing the object and all of its properties into a string representation that is then written to the queue. There it awaits a queue worker to retrieve it from the queue and unserialize it back into a PHP object (Phew!). Problem When complicated objects are serialized, their string representations can be atrociously long, taking up unnecessary resources both on the queue and application servers. Solution Because of this, Laravel offers a trait called SerializesModels which, when added to an object, finds any properties of type Model or Eloquent\Collection during serialization and replaces them with a plain-old-PHP-object (POPO) known as a ModelIdentifier. These identifier objects represent the original properties Model type and ID, or IDs in the case of an Eloquent\Collection,

Laravel Parallel Testing Is Now Available in laravel8 | Laravelnote

 Parallel Testing | Laravelnote As such we know Laravel and PHP Unit execute your tests sequentially within a single process.  As such laravel check the single process doesn’t use multiple cores so that therefore, your test execution is seriously bottlenecked! we glad to say that Parallel Testing is now available in Laravel. You can use this Laravel version8.25 you may also use to laravel8 built-in test Artisan command to run your cmd to tests simultaneously across multiple processes to use significantly reduce the time required for to run the entire test suite. It is about sure that in laravel8 new on top of Paratest Laravel automatically use to handles creating and migrating a test for database for each parallel process. In The  Laravel8 for testing purpose goodies - such as Storage::fake - are ready for used in Parallel too. Laravel Provide Each all individual laravel8 version use test suite will receive a varying benefits from parallel testing. In The Laravel Tests are execution wa

What is HTTP client in laravel8 by laravenote 2021 | Laravelnote

Laravel provides an expressive, minimal API around the Guzzle HTTP client, allowing you to quickly make outgoing HTTP requests to communicate with other web applications. Laravel's wrapper around Guzzle is focused on its most common use cases and a wonderful developer experience. Before getting started, you should ensure that you have installed the Guzzle package as a dependency of your application. By default, Laravel automatically includes this dependency. However, if you have previously removed the package, you may install it again via Composer: composer require guzzlehttp/guzzle Making Requests To make requests, you may use the get, post, put, patch, and delete methods provided by the Http facade. First, let's examine how to make a basic GET request to another URL: use Illuminate\Support\Facades\Http; $response = Http::get('http://example.com'); The get method returns an instance of Illuminate\Http\Client\Response, which provides a variety of methods that may be use