go/nix: teach bnix how to talk to a remote store over SSH
This commit is contained in:
parent
b11cc9d3c8
commit
0563c19125
6 changed files with 152 additions and 6 deletions
|
@ -4,18 +4,25 @@ import (
|
|||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"hg.lukegb.com/lukegb/depot/go/nix/nixdrv"
|
||||
"hg.lukegb.com/lukegb/depot/go/nix/nixstore"
|
||||
)
|
||||
|
||||
var (
|
||||
remoteFlag = flag.String("remote", "unix://", "Remote.")
|
||||
)
|
||||
|
||||
func ensure(ctx context.Context, d *nixstore.Daemon, at *nixstore.ActivityTracker, path string) error {
|
||||
return d.EnsurePath(at, path)
|
||||
}
|
||||
|
||||
var rs nixdrv.LocalFSResolver
|
||||
var rs nixdrv.Resolver
|
||||
|
||||
func loadDerivation(ctx context.Context, path string) (*nixdrv.Derivation, error) {
|
||||
return rs.LoadDerivation(path)
|
||||
|
@ -40,6 +47,115 @@ func buildDerivation(ctx context.Context, d *nixstore.Daemon, at *nixstore.Activ
|
|||
return nil
|
||||
}
|
||||
|
||||
func connectTo(ctx context.Context, remote string) (*nixstore.Daemon, nixdrv.Resolver, error) {
|
||||
u, err := url.Parse(remote)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parsing remote %q as URL: %w", remote, err)
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "unix":
|
||||
if u.Path == "" {
|
||||
u.Path = nixstore.DaemonSock
|
||||
}
|
||||
d, err := nixstore.OpenDaemon(u.Path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return d, nixdrv.LocalFSResolver{}, nil
|
||||
case "ssh-ng":
|
||||
// Construct a ClientConfig from the URL.
|
||||
cfg := &ssh.ClientConfig{}
|
||||
if u.Query().Has("privkey") {
|
||||
var keys []ssh.Signer
|
||||
for _, privkeyPath := range u.Query()["privkey"] {
|
||||
privkeyF, err := os.Open(privkeyPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("opening privkey %q: %w", privkeyPath, err)
|
||||
}
|
||||
defer privkeyF.Close()
|
||||
privkeyB, err := io.ReadAll(privkeyF)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("reading privkey %q: %w", privkeyPath, err)
|
||||
}
|
||||
|
||||
privkey, err := ssh.ParsePrivateKey(privkeyB)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parsing privkey %q: %w", privkeyPath, err)
|
||||
}
|
||||
keys = append(keys, privkey)
|
||||
}
|
||||
cfg.Auth = append(cfg.Auth, ssh.PublicKeys(keys...))
|
||||
}
|
||||
if u.User != nil {
|
||||
cfg.User = u.User.Username()
|
||||
if pw, ok := u.User.Password(); ok {
|
||||
cfg.Auth = append(cfg.Auth, ssh.Password(pw))
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case u.Query().Has("host-key"):
|
||||
hkStr := u.Query().Get("host-key")
|
||||
_, _, hk, _, _, err := ssh.ParseKnownHosts(append([]byte("x "), []byte(hkStr)...))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parsing host-key %q: %w", hkStr, err)
|
||||
}
|
||||
cfg.HostKeyCallback = ssh.FixedHostKey(hk)
|
||||
case u.Query().Has("insecure-allow-any-ssh-host-key"):
|
||||
cfg.HostKeyCallback = ssh.InsecureIgnoreHostKey()
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("some SSH host key configuration is required (?host-key=; ?insecure-allow-any-ssh-host-key)")
|
||||
}
|
||||
|
||||
// Work out other misc parameters.
|
||||
// ...remote command.
|
||||
remoteCmd := "nix-daemon --stdio"
|
||||
if u.Query().Has("remote-cmd") {
|
||||
remoteCmd = u.Query().Get("remote-cmd")
|
||||
}
|
||||
|
||||
// Work out the host:port to connect to.
|
||||
remote := u.Hostname()
|
||||
if portStr := u.Port(); portStr != "" {
|
||||
remote = remote + ":" + portStr
|
||||
} else {
|
||||
remote = remote + ":22"
|
||||
}
|
||||
|
||||
conn, err := ssh.Dial("tcp", remote, cfg)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("dialing %v via SSH: %w", remote, err)
|
||||
}
|
||||
sess, err := conn.NewSession()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, nil, fmt.Errorf("opening SSH session to %v: %w", remote, err)
|
||||
}
|
||||
stdin, err := sess.StdinPipe()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, nil, fmt.Errorf("opening stdin pipe: %w", err)
|
||||
}
|
||||
stdout, err := sess.StdoutPipe()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, nil, fmt.Errorf("opening stdout pipe: %w", err)
|
||||
}
|
||||
if err := sess.Start(remoteCmd); err != nil {
|
||||
conn.Close()
|
||||
return nil, nil, fmt.Errorf("starting %q: %w", remoteCmd, err)
|
||||
}
|
||||
d, err := nixstore.OpenDaemonWithIOs(stdout, stdin, conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, nil, fmt.Errorf("establishing connection to daemon: %w", err)
|
||||
}
|
||||
return d, nixdrv.LocalFSResolver{}, nil // TODO: change from LocalFSResolver?
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unknown remote %q", remote)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
|
@ -87,13 +203,14 @@ func main() {
|
|||
|
||||
at := nixstore.NewActivityTracker()
|
||||
|
||||
d, err := nixstore.OpenDaemon(nixstore.DaemonSock)
|
||||
ctx := context.Background()
|
||||
d, rss, err := connectTo(ctx, *remoteFlag)
|
||||
if err != nil {
|
||||
log.Fatalf("OpenDaemon: %v", err)
|
||||
log.Fatalf("connectTo(%q): %v", *remoteFlag, err)
|
||||
}
|
||||
defer d.Close()
|
||||
rs = rss
|
||||
|
||||
ctx := context.Background()
|
||||
if err := cmd(ctx, d, at); err != nil {
|
||||
log.Fatalf("%s: %s", flag.Arg(0), err)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
args:
|
||||
{
|
||||
nar = import ./nar args;
|
||||
nixdrv = import ./nixdrv args;
|
||||
nixstore = import ./nixstore args;
|
||||
nixwire = import ./nixwire args;
|
||||
bcachegc = import ./bcachegc args;
|
||||
|
|
15
go/nix/nixdrv/default.nix
Normal file
15
go/nix/nixdrv/default.nix
Normal file
|
@ -0,0 +1,15 @@
|
|||
# SPDX-FileCopyrightText: 2023 Luke Granger-Brown <depot@lukegb.com>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
{ depot, ... }:
|
||||
depot.third_party.buildGo.package {
|
||||
name = "nixdrv";
|
||||
path = "hg.lukegb.com/lukegb/depot/go/nix/nixdrv";
|
||||
srcs = [
|
||||
./nixdrv.go
|
||||
./localfs.go
|
||||
];
|
||||
deps = with depot; [
|
||||
];
|
||||
}
|
|
@ -7,6 +7,7 @@ depot.third_party.buildGo.package {
|
|||
name = "nixstore";
|
||||
path = "hg.lukegb.com/lukegb/depot/go/nix/nixstore";
|
||||
srcs = [
|
||||
./activities.go
|
||||
./nixstore.go
|
||||
./remotestore.go
|
||||
./sqlitestore.go
|
||||
|
|
|
@ -29,7 +29,7 @@ const (
|
|||
)
|
||||
|
||||
type Daemon struct {
|
||||
conn net.Conn
|
||||
conn io.Closer
|
||||
w *nixwire.Serializer
|
||||
r *nixwire.Deserializer
|
||||
mu sync.Mutex
|
||||
|
@ -453,6 +453,15 @@ func OpenDaemon(path string) (*Daemon, error) {
|
|||
return nil, fmt.Errorf("sending hello to %v: %w", path, err)
|
||||
}
|
||||
|
||||
// /nix/var/nix/daemon-socket/socket
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func OpenDaemonWithIOs(r io.Reader, w io.Writer, c io.Closer) (*Daemon, error) {
|
||||
d := &Daemon{conn: c, w: &nixwire.Serializer{w}, r: &nixwire.Deserializer{r}}
|
||||
if err := d.hello(); err != nil {
|
||||
d.Close()
|
||||
return nil, fmt.Errorf("sending hello: %w", err)
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
|
|
@ -9,4 +9,7 @@ depot.third_party.buildGo.package {
|
|||
srcs = [
|
||||
./nixwire.go
|
||||
];
|
||||
deps = with depot; [
|
||||
go.nix.nixdrv
|
||||
];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue