1
0
forked from x/icebergs
This commit is contained in:
IT 老营长 @云轩领航-创始人 2023-10-11 20:30:53 +08:00
parent 15a3f015c7
commit 50e99b9fed
11 changed files with 143 additions and 109 deletions

View File

@ -68,6 +68,7 @@ const (
)
const (
LISTEN = "listen"
UNIX = "unix"
)
const SERVER = "server"

View File

@ -129,7 +129,7 @@ func ToastProcess(m *ice.Message, arg ...ice.Any) func() {
Toast(m, toastContent(m, ice.PROCESS), arg...)
return func() { Toast(m, toastContent(m, ice.SUCCESS)) }
}
func GoToast(m *ice.Message, title string, cb func(toast func(string, int, int)) []string) {
func GoToast(m *ice.Message, title string, cb func(toast func(string, int, int)) []string) *ice.Message {
_total := 0
toast := func(name string, count, total int) {
kit.If(total == 0, func() { total = 1 })
@ -144,4 +144,5 @@ func GoToast(m *ice.Message, title string, cb func(toast func(string, int, int))
} else {
toast(ice.SUCCESS, _total, _total)
}
return m
}

View File

@ -41,7 +41,7 @@ func _xterm_get(m *ice.Message, h string) xterm.XTerm {
for {
if n, e := term.Read(buf); !m.Warn(e) && e == nil {
if _xterm_echo(m, h, string(buf[:n])); len(text) > 0 {
kit.If(text[0], func(cmd string) { m.Go(func() { m.Sleep30ms(); term.Writeln(cmd) }) })
kit.If(text[0], func(cmd string) { m.Go(func() { m.Sleep30ms(); term.Write([]byte(cmd + lex.NL)) }) })
text = text[1:]
}
} else {
@ -54,11 +54,12 @@ func _xterm_get(m *ice.Message, h string) xterm.XTerm {
}).(xterm.XTerm)
}
func _xterm_echo(m *ice.Message, h string, str string) {
m.Options(ice.MSG_COUNT, "0", ice.LOG_DISABLE, ice.TRUE, "__target", "", ice.MSG_DAEMON, mdb.HashSelectField(m, h, cli.DAEMON))
m.Options(ice.MSG_DAEMON, mdb.HashSelectField(m, h, cli.DAEMON), ice.MSG_COUNT, "0", "__target", "")
m.Options(ice.LOG_DISABLE, ice.TRUE)
web.PushNoticeGrow(m, h, str)
}
func _xterm_cmds(m *ice.Message, h string, cmd string, arg ...ice.Any) {
kit.If(cmd != "", func() { _xterm_get(m, h).Writeln(cmd, arg...) })
kit.If(cmd != "", func() { _xterm_get(m, h).Write([]byte(kit.Format(cmd, arg...) + lex.NL)) })
m.ProcessHold()
}

View File

@ -65,6 +65,11 @@ func (m *Message) Go(cb func(), arg ...Any) *Message {
task.Put(m.FormatTaskMeta(), arg[0], func(task *task.Task) { m.TryCatch(true, func(m *Message) { cb() }) })
return m
}
func (m *Message) GoWait(cb func(func()), arg ...Any) *Message {
res := make(chan bool, 2)
defer func() { <-res }()
return m.Go(func() { cb(func() { res <- true }) }, arg...)
}
func (m *Message) Wait(d string, cb ...Handler) (wait func() bool, done Handler) {
sync := make(chan bool, 2)
t := time.AfterFunc(kit.Duration(d), func() { sync <- false })

View File

@ -4,9 +4,9 @@ websocket
webview
qrcode
xterm
git
ssh
vim
bash
tmux

View File

@ -20,6 +20,9 @@ import (
"shylinux.com/x/icebergs/base/nfs"
psh "shylinux.com/x/icebergs/base/ssh"
"shylinux.com/x/icebergs/base/tcp"
"shylinux.com/x/icebergs/base/web"
"shylinux.com/x/icebergs/core/code"
"shylinux.com/x/icebergs/misc/xterm"
kit "shylinux.com/x/toolkits"
)
@ -30,26 +33,26 @@ func _ssh_open(m *ice.Message, arg ...string) {
defer terminal.Restore(fd, oldState)
}
w, h, _ := terminal.GetSize(fd)
c.Write([]byte(fmt.Sprintf("#height:%d,width:%d\n", h, w)))
for _, item := range kit.Simple(m.Optionv(ice.INIT)) {
c.Write([]byte(fmt.Sprintf("#height:%d,width:%d"+lex.NL, h, w)))
kit.For(kit.Simple(m.Optionv(ice.INIT)), func(cmd string) {
defer c.Write([]byte(cmd + lex.NL))
m.Sleep300ms()
c.Write([]byte(item + lex.NL))
}
m.Go(func() { io.Copy(c, os.Stdin) })
io.Copy(os.Stdout, c)
})
m.Go(func() { io.Copy(os.Stdout, c) })
io.Copy(c, os.Stdin)
}, arg...)
}
func _ssh_dial(m *ice.Message, cb func(net.Conn), arg ...string) {
p := kit.HomePath(".ssh", fmt.Sprintf("%s@%s:%s", m.Option(aaa.USERNAME), m.Option(tcp.HOST), m.Option(tcp.PORT)))
if nfs.Exists(m, p) {
if c, e := net.Dial("unix", p); e == nil {
if c, e := net.Dial(tcp.UNIX, p); e == nil {
cb(c)
return
}
nfs.Remove(m, p)
}
_ssh_conn(m, func(client *ssh.Client) {
if l, e := net.Listen("unix", p); !m.Warn(e, ice.ErrNotValid) {
if l, e := net.Listen(tcp.UNIX, p); !m.Warn(e, ice.ErrNotValid) {
defer func() { nfs.Remove(m, p) }()
defer l.Close()
m.Go(func() {
@ -72,18 +75,15 @@ func _ssh_dial(m *ice.Message, cb func(net.Conn), arg ...string) {
}
s.Stdin, s.Stdout, s.Stderr = c, c, c
s.RequestPty(kit.Env(cli.TERM), h, w, ssh.TerminalModes{ssh.ECHO: 1, ssh.TTY_OP_ISPEED: 14400, ssh.TTY_OP_OSPEED: 14400})
gdb.SignalNotify(m, 28, func() { w, h, _ := terminal.GetSize(int(os.Stdin.Fd())); s.WindowChange(h, w) })
defer s.Wait()
gdb.SignalNotify(m, 28, func() {
w, h, _ := terminal.GetSize(int(os.Stdin.Fd()))
s.WindowChange(h, w)
})
s.Shell()
})
}(c)
}
})
}
if c, e := net.Dial("unix", p); e == nil {
if c, e := net.Dial(tcp.UNIX, p); !m.Warn(e) {
cb(c)
}
}, arg...)
@ -109,7 +109,6 @@ func _ssh_conn(m *ice.Message, cb func(*ssh.Client), arg ...string) {
}
case strings.HasSuffix(p, "password:"):
res = append(res, m.Option(aaa.PASSWORD))
default:
}
}
return
@ -124,54 +123,108 @@ func _ssh_conn(m *ice.Message, cb func(*ssh.Client), arg ...string) {
}
})
}
func _ssh_hold(m *ice.Message, c *ssh.Client) {
if s, e := _ssh_session(m, c); !m.Warn(e, ice.ErrNotValid) {
defer s.Wait()
s.Shell()
}
}
func _ssh_target(m *ice.Message, name string) *ssh.Client {
return mdb.HashSelectTarget(m, name, func(value ice.Maps) (res ice.Any) {
m.GoWait(func(done func()) {
_ssh_conn(m.Spawn(value), func(c *ssh.Client) {
defer _ssh_hold(m, c)
defer done()
res = c
})
})
return
}).(*ssh.Client)
}
const SSH = "ssh"
const (
DIRECT = "direct"
)
const CONNECT = "connect"
func init() {
psh.Index.MergeCommands(ice.Commands{
CONNECT: {Name: "connect name auto", Help: "连接", Actions: ice.MergeActions(ice.Actions{
ice.CTX_INIT: {Hand: func(m *ice.Message, arg ...string) {
mdb.HashSelect(m).Table(func(value ice.Maps) {
if value[mdb.STATUS] == tcp.OPEN {
m.Cmd("", tcp.DIAL, mdb.NAME, value[mdb.NAME], value)
}
})
}},
CONNECT: {Help: "连接", Actions: ice.MergeActions(ice.Actions{
tcp.OPEN: {Name: "open authfile username=shy password verfiy host=shylinux.com port=22 private=.ssh/id_rsa", Help: "终端", Hand: func(m *ice.Message, arg ...string) {
defer nfs.OptionLoad(m, m.Option("authfile")).Echo("exit %s@%s:%s\n", m.Option(aaa.USERNAME), m.Option(tcp.HOST), m.Option(tcp.PORT))
_ssh_open(m, arg...)
}},
tcp.DIAL: {Name: "dial name=shylinux host=shylinux.com port=22 username=shy private=.ssh/id_rsa", Help: "添加", Hand: func(m *ice.Message, arg ...string) {
m.Go(func() {
_ssh_conn(m, func(client *ssh.Client) {
mdb.HashCreate(m.Spawn(), m.OptionSimple(mdb.NAME, tcp.HOST, tcp.PORT, aaa.USERNAME), mdb.STATUS, tcp.OPEN, kit.Dict(mdb.TARGET, client))
m.Cmd("", SESSION, m.OptionSimple(mdb.NAME))
msg := m.Spawn()
_ssh_conn(m, func(c *ssh.Client) {
defer _ssh_hold(m, c)
mdb.HashCreate(msg, m.OptionSimple(mdb.NAME, tcp.HOST, tcp.PORT, aaa.USERNAME, PRIVATE), kit.Dict(mdb.TARGET, c))
}, arg...)
})
m.Sleep300ms()
}).Sleep3s()
}},
SESSION: {Help: "会话", Hand: func(m *ice.Message, arg ...string) {
if c, e := _ssh_session(m, mdb.HashSelectTarget(m, m.Option(mdb.NAME), nil).(*ssh.Client)); !m.Warn(e, ice.ErrNotValid) {
defer c.Wait()
c.Shell()
}
}},
ctx.COMMAND: {Name: "command cmd=pwd", Help: "命令", Hand: func(m *ice.Message, arg ...string) {
client := mdb.HashSelectTarget(m, m.Option(mdb.NAME), nil).(*ssh.Client)
if s, e := client.NewSession(); !m.Warn(e, ice.ErrNotValid) {
SESSION: {Help: "会话", Hand: func(m *ice.Message, arg ...string) { _ssh_hold(m, _ssh_target(m, m.Option(mdb.NAME))) }},
DIRECT: {Name: "direct cmd=pwd", Help: "命令", Hand: func(m *ice.Message, arg ...string) {
if m.Option(mdb.NAME) == "" {
msg := m.Cmds("")
web.GoToast(m, m.Option(ice.CMD), func(toast func(string, int, int)) []string {
count, total := 0, msg.Length()
toast("", count, total)
msg.Table(func(value ice.Maps) {
toast(value[mdb.NAME], count, total)
msg := m.Cmds("", m.ActionKey(), value)
kit.If(len(msg.Resultv()) == 0, func() { msg.TableEcho() })
m.Push(mdb.TIME, msg.Time())
m.Push(mdb.NAME, value[mdb.NAME])
m.Push(cli.COST, m.FormatCost())
m.Push(RES, msg.Result())
count++
})
return nil
}).ProcessInner()
} else if s, e := _ssh_target(m, m.Option(mdb.NAME)).NewSession(); !m.Warn(e, ice.ErrNotValid) {
defer s.Close()
if b, e := s.CombinedOutput(m.Option(ice.CMD)); !m.Warn(e, ice.ErrNotValid) {
m.Echo(string(b))
m.Echo(string(b)).ProcessInner()
}
} else {
mdb.HashSelectUpdate(m, m.Option(mdb.NAME), func(value ice.Map) { delete(value, mdb.TARGET) })
}
}},
}, mdb.StatusHashAction(mdb.SHORT, mdb.NAME, mdb.FIELD, "time,name,status,username,host,port")), Hand: func(m *ice.Message, arg ...string) {
if mdb.HashSelect(m, arg...).Table(func(value ice.Maps) {
m.PushButton(kit.Select("", "command,session", value[mdb.STATUS] == tcp.OPEN), mdb.REMOVE)
}); len(arg) == 0 {
m.Action(tcp.DIAL)
code.XTERM: {Hand: func(m *ice.Message, arg ...string) {
ctx.Process(m, code.XTERM, []string{SSH + lex.SP + m.Option(mdb.NAME)}, arg...)
}},
}, mdb.StatusHashAction(mdb.SHORT, mdb.NAME, mdb.FIELD, "time,name,username,private,host,port"), mdb.ImportantHashAction()), Hand: func(m *ice.Message, arg ...string) {
if mdb.HashSelect(m, arg...).PushAction(code.XTERM, DIRECT, SESSION, mdb.REMOVE); len(arg) == 0 {
m.Sort(mdb.NAME).Action(tcp.DIAL, DIRECT)
}
}},
})
}
type session struct {
name string
sess *ssh.Session
pty *os.File
}
func NewSession(m *ice.Message, arg ...string) (xterm.XTerm, error) {
sess := &session{name: arg[0]}
m.GoWait(func(done func()) {
m.Cmd("ssh.connect", SESSION, kit.Dict(mdb.NAME, arg[0]), func(s *ssh.Session) {
defer done()
pty, tty, _ := xterm.Open()
sess.sess, sess.pty = s, pty
s.Stdin, s.Stdout, s.Stderr = tty, tty, tty
s.RequestPty(kit.Env(cli.TERM), 24, 80, ssh.TerminalModes{ssh.ECHO: 0, ssh.TTY_OP_ISPEED: 14400, ssh.TTY_OP_OSPEED: 14400})
})
})
return sess, nil
}
func (s session) Setsize(h, w string) error { return s.sess.WindowChange(kit.Int(h), kit.Int(w)) }
func (s session) Write(buf []byte) (int, error) { return s.pty.Write(buf) }
func (s session) Read(buf []byte) (int, error) { return s.pty.Read(buf) }
func (s session) Close() error { return s.sess.Close() }
func init() { xterm.AddCommand(SSH, NewSession) }

View File

@ -1,14 +1,12 @@
package ssh
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"path"
"strings"
"golang.org/x/crypto/ssh"
@ -21,10 +19,8 @@ import (
)
const (
PUBLIC = "public"
PRIVATE = "private"
VERIFY = "verify"
SIGN = "sign"
PUBLIC = "public"
)
const RSA = "rsa"
@ -46,7 +42,8 @@ func init() {
mdb.CREATE: {Name: "create bits=2048,4096 title=some", Hand: func(m *ice.Message, arg ...string) {
if key, err := rsa.GenerateKey(rand.Reader, kit.Int(m.Option(BITS))); !m.Warn(err, ice.ErrNotValid) {
if pub, err := ssh.NewPublicKey(key.Public()); !m.Warn(err, ice.ErrNotValid) {
mdb.HashCreate(m, m.OptionSimple(TITLE), PUBLIC, string(ssh.MarshalAuthorizedKey(pub))+lex.SP+m.Option(TITLE),
mdb.HashCreate(m, m.OptionSimple(TITLE),
PUBLIC, strings.TrimSpace(string(ssh.MarshalAuthorizedKey(pub)))+lex.SP+strings.TrimSpace(m.Option(TITLE)),
PRIVATE, string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})),
)
}
@ -65,46 +62,6 @@ func init() {
PUBLIC, m.Cmdx(nfs.CAT, kit.HomePath(m.Option(PUB))),
))
}},
SIGN: {Hand: func(m *ice.Message, arg ...string) {
if !nfs.Exists(m, "etc/id_rsa") {
if key, err := rsa.GenerateKey(rand.Reader, kit.Int("2048")); !m.Warn(err, ice.ErrNotValid) {
m.Cmd(nfs.SAVE, "etc/id_rsa", string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})))
m.Cmd(nfs.SAVE, "etc/id_rsa.pub", string(pem.EncodeToMemory(&pem.Block{Type: "RSA PUBLIC KEY", Bytes: x509.MarshalPKCS1PublicKey(key.Public().(*rsa.PublicKey))})))
}
}
block, _ := pem.Decode([]byte(m.Cmdx(nfs.CAT, "etc/id_rsa")))
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if m.Warn(err) {
return
}
hash := sha256.New()
if _, err := hash.Write([]byte(arg[0])); m.Warn(err) {
return
}
signature, err := rsa.SignPSS(rand.Reader, key, crypto.SHA256, hash.Sum(nil), nil)
if m.Warn(err) {
return
}
m.Echo(hex.EncodeToString(signature))
}},
VERIFY: {Hand: func(m *ice.Message, arg ...string) {
block, _ := pem.Decode([]byte(m.Cmdx(nfs.CAT, "etc/id_rsa.pub")))
pub, err := x509.ParsePKCS1PublicKey(block.Bytes)
if m.Warn(err) {
return
}
signature, err := hex.DecodeString(arg[1])
if m.Warn(err) {
return
}
hash := sha256.New()
if _, err := hash.Write([]byte(arg[0])); m.Warn(err) {
return
}
if !m.Warn(rsa.VerifyPSS(pub, crypto.SHA256, hash.Sum(nil), signature, nil)) {
m.Echo(ice.OK)
}
}},
}, mdb.HashAction(mdb.SHORT, PRIVATE, mdb.FIELD, "time,hash,title,public,private")), Hand: func(m *ice.Message, arg ...string) {
if mdb.HashSelect(m, arg...).PushAction(mdb.EXPORT, mdb.REMOVE); len(arg) == 0 {
m.Action(mdb.CREATE, mdb.IMPORT)

View File

@ -145,7 +145,7 @@ const SERVICE = "service"
func init() {
psh.Index.MergeCommands(ice.Commands{
SERVICE: {Name: "service port id auto listen prunes", Icon: "ssh.png", Help: "服务", Actions: ice.MergeActions(ice.Actions{
SERVICE: {Name: "service port id auto", Icon: "ssh.png", Help: "服务", Actions: ice.MergeActions(ice.Actions{
ice.CTX_INIT: {Hand: func(m *ice.Message, arg ...string) {
mdb.HashSelect(m).Table(func(value ice.Maps) {
if value[mdb.STATUS] == tcp.OPEN {
@ -190,16 +190,14 @@ func init() {
}},
aaa.INVITE: {Help: "邀请", Hand: func(m *ice.Message, arg ...string) {
m.Option(cli.HOSTNAME, tcp.PublishLocalhost(m, web.UserWeb(m).Hostname()))
if buf, err := kit.Render(`ssh -p {{.Option "port"}} {{.Option "user.name"}}@{{.Option "hostname"}}`, m); err == nil {
m.EchoScript(string(buf))
}
m.EchoScript(kit.Renders(`ssh -p {{.Option "port"}} {{.Option "user.name"}}@{{.Option "hostname"}}`, m))
}},
}, mdb.StatusHashAction(
mdb.SHORT, tcp.PORT, mdb.FIELD, "time,port,status,private,authkey,count", mdb.FIELDS, "time,id,type,name,text",
WELCOME, "welcome to contexts world\r\n", GOODBYE, "goodbye of contexts world\r\n",
)), Hand: func(m *ice.Message, arg ...string) {
if mdb.ZoneSelect(m, arg...); len(arg) == 0 {
m.PushAction(aaa.INVITE, mdb.INSERT, ctx.LOAD, ctx.SAVE, mdb.REMOVE)
if mdb.ZoneSelect(m, arg...).PushAction(aaa.INVITE, mdb.INSERT, ctx.LOAD, ctx.SAVE, mdb.REMOVE); len(arg) == 0 {
m.Action(tcp.LISTEN)
}
}},
})

View File

@ -11,16 +11,27 @@ import (
"shylinux.com/x/icebergs/base/mdb"
psh "shylinux.com/x/icebergs/base/ssh"
"shylinux.com/x/icebergs/base/tcp"
"shylinux.com/x/icebergs/core/code"
kit "shylinux.com/x/toolkits"
)
func _ssh_session(m *ice.Message, client *ssh.Client) (*ssh.Session, error) {
s, e := client.NewSession()
func _ssh_session(m *ice.Message, c *ssh.Client) (*ssh.Session, error) {
s, e := c.NewSession()
m.Assert(e)
switch cb := m.OptionCB("").(type) {
case func(s *ssh.Session):
cb(s)
return s, nil
}
out, e := s.StdoutPipe()
m.Assert(e)
in, e := s.StdinPipe()
m.Assert(e)
switch cb := m.OptionCB("").(type) {
case func(s *ssh.Session, in io.Writer, out io.Reader):
cb(s, in, out)
return s, nil
}
h := m.Cmdx(SESSION, mdb.CREATE, mdb.STATUS, tcp.OPEN, CONNECT, m.Option(mdb.NAME), kit.Dict(mdb.TARGET, in))
m.Go(func() {
buf := make([]byte, ice.MOD_BUFS)
@ -28,7 +39,7 @@ func _ssh_session(m *ice.Message, client *ssh.Client) (*ssh.Session, error) {
if n, e := out.Read(buf); e != nil {
break
} else {
m.Cmd(SESSION, mdb.INSERT, mdb.HASH, h, mdb.TYPE, RES, mdb.TEXT, string(buf[:n]))
m.Cmd(SESSION, mdb.INSERT, mdb.ZONE, h, mdb.TYPE, RES, mdb.TEXT, string(buf[:n]))
}
}
})
@ -51,7 +62,7 @@ const SESSION = "session"
func init() {
psh.Index.MergeCommands(ice.Commands{
SESSION: {Name: "session hash id auto", Help: "会话", Actions: ice.MergeActions(ice.Actions{
SESSION: {Help: "会话", Actions: ice.MergeActions(ice.Actions{
mdb.REPEAT: {Help: "执行", Hand: func(m *ice.Message, arg ...string) { m.Cmdy("", ctx.ACTION, ctx.COMMAND, CMD, m.Option(mdb.TEXT)) }},
ctx.COMMAND: {Name: "command cmd=pwd", Help: "命令", Hand: func(m *ice.Message, arg ...string) {
mdb.ZoneInsert(m, m.OptionSimple(mdb.HASH), mdb.TYPE, CMD, mdb.TEXT, m.Option(CMD))
@ -59,7 +70,9 @@ func init() {
w.Write([]byte(m.Option(CMD) + lex.NL))
m.Sleep300ms()
}
m.ProcessRefresh()
}},
code.XTERM: {},
}, mdb.PageZoneAction(mdb.SHORT, mdb.UNIQ, mdb.FIELD, "time,hash,count,status,connect", mdb.FIELDS, "time,id,type,text")), Hand: func(m *ice.Message, arg ...string) {
if mdb.PageZoneSelect(m, arg...); len(arg) == 0 {
m.Table(func(value ice.Maps) {

View File

@ -35,7 +35,8 @@ type iterm struct {
*idata
}
func NewITerm(m *ice.Message) (XTerm, error) {
func init() { AddCommand("ish", NewITerm) }
func NewITerm(m *ice.Message, arg ...string) (XTerm, error) {
r, w, e := os.Pipe()
return &iterm{m: m, r: r, w: w, idata: &idata{cmds: kit.Simple(
kit.SortedKey(ice.Info.Index),

View File

@ -20,7 +20,6 @@ type Winsize struct {
type XTerm interface {
Setsize(rows, cols string) error
Writeln(data string, arg ...ice.Any)
Write(buf []byte) (int, error)
Read(buf []byte) (int, error)
Close() error
@ -38,11 +37,16 @@ func (s xterm) Write(buf []byte) (int, error) { return s.File.Write(buf) }
func (s xterm) Read(buf []byte) (int, error) { return s.File.Read(buf) }
func (s xterm) Close() error { return s.Cmd.Process.Kill() }
type handler func(m *ice.Message, arg ...string) (XTerm, error)
var list = map[string]handler{}
func AddCommand(key string, cb handler) { list[key] = cb }
func Command(m *ice.Message, dir string, cli string, arg ...string) (XTerm, error) {
if path.Base(cli) == "ish" {
return NewITerm(m)
if cb, ok := list[path.Base(cli)]; ok {
return cb(m.Spawn(), arg...)
}
m.Debug("command %v %v", cli, arg)
cmd := exec.Command(cli, arg...)
cmd.Dir = nfs.MkdirAll(m, kit.Path(dir))
cmd.Env = append(cmd.Env, os.Environ()...)