--- title: "Running firefly3 on alpine" date: 2022-07-18 tags: ['alpine', 'linux', 'php', 'nginx'] --- **Disclaimer:** before starting be aware that I'm not a sysadmin nor I have a deep knowledge in security. This is me reporting the steps I did as a learning experiment, so take this tutorial as your own risk. I have a pretty decent knowledge in container technology, I maintain several container on my local server for many applications. However I've decided to take a step back and learn a bit more how those applications are really deployed and kept without containers, and first candidate being Firefly[^1]. I have it currently running on container but let's install in a distribution. For the distro of choice I'll pick alpine, for its small footprint and the use of OpenRC (nothing against systemd though), and it will help me later to better understand how to properly setup an alpine image on container environment. *I don't want to extend this tutorial to cover every single part, so for the next steps I'll assume that you have a running instance of PostgreSQL and Alpine.* ## Dependencies First we need to install all the necessary packages to get firefly running. Let's go through them and check what they are used for. ```shell apk add curl tar gzip ``` cURL is needed to download the source code from Github and tar gzip are for extracting the compressed code. ```shell apk add composer ``` Composer is a dependency manager for PHP. It is required to download the dependencies of the project, as the source code from tar ball does have all its dependencies included. Now we need to download the dependencies listed in the site[^2]. ``` Extra packages Install the following PHP modules: PHP BCMath Arbitrary Precision Mathematics PHP Internationalization extension PHP Curl PHP Zip PHP Sodium PHP GD PHP XML PHP MBString PHP whatever database you're gonna use. ``` And for those I could gather the following alpine packages: ```shell apk add \ php8 \ php8-curl \ php8-zip \ php8-sodium \ php8-gd \ php8-xml \ php8-mbstring \ php8-bcmath \ php8-pgsql ``` But that is not everything. I don't know if I lack knowledge in the PHP stack but the application will later complain about some other missing dependencies. Those being: ```shell apk add \ php8-fileinfo \ php8-intl \ php8-session \ php8-simplexml \ php8-tokenizer \ php8-xmlwriter \ php8-dom \ php8-pdo_pgsql \ php8-shmop ``` A tip that may as well help you later. Some of those not listed packages are described in their docker repository[^3] and its base image[^4]. It can also help with describing some other necessary steps. As the next step we need to install the pieces of software that will actually run the project: ```shell apk add nginx php8-fpm ``` Nginx will act as reverse proxy and php8-fpm will actually run the project. You can use lighttpd as well as some others. ## Deploying the code Now we have all necessary packages, lets download the project into on server, grab the latest release from Github, at the time of this writing is `5.7.9`. Download into the `/var/www/firefly`. The folder location is kinda up you, I think nginx itself has another default folder for its sites, but I always use www folder to store the projects. ```shell mkdir -p /var/www/firefly ``` Create the folder then download/extract the source code: ```shell curl -SL https://github.com/firefly-iii/firefly-iii/archive/refs/tags/5.7.9.tar.gz | \ tar zxC /var/www/firefly --strip-components 1 ``` This piece of code was taken from the dockerfile[^5]. Now move to the `/var/www/firefly` and install its dependencies with composer: ```shell cd /var/www/firefly composer install --prefer-dist --no-dev --no-scripts ``` ## Configurations ### Firefly Firefly makes the process of setting up the connection strings and other configuration quite easy. We'll only need to create an `.env` file with all the information needed. Fill the information according with your setup: ```ini # /var/wwww/firefly/.env DB_CONNECTION=pgsql DB_HOST=localhost DB_PORT=5432 DB_DATABASE=firefly DB_USERNAME=admin DB_PASSWORD=admin APP_KEY= ``` To generate a random key just run: ```shell head /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 32 && echo ``` Once you have set it up we need to bootstrap the project. First we need to update the cached configuration. ```shell php artisan config:cache ``` Second we need to migrate and seed the database: ```shell php artisan firefly-iii:create-database php artisan migrate:refresh --seed php artisan firefly-iii:upgrade-database ``` If everything is setup properly the processes finish successfully. ### Permission Now comes the part where we should be careful. So far we (or at least I) have been setting up everything as root but that is not ideal. Usually we want to restrict as much as possible the permissions of processes, it should only see do what it meant to. So to minimize the area of effect of the process we will make it run as a user with almost no permission, and for purpose of running the php-fpm we will create a `www-data` user. Quite often that user is already created and if it is not, run the following command: ```shell adduser www-data --disabled-password ``` Add `--ingroup www-data` if it complains if the groups exists. `--disabled-password` is given so we don't allow login with password, because it is not meant to be logged with. Once the user is created we need to change the which user the process runs on. By default it uses a `nobody` which is a user with no permission except those which every other user has. Update the user given in the `/etc/php8/php-fpm.d/www.conf` file. From: ```shell user = nobody group = nobody ``` To: ```shell user = www-data group = www-data ``` If the `php-fpm8` is running restart it: ```shell rc-service php-fpm8 restart ``` At last we need to recursively update the permission of www folder because probably it is owned by root. ```shell chown -R www-data:www-data /var/www/ ``` ### Nginx We will need to edit the nginx config file to find and run the project. Add the following server inside of `/etc/nginx/http.d/`, by default nginx will read all `.conf` inside of that folder. Just like the www folder this is more of a personal choice, you have some room to choose where you want to store the config file. ```nginx # /etc/nginx/http.d/firefly.conf server { listen 8080; server_name localhost; root /var/www/firefly/public; location ~ \.php$ { try_files $uri $uri/ =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass 127.0.0.1:9000; include fastcgi.conf; } location / { try_files $uri /index.php$is_args$args; } } ``` This will set up the process in the port 8080. It is just an exemple, adapt it to your needs. ### Services Now that we have everything set up we can start the service to serve firefly: ```shell rc-service php-fpm8 start rc-service nginx start ``` `http://localhot:8080/` (or your server's hostname) should be up and running. And to make autostart: ```shell rc-update add php-fpm8 default rc-update add nginx default ``` ## Debugging In case of error you can add debugging setting to your env file so it will nicely return the error. ```ini # /var/wwww/firefly/.env APP_DEBUG=true APP_LOG_LEVEL=debug ``` ## Bonus config with socket Another thing to look at is where php-fpm is running the service. I think by default on alpine it runs on `http://127.0.0.1:9000` but it can also be running on a socket, check the `www.conf` file for the `listen` property: Config for http ``` listen = 127.0.0.1:9000 ``` Config for socket ``` listen = /run/php-fpm8/fpm.sock ``` If you want you can set it up to run on socket. You will need to change two things. First, update the www.conf file to run the process on a socket, and to change the owner of the socket file. This is important so later nginx is capable of reading/writing the file. On the `/etc/php8/php-fpm.d/www.conf` update it: ```shell listen = /run/php-fpm8/fpm.sock listen.owner = nginx listen.group = nginx listen.mode = 0660 ``` Second, change the nginx to connect to socket instead of an tcp connection, update the following property: ```nginx fastcgi_pass unix:/run/php-fpm8/fpm.sock; ``` [^1]: https://www.firefly-iii.org/ [^2]: https://docs.firefly-iii.org/firefly-iii/installation/self_hosted/?mtm_campaign=docu-internal&mtm_kwd=self_hosted [^3]: https://dev.azure.com/Firefly-III/_git/MainImage [^4]: https://dev.azure.com/firefly-iii/_git/BaseImage [^5]: https://dev.azure.com/Firefly-III/MainImage/_git/MainImage?path=/Dockerfile&version=GC520b8f865ea623a8625fe64e9f583406849be91a&line=14&lineEnd=15&lineStartColumn=1&lineEndColumn=1&lineStyle=plain&_a=contents