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..73b77f2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM dunglas/frankenphp:1-php8.3 + +RUN install-php-extensions curl yaml + +WORKDIR /app +COPY app ./app +COPY Caddyfile /etc/frankenphp/Caddyfile diff --git a/app/public/index.php b/app/public/index.php new file mode 100644 index 0000000..88e9631 --- /dev/null +++ b/app/public/index.php @@ -0,0 +1,109 @@ + 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; +} + +$requestPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?? '/'; +$name = trim(rawurldecode($requestPath), '/'); + +if ($name === '') { + sendPlainTextError(400, 'Missing name of the installable item'); +} + +$manifestYaml = fetchRaw(MANIFEST_PATH); + +if ($manifestYaml === false) { + sendPlainTextError(502, 'Failed to fetch the manifest from git.fnet.cz'); +} + +$manifest = yaml_parse($manifestYaml); + +if ($manifest === false) { + sendPlainTextError(502, 'Failed to parse the manifest from git.fnet.cz'); +} + +$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'"); +} + +$variables = [ + 'SOURCE_URL' => REPO_RAW_BASE_URL . escapeForDoubleQuotedShellString(ltrim($entry['source'], '/')), + '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; 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/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