Merge sshinstall: add cross-platform SSH bootstrap scripts

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
edemilovandClaude Sonnet 5 committed 2026-09-17 15:30:44 +03:00
commit 0480c7aed5
4 files changed
+308 -14

No files matched your search

+8
View File
@@ -13,6 +13,12 @@ bash <(curl -s https://git.edemilov.ru/edemilov/scripts/raw/branch/main/netdrive
# Windows (PowerShell as Admin)
irm https://git.edemilov.ru/edemilov/scripts/raw/branch/main/apps.ps1 | iex
# Bootstrap SSH access on a new machine (installs+enables sshd, deploys admin key)
curl -s https://git.edemilov.ru/edemilov/scripts/raw/branch/master/sshinstall.sh | sudo bash
# Windows (PowerShell as Admin)
irm https://git.edemilov.ru/edemilov/scripts/raw/branch/master/sshinstall.ps1 | iex
```
## Scripts
@@ -22,6 +28,8 @@ irm https://git.edemilov.ru/edemilov/scripts/raw/branch/main/apps.ps1 | iex
| `apps.sh` | Install common dev/security/daily tools | Arch, Parrot, Kali, Debian, Ubuntu |
| `netdrivelink.sh` | Mount vanga Samba shares + 2nd drive symlinks | Same |
| `apps.ps1` | Windows equivalent via winget + map network drives | Windows 10/11 |
| `sshinstall.sh` | Install/enable SSH server + deploy admin key | Arch, Debian, Ubuntu, RHEL, Fedora |
| `sshinstall.ps1` | Windows equivalent | Windows 10, 11 |
| `gitea-ct-setup.sh` | Create CT116 with Gitea on Proxmox host | Proxmox 8+ |
## Samba shares (vanga — 10.0.99.100)
@@ -15,10 +15,10 @@ passwordless. Follows the exact usage convention already established by `apps.sh
`apps.ps1` in this same repo (see `README.md`):
```bash
curl -s https://git.edemilov.ru/edemilov/scripts/raw/branch/main/sshinstall.sh | sudo bash
curl -s https://git.edemilov.ru/edemilov/scripts/raw/branch/master/sshinstall.sh | sudo bash
```
```powershell
irm https://git.edemilov.ru/edemilov/scripts/raw/branch/main/sshinstall.ps1 | iex
irm https://git.edemilov.ru/edemilov/scripts/raw/branch/master/sshinstall.ps1 | iex
```
## Key deployed
@@ -54,15 +54,17 @@ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH/hWD8YgjVzA/9V364W4w2HiEpuPjKMQ/Id3SRlyriM
3. **Install the SSH server, if not already present.**
- Linux: `openssh-server` via the detected package manager (idempotent — package
managers already no-op on an installed package).
- Windows: try `Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0` first
(native path, works on Windows 10 1809+ and 11, gets Windows-Update-maintained
binaries). If that cmdlet doesn't exist or the capability install fails (this is the
path that covers **Windows 8**, which has no such capability at all), fall back to
downloading the portable Win32-OpenSSH release zip, extracting it to
`C:\Program Files\OpenSSH`, and registering `sshd`/`ssh-agent` as services via
the bundled `install-sshd.ps1` (the same mechanism Windows 10/11 use internally,
just done by hand). One fallback path covers the edge case rather than a
Windows-8-specific branch.
- Windows: `Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0` (native
path, Windows 10 1809+ and 11, Windows-Update-maintained binaries).
**Revised 2026-09-17, after final review**: the original design also planned a
portable Win32-OpenSSH-zip fallback for Windows 8 (no `Add-WindowsCapability` at
all). Dropped — it depended on `Expand-Archive` (PowerShell 5.0+) and TLS 1.2
support neither of which exist on stock Windows 8's PowerShell 3.0/4.0, so the one
path that existed specifically for Windows 8 was guaranteed to fail on Windows 8. If
the capability isn't available, the script now just fails with one clear
unsupported-OS message instead of attempting a fallback that can't work. **Windows
support is 10/11 only**, not 8/10/11 as originally scoped.
4. **Enable and start the service, if not already running.**
- Linux: `systemctl enable --now sshd` (or `ssh` — service name varies by distro;
check both).
@@ -78,7 +80,9 @@ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH/hWD8YgjVzA/9V364W4w2HiEpuPjKMQ/Id3SRlyriM
for port 22 doesn't already exist (the native capability install actually creates
this automatically — the manual/portable fallback path needs to do it explicitly).
6. **Deploy the embedded public key, idempotently.**
- Linux: append to the invoking (root) user's `~/.ssh/authorized_keys`, creating
- Linux: append to the invoking real user's `~/.ssh/authorized_keys` (the user who
ran `sudo`, resolved via `$SUDO_USER` — not literally `/root/`, unless the script
is run as root directly with no `sudo`), creating
`~/.ssh` (mode `700`) and the file (mode `600`) if missing. Skip the append if the
exact key line is already present (idempotent re-run).
- Windows: **must** go into `%ProgramData%\ssh\administrators_authorized_keys`, not
@@ -133,7 +137,8 @@ approach — real-machine verification only). Plan:
`administrators_authorized_keys` with correct ACLs (verify via `icacls`), and
`ssh <target>` from a machine holding the laptop's private key connects with no
password prompt.
4. If a Windows 8 machine/VM is available, confirm the portable-binary fallback path
is actually triggered (not silently skipped) and produces the same end state.
4. On a machine without the OpenSSH capability available (or an older Windows build),
confirm the script fails with the clear unsupported-OS message rather than
attempting an install.
5. Confirm a non-elevated run on both platforms fails fast with the one-line error and
makes no changes.
+131
View File
@@ -0,0 +1,131 @@
# ═══════════════════════════════════════════════════════════════════
# sshinstall.ps1
# Windows equivalent of sshinstall.sh — installs/enables OpenSSH Server,
# deploys the shared admin key for passwordless login
# Run in PowerShell as Administrator:
# irm https://git.edemilov.ru/edemilov/scripts/raw/branch/master/sshinstall.ps1 | iex
# ═══════════════════════════════════════════════════════════════════
#Requires -RunAsAdministrator
$ErrorActionPreference = "Stop"
function Write-Header { param($t) Write-Host "`n── $t ──────────────────────────────" -ForegroundColor Cyan }
function Write-Info { param($t) Write-Host "[•] $t" -ForegroundColor Cyan }
function Write-Success { param($t) Write-Host "[✓] $t" -ForegroundColor Green }
function Write-Skip { param($t) Write-Host "[~] $t (already done)" -ForegroundColor Magenta }
function Write-Warn { param($t) Write-Host "[!] $t" -ForegroundColor Yellow }
# ── Require elevation ────────────────────────────────────────────
# #Requires -RunAsAdministrator above does not enforce under
# `irm ... | iex` (piping into Invoke-Expression is widely reported not to
# trigger the #Requires check), so this explicit check is the real guard.
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Warn "Must be run as Administrator — re-run PowerShell as Administrator"
exit 1
}
$AdminKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH/hWD8YgjVzA/9V364W4w2HiEpuPjKMQ/Id3SRlyriM edemilov@laptop"
Write-Host ""
Write-Host " ╔══════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host " ║ sshinstall.ps1 — SSH bootstrap ║" -ForegroundColor Cyan
Write-Host " ╚══════════════════════════════════════════════╝" -ForegroundColor Cyan
Write-Host ""
# ── Install OpenSSH Server ────────────────────────────────────────
Write-Header "SSH server"
$capability = Get-WindowsCapability -Online -Name "OpenSSH.Server~~~~0.0.1.0" -ErrorAction SilentlyContinue
$sshdPath = "$env:ProgramFiles\OpenSSH\sshd.exe"
if ($capability -and $capability.State -eq "Installed") {
Write-Skip "OpenSSH Server (Windows capability)"
} elseif ($capability) {
Write-Info "Installing OpenSSH Server via Windows capability..."
Add-WindowsCapability -Online -Name "OpenSSH.Server~~~~0.0.1.0" | Out-Null
Write-Success "OpenSSH Server capability installed"
} elseif (Test-Path $sshdPath) {
Write-Skip "OpenSSH Server (portable install found at $sshdPath)"
} else {
Write-Warn "OpenSSH Server capability is not available on this Windows version. This script supports Windows 10 (1809+) and Windows 11 only."
exit 1
}
# ── Enable + start service ────────────────────────────────────────
Write-Header "SSH service"
$svc = Get-Service -Name sshd -ErrorAction SilentlyContinue
if ($svc -and $svc.Status -eq "Running") {
Write-Skip "sshd service"
} else {
Write-Info "Enabling + starting sshd..."
Set-Service -Name sshd -StartupType Automatic
Start-Service sshd
Write-Success "sshd running"
}
# ── Firewall ───────────────────────────────────────────────────────
Write-Header "Firewall"
$rule = Get-NetFirewallRule -ErrorAction SilentlyContinue | Where-Object {
$_.Enabled -eq "True" -and $_.Direction -eq "Inbound" -and
(Get-NetFirewallPortFilter -AssociatedNetFirewallRule $_ -ErrorAction SilentlyContinue | Where-Object { $_.LocalPort -eq 22 })
}
if ($rule) {
Write-Skip "Inbound firewall rule for port 22"
} else {
Write-Info "Adding inbound firewall rule for port 22..."
New-NetFirewallRule -Name "OpenSSH-Server-In-TCP-sshinstall" -DisplayName "OpenSSH Server (sshinstall)" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 | Out-Null
Write-Success "Firewall rule added"
}
# ── Deploy admin key ───────────────────────────────────────────────
Write-Header "Admin key"
$adminSid = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
$isAdminAccount = ([Security.Principal.WindowsIdentity]::GetCurrent().Groups -contains $adminSid)
if ($isAdminAccount) {
$keyFile = "$env:ProgramData\ssh\administrators_authorized_keys"
} else {
$keyFile = "$env:USERPROFILE\.ssh\authorized_keys"
}
$keyDir = Split-Path $keyFile
if (-not (Test-Path $keyDir)) { New-Item -ItemType Directory -Path $keyDir -Force | Out-Null }
if (-not (Test-Path $keyFile)) { New-Item -ItemType File -Path $keyFile -Force | Out-Null }
# Add-Content resumes writing at the file's current end-of-file position and only
# guarantees a trailing newline after what IT writes — it does not fix up a pre-existing
# last line that lacks one (e.g. a hand-edited or externally-written file). Without this
# guard that can glue the new key onto the end of the last existing line, corrupting both,
# same as the bash >> case this fix wave addresses.
if ((Get-Item $keyFile).Length -gt 0) {
$existingRaw = Get-Content -Path $keyFile -Raw
if ($existingRaw.Length -gt 0 -and $existingRaw[-1] -ne "`n") {
Add-Content -Path $keyFile -Value ""
}
}
$existing = Get-Content $keyFile -ErrorAction SilentlyContinue
if ($existing -contains $AdminKey) {
Write-Skip "admin key in $keyFile"
} else {
Add-Content -Path $keyFile -Value $AdminKey
Write-Success "admin key added to $keyFile"
}
if ($isAdminAccount) {
$aclOutput = (icacls $keyFile) -join "`n"
if ($aclOutput -match 'BUILTIN\\Users|Everyone|Authenticated Users|BUILTIN\\Guests') {
icacls $keyFile /inheritance:r | Out-Null
icacls $keyFile /grant "SYSTEM:F" | Out-Null
icacls $keyFile /grant "Administrators:F" | Out-Null
Write-Success "Locked down ACLs on $keyFile (SYSTEM + Administrators only)"
} else {
Write-Skip "ACLs on $keyFile"
}
}
Write-Host ""
Write-Host " ╔══════════════════════════════════════════════╗" -ForegroundColor Green
Write-Host " ║ Done — SSH ready, key deployed ║" -ForegroundColor Green
Write-Host " ╚══════════════════════════════════════════════╝" -ForegroundColor Green
Write-Host ""
Executable
+150
View File
@@ -0,0 +1,150 @@
#!/bin/bash
# ═══════════════════════════════════════════════════════════════════
# sshinstall.sh — SSH bootstrap (install + enable sshd + deploy admin key)
# Debian/Ubuntu / Arch / RHEL/Fedora
#
# curl -s https://git.edemilov.ru/edemilov/scripts/raw/branch/master/sshinstall.sh | sudo bash
# ═══════════════════════════════════════════════════════════════════
set -e
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; MAGENTA='\033[0;35m'; BOLD='\033[1m'; NC='\033[0m'
info() { echo -e "${CYAN}[•]${NC} $1"; }
success() { echo -e "${GREEN}[✓]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
skip() { echo -e "${MAGENTA}[~]${NC} $1 (already done)"; }
die() { echo -e "${RED}[✗]${NC} $1"; exit 1; }
header() { echo -e "\n${BOLD}${CYAN}── $1 ──────────────────────────────${NC}"; }
ADMIN_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH/hWD8YgjVzA/9V364W4w2HiEpuPjKMQ/Id3SRlyriM edemilov@laptop"
echo -e "${BOLD}"
echo " ╔══════════════════════════════════════════════╗"
echo " ║ sshinstall.sh — SSH bootstrap ║"
echo " ╚══════════════════════════════════════════════╝"
echo -e "${NC}"
# ── Require root ─────────────────────────────────────────────────
[[ "$EUID" -eq 0 ]] || die "Must be run as root — re-run with sudo"
# ── Detect distro ─────────────────────────────────────────────────
header "Detecting system"
[[ -f /etc/os-release ]] && . /etc/os-release || die "Cannot detect distro"
DISTRO_ID="${ID,,}"
DISTRO_LIKE="${ID_LIKE,,}"
info "Distro: ${PRETTY_NAME}"
if command -v pacman &>/dev/null; then
FAMILY="arch"
elif [[ "$DISTRO_ID" =~ ^(kali|parrot|debian|ubuntu|linuxmint|pop)$ ]] || \
[[ "$DISTRO_LIKE" =~ debian ]] || [[ "$DISTRO_LIKE" =~ ubuntu ]]; then
FAMILY="debian"
elif command -v dnf &>/dev/null || command -v yum &>/dev/null; then
FAMILY="fedora"
else
die "Unsupported distro: $DISTRO_ID"
fi
info "Family: $FAMILY"
# ── Install SSH server ────────────────────────────────────────────
header "SSH server package"
case $FAMILY in
arch)
SERVICE="sshd"
if pacman -Qi openssh &>/dev/null; then
skip "openssh"
else
info "Installing openssh..."
pacman -S --noconfirm --needed openssh
success "openssh installed"
fi
;;
debian)
SERVICE="ssh"
if dpkg -l openssh-server 2>/dev/null | grep -q "^ii"; then
skip "openssh-server"
else
info "Installing openssh-server..."
apt-get update -qq
apt-get install -y -qq openssh-server
success "openssh-server installed"
fi
;;
fedora)
SERVICE="sshd"
if rpm -q openssh-server &>/dev/null; then
skip "openssh-server"
else
info "Installing openssh-server..."
if command -v dnf &>/dev/null; then
dnf install -y -q openssh-server
else
yum install -y -q openssh-server
fi
success "openssh-server installed"
fi
;;
esac
# ── Enable + start service ────────────────────────────────────────
header "SSH service"
if systemctl is-active --quiet "$SERVICE"; then
skip "$SERVICE service"
else
info "Enabling + starting $SERVICE..."
systemctl enable --now "$SERVICE"
success "$SERVICE running"
fi
# ── Firewall ───────────────────────────────────────────────────────
header "Firewall"
if command -v ufw &>/dev/null && ufw status | grep -q "Status: active"; then
if ufw status | grep -qE "^22(/tcp)?[[:space:]]+ALLOW"; then
skip "ufw port 22"
else
info "Opening port 22 via ufw..."
ufw allow 22/tcp
success "ufw rule added"
fi
elif command -v firewall-cmd &>/dev/null && systemctl is-active --quiet firewalld; then
if firewall-cmd --list-services | grep -qw ssh; then
skip "firewalld ssh service"
else
info "Opening ssh via firewalld..."
firewall-cmd --add-service=ssh --permanent
firewall-cmd --reload
success "firewalld rule added"
fi
else
warn "No active ufw/firewalld found — skipping firewall step (port 22 may already be open, or another firewall manager is in use)"
fi
# ── Deploy admin key ───────────────────────────────────────────────
header "Admin key"
REAL_USER="${SUDO_USER:-$(logname 2>/dev/null || echo $USER)}"
REAL_HOME=$(eval echo "~$REAL_USER")
SSH_DIR="$REAL_HOME/.ssh"
AUTH_KEYS="$SSH_DIR/authorized_keys"
mkdir -p "$SSH_DIR"
chmod 700 "$SSH_DIR"
touch "$AUTH_KEYS"
chmod 600 "$AUTH_KEYS"
chown -R "$REAL_USER:$REAL_USER" "$SSH_DIR"
if [[ -s "$AUTH_KEYS" ]] && [[ -n "$(tail -c1 "$AUTH_KEYS")" ]]; then
echo >> "$AUTH_KEYS"
fi
if grep -qF "$ADMIN_KEY" "$AUTH_KEYS" 2>/dev/null; then
skip "admin key in $AUTH_KEYS"
else
echo "$ADMIN_KEY" >> "$AUTH_KEYS"
success "admin key added to $AUTH_KEYS"
fi
echo -e "${BOLD}${GREEN}"
echo " ╔══════════════════════════════════════════════╗"
echo " ║ Done — SSH ready, key deployed ║"
echo " ╚══════════════════════════════════════════════╝"
echo -e "${NC}"