diff --git a/.gitignore b/.gitignore index f9e733a..7f5c37d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /composer.lock -/config.json -/data/ +/crawler.json +/server/ /vendor/ diff --git a/README.md b/README.md index 8bfaff4..bea3bd1 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ Simple RSS feed converter to static Gemtext format, useful for news portals or l ## Usage 1. `git clone https://github.com/YGGverse/Pulsar.git` -2. `cp example/config.json config.json` - setup your feeds -3. `php src/crawler.php` - create crontab schedule +2. `cp example/crawler.json crawler.json` - setup your feed locations +3. `php src/crawler.php` - grab feeds manually or create the crontab task ## Config @@ -26,4 +26,32 @@ Configuration file supports multiple feed channels with custom settings: * `{title}` - item title * `{description}` - item description -Resulting files could be generated to the any folder for personal reading on localhost, or shared with others using [gmid](https://github.com/omar-polo/gmid), [twins](https://code.rocket9labs.com/tslocum/twins) or any other [Gemini server](https://github.com/kr1sp1n/awesome-gemini#servers). \ No newline at end of file +Resulting files could be generated to the any folder for personal reading on localhost, or shared with others using [gmid](https://github.com/omar-polo/gmid), [twins](https://code.rocket9labs.com/tslocum/twins) or any other [Gemini server](https://github.com/kr1sp1n/awesome-gemini#servers). + +## Server + +Pulsar comes with build-in [Titan-II](https://github.com/YGGverse/titan-II) server implementation. + +It's especially useful for [Yggdrasil](https://github.com/yggdrasil-network/yggdrasil-go) users, who wish to host their feeds using plain IPv6 `0200::/7` addresses as the `CN` record. Build-in server contain this feature implemented from the box. + +### Setup + +* `cd Pulsar` - navigate to the project folder +* `composer update` - download server dependencies with Composer +* `mkdir server/127.0.0.1` - init server location (you can define any other destination, but `server` one is just git ignored) +* `cp example/host.json server/127.0.0.1/host.json` - copy configuration example to the destination folder +* `cd server/127.0.0.1` - navigate to server folder created and generate new self-signed certificate + +On example above, certificate could be generated with following command: + +``` +openssl req -x509 -newkey rsa:4096 -keyout key.rsa -out cert.pem -days 365 -nodes -subj "/CN=127.0.0.1" +``` + +* _tip: for IPv6 address, just skip square brackets from `CN` value_ + +### Launch + +* `php src/server.php server/127.0.0.1` - supported relative or absolute paths for systemd service + +Open `gemini://127.0.0.1` in [Gemini browser](https://github.com/kr1sp1n/awesome-gemini#clients)! \ No newline at end of file diff --git a/composer.json b/composer.json index 1d19097..e9fa09c 100644 --- a/composer.json +++ b/composer.json @@ -15,5 +15,7 @@ "name": "YGGverse" } ], - "require": {} + "require": { + "yggverse/titan-ii": "^1.0" + } } diff --git a/example/config.json b/example/crawler.json similarity index 77% rename from example/config.json rename to example/crawler.json index 210511c..35dd993 100644 --- a/example/config.json +++ b/example/crawler.json @@ -3,7 +3,7 @@ [ { "source":"https://www.omglinux.com/feed", - "target":"data/omglinux.com/feed.gmi", + "target":"server/127.0.0.1/public/omglinux/feed.gmi", "item": { "template":"=> {link} {title}{nl}{nl}{description}", @@ -12,7 +12,7 @@ }, { "source":"https://omgubuntu.co.uk/feed", - "target":"data/omgubuntu.co.uk/feed.gmi", + "target":"server/127.0.0.1/public/omgubuntu/feed.gmi", "item": { "template":"=> {link} {title}{nl}{nl}{description}", diff --git a/example/host.json b/example/host.json new file mode 100644 index 0000000..5bead46 --- /dev/null +++ b/example/host.json @@ -0,0 +1,12 @@ +{ + "host":"127.0.0.1", + "port":1965, + "cert":"cert.pem", + "key":"key.rsa", + "data": + { + "directory":"public", + "index":"index.gmi", + "listing":true + } +} \ No newline at end of file diff --git a/src/crawler.php b/src/crawler.php index 5222575..d3b7a3b 100644 --- a/src/crawler.php +++ b/src/crawler.php @@ -15,7 +15,7 @@ if (false === sem_acquire($semaphore, true)) // Init config $config = json_decode( file_get_contents( - __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'config.json' + __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'crawler.json' ) ); diff --git a/src/server.php b/src/server.php new file mode 100644 index 0000000..09b8b15 --- /dev/null +++ b/src/server.php @@ -0,0 +1,249 @@ +cert, + DIRECTORY_SEPARATOR + ) ? $config->cert : PULSAR_SERVER_DIRECTORY . $config->cert +); + +if (!file_exists(PULSAR_SERVER_CERT)) +{ + throw new \Exception( + _('Certificate file not found') + ); +} + +// Init server key +define( + 'PULSAR_SERVER_KEY', + str_starts_with( + $config->key, + DIRECTORY_SEPARATOR + ) ? $config->key : PULSAR_SERVER_DIRECTORY . $config->key +); + +if (!file_exists(PULSAR_SERVER_KEY)) +{ + throw new \Exception( + _('Key file not found') + ); +} + +// Init data directory +define( + 'PULSAR_SERVER_DATA_DIRECTORY', + str_starts_with( + $config->data->directory, + DIRECTORY_SEPARATOR + ) ? $config->data->directory : PULSAR_SERVER_DIRECTORY . $config->data->directory +); + +if (!is_dir(PULSAR_SERVER_DATA_DIRECTORY)) +{ + throw new \Exception( + _('Data directory not found') + ); +} + +// Init server +$server = new \Yggverse\TitanII\Server(); + +$server->setCert( + PULSAR_SERVER_CERT +); + +$server->setKey( + PULSAR_SERVER_KEY +); + +$server->setHandler( + function (\Yggverse\TitanII\Request $request): \Yggverse\TitanII\Response + { + global $config; + + $response = new \Yggverse\TitanII\Response; + + // Filter path request + $path = trim( + preg_replace( + [ + '/\/[\.]+\//', + '/\/[\/]+\//', + ], + '/', + $request->getPath() + ) + ); + + if ($path != $request->getPath() || in_array($path, ['', null, false])) + { + $response->setCode( + 30 + ); + + $response->setMeta( + sprintf( + 'gemini://%s%s/%s', + $config->host, + $config->port == 1965 ? null : ':' . $config->port, + trim( + (string) $path, + '/' + ) + ) + ); + + return $response; + } + + // Directory request + if (is_dir(PULSAR_SERVER_DATA_DIRECTORY . $path)) + { + // Try index + if (file_exists(PULSAR_SERVER_DATA_DIRECTORY . $path . $config->data->index)) + { + $response->setContent( + file_get_contents( + PULSAR_SERVER_DATA_DIRECTORY . $path . $config->data->index + ) + ); + + $response->setCode( + 20 + ); + + $response->setMeta( + 'text/gemini; charset=utf-8' + ); + + return $response; + } + + // Build listing + if ($config->data->listing) + { + $response->setCode( + 20 + ); + + $response->setMeta( + 'text/gemini; charset=utf-8' + ); + + $links = []; + + foreach ((array) scandir(PULSAR_SERVER_DATA_DIRECTORY . $path) as $link) + { + if (!str_starts_with($link, '.')) + { + if (is_dir(PULSAR_SERVER_DATA_DIRECTORY . $path . $link)) + { + $links[] = sprintf( + '=> %s/', + $link + ); + } + + else + { + $links[] = sprintf( + '=> %s', + $link + ); + } + } + } + + $response->setContent( + implode( + PHP_EOL, + $links + ) + ); + + return $response; + } + } + + // File request + if (file_exists(PULSAR_SERVER_DATA_DIRECTORY . $path)) + { + $response->setCode( + 20 + ); + + $response->setMeta( + 'text/gemini; charset=utf-8' + ); + + $response->setContent( + file_get_contents( + PULSAR_SERVER_DATA_DIRECTORY . $path + ) + ); + + return $response; + } + + // Noting found + $response->setCode( + 51 + ); + + return $response; + } +); + +// Start server +echo sprintf( + _('Server started on %s:%d'), + $config->host, + $config->port +); + +$server->start( + $config->host, + $config->port +); \ No newline at end of file