feat: tools/tunnel-einrichten.ps1 -- der Tunnel als Hintergrundaufgabe
LAEUFT AUF DEM HEIMRECHNER, nicht auf dem Server.
Seit dem 13.08.2026 ist RDP am Server gesperrt; hinein geht es nur noch durch
einen SSH-Tunnel mit Schluessel. Von Hand hiesse das: vor jeder Sitzung ein
PowerShell-Fenster oeffnen, einen Befehl tippen, das Fenster offen lassen.
DAS HAELT NIEMAND DURCH, UND WAS NIEMAND DURCHHAELT, WIRD UMGANGEN -- am Ende
steht dann wieder ein offener Port. Das Skript macht den Tunnel deshalb zu
etwas, das man nicht sieht: eine Aufgabe, die beim Anmelden startet, sich bei
Abbruch selbst neu aufbaut und kein Fenster zeigt. Danach verbindet man sich
wie immer, nur auf localhost statt auf die Server-Adresse.
Drei Sachen, die es prueft statt sie anzunehmen:
- Ist ein SSH-Client da? Sonst der Nachinstallationsbefehl statt eines
kryptischen Fehlers.
- Gibt es einen Schluessel? Sonst anlegen UND den oeffentlichen Teil gross
ausgeben, damit klar ist, was zu schicken ist.
- Ist der lokale Port frei? Das ist der Fehler, der uns heute Nacht eine
halbe Stunde gekostet hat: 3389 ist unter Windows selbst belegt, die
Verbindung landet dann auf dem EIGENEN PC, und die Meldung lautet
"Konsolensitzung bereits aktiv" -- man sucht den Fehler ueberall, nur
nicht beim Port. Standard ist deshalb 13389.
Die Schleife um ssh ist Absicht: bei Zwangstrennung, Standby oder kurzem
Aussetzer endet ssh, und ohne Schleife waere der Tunnel weg, ohne dass es
jemand merkt -- bis die naechste Verbindung scheitert.
ExitOnForwardFailure=yes gehoert dazu, sonst laeuft ssh scheinbar, waehrend die
Weiterleitung gar nicht steht.
Gedacht auch fuer Mitbenutzer, die sich mit Rechnern nicht auskennen: einmal
ausfuehren, oeffentlichen Schluessel schicken, fertig. Danach sehen sie vom
Tunnel nie wieder etwas.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
# Richtet den SSH-Tunnel zum Server als HINTERGRUNDAUFGABE ein.
|
||||
#
|
||||
# LÄUFT AUF DEM HEIMRECHNER, nicht auf dem Server.
|
||||
#
|
||||
# pwsh -File tunnel-einrichten.ps1
|
||||
# pwsh -File tunnel-einrichten.ps1 -Benutzer Florian -LokalerPort 13390
|
||||
#
|
||||
# WOZU. Seit dem 13.08.2026 ist RDP am Server gesperrt (Hintergrund:
|
||||
# docs/vorfall-2026-08-12-gitea.md). Hinein geht es nur noch durch einen
|
||||
# SSH-Tunnel mit Schlüssel. Von Hand hieße das: vor jeder Sitzung ein
|
||||
# PowerShell-Fenster öffnen, einen Befehl tippen, das Fenster offen lassen.
|
||||
#
|
||||
# Das hält niemand durch, und was niemand durchhält, wird umgangen -- am Ende
|
||||
# steht dann wieder ein offener Port. Deshalb wird der Tunnel hier zu etwas,
|
||||
# das man nicht sieht: eine Aufgabe, die beim Anmelden startet, sich bei
|
||||
# Verbindungsabbruch selbst neu aufbaut und kein Fenster zeigt.
|
||||
#
|
||||
# Danach verbindet man sich wie immer -- nur nicht mehr auf die Server-Adresse,
|
||||
# sondern auf localhost mit dem hier eingestellten Port.
|
||||
#
|
||||
# WAS ES BRAUCHT: einen SSH-Schlüssel, dessen öffentlicher Teil auf dem Server
|
||||
# hinterlegt ist. Fehlt einer, legt dieses Skript ihn an und zeigt, was zu
|
||||
# schicken ist.
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Server = '88.218.224.10',
|
||||
[string]$Benutzer = $env:USERNAME,
|
||||
# Der Port auf DIESEM Rechner. Nicht 3389 nehmen: Windows lauscht dort
|
||||
# selbst, und dann landet die Verbindung auf dem eigenen PC statt im Tunnel
|
||||
# -- mit der irreführenden Meldung "Konsolensitzung bereits aktiv".
|
||||
[int]$LokalerPort = 13389,
|
||||
[int]$ZielPort = 3389,
|
||||
[string]$AufgabenName = 'SSH-Tunnel zum Rootserver'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
function Schritt($t) { Write-Host "==> $t" -ForegroundColor Cyan }
|
||||
function Gut($t) { Write-Host " $t" -ForegroundColor Green }
|
||||
function Warnung($t) { Write-Host " $t" -ForegroundColor Yellow }
|
||||
|
||||
# -- 1. Ist überhaupt ein SSH-Client da? ---------------------------------------
|
||||
|
||||
Schritt 'Voraussetzungen prüfen'
|
||||
$ssh = (Get-Command ssh.exe -ErrorAction SilentlyContinue).Source
|
||||
if (-not $ssh) {
|
||||
Write-Host ' Kein ssh.exe gefunden. Unter Windows 10/11 nachinstallieren mit:' -ForegroundColor Red
|
||||
Write-Host ' Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0' -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Gut "ssh.exe: $ssh"
|
||||
|
||||
# -- 2. Schlüssel ---------------------------------------------------------------
|
||||
|
||||
$schluessel = Join-Path $env:USERPROFILE '.ssh\id_ed25519'
|
||||
if (-not (Test-Path $schluessel)) {
|
||||
Schritt 'Es gibt noch keinen Schlüssel -- einen anlegen'
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path $schluessel) | Out-Null
|
||||
# Ohne Passphrase, damit die Hintergrundaufgabe ohne Rückfrage startet. Der
|
||||
# Schutz liegt dann darin, dass die Datei nur diesem Windows-Konto gehört.
|
||||
# Wer eine Passphrase will, nimmt sie -- muss den Schlüssel dann aber in den
|
||||
# ssh-agent legen, sonst hängt die Aufgabe an der Abfrage.
|
||||
& ssh-keygen -t ed25519 -N '""' -f $schluessel -C "$Benutzer-$env:COMPUTERNAME" | Out-Null
|
||||
Gut 'Schlüssel angelegt.'
|
||||
Write-Host ''
|
||||
Write-Host ' ===> DIESE ZEILE muss auf dem Server hinterlegt werden:' -ForegroundColor Yellow
|
||||
Write-Host ''
|
||||
Write-Host (" " + (Get-Content "$schluessel.pub")) -ForegroundColor White
|
||||
Write-Host ''
|
||||
Write-Host ' Schick sie demjenigen, der den Server betreut. Erst danach' -ForegroundColor Yellow
|
||||
Write-Host ' funktioniert der Tunnel -- das Skript richtet ihn trotzdem' -ForegroundColor Yellow
|
||||
Write-Host ' schon ein.' -ForegroundColor Yellow
|
||||
Write-Host ''
|
||||
} else {
|
||||
Gut "Schlüssel vorhanden: $schluessel"
|
||||
}
|
||||
|
||||
# -- 3. Ist der lokale Port frei? ----------------------------------------------
|
||||
#
|
||||
# Vorher prüfen und nicht hinterher rätseln: ist er belegt, bindet ssh nicht,
|
||||
# und die RDP-Verbindung landet bei dem Programm, das dort schon lauscht.
|
||||
|
||||
Schritt "Lokalen Port $LokalerPort prüfen"
|
||||
$belegt = Get-NetTCPConnection -State Listen -LocalPort $LokalerPort -ErrorAction SilentlyContinue
|
||||
if ($belegt) {
|
||||
$wer = (Get-Process -Id $belegt[0].OwningProcess -ErrorAction SilentlyContinue).ProcessName
|
||||
Write-Host " Port $LokalerPort ist belegt (von $wer). Nimm einen anderen:" -ForegroundColor Red
|
||||
Write-Host " pwsh -File tunnel-einrichten.ps1 -LokalerPort $($LokalerPort + 1)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Gut 'frei'
|
||||
|
||||
# -- 4. Das Tunnelskript ablegen ------------------------------------------------
|
||||
#
|
||||
# Eine Schleife und kein einzelner Aufruf: bei Zwangstrennung, Standby oder
|
||||
# kurzem Leitungsaussetzer endet ssh, und ohne Schleife wäre der Tunnel dann
|
||||
# weg -- ohne dass es jemand merkt, bis die nächste Verbindung scheitert.
|
||||
|
||||
$ordner = Join-Path $env:LOCALAPPDATA 'SSH-Tunnel'
|
||||
New-Item -ItemType Directory -Force -Path $ordner | Out-Null
|
||||
$skript = Join-Path $ordner 'tunnel.ps1'
|
||||
|
||||
@"
|
||||
# Erzeugt von tunnel-einrichten.ps1 -- von Hand ändern lohnt nicht,
|
||||
# lieber das Einrichtungsskript neu laufen lassen.
|
||||
while (`$true) {
|
||||
# -N: nur weiterleiten, keine Shell.
|
||||
# ExitOnForwardFailure: lieber sofort scheitern und neu versuchen, als
|
||||
# scheinbar zu laufen, während die Weiterleitung gar nicht steht.
|
||||
# ServerAliveInterval: erkennt tote Leitungen, statt still einzufrieren.
|
||||
& '$ssh' -N ``
|
||||
-o ExitOnForwardFailure=yes ``
|
||||
-o ServerAliveInterval=30 -o ServerAliveCountMax=3 ``
|
||||
-o StrictHostKeyChecking=accept-new ``
|
||||
-L ${LokalerPort}:localhost:${ZielPort} ``
|
||||
$Benutzer@$Server
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
"@ | Set-Content -Path $skript -Encoding UTF8
|
||||
|
||||
Gut "Tunnelskript: $skript"
|
||||
|
||||
# -- 5. Als Aufgabe beim Anmelden ----------------------------------------------
|
||||
|
||||
Schritt 'Als Hintergrundaufgabe eintragen'
|
||||
Unregister-ScheduledTask -TaskName $AufgabenName -Confirm:$false -ErrorAction SilentlyContinue
|
||||
|
||||
$pwsh = (Get-Command pwsh.exe -ErrorAction SilentlyContinue).Source
|
||||
if (-not $pwsh) { $pwsh = (Get-Command powershell.exe).Source }
|
||||
|
||||
$aktion = New-ScheduledTaskAction -Execute $pwsh `
|
||||
-Argument "-NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$skript`""
|
||||
$ausloeser = New-ScheduledTaskTrigger -AtLogOn -User "$env:USERDOMAIN\$env:USERNAME"
|
||||
# Interactive, nicht S4U: der Schlüssel liegt im Benutzerprofil, und die
|
||||
# Aufgabe muss ihn lesen können.
|
||||
$prinzipal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Interactive -RunLevel Limited
|
||||
$optionen = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
|
||||
-ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1)
|
||||
|
||||
Register-ScheduledTask -TaskName $AufgabenName -Action $aktion -Trigger $ausloeser `
|
||||
-Principal $prinzipal -Settings $optionen `
|
||||
-Description "Haelt den SSH-Tunnel zu $Server offen. RDP verbindet sich danach auf localhost:$LokalerPort." | Out-Null
|
||||
Gut 'eingetragen (startet künftig bei jeder Anmeldung)'
|
||||
|
||||
# -- 6. Jetzt starten und nachsehen ---------------------------------------------
|
||||
|
||||
Schritt 'Starten und nachmessen'
|
||||
Start-ScheduledTask -TaskName $AufgabenName
|
||||
$bis = (Get-Date).AddSeconds(25)
|
||||
do {
|
||||
Start-Sleep -Seconds 2
|
||||
$steht = Get-NetTCPConnection -State Listen -LocalPort $LokalerPort -ErrorAction SilentlyContinue
|
||||
} while (-not $steht -and (Get-Date) -lt $bis)
|
||||
|
||||
Write-Host ''
|
||||
if ($steht) {
|
||||
Gut "Der Tunnel steht: localhost:$LokalerPort -> ${Server}:$ZielPort"
|
||||
Write-Host ''
|
||||
Write-Host " In der Fernwartung ab jetzt eintragen:" -ForegroundColor White
|
||||
Write-Host " Hostname : localhost" -ForegroundColor White
|
||||
Write-Host " Port : $LokalerPort" -ForegroundColor White
|
||||
Write-Host ''
|
||||
Write-Host " Nicht mehr auf $Server verbinden -- das ist gesperrt." -ForegroundColor DarkGray
|
||||
} else {
|
||||
Warnung "Der Tunnel steht noch nicht auf Port $LokalerPort."
|
||||
Warnung 'Häufigste Ursache: der öffentliche Schlüssel ist auf dem Server noch'
|
||||
Warnung 'nicht hinterlegt. Zum Nachsehen einmal von Hand, dann sagt ssh warum:'
|
||||
Write-Host ""
|
||||
Write-Host " ssh $Benutzer@$Server" -ForegroundColor White
|
||||
}
|
||||
Reference in New Issue
Block a user