Compare commits

4 Commits

Author SHA1 Message Date
lachtan obecny
b075ed11c8 fixup! fixup! fixup! Pritup pres PHP a docker 2026-08-18 05:37:33 +02:00
lachtan obecny
3126173f82 fixup! fixup! Pritup pres PHP a docker 2026-08-17 21:18:32 +02:00
lachtan obecny
4ae2da01c2 fixup! Pritup pres PHP a docker 2026-08-17 20:53:20 +02:00
Martin Blazik
d2099b9a6c Pritup pres PHP a docker 2026-08-16 20:09:42 +02:00
11 changed files with 306 additions and 2 deletions

37
.claude/settings.json Normal file
View File

@@ -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"
]
}
}

9
Caddyfile Normal file
View File

@@ -0,0 +1,9 @@
{
frankenphp
}
:80 {
root * /app/app/public
encode gzip
php_server
}

9
Dockerfile Normal file
View File

@@ -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

12
Makefile Normal file
View File

@@ -0,0 +1,12 @@
COMPOSE ?= docker compose
.PHONY: build up down
build:
$(COMPOSE) build
up:
$(COMPOSE) up -d
down:
$(COMPOSE) down

23
app/public/index.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
require __DIR__ . '/../src/functions.php';
define('PUBLIC_BASE_URL', resolvePublicBaseUrl());
$name = resolveRequestedName();
if ($name === '') {
handleIndexRequest();
}
if ($name === 'list') {
handleListRequest();
}
if (str_starts_with($name, 'raw/')) {
handleRawRequest(substr($name, strlen('raw/')));
}
handleInstallRequest($name);

175
app/src/functions.php Normal file
View 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;
}

View File

@@ -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}}

17
app/templates/index.tpl Normal file
View 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>

8
compose.yaml Normal file
View File

@@ -0,0 +1,8 @@
services:
get:
build: .
environment:
PUBLIC_BASE_URL: https://get.fnet.cz/
ports:
- "8082:80"
restart: unless-stopped

View File

@@ -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

4
manifest.yaml Normal file
View File

@@ -0,0 +1,4 @@
tmux:
template: config
source: conf/tmux.conf
target: ~/.config/tmux/tmux.conf