Добавить bootstrap для новых ПК и Linux-скрипты.
SSH-ключи и Gitea API до clone Rules; init-cursor копирует из локального $HOME/CursorRules.
This commit is contained in:
@@ -1,17 +1,42 @@
|
|||||||
# Rules
|
# Rules
|
||||||
|
|
||||||
Правила и скрипты для настройки нового компьютера: Cursor rules, git remotes, home Gitea.
|
Правила и скрипты для настройки нового компьютера: SSH-ключи, Cursor rules, git remotes, home Gitea.
|
||||||
|
|
||||||
## Первичная настройка
|
## Как это устроено
|
||||||
|
|
||||||
|
| Этап | Скрипт | Что делает |
|
||||||
|
|------|--------|------------|
|
||||||
|
| Новый ПК | `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` **не** тянет правила с remote напрямую — только из локального клона. Сначала нужен bootstrap (или ручной clone после настройки SSH).
|
||||||
|
|
||||||
|
## Новый Windows-ПК
|
||||||
|
|
||||||
|
На голом ПК нет доступа к приватному git — сначала скопируйте каталог Rules с рабочей машины:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
git clone https://git.kalinamall.ru/PapaTramp/Rules $HOME\CursorRules
|
scp -r user@working-pc:~/CursorRules $env:TEMP\rules-bootstrap
|
||||||
|
cd $env:TEMP\rules-bootstrap
|
||||||
|
.\bootstrap.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
Добавьте `init-cursor` в профиль PowerShell (один раз):
|
Bootstrap:
|
||||||
|
|
||||||
```powershell
|
1. `git config` user.name / user.email (`local/git-user.conf`)
|
||||||
. $HOME\CursorRules\init-cursor.ps1
|
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
|
||||||
|
|
||||||
|
## Новый Linux-хост
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp -r user@working-pc:~/CursorRules /tmp/rules-bootstrap
|
||||||
|
cd /tmp/rules-bootstrap
|
||||||
|
chmod +x bootstrap.sh scripts/*.sh
|
||||||
|
./bootstrap.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## В каждом проекте
|
## В каждом проекте
|
||||||
@@ -19,6 +44,24 @@ git clone https://git.kalinamall.ru/PapaTramp/Rules $HOME\CursorRules
|
|||||||
```powershell
|
```powershell
|
||||||
cd C:\path\to\repo
|
cd C:\path\to\repo
|
||||||
init-cursor
|
init-cursor
|
||||||
|
init-cursor -Update # подтянуть свежие rules из remote
|
||||||
```
|
```
|
||||||
|
|
||||||
Скрипт копирует `.cursorignore` и `.cursor/rules/*.mdc`, при необходимости дополняет `.gitignore`, настраивает remotes (`home`, `github`, `kalinamall`) и создаёт репозиторий на home Gitea через API.
|
```bash
|
||||||
|
cd ~/projects/myrepo
|
||||||
|
init-cursor
|
||||||
|
init-cursor --update
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт копирует `.cursorignore` и `.cursor/rules/*.mdc`, дополняет `.gitignore`, настраивает remotes (`home`, `github`, `kalinamall`) и создаёт репозиторий на home Gitea через API.
|
||||||
|
|
||||||
|
## Конфиги (local/)
|
||||||
|
|
||||||
|
| Файл | Назначение |
|
||||||
|
|------|------------|
|
||||||
|
| `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` (приватный репозиторий).
|
||||||
|
|||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
#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
|
||||||
|
#>
|
||||||
|
[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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ''
|
||||||
|
Write-Ok 'Bootstrap complete. Open a new shell, then in a project: init-cursor'
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Первичная настройка нового Linux-хоста: SSH, Gitea keys, clone Rules, init-cursor в shell profile.
|
||||||
|
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"; }
|
||||||
|
|
||||||
|
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'
|
||||||
+21
-6
@@ -1,21 +1,23 @@
|
|||||||
#Requires -Version 5.1
|
#Requires -Version 5.1
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Копирует Cursor rules в текущий каталог и настраивает git remotes.
|
Копирует Cursor rules из локального $HOME\CursorRules в текущий проект.
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
1. .cursorignore из $HOME\CursorRules
|
Источник — локальный клон репозитория Rules ($HOME\CursorRules), не remote напрямую.
|
||||||
2. .cursor/rules/*.mdc
|
На новом ПК сначала: bootstrap.ps1 / bootstrap.sh (SSH + clone Rules).
|
||||||
3. В git-репо: Set-GitRemotes.ps1 + Ensure-GiteaHomeRepo.ps1 (home Gitea)
|
Обновить правила из remote: init-cursor -Update
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
cd C:\Users\papat\Projects\MyRepo
|
cd C:\Users\papat\Projects\MyRepo
|
||||||
init-cursor
|
init-cursor
|
||||||
|
init-cursor -Update
|
||||||
#>
|
#>
|
||||||
function init-cursor {
|
function init-cursor {
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[switch]$SkipGitRemotes
|
[switch]$SkipGitRemotes,
|
||||||
|
[switch]$Update
|
||||||
)
|
)
|
||||||
|
|
||||||
$source = Join-Path $HOME 'CursorRules'
|
$source = Join-Path $HOME 'CursorRules'
|
||||||
@@ -23,10 +25,23 @@ function init-cursor {
|
|||||||
$scripts = Join-Path $source 'scripts'
|
$scripts = Join-Path $source 'scripts'
|
||||||
|
|
||||||
if (-not (Test-Path $source)) {
|
if (-not (Test-Path $source)) {
|
||||||
Write-Host "Error: $source not found" -ForegroundColor Red
|
Write-Host "Error: $source not found — run bootstrap.ps1 on this machine first" -ForegroundColor Red
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$cursorIgnore = Join-Path $source '.cursorignore'
|
$cursorIgnore = Join-Path $source '.cursorignore'
|
||||||
if (Test-Path $cursorIgnore) {
|
if (Test-Path $cursorIgnore) {
|
||||||
Copy-Item $cursorIgnore . -Force
|
Copy-Item $cursorIgnore . -Force
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
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 [[ ! -d "$source" ]]; then
|
||||||
|
echo "Error: $source not found — run bootstrap.sh on this machine first" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$update" == '1' && -d "$source/.git" ]]; then
|
||||||
|
echo '[*] Updating CursorRules from remote...'
|
||||||
|
if git -C "$source" pull --ff-only; then
|
||||||
|
echo '[+] CursorRules updated'
|
||||||
|
else
|
||||||
|
echo '[!] git pull failed in CursorRules' >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$source/.cursorignore" ]]; then
|
||||||
|
cp -f "$source/.cursorignore" .
|
||||||
|
echo '[+] .cursorignore copied'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$rules_src" ]]; then
|
||||||
|
mkdir -p ./.cursor/rules
|
||||||
|
cp -f "$rules_src"/*.mdc ./.cursor/rules/
|
||||||
|
echo '[+] .cursor/rules (*.mdc) copied'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d .git ]]; then
|
||||||
|
local gitignore='.gitignore'
|
||||||
|
for line in '.cursor/' '.cursorignore'; do
|
||||||
|
if [[ ! -f "$gitignore" ]]; then
|
||||||
|
printf '%s\n' "$line" > "$gitignore"
|
||||||
|
echo "[+] .gitignore created ($line)"
|
||||||
|
elif ! grep -Fxq "$line" "$gitignore" 2>/dev/null; then
|
||||||
|
printf '%s\n' "$line" >> "$gitignore"
|
||||||
|
echo "[+] .gitignore updated ($line)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$skip_git_remotes" == '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'
|
||||||
|
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
|
||||||
|
local repo_name
|
||||||
|
repo_name="$(basename "$(git rev-parse --show-toplevel)")"
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Профили SSH-ключей и хосты Git. GiteaConfig — файл в local/ для регистрации pubkey через API.
|
||||||
|
|
||||||
|
[profile:kalinamall-git]
|
||||||
|
KeyName=id_ed25519_git_kalinamall
|
||||||
|
KeyComment=papatramp@kalinamall
|
||||||
|
GiteaConfig=kalinamall-gitea.conf
|
||||||
|
|
||||||
|
[host:git.kalinamall.ru]
|
||||||
|
HostName=git.kalinamall.ru
|
||||||
|
Port=2222
|
||||||
|
User=git
|
||||||
|
Profile=kalinamall-git
|
||||||
|
|
||||||
|
[host:git.kalinamall.lan]
|
||||||
|
HostName=192.168.160.129
|
||||||
|
Port=2222
|
||||||
|
User=git
|
||||||
|
Profile=kalinamall-git
|
||||||
|
|
||||||
|
[profile:papatramp-git]
|
||||||
|
KeyName=id_ed25519_git_papatramp
|
||||||
|
KeyComment=papatramp@papatramp.ru
|
||||||
|
GiteaConfig=papatramp-gitea.conf
|
||||||
|
|
||||||
|
[host:git.papatramp.ru]
|
||||||
|
HostName=git.papatramp.ru
|
||||||
|
Port=2222
|
||||||
|
User=git
|
||||||
|
Profile=papatramp-git
|
||||||
|
|
||||||
|
[host:git.papatramp.lan]
|
||||||
|
HostName=192.168.128.48
|
||||||
|
Port=2222
|
||||||
|
User=git
|
||||||
|
Profile=papatramp-git
|
||||||
|
|
||||||
|
[host:git.papatramp-haproxy]
|
||||||
|
HostName=192.168.128.65
|
||||||
|
Port=2222
|
||||||
|
User=git
|
||||||
|
Profile=papatramp-git
|
||||||
|
|
||||||
|
[profile:github]
|
||||||
|
KeyName=id_ed25519_github
|
||||||
|
KeyComment=papatramp@gmail.com
|
||||||
|
GiteaConfig=
|
||||||
|
|
||||||
|
[host:github.com]
|
||||||
|
HostName=github.com
|
||||||
|
Port=22
|
||||||
|
User=git
|
||||||
|
Profile=github
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
UserName=PTah
|
||||||
|
UserEmail=papatramp@gmail.com
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
Type=gitea
|
||||||
|
GiteaHost=git.kalinamall.ru
|
||||||
|
GiteaPort=2222
|
||||||
|
GiteaWebUrl=https://git.kalinamall.ru
|
||||||
|
GiteaApiUrl=http://192.168.160.129:3000
|
||||||
|
GiteaApiUser=papatramp
|
||||||
|
GiteaApiPassword=
|
||||||
|
KeyName=id_ed25519_git_kalinamall
|
||||||
|
KeyComment=papatramp@kalinamall
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
#Requires -Version 5.1
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Регистрирует публичные SSH-ключи в Gitea через API (kalinamall, papatramp).
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$ConfigDir = "$HOME\CursorRules\local",
|
||||||
|
[string]$HostsConfig = '',
|
||||||
|
[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 = @{}
|
||||||
|
Get-Content -LiteralPath $Path | ForEach-Object {
|
||||||
|
if ($_ -match '^([^#=]+)=(.*)$') {
|
||||||
|
$data[$Matches[1].Trim()] = $Matches[2].Trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $data
|
||||||
|
}
|
||||||
|
|
||||||
|
function Read-GitSshHostsConfig {
|
||||||
|
param([string]$Path)
|
||||||
|
|
||||||
|
$profiles = @{}
|
||||||
|
$currentProfile = $null
|
||||||
|
|
||||||
|
Get-Content -LiteralPath $Path | ForEach-Object {
|
||||||
|
$line = $_.Trim()
|
||||||
|
if (-not $line -or $line.StartsWith('#')) { return }
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-GiteaAuthHeader {
|
||||||
|
param([hashtable]$Cfg)
|
||||||
|
$pair = '{0}:{1}' -f $Cfg.GiteaApiUser, $Cfg.GiteaApiPassword
|
||||||
|
$auth = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair))
|
||||||
|
return @{ Authorization = $auth; 'Content-Type' = 'application/json' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Register-GiteaKey {
|
||||||
|
param(
|
||||||
|
[hashtable]$Cfg,
|
||||||
|
[string]$PublicKeyPath,
|
||||||
|
[string]$Title
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not $Cfg.GiteaApiPassword) {
|
||||||
|
Write-Warn "$Title : GiteaApiPassword empty - add key manually in Gitea UI"
|
||||||
|
Write-Host (Get-Content -LiteralPath $PublicKeyPath -Raw).Trim()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$base = $Cfg.GiteaApiUrl.TrimEnd('/')
|
||||||
|
$headers = Get-GiteaAuthHeader -Cfg $Cfg
|
||||||
|
$pub = (Get-Content -LiteralPath $PublicKeyPath -Raw).Trim()
|
||||||
|
|
||||||
|
$existing = Invoke-RestMethod -Method Get -Uri "$base/api/v1/user/keys" -Headers $headers -TimeoutSec 15
|
||||||
|
foreach ($item in @($existing)) {
|
||||||
|
if ($item.key.Trim() -eq $pub) {
|
||||||
|
Write-Ok "$Title : key already registered ($($item.title))"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = (@{ title = $Title; key = $pub } | ConvertTo-Json)
|
||||||
|
if ($WhatIf) {
|
||||||
|
Write-Step "WhatIf: POST $base/api/v1/user/keys ($Title)"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$created = Invoke-RestMethod -Method Post -Uri "$base/api/v1/user/keys" -Headers $headers -Body $body -TimeoutSec 30
|
||||||
|
Write-Ok "$Title : registered ($($created.title))"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $HostsConfig) {
|
||||||
|
$HostsConfig = Join-Path $ConfigDir 'git-ssh-hosts.conf'
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath $HostsConfig)) {
|
||||||
|
throw "Config not found: $HostsConfig"
|
||||||
|
}
|
||||||
|
|
||||||
|
$profiles = Read-GitSshHostsConfig -Path $HostsConfig
|
||||||
|
$sshDir = Join-Path $HOME '.ssh'
|
||||||
|
$machine = $env:COMPUTERNAME
|
||||||
|
if (-not $machine) { $machine = [Environment]::MachineName }
|
||||||
|
$registered = @{}
|
||||||
|
|
||||||
|
foreach ($profileName in $profiles.Keys) {
|
||||||
|
$profile = $profiles[$profileName]
|
||||||
|
$giteaConfigName = $profile.GiteaConfig
|
||||||
|
if (-not $giteaConfigName) { continue }
|
||||||
|
|
||||||
|
$keyName = $profile.KeyName
|
||||||
|
if ($registered.ContainsKey($keyName)) { continue }
|
||||||
|
$registered[$keyName] = $true
|
||||||
|
|
||||||
|
$giteaPath = Join-Path $ConfigDir $giteaConfigName
|
||||||
|
if (-not (Test-Path -LiteralPath $giteaPath)) {
|
||||||
|
Write-Warn "Gitea config not found: $giteaPath"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$cfg = Read-KeyValueFile -Path $giteaPath
|
||||||
|
$pubPath = Join-Path $sshDir "$keyName.pub"
|
||||||
|
if (-not (Test-Path -LiteralPath $pubPath)) {
|
||||||
|
Write-Warn "Public key not found: $pubPath (run Setup-GitSsh first)"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = "$machine-$keyName"
|
||||||
|
Write-Step "Registering $keyName on $($cfg.GiteaHost)"
|
||||||
|
Register-GiteaKey -Cfg $cfg -PublicKeyPath $pubPath -Title $title
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Ok 'Gitea SSH key registration done'
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Стандартные git remote: home, kalinamall, github.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OWNER="${OWNER:-PapaTramp}"
|
||||||
|
GITHUB_ORG="${GITHUB_ORG:-PTah}"
|
||||||
|
REPO="${REPO:-}"
|
||||||
|
SKIP_KALINAMALL="${SKIP_KALINAMALL:-0}"
|
||||||
|
WHAT_IF="${WHAT_IF:-0}"
|
||||||
|
|
||||||
|
log_step() { printf '[*] %s\n' "$1"; }
|
||||||
|
log_ok() { printf '[+] %s\n' "$1"; }
|
||||||
|
|
||||||
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
echo 'Run inside a git repository or set REPO.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
repo_name="${REPO:-$(basename "$(git rev-parse --show-toplevel)")}"
|
||||||
|
home_url="ssh://git@git.papatramp.lan:2222/${OWNER}/${repo_name}.git"
|
||||||
|
kalinamall_url="ssh://git@git.kalinamall.lan:2222/${OWNER}/${repo_name}.git"
|
||||||
|
github_url="git@github.com:${GITHUB_ORG}/${repo_name}.git"
|
||||||
|
|
||||||
|
set_remote() {
|
||||||
|
local name="$1"
|
||||||
|
local url="$2"
|
||||||
|
local existing current old
|
||||||
|
|
||||||
|
if [[ "$WHAT_IF" == '1' ]]; then
|
||||||
|
log_step "WhatIf: remote $name -> $url"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if git remote | grep -Fxq "$name"; then
|
||||||
|
current="$(git remote get-url "$name" | tr -d '\r\n')"
|
||||||
|
if [[ "$current" == "$url" ]]; then
|
||||||
|
log_ok "remote $name OK"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
git remote set-url "$name" "$url"
|
||||||
|
log_ok "remote $name updated -> $url"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$name" in
|
||||||
|
home) legacy=(gitea papatramp origin-home) ;;
|
||||||
|
github) legacy=(origin) ;;
|
||||||
|
kalinamall) legacy=(gitea-kalinamall kalina) ;;
|
||||||
|
*) legacy=() ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
for old in "${legacy[@]}"; do
|
||||||
|
if git remote | grep -Fxq "$old"; then
|
||||||
|
git remote rename "$old" "$name"
|
||||||
|
git remote set-url "$name" "$url"
|
||||||
|
log_ok "remote $old renamed to $name -> $url"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
git remote add "$name" "$url"
|
||||||
|
log_ok "remote $name added -> $url"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_step "Repo: $repo_name"
|
||||||
|
set_remote home "$home_url"
|
||||||
|
set_remote github "$github_url"
|
||||||
|
if [[ "$SKIP_KALINAMALL" != '1' ]]; then
|
||||||
|
set_remote kalinamall "$kalinamall_url"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\n'
|
||||||
|
git remote -v
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
#Requires -Version 5.1
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Генерирует SSH-ключи и ~/.ssh/config для Git-хостов из local/git-ssh-hosts.conf.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$ConfigDir = "$HOME\CursorRules\local",
|
||||||
|
[string]$HostsConfig = '',
|
||||||
|
[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 Read-GitSshHostsConfig {
|
||||||
|
param([string]$Path)
|
||||||
|
|
||||||
|
$profiles = @{}
|
||||||
|
$hosts = @{}
|
||||||
|
$currentProfile = $null
|
||||||
|
$currentHost = $null
|
||||||
|
|
||||||
|
Get-Content -LiteralPath $Path | ForEach-Object {
|
||||||
|
$line = $_.Trim()
|
||||||
|
if (-not $line -or $line.StartsWith('#')) { return }
|
||||||
|
|
||||||
|
if ($line -match '^\[profile:(.+)\]$') {
|
||||||
|
$currentProfile = $Matches[1]
|
||||||
|
$currentHost = $null
|
||||||
|
$profiles[$currentProfile] = @{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ($line -match '^\[host:(.+)\]$') {
|
||||||
|
$currentHost = $Matches[1]
|
||||||
|
$currentProfile = $null
|
||||||
|
$hosts[$currentHost] = @{ Name = $currentHost }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ($line -match '^([^=]+)=(.*)$') {
|
||||||
|
$key = $Matches[1].Trim()
|
||||||
|
$value = $Matches[2].Trim()
|
||||||
|
if ($currentHost) { $hosts[$currentHost][$key] = $value }
|
||||||
|
elseif ($currentProfile) { $profiles[$currentProfile][$key] = $value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return @{ Profiles = $profiles; Hosts = $hosts }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SshConfigBlock {
|
||||||
|
param(
|
||||||
|
[string]$HostAlias,
|
||||||
|
[hashtable]$HostEntry,
|
||||||
|
[string]$IdentityFile
|
||||||
|
)
|
||||||
|
|
||||||
|
@(
|
||||||
|
"# BEGIN Setup-SshAccess:$HostAlias"
|
||||||
|
"Host $HostAlias"
|
||||||
|
" HostName $($HostEntry.HostName)"
|
||||||
|
" User $($HostEntry.User)"
|
||||||
|
" Port $($HostEntry.Port)"
|
||||||
|
" IdentityFile $IdentityFile"
|
||||||
|
" IdentitiesOnly yes"
|
||||||
|
" AddKeysToAgent yes"
|
||||||
|
" StrictHostKeyChecking accept-new"
|
||||||
|
"# END Setup-SshAccess:$HostAlias"
|
||||||
|
) -join "`n"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update-SshConfig {
|
||||||
|
param(
|
||||||
|
[string]$ConfigPath,
|
||||||
|
[string]$HostAlias,
|
||||||
|
[string]$Block
|
||||||
|
)
|
||||||
|
|
||||||
|
$begin = "# BEGIN Setup-SshAccess:$HostAlias"
|
||||||
|
$end = "# END Setup-SshAccess:$HostAlias"
|
||||||
|
$existing = if (Test-Path -LiteralPath $ConfigPath) {
|
||||||
|
Get-Content -LiteralPath $ConfigPath -Raw
|
||||||
|
} else {
|
||||||
|
''
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = "(?ms)^$([regex]::Escape($begin)).*?$([regex]::Escape($end))\r?\n?"
|
||||||
|
if ($existing -match $pattern) {
|
||||||
|
return ($existing -replace $pattern, ($Block + "`n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existing -and -not $existing.EndsWith("`n")) {
|
||||||
|
$existing += "`n"
|
||||||
|
}
|
||||||
|
return $existing + $Block + "`n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $HostsConfig) {
|
||||||
|
$HostsConfig = Join-Path $ConfigDir 'git-ssh-hosts.conf'
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath $HostsConfig)) {
|
||||||
|
throw "Config not found: $HostsConfig"
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed = Read-GitSshHostsConfig -Path $HostsConfig
|
||||||
|
$sshDir = Join-Path $HOME '.ssh'
|
||||||
|
$sshConfigPath = Join-Path $sshDir 'config'
|
||||||
|
|
||||||
|
if (-not $WhatIf) {
|
||||||
|
New-Item -ItemType Directory -Path $sshDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$configText = if (Test-Path -LiteralPath $sshConfigPath) {
|
||||||
|
Get-Content -LiteralPath $sshConfigPath -Raw
|
||||||
|
} else {
|
||||||
|
''
|
||||||
|
}
|
||||||
|
|
||||||
|
$profileKeys = @{}
|
||||||
|
foreach ($profileName in $parsed.Profiles.Keys) {
|
||||||
|
$profile = $parsed.Profiles[$profileName]
|
||||||
|
$keyName = $profile.KeyName
|
||||||
|
if (-not $keyName -or $profileKeys.ContainsKey($keyName)) { continue }
|
||||||
|
|
||||||
|
$keyPath = Join-Path $sshDir $keyName
|
||||||
|
$comment = if ($profile.KeyComment) { $profile.KeyComment } else { $keyName }
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $keyPath)) {
|
||||||
|
Write-Step "Generating $keyName"
|
||||||
|
if ($WhatIf) {
|
||||||
|
Write-Warn "WhatIf: ssh-keygen -t ed25519 -f $keyPath"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
& ssh-keygen -t ed25519 -f $keyPath -N '""' -C $comment -q
|
||||||
|
Write-Ok "key $keyName created"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Ok "key $keyName exists"
|
||||||
|
}
|
||||||
|
|
||||||
|
$profileKeys[$keyName] = $profile
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($hostAlias in ($parsed.Hosts.Keys | Sort-Object)) {
|
||||||
|
$hostEntry = $parsed.Hosts[$hostAlias]
|
||||||
|
$profileName = $hostEntry.Profile
|
||||||
|
if (-not $profileName -or -not $parsed.Profiles.ContainsKey($profileName)) {
|
||||||
|
throw "Host ${hostAlias}: unknown profile $profileName"
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile = $parsed.Profiles[$profileName]
|
||||||
|
$identity = "~/.ssh/$($profile.KeyName)"
|
||||||
|
$block = Get-SshConfigBlock -HostAlias $hostAlias -HostEntry $hostEntry -IdentityFile $identity
|
||||||
|
|
||||||
|
if ($WhatIf) {
|
||||||
|
Write-Step "WhatIf: ssh config block for $hostAlias"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$configText = Update-SshConfig -ConfigPath $sshConfigPath -HostAlias $hostAlias -Block $block
|
||||||
|
Write-Ok "ssh config: $hostAlias"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $WhatIf) {
|
||||||
|
Set-Content -LiteralPath $sshConfigPath -Value $configText.TrimEnd() -Encoding UTF8 -NoNewline
|
||||||
|
Add-Content -LiteralPath $sshConfigPath -Value '' -Encoding UTF8
|
||||||
|
|
||||||
|
Get-Service ssh-agent -ErrorAction SilentlyContinue | Where-Object { $_.Status -ne 'Running' } |
|
||||||
|
ForEach-Object { Start-Service ssh-agent | Out-Null }
|
||||||
|
|
||||||
|
foreach ($keyName in $profileKeys.Keys) {
|
||||||
|
$keyPath = Join-Path $sshDir $keyName
|
||||||
|
if (Test-Path -LiteralPath $keyPath) {
|
||||||
|
ssh-add $keyPath 2>$null | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Ok 'SSH setup complete'
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Регистрирует публичные SSH-ключи в Gitea через API.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CONFIG_DIR="${CONFIG_DIR:-$HOME/CursorRules/local}"
|
||||||
|
HOSTS_CONFIG="${HOSTS_CONFIG:-$CONFIG_DIR/git-ssh-hosts.conf}"
|
||||||
|
WHAT_IF="${WHAT_IF:-0}"
|
||||||
|
|
||||||
|
log_step() { printf '[*] %s\n' "$1"; }
|
||||||
|
log_ok() { printf '[+] %s\n' "$1"; }
|
||||||
|
log_warn() { printf '[!] %s\n' "$1"; }
|
||||||
|
|
||||||
|
if [[ ! -f "$HOSTS_CONFIG" ]]; then
|
||||||
|
echo "Config not found: $HOSTS_CONFIG" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
declare -A PROFILE_KEYNAME PROFILE_GITEA
|
||||||
|
current_section=''
|
||||||
|
current_name=''
|
||||||
|
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
line="${line%%#*}"
|
||||||
|
line="${line%"${line##*[![:space:]]}"}"
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
|
||||||
|
if [[ "$line" =~ ^\[profile:(.+)\]$ ]]; then
|
||||||
|
current_section='profile'
|
||||||
|
current_name="${BASH_REMATCH[1]}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ "$line" =~ ^\[host: ]]; then
|
||||||
|
current_section=''
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
[[ "$line" != *=* || "$current_section" != 'profile' ]] && continue
|
||||||
|
|
||||||
|
key="${line%%=*}"
|
||||||
|
value="${line#*=}"
|
||||||
|
key="${key%"${key##*[![:space:]]}"}"
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
|
||||||
|
case "$key" in
|
||||||
|
KeyName) PROFILE_KEYNAME["$current_name"]="$value" ;;
|
||||||
|
GiteaConfig) PROFILE_GITEA["$current_name"]="$value" ;;
|
||||||
|
esac
|
||||||
|
done < "$HOSTS_CONFIG"
|
||||||
|
|
||||||
|
SSH_DIR="$HOME/.ssh"
|
||||||
|
MACHINE="$(hostname -s 2>/dev/null || hostname)"
|
||||||
|
declare -A REGISTERED
|
||||||
|
|
||||||
|
register_gitea_key() {
|
||||||
|
local cfg_file="$1"
|
||||||
|
local pub_path="$2"
|
||||||
|
local title="$3"
|
||||||
|
|
||||||
|
declare -A CFG=()
|
||||||
|
local kv key value
|
||||||
|
while IFS= read -r kv; do
|
||||||
|
key="${kv%%=*}"
|
||||||
|
value="${kv#*=}"
|
||||||
|
CFG["$key"]="$value"
|
||||||
|
done < <(read_kv_file "$cfg_file")
|
||||||
|
|
||||||
|
if [[ -z "${CFG[GiteaApiPassword]:-}" ]]; then
|
||||||
|
log_warn "$title : GiteaApiPassword empty — add key manually in Gitea UI"
|
||||||
|
cat "$pub_path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local base="${CFG[GiteaApiUrl]%/}"
|
||||||
|
local auth user pass pub body response
|
||||||
|
user="${CFG[GiteaApiUser]}"
|
||||||
|
pass="${CFG[GiteaApiPassword]}"
|
||||||
|
auth="$(printf '%s:%s' "$user" "$pass" | base64 -w0 2>/dev/null || printf '%s:%s' "$user" "$pass" | base64)"
|
||||||
|
pub="$(tr -d '\r\n' < "$pub_path")"
|
||||||
|
|
||||||
|
response="$(curl -fsS -H "Authorization: Basic $auth" "$base/api/v1/user/keys" 2>/dev/null || true)"
|
||||||
|
if [[ -n "$response" ]] && printf '%s' "$response" | grep -Fq "$pub"; then
|
||||||
|
log_ok "$title : key already registered"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
body="$(printf '{"title":"%s","key":"%s"}' "$title" "$pub")"
|
||||||
|
|
||||||
|
if [[ "$WHAT_IF" == '1' ]]; then
|
||||||
|
log_step "WhatIf: POST $base/api/v1/user/keys ($title)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl -fsS -X POST -H "Authorization: Basic $auth" -H 'Content-Type: application/json' \
|
||||||
|
-d "$body" "$base/api/v1/user/keys" >/dev/null
|
||||||
|
log_ok "$title : registered"
|
||||||
|
}
|
||||||
|
|
||||||
|
for profile in "${!PROFILE_GITEA[@]}"; do
|
||||||
|
gitea_config="${PROFILE_GITEA[$profile]}"
|
||||||
|
[[ -z "$gitea_config" ]] && continue
|
||||||
|
|
||||||
|
key_name="${PROFILE_KEYNAME[$profile]}"
|
||||||
|
[[ -z "$key_name" || -n "${REGISTERED[$key_name]:-}" ]] && continue
|
||||||
|
REGISTERED["$key_name"]=1
|
||||||
|
|
||||||
|
gitea_path="$CONFIG_DIR/$gitea_config"
|
||||||
|
pub_path="$SSH_DIR/$key_name.pub"
|
||||||
|
|
||||||
|
if [[ ! -f "$gitea_path" ]]; then
|
||||||
|
log_warn "Gitea config not found: $gitea_path"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$pub_path" ]]; then
|
||||||
|
log_warn "Public key not found: $pub_path (run setup-git-ssh.sh first)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
title="$MACHINE-$key_name"
|
||||||
|
log_step "Registering $key_name"
|
||||||
|
register_gitea_key "$gitea_path" "$pub_path" "$title"
|
||||||
|
done
|
||||||
|
|
||||||
|
log_ok 'Gitea SSH key registration done'
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Генерирует SSH-ключи и ~/.ssh/config для Git-хостов из local/git-ssh-hosts.conf.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CONFIG_DIR="${CONFIG_DIR:-$HOME/CursorRules/local}"
|
||||||
|
HOSTS_CONFIG="${HOSTS_CONFIG:-$CONFIG_DIR/git-ssh-hosts.conf}"
|
||||||
|
WHAT_IF="${WHAT_IF:-0}"
|
||||||
|
|
||||||
|
log_step() { printf '[*] %s\n' "$1"; }
|
||||||
|
log_ok() { printf '[+] %s\n' "$1"; }
|
||||||
|
log_warn() { printf '[!] %s\n' "$1"; }
|
||||||
|
|
||||||
|
if [[ ! -f "$HOSTS_CONFIG" ]]; then
|
||||||
|
echo "Config not found: $HOSTS_CONFIG" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SSH_DIR="$HOME/.ssh"
|
||||||
|
SSH_CONFIG="$SSH_DIR/config"
|
||||||
|
mkdir -p "$SSH_DIR"
|
||||||
|
chmod 700 "$SSH_DIR"
|
||||||
|
|
||||||
|
declare -A PROFILE_KEYNAME PROFILE_COMMENT PROFILE_GITEA
|
||||||
|
declare -A HOST_HOSTNAME HOST_PORT HOST_USER HOST_PROFILE
|
||||||
|
|
||||||
|
current_section=''
|
||||||
|
current_name=''
|
||||||
|
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
line="${line%%#*}"
|
||||||
|
line="${line%"${line##*[![:space:]]}"}"
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
|
||||||
|
if [[ "$line" =~ ^\[profile:(.+)\]$ ]]; then
|
||||||
|
current_section='profile'
|
||||||
|
current_name="${BASH_REMATCH[1]}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ "$line" =~ ^\[host:(.+)\]$ ]]; then
|
||||||
|
current_section='host'
|
||||||
|
current_name="${BASH_REMATCH[1]}"
|
||||||
|
HOST_HOSTNAME["$current_name"]=''
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ "$line" != *=* ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
key="${line%%=*}"
|
||||||
|
value="${line#*=}"
|
||||||
|
key="${key%"${key##*[![:space:]]}"}"
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
|
||||||
|
if [[ "$current_section" == 'profile' ]]; then
|
||||||
|
case "$key" in
|
||||||
|
KeyName) PROFILE_KEYNAME["$current_name"]="$value" ;;
|
||||||
|
KeyComment) PROFILE_COMMENT["$current_name"]="$value" ;;
|
||||||
|
GiteaConfig) PROFILE_GITEA["$current_name"]="$value" ;;
|
||||||
|
esac
|
||||||
|
elif [[ "$current_section" == 'host' ]]; then
|
||||||
|
case "$key" in
|
||||||
|
HostName) HOST_HOSTNAME["$current_name"]="$value" ;;
|
||||||
|
Port) HOST_PORT["$current_name"]="$value" ;;
|
||||||
|
User) HOST_USER["$current_name"]="$value" ;;
|
||||||
|
Profile) HOST_PROFILE["$current_name"]="$value" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done < "$HOSTS_CONFIG"
|
||||||
|
|
||||||
|
declare -A SEEN_KEYS
|
||||||
|
|
||||||
|
for profile in "${!PROFILE_KEYNAME[@]}"; do
|
||||||
|
key_name="${PROFILE_KEYNAME[$profile]}"
|
||||||
|
[[ -z "$key_name" || -n "${SEEN_KEYS[$key_name]:-}" ]] && continue
|
||||||
|
SEEN_KEYS["$key_name"]=1
|
||||||
|
|
||||||
|
key_path="$SSH_DIR/$key_name"
|
||||||
|
comment="${PROFILE_COMMENT[$profile]:-$key_name}"
|
||||||
|
|
||||||
|
if [[ ! -f "$key_path" ]]; then
|
||||||
|
log_step "Generating $key_name"
|
||||||
|
if [[ "$WHAT_IF" == '1' ]]; then
|
||||||
|
log_warn "WhatIf: ssh-keygen -t ed25519 -f $key_path"
|
||||||
|
else
|
||||||
|
ssh-keygen -t ed25519 -f "$key_path" -N '' -C "$comment" -q
|
||||||
|
chmod 600 "$key_path"
|
||||||
|
log_ok "key $key_name created"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_ok "key $key_name exists"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
update_ssh_block() {
|
||||||
|
local host_alias="$1"
|
||||||
|
local block="$2"
|
||||||
|
local begin="# BEGIN Setup-SshAccess:$host_alias"
|
||||||
|
local end="# END Setup-SshAccess:$host_alias"
|
||||||
|
local tmp
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
|
||||||
|
if [[ -f "$SSH_CONFIG" ]]; then
|
||||||
|
awk -v begin="$begin" -v end="$end" -v block="$block" '
|
||||||
|
$0 == begin { skip=1; next }
|
||||||
|
$0 == end { skip=0; next }
|
||||||
|
skip { next }
|
||||||
|
{ print }
|
||||||
|
END {
|
||||||
|
if (block != "") {
|
||||||
|
print block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' block="$block" "$SSH_CONFIG" > "$tmp"
|
||||||
|
else
|
||||||
|
printf '%s\n' "$block" > "$tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$WHAT_IF" != '1' ]]; then
|
||||||
|
mv "$tmp" "$SSH_CONFIG"
|
||||||
|
chmod 600 "$SSH_CONFIG"
|
||||||
|
else
|
||||||
|
rm -f "$tmp"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
for host_alias in $(printf '%s\n' "${!HOST_HOSTNAME[@]}" | sort); do
|
||||||
|
profile="${HOST_PROFILE[$host_alias]:-}"
|
||||||
|
key_name="${PROFILE_KEYNAME[$profile]:-}"
|
||||||
|
if [[ -z "$profile" || -z "$key_name" ]]; then
|
||||||
|
echo "Host $host_alias: unknown profile" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
block="# BEGIN Setup-SshAccess:$host_alias
|
||||||
|
Host $host_alias
|
||||||
|
HostName ${HOST_HOSTNAME[$host_alias]}
|
||||||
|
User ${HOST_USER[$host_alias]}
|
||||||
|
Port ${HOST_PORT[$host_alias]}
|
||||||
|
IdentityFile ~/.ssh/$key_name
|
||||||
|
IdentitiesOnly yes
|
||||||
|
AddKeysToAgent yes
|
||||||
|
StrictHostKeyChecking accept-new
|
||||||
|
# END Setup-SshAccess:$host_alias"
|
||||||
|
|
||||||
|
if [[ "$WHAT_IF" == '1' ]]; then
|
||||||
|
log_step "WhatIf: ssh config block for $host_alias"
|
||||||
|
else
|
||||||
|
update_ssh_block "$host_alias" "$block"
|
||||||
|
log_ok "ssh config: $host_alias"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$WHAT_IF" != '1' ]]; then
|
||||||
|
eval "$(ssh-agent -s)" >/dev/null
|
||||||
|
for key_name in "${!SEEN_KEYS[@]}"; do
|
||||||
|
[[ -f "$SSH_DIR/$key_name" ]] && ssh-add "$SSH_DIR/$key_name" >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_ok 'SSH setup complete'
|
||||||
Reference in New Issue
Block a user