aboutsummaryrefslogtreecommitdiff
path: root/content/posts/2022-07-18-firefly-install.md
blob: a71a9485ebd67e4f9c548840a3629e7d200d2de6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
---
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=<RANDON_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...](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)