132 lines
8.1 KiB
Go
132 lines
8.1 KiB
Go
package keymgr
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
func genTimestamp() string {
|
||
return time.Now().UTC().Format("2006-01-02 15:04 UTC")
|
||
}
|
||
|
||
// RenderInstructions produces a plain-Russian, no-jargon README for a
|
||
// non-technical Windows user whose only guaranteed tool is the OpenSSH
|
||
// client bundled with Windows 10/11. Deliberately says nothing about
|
||
// "rights", "forwarding", "jump hosts" etc — just: run this one command and
|
||
// keep it open, then these local ports lead to these services.
|
||
func RenderInstructions(username, keyFileName string, forwards []AllocatedForward) string {
|
||
var b strings.Builder
|
||
|
||
b.WriteString("КАК ПОДКЛЮЧИТЬСЯ (Windows)\n")
|
||
b.WriteString("===========================\n\n")
|
||
fmt.Fprintf(&b, "Ваш логин: %s\n\n", username)
|
||
|
||
b.WriteString("В этом архиве 4 файла:\n")
|
||
fmt.Fprintf(&b, " - %s — ваш секретный ключ (никому не передавайте и не пересылайте)\n", keyFileName)
|
||
fmt.Fprintf(&b, " - %s.pub — открытая часть ключа (можно не трогать)\n", keyFileName)
|
||
b.WriteString(" - config — настройка подключения\n")
|
||
b.WriteString(" - INSTRUCTIONS.txt — этот файл\n\n")
|
||
|
||
b.WriteString("ШАГ 1. Разложите файлы по нужной папке\n")
|
||
b.WriteString("---------------------------------------\n")
|
||
b.WriteString("Создайте (если её ещё нет) папку:\n\n")
|
||
b.WriteString(" C:\\Users\\ВАШЕ_ИМЯ\\.ssh\\\n\n")
|
||
fmt.Fprintf(&b, "И скопируйте туда все 3 файла из архива (%s, %s.pub, config).\n", keyFileName, keyFileName)
|
||
b.WriteString("Важно: у файла config не должно быть никакого расширения (не config.txt).\n\n")
|
||
|
||
b.WriteString("ШАГ 2. Настройте доступ к файлу ключа\n")
|
||
b.WriteString("----------------------------------------\n")
|
||
b.WriteString("Windows откажется использовать секретный ключ, если его может прочитать\n")
|
||
b.WriteString("кто-то ещё, кроме вас. Откройте PowerShell и выполните по очереди (это две\n")
|
||
b.WriteString("отдельные команды, копируйте и выполняйте одну за другой):\n\n")
|
||
fmt.Fprintf(&b, " icacls \"$env:USERPROFILE\\.ssh\\%s\" /inheritance:r\n", keyFileName)
|
||
fmt.Fprintf(&b, " icacls \"$env:USERPROFILE\\.ssh\\%s\" /grant:r \"${env:USERNAME}:(R)\"\n\n", keyFileName)
|
||
b.WriteString("Если файлы лежат не в стандартной папке .ssh, а где-то ещё — замените путь\n")
|
||
b.WriteString("в обеих командах на реальный путь к файлу ключа (в кавычках, как выше).\n\n")
|
||
|
||
b.WriteString("ШАГ 3. Подключитесь\n")
|
||
b.WriteString("---------------------\n")
|
||
b.WriteString("В PowerShell введите команду:\n\n")
|
||
b.WriteString(" ssh -N jump\n\n")
|
||
b.WriteString("Окно «зависнет» и ничего не будет происходить — это нормально, так и должно\n")
|
||
b.WriteString("быть. НЕ ЗАКРЫВАЙТЕ это окно — пока оно открыто, работают все сервисы из\n")
|
||
b.WriteString("таблицы ниже. Закрыли окно (или выключили компьютер) — доступ пропал, просто\n")
|
||
b.WriteString("откройте заново командой выше, когда снова понадобится.\n\n")
|
||
|
||
b.WriteString("ШАГ 4. Пользуйтесь\n")
|
||
b.WriteString("--------------------\n")
|
||
b.WriteString("Эта настройка открывает у вас на компьютере (локально) следующие адреса.\n")
|
||
b.WriteString("В программе, которая должна подключиться к сервису (браузер, клиент базы\n")
|
||
b.WriteString("данных и т.п.), укажите вместо настоящего адреса тот, что указан ниже как\n")
|
||
b.WriteString("«127.0.0.1:порт» — и она подключится к нужному сервису через это соединение.\n\n")
|
||
|
||
// A numbered list rather than an aligned table: this file is opened in
|
||
// Notepad with whatever font the user has, so column alignment based on
|
||
// character counts (especially mixing Cyrillic and digits) can't be
|
||
// relied on to line up — a simple list reads correctly regardless.
|
||
for i, f := range forwards {
|
||
what := f.ServiceName
|
||
if f.Description != "" {
|
||
what = fmt.Sprintf("%s (%s)", f.ServiceName, f.Description)
|
||
}
|
||
fmt.Fprintf(&b, " %d. 127.0.0.1:%d\n", i+1, f.LocalPort)
|
||
fmt.Fprintf(&b, " Это: %s\n", what)
|
||
fmt.Fprintf(&b, " Реально находится на: %s:%d\n\n", f.HostName, f.Port)
|
||
}
|
||
if len(forwards) == 0 {
|
||
b.WriteString(" (сервисы ещё не назначены — обратитесь к администратору)\n")
|
||
}
|
||
b.WriteString("\n")
|
||
|
||
b.WriteString("ЕСЛИ ЧТО-ТО НЕ РАБОТАЕТ\n")
|
||
b.WriteString("------------------------\n")
|
||
b.WriteString("Написано «This account is currently not available» и соединение сразу\n")
|
||
b.WriteString("обрывается? Это нормально, если вы вводили команду БЕЗ \"-N\" (например,\n")
|
||
b.WriteString("просто \"ssh jump\") — обычный вход сюда специально отключён, используйте\n")
|
||
b.WriteString("именно команду из шага 3 (\"ssh -N jump\").\n\n")
|
||
b.WriteString("Если не работает именно шаг 3 или шаг 4 — обратитесь к администратору,\n")
|
||
b.WriteString("который выдал вам этот архив. Возможно, изменился список сервисов — тогда\n")
|
||
b.WriteString("вам пришлют обновлённый архив.\n")
|
||
|
||
return b.String()
|
||
}
|
||
|
||
// RenderConfigUpdateInstructions is a short follow-up notice for a user who
|
||
// already completed the full setup once (RenderInstructions) and just needs
|
||
// to know their service list changed — their key did not change and needs
|
||
// no attention, only the config file is replaced.
|
||
func RenderConfigUpdateInstructions(username string, forwards []AllocatedForward) string {
|
||
var b strings.Builder
|
||
|
||
b.WriteString("ОБНОВЛЕНИЕ СПИСКА СЕРВИСОВ\n")
|
||
b.WriteString("===========================\n\n")
|
||
fmt.Fprintf(&b, "Ваш логин: %s\n\n", username)
|
||
b.WriteString("Ваш ключ НЕ менялся — трогать его не нужно, продолжайте пользоваться тем же.\n")
|
||
b.WriteString("Изменился только список сервисов, поэтому нужно заменить один файл.\n\n")
|
||
|
||
b.WriteString("Замените файл config (из этого архива) на:\n\n")
|
||
b.WriteString(" C:\\Users\\ВАШЕ_ИМЯ\\.ssh\\config\n\n")
|
||
b.WriteString("(тот же путь, что и раньше — просто перезапишите старый файл новым).\n\n")
|
||
|
||
b.WriteString("Дальше подключайтесь как обычно, командой:\n\n")
|
||
b.WriteString(" ssh -N jump\n\n")
|
||
|
||
b.WriteString("Актуальный список сервисов:\n\n")
|
||
for i, f := range forwards {
|
||
what := f.ServiceName
|
||
if f.Description != "" {
|
||
what = fmt.Sprintf("%s (%s)", f.ServiceName, f.Description)
|
||
}
|
||
fmt.Fprintf(&b, " %d. 127.0.0.1:%d\n", i+1, f.LocalPort)
|
||
fmt.Fprintf(&b, " Это: %s\n", what)
|
||
fmt.Fprintf(&b, " Реально находится на: %s:%d\n\n", f.HostName, f.Port)
|
||
}
|
||
if len(forwards) == 0 {
|
||
b.WriteString(" (сервисы не назначены — обратитесь к администратору)\n\n")
|
||
}
|
||
|
||
b.WriteString("Если что-то не работает — обратитесь к администратору.\n")
|
||
return b.String()
|
||
}
|