From 8f5a69c86f7f0a7e45183246bb05206f15a7bc32 Mon Sep 17 00:00:00 2001 From: Martin Blazik Date: Sun, 16 Aug 2026 20:09:42 +0200 Subject: [PATCH] Pritup pres PHP a docker --- .claude/settings.json | 37 ++++++++ Caddyfile | 9 ++ Dockerfile | 9 ++ Makefile | 12 +++ app/public/index.php | 23 +++++ app/src/functions.php | 175 ++++++++++++++++++++++++++++++++++++ app/templates/config.sh.tpl | 11 +++ app/templates/index.tpl | 13 +++ compose.yaml | 8 ++ conf/tmux.conf | 3 +- manifest.yaml | 4 + 11 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 .claude/settings.json create mode 100644 Caddyfile create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 app/public/index.php create mode 100644 app/src/functions.php create mode 100644 app/templates/config.sh.tpl create mode 100644 app/templates/index.tpl create mode 100644 compose.yaml create mode 100644 manifest.yaml diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..10d2c78 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,37 @@ +{ + "env": { + "CLAUDE_CODE_SCROLL_SPEED": "3", + "ANTHROPIC_MODEL": "sonnet" + }, + "model": "opus[1m]", + "effortLevel": "high", + "autoUpdatesChannel": "latest", + "tui": "fullscreen", + "showMessageTimestamps": true, + "permissions": { + "defaultMode": "auto", + "allow": [], + "additionalDirectories": [] + }, + "enabledPlugins": { + "superpowers@claude-plugins-official": true, + "context7@claude-plugins-official": true, + "core@nicecode": true + }, + "extraKnownMarketplaces": { + "nicecode": { + "source": { + "source": "github", + "repo": "lachtan/nicecode" + } + } + }, + "spinnerVerbs": { + "mode": "replace", + "verbs": [ + "Analyzing code", + "Crafting solutions", + "Working hard" + ] + } +} diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..cea5294 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,9 @@ +{ + frankenphp +} + +:80 { + root * /app/app/public + encode gzip + php_server +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7788c4c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM dunglas/frankenphp:1-php8.3 + +RUN install-php-extensions yaml + +WORKDIR /app +COPY app ./app +COPY conf ./conf +COPY manifest.yaml ./manifest.yaml +COPY Caddyfile /etc/frankenphp/Caddyfile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6f4c9bf --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +COMPOSE ?= docker compose + +.PHONY: build up down + +build: + $(COMPOSE) build + +up: + $(COMPOSE) up -d + +down: + $(COMPOSE) down diff --git a/app/public/index.php b/app/public/index.php new file mode 100644 index 0000000..a42fcca --- /dev/null +++ b/app/public/index.php @@ -0,0 +1,23 @@ + $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; +} diff --git a/app/templates/config.sh.tpl b/app/templates/config.sh.tpl new file mode 100644 index 0000000..deaff86 --- /dev/null +++ b/app/templates/config.sh.tpl @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -euo pipefail + +readonly SOURCE_URL="{{SOURCE_URL}}" +readonly TARGET_PATH="{{TARGET_PATH}}" + +mkdir -p "$(dirname "$TARGET_PATH")" +curl -fsSL "$SOURCE_URL" -o "$TARGET_PATH" +echo "Configuration installed: $TARGET_PATH" +{{POST_INSTALL}} diff --git a/app/templates/index.tpl b/app/templates/index.tpl new file mode 100644 index 0000000..a81f60e --- /dev/null +++ b/app/templates/index.tpl @@ -0,0 +1,13 @@ +Fast access to configurations and installation scripts. + +Install a configuration: + + curl -fsSL {{PUBLIC_BASE_URL}}/ | 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/ diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..ee72dd5 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,8 @@ +services: + get: + build: . + environment: + PUBLIC_BASE_URL: https://get.fnet.cz/ + ports: + - "8082:80" + restart: unless-stopped diff --git a/conf/tmux.conf b/conf/tmux.conf index 69bccde..f8f94ce 100644 --- a/conf/tmux.conf +++ b/conf/tmux.conf @@ -1,8 +1,7 @@ # tmux config - screen-like key bindings # ============================================ -# mkdir -p $HOME/.config/tmux -# curl https://git.fnet.cz/lachtan/get/raw/branch/master/conf/tmux.conf -o $HOME/.config/tmux/tmux.conf +# mkdir -p $HOME/.config/tmux && curl https://git.fnet.cz/lachtan/get/raw/branch/master/conf/tmux.conf -o $HOME/.config/tmux/tmux.conf # Searched in this oreder: # /etc/tmux.conf diff --git a/manifest.yaml b/manifest.yaml new file mode 100644 index 0000000..a171a52 --- /dev/null +++ b/manifest.yaml @@ -0,0 +1,4 @@ +tmux: + template: config + source: conf/tmux.conf + target: ~/.config/tmux/tmux.conf