diff --git a/configure-container.sh b/configure-container.sh deleted file mode 100755 index d08c7f3..0000000 --- a/configure-container.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/sh -set -eu - -script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -helper_path="$script_dir/git-credential-hostproxy" -scope='global' -target_repo=$(pwd) - -while [ "$#" -gt 0 ]; do - case "$1" in - --global) - scope='global' - ;; - --local) - scope='local' - ;; - --repo) - shift - if [ "$#" -eq 0 ]; then - printf 'Missing value for --repo\n' >&2 - exit 1 - fi - target_repo="$1" - ;; - *) - printf 'Usage: %s [--global|--local] [--repo PATH]\n' "$0" >&2 - exit 1 - ;; - esac - shift -done - -if [ "$scope" = 'global' ]; then - git config --global --replace-all credential.helper '' - git config --global --add credential.helper "$helper_path" - git config --global credential.useHttpPath true - printf 'Configured global Git credential helper: %s\n' "$helper_path" -else - git -C "$target_repo" config --local --replace-all credential.helper '' - git -C "$target_repo" config --local --add credential.helper "$helper_path" - git -C "$target_repo" config --local credential.useHttpPath true - printf 'Configured local Git credential helper for %s\n' "$target_repo" -fi - -printf 'Proxy URL default: %s\n' "${GIT_CRED_PROXY_URL:-http://host.docker.internal:18765}" -printf 'Protocol filter is configured on the host side\n' diff --git a/git-credential-hostproxy b/git-credential-hostproxy deleted file mode 100755 index ebb8bba..0000000 --- a/git-credential-hostproxy +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -set -eu - -script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) - -if [ -n "${GIT_CRED_PROXY_RUNTIME:-}" ]; then - exec "$GIT_CRED_PROXY_RUNTIME" "$script_dir/helper.mjs" "$@" -fi - -if command -v bun >/dev/null 2>&1; then - exec bun "$script_dir/helper.mjs" "$@" -fi - -if command -v node >/dev/null 2>&1; then - exec node "$script_dir/helper.mjs" "$@" -fi - -printf 'Either bun or node is required to run git-credential-hostproxy\n' >&2 -exit 1 diff --git a/helper.mjs b/helper.mjs deleted file mode 100644 index 6accd51..0000000 --- a/helper.mjs +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env node - -import fs from 'node:fs/promises'; -import http from 'node:http'; -import https from 'node:https'; -import path from 'node:path'; -import process from 'node:process'; -import { fileURLToPath } from 'node:url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const operation = process.argv[2] ?? ''; -const supportedOperations = new Set(['get', 'store', 'erase']); - -if (!supportedOperations.has(operation)) { - process.exit(0); -} - -const proxyUrl = process.env.GIT_CRED_PROXY_URL ?? 'http://host.docker.internal:18765'; -const tokenFile = - process.env.GIT_CRED_PROXY_TOKEN_FILE ?? path.join(__dirname, 'state', 'token'); - -function readStdin() { - return new Promise((resolve, reject) => { - const chunks = []; - - process.stdin.on('data', (chunk) => { - chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); - }); - - process.stdin.on('end', () => { - resolve(Buffer.concat(chunks).toString('utf8')); - }); - - process.stdin.on('error', reject); - }); -} - -function request(url, body, token) { - const client = url.protocol === 'https:' ? https : http; - - return new Promise((resolve, reject) => { - const req = client.request( - url, - { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'text/plain; charset=utf-8', - 'Content-Length': Buffer.byteLength(body), - }, - }, - (res) => { - const chunks = []; - - res.on('data', (chunk) => { - chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); - }); - - res.on('end', () => { - resolve({ - statusCode: res.statusCode ?? 0, - body: Buffer.concat(chunks).toString('utf8'), - }); - }); - }, - ); - - req.on('error', reject); - req.end(body); - }); -} - -async function main() { - const token = (process.env.GIT_CRED_PROXY_TOKEN ?? (await fs.readFile(tokenFile, 'utf8'))).trim(); - const body = await readStdin(); - const url = new URL(`/${operation}`, proxyUrl); - const response = await request(url, body, token); - - if (response.statusCode === 200) { - process.stdout.write(response.body); - return; - } - - if (response.body) { - process.stderr.write(response.body.trimEnd() + '\n'); - } else { - process.stderr.write(`Proxy request failed with status ${response.statusCode}\n`); - } - - process.exit(1); -} - -main().catch((error) => { - process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); - process.exit(1); -}); diff --git a/server.mjs b/server.mjs deleted file mode 100644 index 828af84..0000000 --- a/server.mjs +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env node - -import http from 'node:http'; -import process from 'node:process'; -import { spawn } from 'node:child_process'; - -const host = process.env.GIT_CRED_PROXY_HOST ?? '127.0.0.1'; -const port = Number(process.env.GIT_CRED_PROXY_PORT ?? '18765'); -const token = process.env.GIT_CRED_PROXY_TOKEN ?? ''; - -if (!token) { - process.stderr.write('Missing GIT_CRED_PROXY_TOKEN\n'); - process.exit(1); -} - -const allowedProtocols = new Set( - (process.env.GIT_CRED_PROXY_PROTOCOLS ?? 'https') - .split(',') - .map((value) => value.trim().toLowerCase()) - .filter(Boolean), -); - -const allowedHosts = new Set( - (process.env.GIT_CRED_PROXY_ALLOWED_HOSTS ?? '') - .split(',') - .map((value) => value.trim()) - .filter(Boolean), -); - -const actionMap = new Map([ - ['get', 'fill'], - ['store', 'approve'], - ['erase', 'reject'], - ['fill', 'fill'], - ['approve', 'approve'], - ['reject', 'reject'], -]); - -function parseCredentialBody(body) { - const result = {}; - - for (const line of body.split('\n')) { - if (!line || !line.includes('=')) { - continue; - } - - const index = line.indexOf('='); - const key = line.slice(0, index); - const value = line.slice(index + 1); - result[key] = value; - } - - return result; -} - -function readBody(req) { - return new Promise((resolve, reject) => { - const chunks = []; - let size = 0; - - req.on('data', (chunk) => { - const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); - size += buffer.length; - - if (size > 64 * 1024) { - reject(new Error('Request body too large')); - req.destroy(); - return; - } - - chunks.push(buffer); - }); - - req.on('end', () => { - resolve(Buffer.concat(chunks).toString('utf8')); - }); - - req.on('error', reject); - }); -} - -function runGitCredential(action, input) { - return new Promise((resolve, reject) => { - const child = spawn('git', ['credential', action], { - env: { - ...process.env, - GIT_TERMINAL_PROMPT: '0', - }, - stdio: ['pipe', 'pipe', 'pipe'], - }); - - let stdout = ''; - let stderr = ''; - - child.stdout.on('data', (chunk) => { - stdout += chunk.toString(); - }); - - child.stderr.on('data', (chunk) => { - stderr += chunk.toString(); - }); - - child.on('error', reject); - child.on('close', (code) => { - resolve({ code: code ?? 1, stdout, stderr }); - }); - - child.stdin.end(input); - }); -} - -function sendText(res, statusCode, body) { - res.writeHead(statusCode, { - 'Content-Type': 'text/plain; charset=utf-8', - }); - res.end(body); -} - -function isMissingCredentialError(stderr) { - const normalized = stderr.toLowerCase(); - return ( - normalized.includes('terminal prompts disabled') || - normalized.includes('could not read username') || - normalized.includes('could not read password') - ); -} - -const server = http.createServer(async (req, res) => { - try { - const url = new URL(req.url ?? '/', 'http://localhost'); - - if (req.method === 'GET' && url.pathname === '/healthz') { - sendText(res, 200, 'ok\n'); - return; - } - - if (req.method !== 'POST') { - sendText(res, 405, 'Method Not Allowed\n'); - return; - } - - const authHeader = req.headers.authorization ?? ''; - if (authHeader !== `Bearer ${token}`) { - sendText(res, 401, 'Unauthorized\n'); - return; - } - - const action = actionMap.get(url.pathname.replace(/^\//, '')); - if (!action) { - sendText(res, 404, 'Not Found\n'); - return; - } - - const body = await readBody(req); - const attrs = parseCredentialBody(body); - const protocol = attrs.protocol?.toLowerCase(); - const requestHost = attrs.host; - - if (allowedProtocols.size > 0) { - if (!protocol) { - sendText(res, 400, 'Missing protocol\n'); - return; - } - - if (!allowedProtocols.has(protocol)) { - sendText(res, 403, 'Protocol not allowed\n'); - return; - } - } - - if (allowedHosts.size > 0 && requestHost && !allowedHosts.has(requestHost)) { - sendText(res, 403, 'Host not allowed\n'); - return; - } - - const result = await runGitCredential(action, body); - - if (result.code === 0) { - sendText(res, 200, result.stdout); - return; - } - - if (action === 'fill' && isMissingCredentialError(result.stderr)) { - sendText(res, 200, ''); - return; - } - - sendText(res, 502, result.stderr || `git credential ${action} failed\n`); - } catch (error) { - sendText(res, 500, `${error instanceof Error ? error.message : String(error)}\n`); - } -}); - -server.listen(port, host, () => { - process.stdout.write(`Git credential proxy listening on http://${host}:${port}\n`); -}); - -function shutdown() { - server.close(() => { - process.exit(0); - }); -} - -process.on('SIGINT', shutdown); -process.on('SIGTERM', shutdown); diff --git a/start.sh b/start.sh deleted file mode 100755 index 02a19f3..0000000 --- a/start.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/sh -set -eu - -script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -state_dir="$script_dir/state" -pid_file="$state_dir/server.pid" -log_file="$state_dir/server.log" -token_file="$state_dir/token" -config_file="$state_dir/config.env" - -host="${GIT_CRED_PROXY_HOST:-127.0.0.1}" -port="${GIT_CRED_PROXY_PORT:-18765}" -protocols="${GIT_CRED_PROXY_PROTOCOLS:-https}" -allowed_hosts="${GIT_CRED_PROXY_ALLOWED_HOSTS:-}" -public_url="${GIT_CRED_PROXY_PUBLIC_URL:-http://host.docker.internal:${port}}" - -mkdir -p "$state_dir" - -if [ -f "$pid_file" ]; then - old_pid=$(cat "$pid_file" 2>/dev/null || true) - if [ -n "${old_pid:-}" ] && kill -0 "$old_pid" 2>/dev/null; then - printf 'Proxy already running: pid=%s\n' "$old_pid" - printf 'Container URL: %s\n' "$public_url" - printf 'Token file: %s\n' "$token_file" - exit 0 - fi - rm -f "$pid_file" -fi - -if [ ! -f "$token_file" ]; then - if ! command -v openssl >/dev/null 2>&1; then - printf 'openssl is required to create the token file\n' >&2 - exit 1 - fi - - umask 077 - openssl rand -hex 32 > "$token_file" -fi - -runtime='' -if [ -n "${GIT_CRED_PROXY_RUNTIME:-}" ]; then - runtime="$GIT_CRED_PROXY_RUNTIME" -elif command -v bun >/dev/null 2>&1; then - runtime='bun' -elif command -v node >/dev/null 2>&1; then - runtime='node' -else - printf 'Either bun or node is required to start the proxy\n' >&2 - exit 1 -fi - -token=$(tr -d '\r\n' < "$token_file") - -cat > "$config_file" <>"$log_file" 2>&1 & - -pid=$! -printf '%s\n' "$pid" > "$pid_file" - -sleep 1 - -if ! kill -0 "$pid" 2>/dev/null; then - printf 'Proxy failed to start. Check %s\n' "$log_file" >&2 - exit 1 -fi - -printf 'Proxy started\n' -printf 'Host listen URL: http://%s:%s\n' "$host" "$port" -printf 'Container URL: %s\n' "$public_url" -printf 'Token file: %s\n' "$token_file" -printf 'Log file: %s\n' "$log_file" -printf 'Next: run /workspaces/host-git-cred-proxy/configure-container.sh inside the container\n' diff --git a/status.sh b/status.sh deleted file mode 100755 index ecae8e8..0000000 --- a/status.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/sh -set -eu - -script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -state_dir="$script_dir/state" -pid_file="$state_dir/server.pid" -log_file="$state_dir/server.log" -config_file="$state_dir/config.env" - -host='127.0.0.1' -port='18765' -public_url="http://host.docker.internal:${port}" - -if [ -f "$config_file" ]; then - . "$config_file" - host="${GIT_CRED_PROXY_HOST:-$host}" - port="${GIT_CRED_PROXY_PORT:-$port}" - public_url="${GIT_CRED_PROXY_PUBLIC_URL:-$public_url}" -fi - -printf 'Host listen URL: http://%s:%s\n' "$host" "$port" -printf 'Container URL: %s\n' "$public_url" - -if [ ! -f "$pid_file" ]; then - printf 'Status: stopped\n' - exit 1 -fi - -pid=$(cat "$pid_file" 2>/dev/null || true) - -if [ -z "${pid:-}" ] || ! kill -0 "$pid" 2>/dev/null; then - printf 'Status: stale pid file\n' - exit 1 -fi - -printf 'Status: running (pid=%s)\n' "$pid" - -if command -v curl >/dev/null 2>&1; then - if curl -fsS "http://${host}:${port}/healthz" >/dev/null 2>&1; then - printf 'Health: ok\n' - else - printf 'Health: check failed\n' - fi -fi - -if [ -f "$log_file" ]; then - printf 'Log file: %s\n' "$log_file" -fi diff --git a/stop.sh b/stop.sh deleted file mode 100755 index 9d59590..0000000 --- a/stop.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh -set -eu - -script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -pid_file="$script_dir/state/server.pid" - -if [ ! -f "$pid_file" ]; then - printf 'Proxy is not running\n' - exit 0 -fi - -pid=$(cat "$pid_file" 2>/dev/null || true) - -if [ -z "${pid:-}" ]; then - rm -f "$pid_file" - printf 'Stale pid file removed\n' - exit 0 -fi - -if kill -0 "$pid" 2>/dev/null; then - kill "$pid" - printf 'Stopped proxy pid=%s\n' "$pid" -else - printf 'Proxy process was not running, removing stale pid file\n' -fi - -rm -f "$pid_file"