fixup! fixup! fixup! Pritup pres PHP a docker
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
FROM dunglas/frankenphp:1-php8.3
|
||||
|
||||
RUN install-php-extensions curl yaml
|
||||
RUN install-php-extensions yaml
|
||||
|
||||
WORKDIR /app
|
||||
COPY app ./app
|
||||
COPY conf ./conf
|
||||
COPY manifest.yaml ./manifest.yaml
|
||||
COPY Caddyfile /etc/frankenphp/Caddyfile
|
||||
|
||||
@@ -2,143 +2,22 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
const MANIFEST_PATH = 'manifest.yaml';
|
||||
const TEMPLATES_DIRECTORY = __DIR__ . '/../templates';
|
||||
const FETCH_TIMEOUT_SECONDS = 5;
|
||||
require __DIR__ . '/../src/functions.php';
|
||||
|
||||
function sendPlainTextError(int $statusCode, string $message): never
|
||||
{
|
||||
http_response_code($statusCode);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $message . "\n";
|
||||
exit;
|
||||
}
|
||||
define('PUBLIC_BASE_URL', resolvePublicBaseUrl());
|
||||
|
||||
function fetchRaw(string $relativePath): string|false
|
||||
{
|
||||
$url = REPO_RAW_BASE_URL . ltrim($relativePath, '/');
|
||||
|
||||
$curl = curl_init($url);
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 3,
|
||||
CURLOPT_TIMEOUT => FETCH_TIMEOUT_SECONDS,
|
||||
]);
|
||||
|
||||
$content = curl_exec($curl);
|
||||
|
||||
return is_string($content) ? $content : false;
|
||||
}
|
||||
|
||||
function escapeForDoubleQuotedShellString(string $value): string
|
||||
{
|
||||
return str_replace(['\\', '"', '$', '`'], ['\\\\', '\\"', '\\$', '\\`'], $value);
|
||||
}
|
||||
|
||||
function resolveHomePath(string $path): string
|
||||
{
|
||||
if ($path === '~' || str_starts_with($path, '~/')) {
|
||||
return '$HOME' . escapeForDoubleQuotedShellString(substr($path, 1));
|
||||
}
|
||||
|
||||
return escapeForDoubleQuotedShellString($path);
|
||||
}
|
||||
|
||||
function renderScriptTemplate(string $templateName, array $placeholders): string|false
|
||||
{
|
||||
$path = TEMPLATES_DIRECTORY . '/' . $templateName . '.sh.tpl';
|
||||
|
||||
if (!is_file($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$script = file_get_contents($path);
|
||||
|
||||
foreach ($placeholders as $key => $value) {
|
||||
$script = str_replace('{{' . $key . '}}', $value, $script);
|
||||
}
|
||||
|
||||
return $script;
|
||||
}
|
||||
|
||||
$repoRawBaseUrl = getenv('REPO_RAW_BASE_URL');
|
||||
|
||||
if ($repoRawBaseUrl === false || $repoRawBaseUrl === '') {
|
||||
sendPlainTextError(500, 'REPO_RAW_BASE_URL is not configured');
|
||||
}
|
||||
|
||||
define('REPO_RAW_BASE_URL', $repoRawBaseUrl);
|
||||
|
||||
$publicBaseUrl = getenv('PUBLIC_BASE_URL');
|
||||
|
||||
if ($publicBaseUrl === false || $publicBaseUrl === '') {
|
||||
sendPlainTextError(500, 'PUBLIC_BASE_URL is not configured');
|
||||
}
|
||||
|
||||
define('PUBLIC_BASE_URL', $publicBaseUrl);
|
||||
|
||||
$requestPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?? '/';
|
||||
$name = trim(rawurldecode($requestPath), '/');
|
||||
$name = resolveRequestedName();
|
||||
|
||||
if ($name === '') {
|
||||
sendPlainTextError(400, 'Missing name of the installable item');
|
||||
handleIndexRequest();
|
||||
}
|
||||
|
||||
$isRawRequest = str_starts_with($name, 'raw/');
|
||||
|
||||
if ($isRawRequest) {
|
||||
$name = substr($name, strlen('raw/'));
|
||||
if ($name === 'list') {
|
||||
handleListRequest();
|
||||
}
|
||||
|
||||
$repoHost = parse_url(REPO_RAW_BASE_URL, PHP_URL_HOST);
|
||||
|
||||
$manifestYaml = fetchRaw(MANIFEST_PATH);
|
||||
|
||||
if ($manifestYaml === false) {
|
||||
sendPlainTextError(502, "Failed to fetch the manifest from $repoHost");
|
||||
if (str_starts_with($name, 'raw/')) {
|
||||
handleRawRequest(substr($name, strlen('raw/')));
|
||||
}
|
||||
|
||||
$manifest = yaml_parse($manifestYaml);
|
||||
|
||||
if ($manifest === false) {
|
||||
sendPlainTextError(502, "Failed to parse the manifest from $repoHost");
|
||||
}
|
||||
|
||||
$entry = $manifest[$name] ?? null;
|
||||
|
||||
if ($entry === null) {
|
||||
sendPlainTextError(404, "Unknown item '$name'");
|
||||
}
|
||||
|
||||
if (!is_string($entry['source'] ?? null) || !is_string($entry['target'] ?? null) || !is_string($entry['template'] ?? null)) {
|
||||
sendPlainTextError(500, "Malformed manifest entry for item '$name'");
|
||||
}
|
||||
|
||||
if ($isRawRequest) {
|
||||
$content = fetchRaw($entry['source']);
|
||||
|
||||
if ($content === false) {
|
||||
sendPlainTextError(502, "Failed to fetch the file from $repoHost");
|
||||
}
|
||||
|
||||
header('Content-Type: application/octet-stream');
|
||||
echo $content;
|
||||
exit;
|
||||
}
|
||||
|
||||
$variables = [
|
||||
'SOURCE_URL' => rtrim(PUBLIC_BASE_URL, '/') . '/raw/' . rawurlencode($name),
|
||||
'TARGET_PATH' => resolveHomePath($entry['target']),
|
||||
'POST_INSTALL' => $entry['post_install'] ?? '',
|
||||
];
|
||||
|
||||
$script = renderScriptTemplate($entry['template'], $variables);
|
||||
|
||||
if ($script === false) {
|
||||
sendPlainTextError(500, 'Template for this item is missing');
|
||||
}
|
||||
|
||||
header('Content-Type: text/x-shellscript; charset=utf-8');
|
||||
echo $script;
|
||||
handleInstallRequest($name);
|
||||
|
||||
175
app/src/functions.php
Normal file
175
app/src/functions.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
const MANIFEST_PATH = 'manifest.yaml';
|
||||
const REPO_ROOT_DIRECTORY = __DIR__ . '/../..';
|
||||
const TEMPLATES_DIRECTORY = __DIR__ . '/../templates';
|
||||
|
||||
function sendPlainTextError(int $statusCode, string $message): never
|
||||
{
|
||||
http_response_code($statusCode);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $message . "\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
function escapeForDoubleQuotedShellString(string $value): string
|
||||
{
|
||||
return str_replace(['\\', '"', '$', '`'], ['\\\\', '\\"', '\\$', '\\`'], $value);
|
||||
}
|
||||
|
||||
function resolveHomePath(string $path): string
|
||||
{
|
||||
if ($path === '~' || str_starts_with($path, '~/')) {
|
||||
return '$HOME' . escapeForDoubleQuotedShellString(substr($path, 1));
|
||||
}
|
||||
|
||||
return escapeForDoubleQuotedShellString($path);
|
||||
}
|
||||
|
||||
function renderTemplate(string $path, array $placeholders): string|false
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = file_get_contents($path);
|
||||
|
||||
foreach ($placeholders as $key => $value) {
|
||||
$content = str_replace('{{' . $key . '}}', $value, $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
function readRepoFile(string $relativePath): string|false
|
||||
{
|
||||
$path = REPO_ROOT_DIRECTORY . '/' . ltrim($relativePath, '/');
|
||||
|
||||
if (!is_file($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = file_get_contents($path);
|
||||
|
||||
return is_string($content) ? $content : false;
|
||||
}
|
||||
|
||||
function loadManifest(): array
|
||||
{
|
||||
$manifestYaml = readRepoFile(MANIFEST_PATH);
|
||||
|
||||
if ($manifestYaml === false) {
|
||||
sendPlainTextError(500, 'Failed to read the manifest');
|
||||
}
|
||||
|
||||
$manifest = yaml_parse($manifestYaml);
|
||||
|
||||
if ($manifest === false) {
|
||||
sendPlainTextError(500, 'Failed to parse the manifest');
|
||||
}
|
||||
|
||||
return $manifest;
|
||||
}
|
||||
|
||||
function findManifestEntry(string $name): array
|
||||
{
|
||||
$manifest = loadManifest();
|
||||
|
||||
$entry = $manifest[$name] ?? null;
|
||||
|
||||
if ($entry === null) {
|
||||
sendPlainTextError(404, "Unknown item '$name'");
|
||||
}
|
||||
|
||||
if (!is_string($entry['source'] ?? null) || !is_string($entry['target'] ?? null) || !is_string($entry['template'] ?? null)) {
|
||||
sendPlainTextError(500, "Malformed manifest entry for item '$name'");
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
function resolvePublicBaseUrl(): string
|
||||
{
|
||||
$publicBaseUrl = getenv('PUBLIC_BASE_URL');
|
||||
|
||||
if ($publicBaseUrl === false || $publicBaseUrl === '') {
|
||||
sendPlainTextError(500, 'PUBLIC_BASE_URL is not configured');
|
||||
}
|
||||
|
||||
return $publicBaseUrl;
|
||||
}
|
||||
|
||||
function resolveRequestedName(): string
|
||||
{
|
||||
$requestPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?? '/';
|
||||
|
||||
return trim(rawurldecode($requestPath), '/');
|
||||
}
|
||||
|
||||
function handleIndexRequest(): never
|
||||
{
|
||||
$text = renderTemplate(TEMPLATES_DIRECTORY . '/index.tpl', [
|
||||
'PUBLIC_BASE_URL' => rtrim(PUBLIC_BASE_URL, '/'),
|
||||
]);
|
||||
|
||||
if ($text === false) {
|
||||
sendPlainTextError(500, 'Index template is missing');
|
||||
}
|
||||
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $text;
|
||||
exit;
|
||||
}
|
||||
|
||||
function handleListRequest(): never
|
||||
{
|
||||
$names = array_keys(loadManifest());
|
||||
sort($names);
|
||||
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo "Available configurations:\n";
|
||||
|
||||
foreach ($names as $name) {
|
||||
echo " $name\n";
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
function handleRawRequest(string $name): never
|
||||
{
|
||||
$entry = findManifestEntry($name);
|
||||
|
||||
$content = readRepoFile($entry['source']);
|
||||
|
||||
if ($content === false) {
|
||||
sendPlainTextError(500, "Failed to read the file for item '$name'");
|
||||
}
|
||||
|
||||
header('Content-Type: application/octet-stream');
|
||||
echo $content;
|
||||
exit;
|
||||
}
|
||||
|
||||
function handleInstallRequest(string $name): never
|
||||
{
|
||||
$entry = findManifestEntry($name);
|
||||
|
||||
$variables = [
|
||||
'SOURCE_URL' => rtrim(PUBLIC_BASE_URL, '/') . '/raw/' . rawurlencode($name),
|
||||
'TARGET_PATH' => resolveHomePath($entry['target']),
|
||||
'POST_INSTALL' => $entry['post_install'] ?? '',
|
||||
];
|
||||
|
||||
$script = renderTemplate(TEMPLATES_DIRECTORY . '/' . $entry['template'] . '.sh.tpl', $variables);
|
||||
|
||||
if ($script === false) {
|
||||
sendPlainTextError(500, 'Template for this item is missing');
|
||||
}
|
||||
|
||||
header('Content-Type: text/x-shellscript; charset=utf-8');
|
||||
echo $script;
|
||||
exit;
|
||||
}
|
||||
17
app/templates/index.tpl
Normal file
17
app/templates/index.tpl
Normal file
@@ -0,0 +1,17 @@
|
||||
# GET fnet.cz
|
||||
|
||||
Fast access to configurations and installation scripts.
|
||||
|
||||
## Usage
|
||||
|
||||
Install a configuration:
|
||||
|
||||
curl -fsSL {{PUBLIC_BASE_URL}}/<name> | bash
|
||||
|
||||
List all available configurations:
|
||||
|
||||
curl -fsSL {{PUBLIC_BASE_URL}}/list
|
||||
|
||||
Download the raw source file instead of the install script:
|
||||
|
||||
curl -fsSL {{PUBLIC_BASE_URL}}/raw/<name>
|
||||
@@ -2,7 +2,6 @@ services:
|
||||
get:
|
||||
build: .
|
||||
environment:
|
||||
REPO_RAW_BASE_URL: https://git.hell/lachtan/get/raw/branch/master/
|
||||
PUBLIC_BASE_URL: https://get.fnet.cz/
|
||||
ports:
|
||||
- "8082:80"
|
||||
|
||||
Reference in New Issue
Block a user