Réplication de nœuds : réseau et DNS par l'API, multipath, chrony, sysctl par SSH
Deuxième mode de réplication, séparé de la réplication cluster vers cluster
(sélecteur « Clusters (API) | Nœuds (fichiers) » en haut de page) : un profil
réplique la configuration système d'un nœud maître vers des nœuds cibles.
- réseau par /nodes/{node}/network (interfaces.new puis rechargement), adresses
et passerelles propres à chaque nœud, mapping des noms d'interfaces par cible ;
DNS par /nodes/{node}/dns ; multipath.conf, wwids (ajout seul), chrony,
sysctl.d, interfaces.d (hors sdn) par SSH
- découverte des nœuds (/cluster/status), accès SSH par nœud (mot de passe du
cluster ou propre), clé d'hôte épinglée, contrôle du nom du nœud
- dry-run avec diff des fichiers ; apply (admin) avec contrôle SHA-256,
sauvegarde et restore.sh sur le nœud, écriture atomique, rechargements,
restauration si un rechargement échoue, timer systemd de restauration si le
nœud est injoignable après le rechargement du réseau, confirmation tapée
- vue Nœuds et profils, journal séparé par mode, démo avec nœuds simulés
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
dc1a7ff40e
commit
1e667ce8b9
24 files changed
+4973
-65
No files matched your search
@@ -17,18 +17,27 @@ import (
|
||||
// sshServer answers the commands pve-confsync runs on a node: pveversion, the
|
||||
// read of priv/shadow.cfg and its locked write (adapter password).
|
||||
type sshServer struct {
|
||||
ln net.Listener
|
||||
key ssh.PublicKey
|
||||
ln net.Listener
|
||||
key ssh.PublicKey
|
||||
exec func(cmd string, in io.Reader, out, errw io.Writer) int
|
||||
}
|
||||
|
||||
func startSSH(s *Server) *sshServer {
|
||||
return newSSH(func(user, pass string) bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return user == "root" && pass == s.Password
|
||||
}, s.exec)
|
||||
}
|
||||
|
||||
// newSSH starts an SSH server accepting the password check ok and running
|
||||
// the commands with exec.
|
||||
func newSSH(ok func(user, pass string) bool, exec func(cmd string, in io.Reader, out, errw io.Writer) int) *sshServer {
|
||||
_, priv, _ := ed25519.GenerateKey(rand.Reader)
|
||||
signer, _ := ssh.NewSignerFromKey(priv)
|
||||
cfg := &ssh.ServerConfig{
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if c.User() == "root" && string(pass) == s.Password {
|
||||
if ok(c.User(), string(pass)) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("denied")
|
||||
@@ -39,14 +48,14 @@ func startSSH(s *Server) *sshServer {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
srv := &sshServer{ln: ln, key: signer.PublicKey()}
|
||||
srv := &sshServer{ln: ln, key: signer.PublicKey(), exec: exec}
|
||||
go func() {
|
||||
for {
|
||||
nc, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go s.serveSSH(nc, cfg)
|
||||
go srv.serve(nc, cfg)
|
||||
}
|
||||
}()
|
||||
return srv
|
||||
@@ -65,7 +74,7 @@ func (s *Server) SSHHostKey() string {
|
||||
return strings.TrimSpace(string(ssh.MarshalAuthorizedKey(s.ssh.key)))
|
||||
}
|
||||
|
||||
func (s *Server) serveSSH(nc net.Conn, cfg *ssh.ServerConfig) {
|
||||
func (x *sshServer) serve(nc net.Conn, cfg *ssh.ServerConfig) {
|
||||
_, chans, reqs, err := ssh.NewServerConn(nc, cfg)
|
||||
if err != nil {
|
||||
nc.Close()
|
||||
@@ -91,7 +100,7 @@ func (s *Server) serveSSH(nc net.Conn, cfg *ssh.ServerConfig) {
|
||||
var p struct{ Cmd string }
|
||||
_ = ssh.Unmarshal(req.Payload, &p)
|
||||
_ = req.Reply(true, nil)
|
||||
status := s.exec(p.Cmd, ch, ch, ch.Stderr())
|
||||
status := x.exec(p.Cmd, ch, ch, ch.Stderr())
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, uint32(status))
|
||||
_, _ = ch.SendRequest("exit-status", false, b)
|
||||
|
||||
Reference in new issue
Block a user