Папки шлюза: тестовый — igate_exchange_test, боевой — igate_exchange; все папки в сообщении; Samba — в установщике
- Имена папок: тестовый контур — igate_exchange_test, боевой — igate_exchange (было igate_exchange и igate_exchange_prod); bj переименовывает открытые по прежней схеме сам — при сверке, в первый раз через 30 с после запуска. - После сохранения — все открытые папки с контуром: \\IP\igate_exchange_test (тестовый TEST3), \\IP\igate_exchange (боевой). - Закрыть папки — Samba выключается (сервер не отвечает по SMB, пока папок нет). - Установщик (bj-installer, preseed Debian, слой пакетов образа Ubuntu): samba, winbind, libnss-winbind — заранее, чтобы в закрытом контуре папки открывались без интернета; необязательной частью (нет на диске и нет сети — установка bj продолжается); службы выключены до открытия папок. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -300,8 +300,9 @@ func (in *installer) stepPackages() (string, error) {
|
|||||||
withJava := append(pkgs, javaPkg(debs))
|
withJava := append(pkgs, javaPkg(debs))
|
||||||
args := append([]string{"install", "-y", "-qq", "--no-install-recommends"}, debs...)
|
args := append([]string{"install", "-y", "-qq", "--no-install-recommends"}, debs...)
|
||||||
if _, err := runEnv(env, "apt-get", append(args, withJava...)...); err == nil {
|
if _, err := runEnv(env, "apt-get", append(args, withJava...)...); err == nil {
|
||||||
|
smb := in.stepSamba(env, debs)
|
||||||
_ = os.RemoveAll(in.P(debsDir))
|
_ = os.RemoveAll(in.P(debsDir))
|
||||||
return strings.Join(withJava, ", ") + " (с образа, без загрузки)", nil
|
return strings.Join(withJava, ", ") + " (с образа, без загрузки)" + smb, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if out, err := runEnv(env, "apt-get", "update", "-qq"); err != nil {
|
if out, err := runEnv(env, "apt-get", "update", "-qq"); err != nil {
|
||||||
@@ -312,7 +313,28 @@ func (in *installer) stepPackages() (string, error) {
|
|||||||
if out, err := runEnv(env, "apt-get", args...); err != nil {
|
if out, err := runEnv(env, "apt-get", args...); err != nil {
|
||||||
return "", fmt.Errorf("apt-get install: %v\n%s", err, tail(out, 12))
|
return "", fmt.Errorf("apt-get install: %v\n%s", err, tail(out, 12))
|
||||||
}
|
}
|
||||||
return strings.Join(pkgs, ", "), nil
|
return strings.Join(pkgs, ", ") + in.stepSamba(env, nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sambaPkgs — для папок обмена шлюза НРД по сети (Настройка → НРД → «Папки шлюза по сети»): Samba и вход
|
||||||
|
// через домен (winbind). Ставятся заранее, чтобы в закрытом контуре папки открывались без интернета.
|
||||||
|
var sambaPkgs = []string{"samba", "winbind", "libnss-winbind"}
|
||||||
|
|
||||||
|
// stepSamba — поставить Samba необязательной частью: не вышло (нет на диске образа и нет сети) — установка bj
|
||||||
|
// продолжается, Samba поставится при первом открытии папок. Службы выключены, пока папки не открыты в bj:
|
||||||
|
// иначе сервер сразу отвечал бы по SMB.
|
||||||
|
func (in *installer) stepSamba(env []string, debs []string) string {
|
||||||
|
if in.root != "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
args := append([]string{"install", "-y", "-qq", "--no-install-recommends"}, debs...)
|
||||||
|
if _, err := runEnv(env, "apt-get", append(args, sambaPkgs...)...); err != nil {
|
||||||
|
return "; Samba не установлена — поставится при первом открытии папок шлюза по сети"
|
||||||
|
}
|
||||||
|
for _, u := range []string{"smbd", "nmbd", "winbind", "samba-ad-dc"} {
|
||||||
|
_, _ = run("systemctl", "disable", "--now", u)
|
||||||
|
}
|
||||||
|
return ", " + strings.Join(sambaPkgs, ", ") + " (службы выключены до открытия папок)"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (in *installer) stepSystem() (string, error) {
|
func (in *installer) stepSystem() (string, error) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ OUT=${2:?выходной каталог}
|
|||||||
[ "$(id -u)" = 0 ] || { echo "нужен root"; exit 1; }
|
[ "$(id -u)" = 0 ] || { echo "нужен root"; exit 1; }
|
||||||
for c in mksquashfs xorriso; do command -v $c >/dev/null || { echo "нужен $c"; exit 1; }; done
|
for c in mksquashfs xorriso; do command -v $c >/dev/null || { echo "нужен $c"; exit 1; }; done
|
||||||
# пакеты — как в bj-installer (stepPackages); Java 17 — служба подписи собрана под неё (в 22.04 по умолчанию 11)
|
# пакеты — как в bj-installer (stepPackages); Java 17 — служба подписи собрана под неё (в 22.04 по умолчанию 11)
|
||||||
BJ_PKGS="ca-certificates curl postgresql postgresql-client openjdk-17-jre-headless pcscd python3 smbclient gnome-keyring dbus xvfb xauth libsecret-tools unzip"
|
BJ_PKGS="ca-certificates curl postgresql postgresql-client openjdk-17-jre-headless pcscd python3 smbclient gnome-keyring dbus xvfb xauth libsecret-tools unzip samba winbind libnss-winbind"
|
||||||
LAYER=ubuntu-server-minimal.ubuntu-server.bj.squashfs
|
LAYER=ubuntu-server-minimal.ubuntu-server.bj.squashfs
|
||||||
|
|
||||||
mkdir -p "$OUT"
|
mkdir -p "$OUT"
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ d-i partman-md/device_remove_md boolean true
|
|||||||
|
|
||||||
# Пакеты: без графики; SSH и всё, что нужно bj, ставится сразу
|
# Пакеты: без графики; SSH и всё, что нужно bj, ставится сразу
|
||||||
tasksel tasksel/first multiselect standard, ssh-server
|
tasksel tasksel/first multiselect standard, ssh-server
|
||||||
d-i pkgsel/include string ca-certificates curl postgresql postgresql-client default-jre-headless pcscd python3 smbclient gnome-keyring dbus xvfb xauth libsecret-tools unzip sudo
|
d-i pkgsel/include string ca-certificates curl postgresql postgresql-client default-jre-headless pcscd python3 smbclient gnome-keyring dbus xvfb xauth libsecret-tools unzip sudo samba winbind libnss-winbind
|
||||||
d-i pkgsel/upgrade select safe-upgrade
|
d-i pkgsel/upgrade select safe-upgrade
|
||||||
popularity-contest popularity-contest/participate boolean false
|
popularity-contest popularity-contest/participate boolean false
|
||||||
|
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ SQL
|
|||||||
CONF=/etc/samba/smb.conf; B='# >>> bj: папки шлюза НРД'; E='# <<< bj: папки шлюза НРД'
|
CONF=/etc/samba/smb.conf; B='# >>> bj: папки шлюза НРД'; E='# <<< bj: папки шлюза НРД'
|
||||||
GB='# >>> bj: вход через домен'; GE='# <<< bj: вход через домен'
|
GB='# >>> bj: вход через домен'; GE='# <<< bj: вход через домен'
|
||||||
on=$(j enabled); su=$(j user); sp=$(j password); mode=$(j mode); vu=$(j valid)
|
on=$(j enabled); su=$(j user); sp=$(j password); mode=$(j mode); vu=$(j valid)
|
||||||
manual=false; [ -f "$CONF" ] && sed "/^$B/,/^$E/d" "$CONF" | grep -q '^\[igate_exchange\]' && manual=true
|
manual=false; [ -f "$CONF" ] && sed "/^$B/,/^$E/d" "$CONF" | grep -qE '^\[igate_exchange(_test|_prod)?\]' && manual=true
|
||||||
dmode=simple; [ -f "$CONF" ] && grep -q "^$GB" "$CONF" && dmode=domain
|
dmode=simple; [ -f "$CONF" ] && grep -q "^$GB" "$CONF" && dmode=domain
|
||||||
if [ "${on:-}" = status ] || [ -z "${on:-}" ]; then
|
if [ "${on:-}" = status ] || [ -z "${on:-}" ]; then
|
||||||
mine=false; [ -f "$CONF" ] && grep -q "^$B" "$CONF" && mine=true
|
mine=false; [ -f "$CONF" ] && grep -q "^$B" "$CONF" && mine=true
|
||||||
@@ -347,8 +347,8 @@ SQL
|
|||||||
[ "$manual" = true ] && err "папка igate_exchange уже открыта вручную в $CONF — bj её не меняет; чтобы управлять из bj, уберите её из smb.conf"
|
[ "$manual" = true ] && err "папка igate_exchange уже открыта вручную в $CONF — bj её не меняет; чтобы управлять из bj, уберите её из smb.conf"
|
||||||
if [ "$on" != true ]; then
|
if [ "$on" != true ]; then
|
||||||
[ -f "$CONF" ] && sed -i "/^$B/,/^$E/d" "$CONF"
|
[ -f "$CONF" ] && sed -i "/^$B/,/^$E/d" "$CONF"
|
||||||
systemctl reload smbd >/dev/null 2>&1 || true
|
systemctl disable --now smbd nmbd >/dev/null 2>&1 || true # папок bj нет — сервер не отвечает по SMB
|
||||||
ok "доступ к папкам шлюза по сети закрыт"; exit 0
|
ok "доступ к папкам шлюза по сети закрыт, Samba выключена"; exit 0
|
||||||
fi
|
fi
|
||||||
[ -n "$mode" ] || mode=$dmode
|
[ -n "$mode" ] || mode=$dmode
|
||||||
if ! command -v smbd >/dev/null 2>&1; then
|
if ! command -v smbd >/dev/null 2>&1; then
|
||||||
@@ -398,7 +398,7 @@ SQL
|
|||||||
[ -f "$CONF" ] && sed -i "/^$B/,/^$E/d" "$CONF"
|
[ -f "$CONF" ] && sed -i "/^$B/,/^$E/d" "$CONF"
|
||||||
{ echo "$B"
|
{ echo "$B"
|
||||||
for c in test3 prod; do d=$(exchange_dir "$c"); [ -d "$d" ] || continue
|
for c in test3 prod; do d=$(exchange_dir "$c"); [ -d "$d" ] || continue
|
||||||
n=igate_exchange; [ "$c" = prod ] && n=igate_exchange_prod
|
n=igate_exchange_test; [ "$c" = prod ] && n=igate_exchange # боевой — без суффикса, тестовый — _test
|
||||||
printf '[%s]\n comment = Шлюз НРД: папки обмена %s (INBOX/OUTBOX/SENT/ERRORS)\n path = %s\n browseable = yes\n read only = no\n valid users = %s\n force user = %s\n force group = %s\n' "$n" "$c" "$d" "$valid" "$SVC_USER" "$SVC_USER"
|
printf '[%s]\n comment = Шлюз НРД: папки обмена %s (INBOX/OUTBOX/SENT/ERRORS)\n path = %s\n browseable = yes\n read only = no\n valid users = %s\n force user = %s\n force group = %s\n' "$n" "$c" "$d" "$valid" "$SVC_USER" "$SVC_USER"
|
||||||
done
|
done
|
||||||
echo "$E"; } >> "$CONF"
|
echo "$E"; } >> "$CONF"
|
||||||
@@ -407,8 +407,13 @@ SQL
|
|||||||
sed -i -E 's/^([[:space:]]*)map to guest[[:space:]]*=[[:space:]]*bad user.*$/\1map to guest = never/I' "$CONF"
|
sed -i -E 's/^([[:space:]]*)map to guest[[:space:]]*=[[:space:]]*bad user.*$/\1map to guest = never/I' "$CONF"
|
||||||
testparm -s "$CONF" >/dev/null 2>&1 || { sed -i "/^$B/,/^$E/d" "$CONF"; err "smb.conf после правки не проходит проверку (testparm) — правка отменена"; }
|
testparm -s "$CONF" >/dev/null 2>&1 || { sed -i "/^$B/,/^$E/d" "$CONF"; err "smb.conf после правки не проходит проверку (testparm) — правка отменена"; }
|
||||||
systemctl enable smbd >/dev/null 2>&1; systemctl restart smbd >/dev/null 2>&1 || err "служба smbd не перезапустилась"
|
systemctl enable smbd >/dev/null 2>&1; systemctl restart smbd >/dev/null 2>&1 || err "служба smbd не перезапустилась"
|
||||||
if [ "$dmode" = domain ]; then ok "${note}папки шлюза открыты по сети для сотрудников домена с доступом к bj: \\\\$(hostname -I | awk '{print $1}')\\igate_exchange"
|
ip=$(hostname -I | awk '{print $1}'); where=""
|
||||||
else ok "${note}папки шлюза открыты по сети для $su: \\\\$(hostname -I | awk '{print $1}')\\igate_exchange"; fi ;;
|
for n in $(sed -n "/^$B/,/^$E/p" "$CONF" | sed -n 's/^\[\(.*\)\]$/\1/p'); do
|
||||||
|
t="боевой"; case "$n" in *_test) t="тестовый TEST3" ;; esac
|
||||||
|
where="$where${where:+, }\\\\$ip\\$n ($t)"
|
||||||
|
done
|
||||||
|
if [ "$dmode" = domain ]; then ok "${note}папки шлюза открыты по сети для сотрудников домена с доступом к bj: $where"
|
||||||
|
else ok "${note}папки шлюза открыты по сети для $su: $where"; fi ;;
|
||||||
|
|
||||||
*) err "неизвестная команда: $cmd" ;;
|
*) err "неизвестная команда: $cmd" ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -203,6 +203,23 @@ func igateShareSync(ctx context.Context, rc *RuntimeConfig) (string, error) {
|
|||||||
if on, _ := st["on"].(bool); !on {
|
if on, _ := st["on"].(bool); !on {
|
||||||
return "папки закрыты — сверять нечего", nil
|
return "папки закрыты — сверять нечего", nil
|
||||||
}
|
}
|
||||||
|
if man, _ := st["manual"].(bool); man {
|
||||||
|
return "папки открыты вручную — bj их не меняет", nil
|
||||||
|
}
|
||||||
|
// прежние имена (тестовый — igate_exchange, боевой — igate_exchange_prod) → новые (_test и без суффикса)
|
||||||
|
if sh, _ := st["shares"].([]any); igateShareOldNames(sh) {
|
||||||
|
p := map[string]string{"enabled": "true", "mode": "simple"}
|
||||||
|
if m, _ := st["mode"].(string); m == "domain" {
|
||||||
|
valid, err := igateShareValid(rc.Snapshot().Auth)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
p = map[string]string{"enabled": "true", "mode": "domain", "valid": valid}
|
||||||
|
} else {
|
||||||
|
p["user"], _ = st["user"].(string)
|
||||||
|
}
|
||||||
|
return igateShareApply(ctx, p, "папки переименованы: тестовый — igate_exchange_test, боевой — igate_exchange")
|
||||||
|
}
|
||||||
if m, _ := st["mode"].(string); m != "domain" {
|
if m, _ := st["mode"].(string); m != "domain" {
|
||||||
return "простой режим — доступ по одному логину", nil
|
return "простой режим — доступ по одному логину", nil
|
||||||
}
|
}
|
||||||
@@ -213,7 +230,11 @@ func igateShareSync(ctx context.Context, rc *RuntimeConfig) (string, error) {
|
|||||||
if cur, _ := st["user"].(string); cur == valid {
|
if cur, _ := st["user"].(string); cur == valid {
|
||||||
return "список доступа не изменился", nil
|
return "список доступа не изменился", nil
|
||||||
}
|
}
|
||||||
extra, _ := json.Marshal(map[string]string{"enabled": "true", "mode": "domain", "valid": valid})
|
return igateShareApply(ctx, map[string]string{"enabled": "true", "mode": "domain", "valid": valid}, "доступ к папкам шлюза обновлён: "+valid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func igateShareApply(ctx context.Context, p map[string]string, done string) (string, error) {
|
||||||
|
extra, _ := json.Marshal(p)
|
||||||
res, err := runKeymediaOp(ctx, "igate-share", map[string]string{"extra": string(extra)})
|
res, err := runKeymediaOp(ctx, "igate-share", map[string]string{"extra": string(extra)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -222,8 +243,18 @@ func igateShareSync(ctx context.Context, rc *RuntimeConfig) (string, error) {
|
|||||||
e, _ := res["error"].(string)
|
e, _ := res["error"].(string)
|
||||||
return "", fmt.Errorf("%s", e)
|
return "", fmt.Errorf("%s", e)
|
||||||
}
|
}
|
||||||
log.Printf("igate-share: доступ к папкам шлюза обновлён: %s", valid)
|
log.Printf("igate-share: %s", done)
|
||||||
return "доступ обновлён: " + valid, nil
|
return done, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// igateShareOldNames — папки открыты по прежней схеме имён (до 07.10.2026: боевой был igate_exchange_prod).
|
||||||
|
func igateShareOldNames(shares []any) bool {
|
||||||
|
for _, x := range shares {
|
||||||
|
if n, _ := x.(string); n == "igate_exchange_prod" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// igateShareSyncSoon — после изменения групп или сотрудников: сверить доступ к папкам в фоне (раз в несколько
|
// igateShareSyncSoon — после изменения групп или сотрудников: сверить доступ к папкам в фоне (раз в несколько
|
||||||
@@ -255,10 +286,12 @@ func igateShareSyncSoon(rc *RuntimeConfig) {
|
|||||||
func (s *Server) igateShareSyncLoop(ctx context.Context) {
|
func (s *Server) igateShareSyncLoop(ctx context.Context) {
|
||||||
t := time.NewTicker(15 * time.Minute)
|
t := time.NewTicker(15 * time.Minute)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
|
first := time.After(30 * time.Second) // после запуска (обновления) — сразу, а не через 15 минут
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
|
case <-first:
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
}
|
}
|
||||||
c, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
c, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||||
|
|||||||
@@ -30,3 +30,12 @@ func TestIgateShareValid(t *testing.T) {
|
|||||||
t.Fatal("пустой список доступа должен быть ошибкой")
|
t.Fatal("пустой список доступа должен быть ошибкой")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIgateShareOldNames(t *testing.T) {
|
||||||
|
if !igateShareOldNames([]any{"igate_exchange", "igate_exchange_prod"}) {
|
||||||
|
t.Fatal("прежняя схема имён не распознана")
|
||||||
|
}
|
||||||
|
if igateShareOldNames([]any{"igate_exchange_test", "igate_exchange"}) {
|
||||||
|
t.Fatal("новая схема принята за прежнюю")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -190,7 +190,7 @@
|
|||||||
function addrs(d){ var hosts=[d.ip||location.hostname]; if(d.host && d.host!==hosts[0]) hosts.push(d.host);
|
function addrs(d){ var hosts=[d.ip||location.hostname]; if(d.host && d.host!==hosts[0]) hosts.push(d.host);
|
||||||
var names=(d.shares&&d.shares.length)?d.shares:['igate_exchange'], rows=[];
|
var names=(d.shares&&d.shares.length)?d.shares:['igate_exchange'], rows=[];
|
||||||
names.forEach(function(n){ hosts.forEach(function(h){ var p='\\\\'+h+'\\'+n;
|
names.forEach(function(n){ hosts.forEach(function(h){ var p='\\\\'+h+'\\'+n;
|
||||||
rows.push('<div class="ig-addr"><code>'+esc(p)+'</code><button type="button" class="btn sm ghost" data-copy="'+esc(p)+'">Скопировать</button><span class="muted">'+(n.indexOf('prod')>=0?'боевой контур':'тестовый контур TEST3')+(h===d.host?' · по имени сервера':'')+'</span></div>'); }); });
|
rows.push('<div class="ig-addr"><code>'+esc(p)+'</code><button type="button" class="btn sm ghost" data-copy="'+esc(p)+'">Скопировать</button><span class="muted">'+(/_test$/.test(n)?'тестовый контур TEST3':'боевой контур')+(h===d.host?' · по имени сервера':'')+'</span></div>'); }); });
|
||||||
return rows.join(''); }
|
return rows.join(''); }
|
||||||
function show(d){
|
function show(d){
|
||||||
if(!d.ok){ stl.innerHTML=bad(d.error||'состояние не получено'); return; }
|
if(!d.ok){ stl.innerHTML=bad(d.error||'состояние не получено'); return; }
|
||||||
|
|||||||
Reference in New Issue
Block a user