Merge sshinstall: add cross-platform SSH bootstrap scripts
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
commit
0480c7aed5
4 files changed
+308
-14
No files matched your search
@@ -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
@@ -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
@@ -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}"
|
||||
Reference in new issue
Block a user