Skip to content

Commit fd0f6a1

Browse files
committed
Use Docker for local development
1 parent 6ace50b commit fd0f6a1

File tree

12 files changed

+157
-42
lines changed

12 files changed

+157
-42
lines changed

.env.example

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# The database connection settings.
2+
#
3+
# DATABASE_HOSTNAME must be 'database' when using Docker.
4+
DATABASE_HOSTNAME=database
5+
DATABASE_USERNAME=grid
6+
DATABASE_PASSWORD=
7+
DATABASE_DATABASE=grid
8+
9+
# The number of consecutive times an error may occur during updates before it is
10+
# reported to standard error.
11+
#
12+
# Set to 0 in development to disable error reporting through standard error, as
13+
# any errors will be visible in standard output anyway.
14+
#
15+
# In production any value greater than 0 may be used, but a value higher than 1
16+
# avoids frequent alerts due to short-lived API issues.
17+
ERROR_REPORTING_THRESHOLD=0
18+
19+
# Cloudflare API settings for visit counts (optional).
20+
CLOUDFLARE_API_TOKEN=
21+
CLOUDFLARE_ZONE_ID=

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
/configuration.php
1+
/.env
2+
/public/favicon.svg
3+
/public/index.html
24
/public/proza-light.woff2
35
/public/proza-regular.woff2

Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM php:8.3-cli-alpine AS php
2+
RUN docker-php-ext-install mysqli
3+
4+
FROM nginx:alpine-slim AS web
5+
RUN sed -i -e '/default_type/a\' -e ' charset utf-8;' /etc/nginx/nginx.conf

README.md

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,46 @@
22

33
This repository contains the source code for [National Grid: Live](https://grid.iamkate.com/).
44

5-
## Installation
5+
## Development
66

7-
Any officially supported versions of PHP and MySQL can be used.
7+
The development environment uses [Docker](https://www.docker.com/).
8+
9+
Copy `.env.example` to `.env` and edit the values as appropriate. At a minimum, `DATABASE_PASSWORD` must be given a value.
10+
11+
To start the containers:
12+
13+
```
14+
docker compose up --detach
15+
```
16+
17+
Once the containers are running, you can view the site at [http://localhost:9714/](http://localhost:9714/). Port 9714 was chosen due to its slight resemblance to the word ‘grid’.
18+
19+
To run the update script:
20+
21+
```
22+
docker compose exec php php /var/grid/update.php
23+
```
24+
25+
To stop the containers:
26+
27+
```
28+
docker compose down
29+
```
30+
31+
## Production
32+
33+
The production environment does not use Docker, instead running directly on the server. PHP 8.3 and a recent version MariaDB or MySQL are required.
34+
35+
### Files
36+
37+
Copy `.env.example` to `.env` and edit the values as appropriate. At a minimum, `DATABASE_PASSWORD` must be given a value. `DATABASE_HOSTNAME` should be changed to `localhost` if the database is running on the same server.
38+
39+
Upload `.env`, `update.php`, and the `classes` and `public` directories to the server.
840

941
### Database
1042

1143
Create a database and a user with `SELECT`, `INSERT`, `UPDATE`, and `DELETE` privileges, and import `grid.sql` into the database.
1244

13-
Copy `configuration.php.example` to `configuration.php` and enter the appropriate database connection settings into the PHP constants.
14-
1545
### Web server
1646

1747
Configure the server to serve the contents of the `public` directory. Note that this directory contains only static files, so the web server does not need to support PHP.
@@ -20,15 +50,15 @@ Configure the server to serve the contents of the `public` directory. Note that
2050

2151
Set up a cron job to execute the `update.php` script (using the [PHP CLI SAPI](https://www.php.net/manual/en/features.commandline.usage.php)) every five minutes. The cron job must run as a user with write access to `public/favicon.svg` and `public/index.html`.
2252

23-
The script outputs details of the update process to standard output, and details of errors to standard error. An error with an individual data source does not abort the rest of the update process. The APIs can be unreliable, so the `ERROR_REPORTING_THRESHOLD` constant in `configuration.php` is provided to control how many consecutive times an error must occur before it is reported.
53+
The script outputs details of the update process to standard output, and details of errors to standard error. An error with an individual data source does not abort the rest of the update process.
2454

2555
### Fonts
2656

27-
The CSS refers to `proza-light.woff2` and `proza-regular.woff2`. These are commercial fonts, so are not included in this repository. Licences for [Proza](http://bureauroffa.com/about-proza) can be purchased from [Bureau Roffa](http://bureauroffa.com/). Alterenatively, the simplified free version [Proza Libre](http://bureauroffa.com/about-proza-libre) can be used instead.
57+
The CSS refers to `proza-light.woff2` and `proza-regular.woff2`. These are commercial fonts, so are not included in this repository. Licences for [Proza](http://bureauroffa.com/about-proza) can be purchased from [Bureau Roffa](http://bureauroffa.com/). Alternatively, the simplified free version [Proza Libre](http://bureauroffa.com/about-proza-libre) can be used instead.
2858

2959
### Cloudflare
3060

31-
National Grid: Live uses [Cloudflare](https://www.cloudflare.com/)’s content delivery network. Visit counts will be retrieved from Cloudflare if the `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ZONE_ID` constants in `configuration.php` are set to non-empty strings. The Cloudflare API token must be configured to provide Analytics Read access for the zone.
61+
National Grid: Live uses [Cloudflare](https://www.cloudflare.com/)’s content delivery network. Visit counts will be retrieved from Cloudflare if the `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ZONE_ID` environment variables are set to non-empty strings. The Cloudflare API token must be configured to provide Analytics Read access for the zone.
3262

3363
## Codebase structure
3464

classes/Data/Visits.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ class Visits {
2121
*/
2222
public static function update(Database $database): void {
2323

24-
if (CLOUDFLARE_API_TOKEN === '' || CLOUDFLARE_ZONE_ID === '') {
24+
if (
25+
getenv('CLOUDFLARE_API_TOKEN') === ''
26+
|| getenv('CLOUDFLARE_ZONE_ID') === ''
27+
) {
2528
return;
2629
}
2730

@@ -34,11 +37,11 @@ public static function update(Database $database): void {
3437
);
3538

3639
curl_setopt($curl, CURLOPT_HTTPHEADER, [
37-
'Authorization: Bearer ' . CLOUDFLARE_API_TOKEN,
40+
'Authorization: Bearer ' . getenv('CLOUDFLARE_API_TOKEN'),
3841
'Content-Type: application/json'
3942
]);
4043

41-
$zoneId = CLOUDFLARE_ZONE_ID;
44+
$zoneId = getenv('CLOUDFLARE_ZONE_ID');
4245
$time = $database->getLatestHalfHourTimestamp();
4346
$startTime = gmdate('Y-m-d\\TH:i:s\\Z', $time - 12 * 60 * 60);
4447
$endTime = gmdate('Y-m-d\\TH:i:s\\Z', $time);

classes/Database.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ public function __construct() {
2727
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
2828

2929
$this->connection = new \mysqli(
30-
DATABASE_HOSTNAME,
31-
DATABASE_USERNAME,
32-
DATABASE_PASSWORD,
33-
DATABASE_DATABASE
30+
getenv('DATABASE_HOSTNAME'),
31+
getenv('DATABASE_USERNAME'),
32+
getenv('DATABASE_PASSWORD'),
33+
getenv('DATABASE_DATABASE')
3434
);
3535

3636
$this->connection->set_charset('utf8mb4');

classes/Environment.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace KateMorley\Grid;
4+
5+
/** Loads the environment. */
6+
class Environment {
7+
/**
8+
* Loads the environment from a file.
9+
*
10+
* @param string $path The path to the environment file.
11+
*/
12+
public static function load(string $path): void {
13+
$file = fopen($path, 'r');
14+
15+
while ($line = fgets($file)) {
16+
$line = trim($line);
17+
18+
if ($line !== '' && $line[0] !== '#') {
19+
putenv($line);
20+
}
21+
}
22+
}
23+
}

compose.yaml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
services:
2+
database:
3+
image: mariadb
4+
volumes:
5+
- ./grid.sql:/docker-entrypoint-initdb.d/grid.sql
6+
- database:/var/lib/mysql
7+
environment:
8+
- MARIADB_USER=${DATABASE_USERNAME}
9+
- MARIADB_PASSWORD=${DATABASE_PASSWORD}
10+
- MARIADB_DATABASE=${DATABASE_DATABASE}
11+
- MARIADB_RANDOM_ROOT_PASSWORD=1
12+
healthcheck:
13+
test:
14+
[
15+
"CMD",
16+
"/usr/local/bin/healthcheck.sh",
17+
"--su-mysql",
18+
"--connect",
19+
]
20+
interval: 5s
21+
timeout: 5s
22+
retries: 5
23+
24+
php:
25+
build:
26+
target: php
27+
depends_on:
28+
database:
29+
condition: service_healthy
30+
volumes:
31+
- ./.env:/var/grid/.env
32+
- ./update.php:/var/grid/update.php
33+
- ./classes:/var/grid/classes
34+
- ./public:/var/grid/public
35+
command:
36+
sh -c "php /var/grid/update.php && sleep infinity"
37+
38+
web:
39+
build:
40+
target: web
41+
ports:
42+
- "9714:80"
43+
volumes:
44+
- ./public:/usr/share/nginx/html
45+
46+
volumes:
47+
database:

configuration.php.example

Lines changed: 0 additions & 13 deletions
This file was deleted.

public/favicon.svg

Whitespace-only changes.

public/index.html

Whitespace-only changes.

update.php

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Updates the site
44

55
use KateMorley\Grid\Database;
6+
use KateMorley\Grid\Environment;
67
use KateMorley\Grid\Data\DataException;
78
use KateMorley\Grid\Data\Demand;
89
use KateMorley\Grid\Data\Emissions;
@@ -12,8 +13,6 @@
1213
use KateMorley\Grid\UI\Favicon;
1314
use KateMorley\Grid\UI\UI;
1415

15-
require_once __DIR__ . '/configuration.php';
16-
1716
spl_autoload_register(function ($class) {
1817
require_once(
1918
__DIR__
@@ -23,10 +22,11 @@
2322
);
2423
});
2524

25+
Environment::load(__DIR__ . '/.env');
26+
2627
$database = new Database();
2728

2829
foreach ([
29-
3030
'Updating generation… ' => function ($database) {
3131
Generation::update($database);
3232
},
@@ -53,7 +53,6 @@
5353
},
5454

5555
'Outputting files… ' => function ($database) {
56-
5756
$state = $database->getState();
5857

5958
ob_start();
@@ -65,35 +64,33 @@
6564
Favicon::create($state->getLatest()->getTypes()),
6665
LOCK_EX
6766
);
68-
6967
}
70-
7168
] as $action => $callback) {
72-
7369
echo $action;
7470

7571
$start = microtime(true);
7672

7773
try {
78-
7974
$callback($database);
8075

8176
echo 'OK';
8277

8378
$database->clearErrors($action);
84-
8579
} catch (DataException $e) {
86-
8780
$error = $e->getMessage();
8881
echo 'ERROR: ' . $error;
8982

90-
if ($database->getErrorCount($action, $error) >= ERROR_REPORTING_THRESHOLD) {
83+
if (
84+
$database->getErrorCount($action, $error)
85+
>= (int)getenv('ERROR_REPORTING_THRESHOLD')
86+
) {
9187
$database->clearErrors($action);
92-
trigger_error(trim($action) . ' ' . $error);
93-
}
9488

89+
if ((int)getenv('ERROR_REPORTING_THRESHOLD') > 0) {
90+
trigger_error(trim($action) . ' ' . $error);
91+
}
92+
}
9593
}
9694

9795
echo ' (' . sprintf('%0.3f', microtime(true) - $start) . " seconds)\n";
98-
9996
}

0 commit comments

Comments
 (0)