init-cursor: авто-bootstrap git на новом ПК, install.ps1 с Gitea.
Один вызов init-cursor настраивает SSH при необходимости и копирует rules в проект.
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Настройка git на машине: user, SSH, Gitea keys, clone/update Rules.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Root,
|
||||
[string]$RulesDest = '',
|
||||
[string]$CloneUrl = 'ssh://git@git.kalinamall.lan:2222/PapaTramp/Rules.git',
|
||||
[string]$HttpsCloneUrl = 'https://git.kalinamall.ru/PapaTramp/Rules.git',
|
||||
[switch]$SkipClone,
|
||||
[switch]$SkipProfile,
|
||||
[switch]$SkipGitSetup,
|
||||
[switch]$WhatIf
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Write-Step([string]$Message) { Write-Host "[*] $Message" -ForegroundColor Cyan }
|
||||
function Write-Ok([string]$Message) { Write-Host "[+] $Message" -ForegroundColor Green }
|
||||
function Write-Warn([string]$Message) { Write-Host "[!] $Message" -ForegroundColor Yellow }
|
||||
|
||||
function Read-KeyValueFile {
|
||||
param([string]$Path)
|
||||
$data = @{}
|
||||
if (-not (Test-Path -LiteralPath $Path)) { return $data }
|
||||
Get-Content -LiteralPath $Path | ForEach-Object {
|
||||
if ($_ -match '^([^#=]+)=(.*)$') {
|
||||
$data[$Matches[1].Trim()] = $Matches[2].Trim()
|
||||
}
|
||||
}
|
||||
return $data
|
||||
}
|
||||
|
||||
function Test-MachineGitReady {
|
||||
param([string]$ConfigDir)
|
||||
|
||||
$hostsConfig = Join-Path $ConfigDir 'git-ssh-hosts.conf'
|
||||
if (-not (Test-Path -LiteralPath $hostsConfig)) { return $false }
|
||||
|
||||
$sshConfig = Join-Path $HOME '.ssh\config'
|
||||
if (-not (Test-Path -LiteralPath $sshConfig)) { return $false }
|
||||
|
||||
$profiles = @{}
|
||||
$currentProfile = $null
|
||||
Get-Content -LiteralPath $hostsConfig | ForEach-Object {
|
||||
$line = $_.Trim()
|
||||
if ($line -match '^\[profile:(.+)\]$') {
|
||||
$currentProfile = $Matches[1]
|
||||
$profiles[$currentProfile] = @{}
|
||||
return
|
||||
}
|
||||
if ($line -match '^\[host:') { $currentProfile = $null; return }
|
||||
if ($line -match '^([^=]+)=(.*)$' -and $currentProfile) {
|
||||
$profiles[$currentProfile][$Matches[1].Trim()] = $Matches[2].Trim()
|
||||
}
|
||||
}
|
||||
|
||||
$sshText = Get-Content -LiteralPath $sshConfig -Raw
|
||||
foreach ($profile in $profiles.Values) {
|
||||
$keyName = $profile.KeyName
|
||||
if (-not $keyName) { continue }
|
||||
$keyPath = Join-Path $HOME ".ssh\$keyName"
|
||||
if (-not (Test-Path -LiteralPath $keyPath)) { return $false }
|
||||
}
|
||||
|
||||
if ($sshText -notmatch '(?m)^Host\s+git\.kalinamall\.lan\s*$') { return $false }
|
||||
return $true
|
||||
}
|
||||
|
||||
function Ensure-RulesRepository {
|
||||
param(
|
||||
[string]$Destination,
|
||||
[string]$SshUrl,
|
||||
[string]$HttpsUrl
|
||||
)
|
||||
|
||||
if (Test-Path -LiteralPath $Destination) {
|
||||
if (Test-Path (Join-Path $Destination '.git')) {
|
||||
if ($WhatIf) {
|
||||
Write-Step "WhatIf: git pull $Destination"
|
||||
return
|
||||
}
|
||||
Write-Step "Updating $Destination"
|
||||
git -C $Destination pull --ff-only
|
||||
Write-Ok 'CursorRules updated'
|
||||
return
|
||||
}
|
||||
|
||||
$backup = "${Destination}.bak-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
|
||||
Write-Warn "Replacing non-git folder: $Destination -> $backup"
|
||||
if (-not $WhatIf) {
|
||||
Move-Item -LiteralPath $Destination -Destination $backup
|
||||
}
|
||||
}
|
||||
|
||||
if ($WhatIf) {
|
||||
Write-Step "WhatIf: clone -> $Destination"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Step "Cloning Rules via SSH: $SshUrl"
|
||||
git clone --depth 1 $SshUrl $Destination 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Ok "Cloned to $Destination"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Warn 'SSH clone failed - trying HTTPS (enter Gitea login once)'
|
||||
git clone --depth 1 $HttpsUrl $Destination
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to clone Rules repository"
|
||||
}
|
||||
Write-Ok "Cloned via HTTPS to $Destination"
|
||||
}
|
||||
|
||||
function Ensure-InitCursorProfile {
|
||||
param([string]$RulesDest)
|
||||
|
||||
$profileHook = ". `"$RulesDest\init-cursor.ps1`""
|
||||
$profilePath = $PROFILE.CurrentUserAllHosts
|
||||
if (-not $profilePath) { $profilePath = $PROFILE.CurrentUserCurrentHost }
|
||||
|
||||
if ($WhatIf) {
|
||||
Write-Step "WhatIf: profile hook -> $profilePath"
|
||||
return
|
||||
}
|
||||
|
||||
$profileDir = Split-Path -Parent $profilePath
|
||||
if (-not (Test-Path -LiteralPath $profileDir)) {
|
||||
New-Item -ItemType Directory -Path $profileDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$profileText = if (Test-Path -LiteralPath $profilePath) {
|
||||
Get-Content -LiteralPath $profilePath -Raw
|
||||
} else {
|
||||
''
|
||||
}
|
||||
|
||||
if ($profileText -notmatch [regex]::Escape($profileHook)) {
|
||||
if ($profileText -and -not $profileText.EndsWith("`n")) { $profileText += "`n" }
|
||||
$profileText += $profileHook + "`n"
|
||||
Set-Content -LiteralPath $profilePath -Value $profileText.TrimEnd() -Encoding UTF8
|
||||
Write-Ok "Profile updated: $profilePath"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $RulesDest) {
|
||||
$RulesDest = Join-Path $HOME 'CursorRules'
|
||||
}
|
||||
|
||||
$configDir = Join-Path $Root 'local'
|
||||
if (-not (Test-Path -LiteralPath $configDir)) {
|
||||
throw "Bootstrap config not found: $configDir"
|
||||
}
|
||||
|
||||
$gitReady = Test-MachineGitReady -ConfigDir $configDir
|
||||
$repoReady = Test-Path (Join-Path $RulesDest '.git')
|
||||
|
||||
if (-not $gitReady -and -not $SkipGitSetup) {
|
||||
Write-Step 'Machine bootstrap: git + SSH'
|
||||
|
||||
$userCfgPath = Join-Path $configDir 'git-user.conf'
|
||||
if (Test-Path -LiteralPath $userCfgPath) {
|
||||
$userCfg = Read-KeyValueFile -Path $userCfgPath
|
||||
if ($userCfg.UserName -and -not $WhatIf) {
|
||||
git config --global user.name $userCfg.UserName | Out-Null
|
||||
Write-Ok "git user.name = $($userCfg.UserName)"
|
||||
}
|
||||
if ($userCfg.UserEmail -and -not $WhatIf) {
|
||||
git config --global user.email $userCfg.UserEmail | Out-Null
|
||||
Write-Ok "git user.email = $($userCfg.UserEmail)"
|
||||
}
|
||||
}
|
||||
|
||||
$setupArgs = @{ ConfigDir = $configDir; WhatIf = $WhatIf.IsPresent }
|
||||
& (Join-Path $Root 'scripts\Setup-GitSsh.ps1') @setupArgs
|
||||
|
||||
$registerArgs = @{ ConfigDir = $configDir; WhatIf = $WhatIf.IsPresent }
|
||||
& (Join-Path $Root 'scripts\Register-GiteaSshKeys.ps1') @registerArgs
|
||||
|
||||
if (-not $WhatIf) {
|
||||
Write-Step 'Testing git@git.kalinamall.lan'
|
||||
ssh -T -o BatchMode=yes -o ConnectTimeout=10 git@git.kalinamall.lan 2>&1 | ForEach-Object { Write-Host $_ }
|
||||
}
|
||||
}
|
||||
elseif ($gitReady) {
|
||||
Write-Ok 'Git SSH already configured'
|
||||
}
|
||||
|
||||
if (-not $SkipClone -and -not $repoReady) {
|
||||
Ensure-RulesRepository -Destination $RulesDest -SshUrl $CloneUrl -HttpsUrl $HttpsCloneUrl
|
||||
}
|
||||
elseif ($repoReady -and -not $SkipClone -and -not $WhatIf) {
|
||||
Write-Ok "CursorRules repo OK: $RulesDest"
|
||||
}
|
||||
|
||||
if (-not $SkipProfile) {
|
||||
Ensure-InitCursorProfile -RulesDest $RulesDest
|
||||
}
|
||||
|
||||
Write-Ok 'Machine bootstrap complete'
|
||||
|
||||
# Export test function for init-cursor dot-source
|
||||
function Test-CursorMachineGitReady {
|
||||
param([string]$ConfigDir = (Join-Path $HOME 'CursorRules\local'))
|
||||
return Test-MachineGitReady -ConfigDir $ConfigDir
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env bash
|
||||
# Настройка git на машине: user, SSH, Gitea keys, clone/update Rules.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="${ROOT:?ROOT is required}"
|
||||
RULES_DEST="${RULES_DEST:-$HOME/CursorRules}"
|
||||
CLONE_URL="${CLONE_URL:-ssh://git@git.kalinamall.lan:2222/PapaTramp/Rules.git}"
|
||||
HTTPS_CLONE_URL="${HTTPS_CLONE_URL:-https://git.kalinamall.ru/PapaTramp/Rules.git}"
|
||||
SKIP_CLONE="${SKIP_CLONE:-0}"
|
||||
SKIP_PROFILE="${SKIP_PROFILE:-0}"
|
||||
SKIP_GIT_SETUP="${SKIP_GIT_SETUP:-0}"
|
||||
WHAT_IF="${WHAT_IF:-0}"
|
||||
|
||||
log_step() { printf '[*] %s\n' "$1"; }
|
||||
log_ok() { printf '[+] %s\n' "$1"; }
|
||||
log_warn() { printf '[!] %s\n' "$1"; }
|
||||
|
||||
CONFIG_DIR="$ROOT/local"
|
||||
[[ -d "$CONFIG_DIR" ]] || { echo "Bootstrap config not found: $CONFIG_DIR" >&2; exit 1; }
|
||||
|
||||
test_machine_git_ready() {
|
||||
local hosts_config="$CONFIG_DIR/git-ssh-hosts.conf"
|
||||
local ssh_config="$HOME/.ssh/config"
|
||||
[[ -f "$hosts_config" && -f "$ssh_config" ]] || return 1
|
||||
grep -q '^Host git.kalinamall.lan$' "$ssh_config" || return 1
|
||||
|
||||
local line current_profile='' key_name
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
line="${line%%#*}"
|
||||
line="${line%"${line##*[![:space:]]}"}"
|
||||
[[ -z "$line" ]] && continue
|
||||
if [[ "$line" =~ ^\[profile:(.+)\]$ ]]; then
|
||||
current_profile="${BASH_REMATCH[1]}"
|
||||
continue
|
||||
fi
|
||||
if [[ "$line" =~ ^\[host: ]]; then
|
||||
current_profile=''
|
||||
continue
|
||||
fi
|
||||
if [[ "$line" == KeyName=* && -n "$current_profile" ]]; then
|
||||
key_name="${line#KeyName=}"
|
||||
[[ -f "$HOME/.ssh/$key_name" ]] || return 1
|
||||
fi
|
||||
done < "$hosts_config"
|
||||
return 0
|
||||
}
|
||||
|
||||
ensure_rules_repository() {
|
||||
if [[ -d "$RULES_DEST/.git" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -e "$RULES_DEST" ]]; then
|
||||
local backup="${RULES_DEST}.bak-$(date +%Y%m%d-%H%M%S)"
|
||||
log_warn "Replacing non-git folder: $RULES_DEST -> $backup"
|
||||
if [[ "$WHAT_IF" != '1' ]]; then
|
||||
mv "$RULES_DEST" "$backup"
|
||||
fi
|
||||
fi
|
||||
|
||||
log_step "Cloning Rules via SSH: $CLONE_URL"
|
||||
if [[ "$WHAT_IF" == '1' ]]; then
|
||||
log_step "WhatIf: clone -> $RULES_DEST"
|
||||
return 0
|
||||
fi
|
||||
if git clone --depth 1 "$CLONE_URL" "$RULES_DEST" 2>/dev/null; then
|
||||
log_ok "Cloned to $RULES_DEST"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_warn 'SSH clone failed - trying HTTPS (enter Gitea login once)'
|
||||
git clone --depth 1 "$HTTPS_CLONE_URL" "$RULES_DEST"
|
||||
log_ok "Cloned via HTTPS to $RULES_DEST"
|
||||
}
|
||||
|
||||
ensure_init_cursor_profile() {
|
||||
local hook="source \"$RULES_DEST/init-cursor.sh\""
|
||||
local profile
|
||||
for profile in "$HOME/.bashrc" "$HOME/.zshrc"; do
|
||||
[[ -f "$profile" ]] || continue
|
||||
if [[ "$WHAT_IF" == '1' ]]; then
|
||||
log_step "WhatIf: profile hook -> $profile"
|
||||
continue
|
||||
fi
|
||||
if ! grep -Fq "$hook" "$profile" 2>/dev/null; then
|
||||
printf '\n%s\n' "$hook" >> "$profile"
|
||||
log_ok "Profile updated: $profile"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
git_ready=0
|
||||
repo_ready=0
|
||||
test_machine_git_ready && git_ready=1
|
||||
[[ -d "$RULES_DEST/.git" ]] && repo_ready=1
|
||||
|
||||
if [[ "$git_ready" -eq 0 && "$SKIP_GIT_SETUP" != '1' ]]; then
|
||||
log_step 'Machine bootstrap: git + SSH'
|
||||
if [[ -f "$CONFIG_DIR/git-user.conf" ]]; then
|
||||
while IFS= read -r kv; do
|
||||
key="${kv%%=*}"
|
||||
value="${kv#*=}"
|
||||
case "$key" in
|
||||
UserName) git config --global user.name "$value"; log_ok "git user.name = $value" ;;
|
||||
UserEmail) git config --global user.email "$value"; log_ok "git user.email = $value" ;;
|
||||
esac
|
||||
done < <(grep -v '^#' "$CONFIG_DIR/git-user.conf" | grep '=' || true)
|
||||
fi
|
||||
export CONFIG_DIR WHAT_IF
|
||||
bash "$ROOT/scripts/setup-git-ssh.sh"
|
||||
bash "$ROOT/scripts/register-gitea-ssh-keys.sh"
|
||||
log_step 'Testing git@git.kalinamall.lan'
|
||||
ssh -T -o BatchMode=yes -o ConnectTimeout=10 git@git.kalinamall.lan || true
|
||||
elif [[ "$git_ready" -eq 1 ]]; then
|
||||
log_ok 'Git SSH already configured'
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_CLONE" != '1' && "$repo_ready" -eq 0 ]]; then
|
||||
ensure_rules_repository
|
||||
elif [[ "$repo_ready" -eq 1 ]]; then
|
||||
log_ok "CursorRules repo OK: $RULES_DEST"
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_PROFILE" != '1' ]]; then
|
||||
ensure_init_cursor_profile
|
||||
fi
|
||||
|
||||
log_ok 'Machine bootstrap complete'
|
||||
Reference in New Issue
Block a user