diff --git a/README.md b/README.md index 350113d..b24b711 100644 --- a/README.md +++ b/README.md @@ -2,58 +2,55 @@ Правила и скрипты для настройки нового компьютера: SSH-ключи, Cursor rules, git remotes, home Gitea. -## Как это устроено +## Один вызов: `init-cursor` -| Этап | Скрипт | Что делает | -|------|--------|------------| -| Новый ПК | `bootstrap.ps1` / `bootstrap.sh` | SSH-ключи, регистрация в Gitea, `git clone` Rules → `$HOME/CursorRules` | -| Новый проект | `init-cursor` | Копирует rules **из локального** `$HOME/CursorRules` в текущий каталог | -| Обновить rules | `init-cursor -Update` | `git pull` в `$HOME/CursorRules`, затем копирование | +| Состояние машины | Что делает `init-cursor` | +|------------------|--------------------------| +| Ничего не настроено | SSH-ключи, Gitea API, clone Rules в `$HOME/CursorRules`, профиль shell | +| Git уже настроен | Только копирует rules в текущий проект + remotes | -`init-cursor` **не** тянет правила с remote напрямую — только из локального клона. Сначала нужен bootstrap (или ручной clone после настройки SSH). +Источник правил — локальный клон `$HOME/CursorRules` (после bootstrap). Обновить: `init-cursor -Update`. ## Новый Windows-ПК -На голом ПК нет доступа к приватному git — сначала скопируйте каталог Rules с рабочей машины: +В каталоге **вашего проекта** (один раз, Gitea спросит логин/пароль по HTTPS): ```powershell -scp -r user@working-pc:~/CursorRules $env:TEMP\rules-bootstrap -cd $env:TEMP\rules-bootstrap -.\bootstrap.ps1 +irm https://git.kalinamall.ru/PapaTramp/Rules/raw/branch/main/install.ps1 | iex ``` -Bootstrap: +Дальше в любом проекте: -1. `git config` user.name / user.email (`local/git-user.conf`) -2. Генерирует SSH-ключи, пишет `~/.ssh/config` (`scripts/Setup-GitSsh.ps1`) -3. Регистрирует pubkey в Gitea API (`scripts/Register-GiteaSshKeys.ps1`) -4. `git clone ssh://git@git.kalinamall.lan:2222/PapaTramp/Rules.git` → `$HOME\CursorRules` -5. Добавляет `init-cursor` в PowerShell profile +```powershell +init-cursor +``` ## Новый Linux-хост ```bash -scp -r user@working-pc:~/CursorRules /tmp/rules-bootstrap -cd /tmp/rules-bootstrap -chmod +x bootstrap.sh scripts/*.sh -./bootstrap.sh +cd ~/projects/your-repo +curl -fsSL https://git.kalinamall.ru/PapaTramp/Rules/raw/branch/main/install.sh | bash ``` -## В каждом проекте - -```powershell -cd C:\path\to\repo -init-cursor -init-cursor -Update # подтянуть свежие rules из remote -``` +Затем: ```bash -cd ~/projects/myrepo init-cursor init-cursor --update ``` -Скрипт копирует `.cursorignore` и `.cursor/rules/*.mdc`, дополняет `.gitignore`, настраивает remotes (`home`, `github`, `kalinamall`) и создаёт репозиторий на home Gitea через API. +## Что происходит внутри + +1. **install.ps1 / install.sh** — shallow clone Rules по HTTPS (первый раз) +2. **init-cursor** вызывает `Invoke-MachineBootstrap` / `invoke-machine-bootstrap.sh`: + - `local/git-user.conf` — git user.name / email + - `scripts/Setup-GitSsh*` — ключи и `~/.ssh/config` + - `scripts/Register-GiteaSshKeys*` — pubkey в Gitea API + - clone/update `$HOME/CursorRules` (SSH, fallback HTTPS) +3. Копирует `.cursorignore`, `.cursor/rules/*.mdc`, дополняет `.gitignore` +4. `Set-GitRemotes*` + `Ensure-GiteaHomeRepo.ps1` для текущего репозитория + +`bootstrap.ps1` / `bootstrap.sh` оставлены для ручного запуска без проекта. ## Конфиги (local/) @@ -61,7 +58,5 @@ init-cursor --update |------|------------| | `git-ssh-hosts.conf` | Хосты и профили SSH-ключей | | `git-user.conf` | `user.name` / `user.email` | -| `kalinamall-gitea.conf` | API kalinamall (для регистрации SSH-ключа) | -| `papatramp-gitea.conf` | API home Gitea + Ensure-GiteaHomeRepo | - -Заполните `GiteaApiPassword` в `kalinamall-gitea.conf` (приватный репозиторий). +| `kalinamall-gitea.conf` | API kalinamall | +| `papatramp-gitea.conf` | API home Gitea | diff --git a/bootstrap.ps1 b/bootstrap.ps1 index cdc569c..e6a17b0 100644 --- a/bootstrap.ps1 +++ b/bootstrap.ps1 @@ -1,140 +1,28 @@ #Requires -Version 5.1 <# .SYNOPSIS - Первичная настройка нового компьютера: SSH, Gitea keys, clone Rules, init-cursor в профиль. - -.DESCRIPTION - 1. git user.name / user.email - 2. SSH-ключи и ~/.ssh/config - 3. Регистрация pubkey в Gitea (kalinamall, papatramp) - 4. git clone Rules -> $HOME\CursorRules - 5. Подключение init-cursor в PowerShell profile - - На голом ПК скопируйте каталог Rules с рабочей машины (scp/USB), затем: - .\bootstrap.ps1 - -.EXAMPLE - .\bootstrap.ps1 + Обёртка над Invoke-MachineBootstrap.ps1 (совместимость). #> [CmdletBinding()] param( [string]$RulesDest = '', - [string]$CloneUrl = 'ssh://git@git.kalinamall.lan:2222/PapaTramp/Rules.git', [switch]$SkipClone, [switch]$SkipProfile, [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 } - $root = $PSScriptRoot -$configDir = Join-Path $root 'local' if (-not $RulesDest) { $RulesDest = Join-Path $HOME 'CursorRules' } -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 -} - -Write-Step 'Bootstrap: new machine setup' - -$userCfgPath = Join-Path $configDir 'git-user.conf' -if (Test-Path -LiteralPath $userCfgPath) { - $userCfg = Read-KeyValueFile -Path $userCfgPath - if ($userCfg.UserName) { - git config --global user.name $userCfg.UserName | Out-Null - Write-Ok "git user.name = $($userCfg.UserName)" - } - if ($userCfg.UserEmail) { - 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 $_ } - if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 1) { - Write-Warn 'SSH test failed — check VPN/LAN and Gitea SSH keys' - } -} - -if (-not $SkipClone) { - if ($WhatIf) { - Write-Step "WhatIf: clone $CloneUrl -> $RulesDest" - } - elseif (Test-Path (Join-Path $RulesDest '.git')) { - Write-Step "Updating $RulesDest" - git -C $RulesDest pull --ff-only - Write-Ok 'CursorRules updated' - } - else { - if (Test-Path -LiteralPath $RulesDest) { - throw "Path exists and is not a git repo: $RulesDest" - } - Write-Step "Cloning $CloneUrl" - git clone $CloneUrl $RulesDest - Write-Ok "Cloned to $RulesDest" - } -} - -if (-not $SkipProfile) { - $profileHook = ". `"$RulesDest\init-cursor.ps1`"" - $profilePath = $PROFILE.CurrentUserAllHosts - if (-not $profilePath) { $profilePath = $PROFILE.CurrentUserCurrentHost } - - if ($WhatIf) { - Write-Step "WhatIf: add to profile $profilePath" - } - else { - $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" - } - else { - Write-Ok 'init-cursor already in profile' - } - } +& (Join-Path $root 'scripts\Invoke-MachineBootstrap.ps1') @{ + Root = $root + RulesDest = $RulesDest + SkipClone = $SkipClone.IsPresent + SkipProfile = $SkipProfile.IsPresent + WhatIf = $WhatIf.IsPresent } Write-Host '' -Write-Ok 'Bootstrap complete. Open a new shell, then in a project: init-cursor' +Write-Host '[+] Bootstrap complete. In a project run: init-cursor' -ForegroundColor Green diff --git a/bootstrap.sh b/bootstrap.sh index fd6dc85..f22de98 100644 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,87 +1,13 @@ #!/usr/bin/env bash -# Первичная настройка нового Linux-хоста: SSH, Gitea keys, clone Rules, init-cursor в shell profile. +# Обёртка над invoke-machine-bootstrap.sh (совместимость). set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CONFIG_DIR="$ROOT/local" RULES_DEST="${RULES_DEST:-$HOME/CursorRules}" -CLONE_URL="${CLONE_URL:-ssh://git@git.kalinamall.lan:2222/PapaTramp/Rules.git}" SKIP_CLONE="${SKIP_CLONE:-0}" SKIP_PROFILE="${SKIP_PROFILE:-0}" -WHAT_IF="${WHAT_IF:-0}" -log_step() { printf '[*] %s\n' "$1"; } -log_ok() { printf '[+] %s\n' "$1"; } -log_warn() { printf '[!] %s\n' "$1"; } +ROOT="$ROOT" RULES_DEST="$RULES_DEST" SKIP_CLONE="$SKIP_CLONE" SKIP_PROFILE="$SKIP_PROFILE" \ + bash "$ROOT/scripts/invoke-machine-bootstrap.sh" -read_kv_file() { - local file="$1" - local line key value - while IFS= read -r line || [[ -n "$line" ]]; do - [[ "$line" =~ ^[[:space:]]*# ]] && continue - [[ "$line" != *=* ]] && continue - key="${line%%=*}" - value="${line#*=}" - key="${key%"${key##*[![:space:]]}"}" - value="${value#"${value%%[![:space:]]*}"}" - printf '%s=%s\n' "$key" "$value" - done < "$file" -} - -log_step 'Bootstrap: new machine setup' - -user_cfg="$CONFIG_DIR/git-user.conf" -if [[ -f "$user_cfg" ]]; 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 < <(read_kv_file "$user_cfg") -fi - -export CONFIG_DIR WHAT_IF -bash "$ROOT/scripts/setup-git-ssh.sh" -bash "$ROOT/scripts/register-gitea-ssh-keys.sh" - -if [[ "$WHAT_IF" != '1' ]]; then - log_step 'Testing git@git.kalinamall.lan' - ssh -T -o BatchMode=yes -o ConnectTimeout=10 git@git.kalinamall.lan || true -fi - -if [[ "$SKIP_CLONE" != '1' ]]; then - if [[ "$WHAT_IF" == '1' ]]; then - log_step "WhatIf: clone $CLONE_URL -> $RULES_DEST" - elif [[ -d "$RULES_DEST/.git" ]]; then - log_step "Updating $RULES_DEST" - git -C "$RULES_DEST" pull --ff-only - log_ok 'CursorRules updated' - else - [[ -e "$RULES_DEST" ]] && { echo "Path exists and is not a git repo: $RULES_DEST" >&2; exit 1; } - log_step "Cloning $CLONE_URL" - git clone "$CLONE_URL" "$RULES_DEST" - log_ok "Cloned to $RULES_DEST" - fi -fi - -if [[ "$SKIP_PROFILE" != '1' ]]; then - profile_hook="source \"$RULES_DEST/init-cursor.sh\"" - for profile in "$HOME/.bashrc" "$HOME/.zshrc"; do - [[ -f "$profile" ]] || continue - if ! grep -Fq "$profile_hook" "$profile" 2>/dev/null; then - if [[ "$WHAT_IF" == '1' ]]; then - log_step "WhatIf: add to $profile" - else - printf '\n%s\n' "$profile_hook" >> "$profile" - log_ok "Profile updated: $profile" - fi - else - log_ok "init-cursor already in $profile" - fi - done -fi - -printf '\n' -log_ok 'Bootstrap complete. Open a new shell, then in a project: init-cursor' +printf '\n[+] Bootstrap complete. In a project run: init-cursor\n' diff --git a/init-cursor.ps1 b/init-cursor.ps1 index a84955f..d1b9f7d 100644 --- a/init-cursor.ps1 +++ b/init-cursor.ps1 @@ -1,47 +1,91 @@ #Requires -Version 5.1 <# .SYNOPSIS - Копирует Cursor rules из локального $HOME\CursorRules в текущий проект. + Инициализация Cursor в текущем проекте; на новом ПК сначала настраивает git/SSH. .DESCRIPTION - Источник — локальный клон репозитория Rules ($HOME\CursorRules), не remote напрямую. - На новом ПК сначала: bootstrap.ps1 / bootstrap.sh (SSH + clone Rules). - Обновить правила из remote: init-cursor -Update + Один вызов init-cursor: + - если машина не настроена: SSH, Gitea keys, clone Rules в $HOME\CursorRules + - затем копирует rules в текущий репозиторий и настраивает remotes + + Первый запуск на голом ПК (из каталога проекта): + irm https://git.kalinamall.ru/PapaTramp/Rules/raw/branch/main/install.ps1 | iex .EXAMPLE cd C:\Users\papat\Projects\MyRepo init-cursor init-cursor -Update #> -function init-cursor { +function Get-CursorRulesDest { + return (Join-Path $HOME 'CursorRules') +} + +function Get-CursorBootstrapRoot { + param([string]$RulesDest) + + $scriptRoot = $PSScriptRoot + if (Test-Path (Join-Path $scriptRoot 'scripts\Invoke-MachineBootstrap.ps1')) { + return $scriptRoot + } + if (Test-Path (Join-Path $RulesDest 'local\git-ssh-hosts.conf')) { + return $RulesDest + } + if (Test-Path (Join-Path $scriptRoot 'local\git-ssh-hosts.conf')) { + return $scriptRoot + } + return $null +} + +function Initialize-CursorRulesEnvironment { [CmdletBinding()] param( - [switch]$SkipGitRemotes, [switch]$Update ) - $source = Join-Path $HOME 'CursorRules' - $rulesSrc = Join-Path $source '.cursor\rules' - $scripts = Join-Path $source 'scripts' + $rulesDest = Get-CursorRulesDest + $bootstrapRoot = Get-CursorBootstrapRoot -RulesDest $rulesDest - if (-not (Test-Path $source)) { - Write-Host "Error: $source not found — run bootstrap.ps1 on this machine first" -ForegroundColor Red - return + if (-not $bootstrapRoot) { + Write-Host 'Error: CursorRules not found. Run install.ps1 from Gitea first:' -ForegroundColor Red + Write-Host ' irm https://git.kalinamall.ru/PapaTramp/Rules/raw/branch/main/install.ps1 | iex' -ForegroundColor Yellow + return $false } - if ($Update) { - if (Test-Path (Join-Path $source '.git')) { - Write-Host '[*] Updating CursorRules from remote...' -ForegroundColor Cyan - git -C $source pull --ff-only - if ($LASTEXITCODE -ne 0) { - Write-Host '[!] git pull failed in CursorRules' -ForegroundColor Yellow - } - else { - Write-Host '[+] CursorRules updated' -ForegroundColor Green - } + $bootstrapScript = Join-Path $bootstrapRoot 'scripts\Invoke-MachineBootstrap.ps1' + if (-not (Test-Path -LiteralPath $bootstrapScript)) { + Write-Host "Error: bootstrap script not found: $bootstrapScript" -ForegroundColor Red + return $false + } + + $repoReady = Test-Path (Join-Path $rulesDest '.git') + $skipClone = $repoReady + + & $bootstrapScript -Root $bootstrapRoot -RulesDest $rulesDest -SkipClone:$skipClone + + if ($Update -and $repoReady) { + Write-Host '[*] Updating CursorRules from remote...' -ForegroundColor Cyan + git -C $rulesDest pull --ff-only + if ($LASTEXITCODE -ne 0) { + Write-Host '[!] git pull failed in CursorRules' -ForegroundColor Yellow + } + else { + Write-Host '[+] CursorRules updated' -ForegroundColor Green } } + return $true +} + +function Install-CursorInCurrentProject { + [CmdletBinding()] + param( + [switch]$SkipGitRemotes + ) + + $source = Get-CursorRulesDest + $rulesSrc = Join-Path $source '.cursor\rules' + $scripts = Join-Path $source 'scripts' + $cursorIgnore = Join-Path $source '.cursorignore' if (Test-Path $cursorIgnore) { Copy-Item $cursorIgnore . -Force @@ -83,7 +127,7 @@ function init-cursor { git rev-parse --is-inside-work-tree 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { - Write-Host '[!] Not a git repo — remotes skipped' -ForegroundColor Yellow + Write-Host '[!] Not a git repo - remotes skipped' -ForegroundColor Yellow return } @@ -105,3 +149,18 @@ function init-cursor { } } } + +function init-cursor { + [CmdletBinding()] + param( + [switch]$SkipGitRemotes, + [switch]$Update + ) + + if (-not (Initialize-CursorRulesEnvironment -Update:$Update)) { + return + } + + Install-CursorInCurrentProject -SkipGitRemotes:$SkipGitRemotes + Write-Host '[+] init-cursor complete' -ForegroundColor Green +} diff --git a/init-cursor.sh b/init-cursor.sh index e8e1169..3b9f882 100644 --- a/init-cursor.sh +++ b/init-cursor.sh @@ -1,33 +1,71 @@ #!/usr/bin/env bash -# Копирует Cursor rules из локального $HOME/CursorRules в текущий проект. -init-cursor() { - local source="$HOME/CursorRules" - local rules_src="$source/.cursor/rules" - local scripts="$source/scripts" - local update=0 - local skip_git_remotes=0 +# Инициализация Cursor в текущем проекте; на новом хосте сначала настраивает git/SSH. - while [[ $# -gt 0 ]]; do - case "$1" in - -Update|--update) update=1; shift ;; - -SkipGitRemotes|--skip-git-remotes) skip_git_remotes=1; shift ;; - *) shift ;; - esac - done +get_cursor_rules_dest() { + printf '%s' "$HOME/CursorRules" +} - if [[ ! -d "$source" ]]; then - echo "Error: $source not found — run bootstrap.sh on this machine first" >&2 - return 1 +get_cursor_bootstrap_root() { + local rules_dest script_dir + rules_dest="$(get_cursor_rules_dest)" + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + + if [[ -f "$script_dir/scripts/invoke-machine-bootstrap.sh" ]]; then + printf '%s' "$script_dir" + return 0 fi + if [[ -f "$rules_dest/local/git-ssh-hosts.conf" ]]; then + printf '%s' "$rules_dest" + return 0 + fi + if [[ -f "$script_dir/local/git-ssh-hosts.conf" ]]; then + printf '%s' "$script_dir" + return 0 + fi + return 1 +} - if [[ "$update" == '1' && -d "$source/.git" ]]; then +initialize_cursor_rules_environment() { + local update=0 + [[ "${1:-}" == '--update' ]] && update=1 + + local rules_dest bootstrap_root + rules_dest="$(get_cursor_rules_dest)" + bootstrap_root="$(get_cursor_bootstrap_root)" || { + echo 'Error: CursorRules not found. Run install.sh from Gitea first:' >&2 + echo ' curl -fsSL https://git.kalinamall.ru/PapaTramp/Rules/raw/branch/main/install.sh | bash' >&2 + return 1 + } + + local skip_clone=0 + [[ -d "$rules_dest/.git" ]] && skip_clone=1 + + ROOT="$bootstrap_root" RULES_DEST="$rules_dest" SKIP_CLONE="$skip_clone" \ + bash "$bootstrap_root/scripts/invoke-machine-bootstrap.sh" + + if [[ "$update" -eq 1 && -d "$rules_dest/.git" ]]; then echo '[*] Updating CursorRules from remote...' - if git -C "$source" pull --ff-only; then + if git -C "$rules_dest" pull --ff-only; then echo '[+] CursorRules updated' else echo '[!] git pull failed in CursorRules' >&2 fi fi +} + +install_cursor_in_current_project() { + local skip_git_remotes=0 + while [[ $# -gt 0 ]]; do + case "$1" in + --skip-git-remotes) skip_git_remotes=1; shift ;; + *) shift ;; + esac + done + + local source rules_src scripts + source="$(get_cursor_rules_dest)" + rules_src="$source/.cursor/rules" + scripts="$source/scripts" if [[ -f "$source/.cursorignore" ]]; then cp -f "$source/.cursorignore" . @@ -41,7 +79,7 @@ init-cursor() { fi if [[ -d .git ]]; then - local gitignore='.gitignore' + local gitignore='.gitignore' line for line in '.cursor/' '.cursorignore'; do if [[ ! -f "$gitignore" ]]; then printf '%s\n' "$line" > "$gitignore" @@ -53,21 +91,19 @@ init-cursor() { done fi - if [[ "$skip_git_remotes" == '1' ]]; then + if [[ "$skip_git_remotes" -eq 1 ]]; then echo '[!] Git remotes skipped' return 0 fi if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo '[!] Not a git repo — remotes skipped' + echo '[!] Not a git repo - remotes skipped' return 0 fi if [[ -f "$scripts/Set-GitRemotes.sh" ]]; then echo '[*] Configuring git remotes...' bash "$scripts/Set-GitRemotes.sh" || true - elif [[ -f "$scripts/Set-GitRemotes.ps1" ]]; then - echo '[!] Set-GitRemotes.ps1 only; install bash script or pwsh' >&2 fi if [[ -f "$scripts/Ensure-GiteaHomeRepo.ps1" ]]; then @@ -76,10 +112,31 @@ init-cursor() { echo "[*] Ensuring home Gitea repo: $repo_name" if command -v pwsh >/dev/null 2>&1; then pwsh -NoProfile -File "$scripts/Ensure-GiteaHomeRepo.ps1" -RepoName "$repo_name" || true - elif command -v powershell >/dev/null 2>&1; then - powershell -NoProfile -ExecutionPolicy Bypass -File "$scripts/Ensure-GiteaHomeRepo.ps1" -RepoName "$repo_name" || true - else - echo '[!] PowerShell not found — skip Ensure-GiteaHomeRepo.ps1' >&2 fi fi } + +init-cursor() { + local update=0 + local skip_git_remotes=0 + while [[ $# -gt 0 ]]; do + case "$1" in + -Update|--update) update=1; shift ;; + -SkipGitRemotes|--skip-git-remotes) skip_git_remotes=1; shift ;; + *) shift ;; + esac + done + + if [[ "$update" -eq 1 ]]; then + initialize_cursor_rules_environment --update || return 1 + else + initialize_cursor_rules_environment || return 1 + fi + + if [[ "$skip_git_remotes" -eq 1 ]]; then + install_cursor_in_current_project --skip-git-remotes + else + install_cursor_in_current_project + fi + echo '[+] init-cursor complete' +} diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..a235f4f --- /dev/null +++ b/install.ps1 @@ -0,0 +1,36 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Первая загрузка с Gitea: clone Rules и запуск init-cursor в текущем проекте. + +.EXAMPLE + cd C:\path\to\your-repo + irm https://git.kalinamall.ru/PapaTramp/Rules/raw/branch/main/install.ps1 | iex +#> +[CmdletBinding()] +param( + [Parameter(ValueFromRemainingArguments = $true)] + [object[]]$InitCursorArgs +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$rulesDest = Join-Path $HOME 'CursorRules' +$httpsUrl = 'https://git.kalinamall.ru/PapaTramp/Rules.git' +$sshUrl = 'ssh://git@git.kalinamall.lan:2222/PapaTramp/Rules.git' + +if (-not (Test-Path (Join-Path $rulesDest '.git'))) { + Write-Host '[*] Cloning Rules from Gitea (HTTPS, login once if asked)...' -ForegroundColor Cyan + if (Test-Path -LiteralPath $rulesDest) { + throw "Path exists and is not a git repo: $rulesDest" + } + git clone --depth 1 $httpsUrl $rulesDest + if ($LASTEXITCODE -ne 0) { + throw 'git clone failed' + } + Write-Host "[+] Cloned to $rulesDest" -ForegroundColor Green +} + +. (Join-Path $rulesDest 'init-cursor.ps1') +init-cursor @InitCursorArgs diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..60e8dcf --- /dev/null +++ b/install.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Первая загрузка с Gitea: clone Rules и запуск init-cursor в текущем проекте. +set -euo pipefail + +RULES_DEST="${RULES_DEST:-$HOME/CursorRules}" +HTTPS_URL="${HTTPS_URL:-https://git.kalinamall.ru/PapaTramp/Rules.git}" + +if [[ ! -d "$RULES_DEST/.git" ]]; then + echo '[*] Cloning Rules from Gitea (HTTPS, login once if asked)...' + [[ -e "$RULES_DEST" ]] && { echo "Path exists and is not a git repo: $RULES_DEST" >&2; exit 1; } + git clone --depth 1 "$HTTPS_URL" "$RULES_DEST" + echo "[+] Cloned to $RULES_DEST" +fi + +# shellcheck source=/dev/null +source "$RULES_DEST/init-cursor.sh" +init-cursor "$@" diff --git a/local/kalinamall-gitea.conf b/local/kalinamall-gitea.conf index 79ce465..934f833 100644 --- a/local/kalinamall-gitea.conf +++ b/local/kalinamall-gitea.conf @@ -4,6 +4,6 @@ GiteaPort=2222 GiteaWebUrl=https://git.kalinamall.ru GiteaApiUrl=http://192.168.160.129:3000 GiteaApiUser=papatramp -GiteaApiPassword= +GiteaApiPassword=R3tt3tnatur KeyName=id_ed25519_git_kalinamall KeyComment=papatramp@kalinamall diff --git a/scripts/Invoke-MachineBootstrap.ps1 b/scripts/Invoke-MachineBootstrap.ps1 new file mode 100644 index 0000000..bab1b69 --- /dev/null +++ b/scripts/Invoke-MachineBootstrap.ps1 @@ -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 +} diff --git a/scripts/invoke-machine-bootstrap.sh b/scripts/invoke-machine-bootstrap.sh new file mode 100644 index 0000000..b4995a3 --- /dev/null +++ b/scripts/invoke-machine-bootstrap.sh @@ -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'