Renew the engine placed by hand to the proved build, and take a hand-over from the controller's terminal (hq issue 356)
The file on PATH was the engine a person copied at installation, and every delivered engine ran from the versions directory: the operator typed hand-over and a build from before it answered "unexpected argument". A proved engine now copies itself over that file when the bytes differ (never a link). And the hand-over of a directory used as found (issue 339) gets its channel: the controller's terminal asks this node's engine on mesh.node.<node>.ask.hand-over; the engine judges every value — the ask's size and shape, who asked, the path as it states it, a directory and no link, through no link — and records it or refuses and records nothing. Every text the engine addresses to the operator names the nox line (ADR 0272); the engine's own subcommands are install-time and rescue, and its usage says so.
This commit is contained in:
+95
-31
@@ -22,14 +22,20 @@ import (
|
||||
// directory it never made to whichever account a declaration named. That is safe and, said nowhere, a module
|
||||
// whose data directory it cannot write would simply fail. So each such directory is said three ways: a WARNING
|
||||
// line in every apply's log; a resource of its module of kind `directory`, stated unhealthy on every look,
|
||||
// which the controller raises as that module's condition naming the directory and its owner; and a way out at
|
||||
// the machine's terminal, `mesh-host hand-over <path>`, after which the next apply gives it the declared owner
|
||||
// and mode like any directory of the mesh's.
|
||||
// which the controller raises as that module's condition naming the directory and its owner; and a way out,
|
||||
// which is the operator's line at the controller's terminal — `nox node hand-over <node> <path>` on the
|
||||
// control-node (novox/hq ADR 0272, issue 356) — after which the next apply gives it the declared owner and
|
||||
// mode like any directory of the mesh's. The controller carries that line to this engine over the bus
|
||||
// (link.AskHandOverSubject); the engine's own `hand-over` subcommand is the same act for a machine with no
|
||||
// mesh to reach.
|
||||
|
||||
// foundState is where the node's state is, for the looks to read the directories used as found from; empty
|
||||
// until the engine runs.
|
||||
var foundState atomic.Value
|
||||
|
||||
// foundNode is this node's name in the mesh, for the way out to name it; empty until the engine is enrolled.
|
||||
var foundNode atomic.Value
|
||||
|
||||
// handedOverFrom has the apply read the directories handed over from beside the state at statePath.
|
||||
func handedOverFrom(statePath string) {
|
||||
apply.HandedOver = func() map[string]bool {
|
||||
@@ -58,9 +64,21 @@ func foundDirectories(known store.State) []store.Applied {
|
||||
return out
|
||||
}
|
||||
|
||||
// handOverLine is the operator's line that hands a directory to the mesh, with this node's name when it is
|
||||
// known (novox/hq ADR 0272): typed on the control-node, from the operator's own account. The directory's
|
||||
// path is the resource's target beside the reason, never in it: the controller says a reason on the
|
||||
// operator's channel, which withholds a path (ADR 0234 §6), and keeps the target as evidence.
|
||||
func handOverLine() string {
|
||||
node, _ := foundNode.Load().(string)
|
||||
if node == "" {
|
||||
node = "<node>"
|
||||
}
|
||||
return fmt.Sprintf("`nox node hand-over %s <directory>` on the control-node", node)
|
||||
}
|
||||
|
||||
// withFoundDirectories adds to a statement every directory used as found, as its module's resource of kind
|
||||
// directory, unhealthy: the reason names its owner and mode as they are now, what was declared, and how to hand
|
||||
// it over. The path is the target, which the controller keeps as evidence.
|
||||
// directory, unhealthy: the reason names its owner and mode as they are now, what was declared, and the line
|
||||
// that hands it over. The path is the target, which the controller keeps as evidence.
|
||||
func withFoundDirectories(h *link.Health, found []store.Applied) *link.Health {
|
||||
for _, r := range found {
|
||||
at := strings.LastIndex(r.ID, ".")
|
||||
@@ -80,9 +98,8 @@ func withFoundDirectories(h *link.Health, found []store.Applied) *link.Health {
|
||||
}
|
||||
h.Resources = append(h.Resources, link.ResourceHealth{Module: r.ID[:at], Resource: r.ID,
|
||||
Kind: link.KindDirectory, Target: r.Target, State: link.StateUnhealthy,
|
||||
Reason: fmt.Sprintf("%s owned by %s, mode %s, as found; %s was declared and not given it — "+
|
||||
"`mesh-host hand-over` at the machine hands it to the mesh", link.ReasonUsedAsFound, owner, mode,
|
||||
declared),
|
||||
Reason: fmt.Sprintf("%s owned by %s, mode %s, as found; %s was declared and not given it — %s, with its "+
|
||||
"path, hands it to the mesh", link.ReasonUsedAsFound, owner, mode, declared, handOverLine()),
|
||||
Since: foundSince(r).UTC()})
|
||||
}
|
||||
return h
|
||||
@@ -102,34 +119,81 @@ func foundNow() []store.Applied {
|
||||
return foundDirectories(known)
|
||||
}
|
||||
|
||||
// handOverCommand hands one directory used as found to the mesh: run by a person, as root, at the machine.
|
||||
// It changes nothing on disk itself; the next apply gives the directory its declared owner and mode, and says so.
|
||||
func handOverCommand(opts options) error {
|
||||
path := filepath.Clean(opts.file)
|
||||
// handOverFound hands one directory used as found to the mesh, on the operator's word carried by the
|
||||
// controller (link.HandOverFunc) or typed at this machine. It changes nothing on disk itself; the next apply
|
||||
// gives the directory its declared owner and mode, and says so.
|
||||
//
|
||||
// **Every value is judged, and a refusal records nothing.** The path is the directory's absolute path exactly
|
||||
// as the engine states it — clean, so `..` and a doubled separator never name a directory; one this machine's
|
||||
// state records as used as found; on disk a directory and not a link, reached through no link. The engine
|
||||
// never follows a path it did not state: a hand-over is root giving a directory to an account at the next
|
||||
// apply, and a path that resolves elsewhere would give that elsewhere.
|
||||
func handOverFound(statePath, path, by string) (string, error) {
|
||||
if !filepath.IsAbs(path) {
|
||||
return fmt.Errorf("hand-over takes the directory's absolute path, and %q is not one", opts.file)
|
||||
return "", fmt.Errorf("hand-over takes the directory's absolute path, and %q is not one; nothing was handed over", path)
|
||||
}
|
||||
known, err := store.Load(opts.state)
|
||||
if filepath.Clean(path) != path {
|
||||
return "", fmt.Errorf("%q is not the directory's path as the engine states it (no `..`, no doubled or trailing "+
|
||||
"separator); nothing was handed over", path)
|
||||
}
|
||||
known, err := store.Load(statePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var found *store.Applied
|
||||
for _, r := range foundDirectories(known) {
|
||||
if filepath.Clean(r.Target) == path {
|
||||
r := r
|
||||
found = &r
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
return "", errors.New(path + " is not a directory this machine uses as found (the module's condition names " +
|
||||
"the ones that are); nothing was handed over")
|
||||
}
|
||||
info, err := os.Lstat(path)
|
||||
switch {
|
||||
case err != nil:
|
||||
return "", fmt.Errorf("%s cannot be read on this machine: %v; nothing was handed over", path, err)
|
||||
case info.Mode()&os.ModeSymlink != 0:
|
||||
return "", fmt.Errorf("%s is a link, and the mesh hands over no link (novox/hq ADR 0012); nothing was handed over", path)
|
||||
case !info.IsDir():
|
||||
return "", fmt.Errorf("%s is not a directory on this machine; nothing was handed over", path)
|
||||
}
|
||||
resolved, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s cannot be resolved on this machine: %v; nothing was handed over", path, err)
|
||||
}
|
||||
if resolved != path {
|
||||
return "", fmt.Errorf("%s is reached through a link (it is %s), and the mesh hands over nothing through one "+
|
||||
"(novox/hq issue 339); nothing was handed over", path, resolved)
|
||||
}
|
||||
if err := store.AddHandedOver(statePath, store.HandOver{ID: found.ID, Path: path, At: time.Now().UTC(), By: by}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s (%s) is handed to the mesh by %s: the next apply gives it %s, and it is the mesh's from "+
|
||||
"then on — removed when empty and no longer declared", path, found.ID, by, orUnsaid(found.Declared)), nil
|
||||
}
|
||||
|
||||
// handOverFrom is the engine's hand-over for the controller's ask: the state beside statePath, the path as asked.
|
||||
func handOverFrom(statePath string) link.HandOverFunc {
|
||||
return func(path, by string) (string, error) { return handOverFound(statePath, path, by) }
|
||||
}
|
||||
|
||||
// handOverCommand is `nox-mesh-host hand-over <dir>` at this machine, as root: the same act, for a machine
|
||||
// with no mesh to reach. The operator's line is `nox node hand-over <node> <dir>` on the control-node.
|
||||
func handOverCommand(opts options) error {
|
||||
by := "a person at the machine"
|
||||
if u, err := user.Current(); err == nil {
|
||||
by = u.Username + " at the machine"
|
||||
}
|
||||
said, err := handOverFound(opts.state, filepath.Clean(opts.file), by)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range foundDirectories(known) {
|
||||
if filepath.Clean(r.Target) != path {
|
||||
continue
|
||||
}
|
||||
by := "a person at the machine"
|
||||
if u, err := user.Current(); err == nil {
|
||||
by = u.Username
|
||||
}
|
||||
if err := store.AddHandedOver(opts.state, store.HandOver{ID: r.ID, Path: path, At: time.Now().UTC(), By: by}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(opts.out, "%s (%s) is handed to the mesh: the next apply gives it %s, and it is the mesh's from "+
|
||||
"then on — removed when empty and no longer declared\n", path, r.ID, orUnsaid(r.Declared))
|
||||
return nil
|
||||
}
|
||||
return errors.New(path + " is not a directory this machine uses as found (`mesh-host owned` lists what the " +
|
||||
"engine applied); nothing was handed over")
|
||||
fmt.Fprintln(opts.out, said)
|
||||
return nil
|
||||
}
|
||||
|
||||
// foundSince is when a directory was first found as it is: kept across applies, so the controller can tell one
|
||||
|
||||
+116
-6
@@ -14,13 +14,16 @@ import (
|
||||
)
|
||||
|
||||
// Every directory used as found is its module's resource of kind directory, unhealthy, naming its owner, on
|
||||
// every statement (novox/hq issue 339): the controller raises that as the module's condition.
|
||||
// every statement (novox/hq issue 339): the controller raises that as the module's condition. The way out it
|
||||
// names is the operator's line, with this node's name (novox/hq ADR 0272, issue 356).
|
||||
func TestAFoundDirectoryIsStatedUnhealthy(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
known := store.State{Resources: []store.Applied{
|
||||
{ID: "notes.data", Type: "directory", Target: dir, AsFound: true, Declared: "root, mode 755", AppliedAt: time.Now()},
|
||||
{ID: "notes.cache", Type: "directory", Target: dir},
|
||||
}}
|
||||
foundNode.Store("laptop")
|
||||
t.Cleanup(func() { foundNode.Store("") })
|
||||
h := withFoundDirectories(&link.Health{}, foundDirectories(known))
|
||||
if len(h.Resources) != 1 {
|
||||
t.Fatalf("stated: %+v", h.Resources)
|
||||
@@ -28,16 +31,45 @@ func TestAFoundDirectoryIsStatedUnhealthy(t *testing.T) {
|
||||
r := h.Resources[0]
|
||||
if r.Module != "notes" || r.Kind != link.KindDirectory || r.State != link.StateUnhealthy || r.Target != dir ||
|
||||
!strings.HasPrefix(r.Reason, link.ReasonUsedAsFound) || !strings.Contains(r.Reason, "owned by ") ||
|
||||
!strings.Contains(r.Reason, "root, mode 755") || !strings.Contains(r.Reason, "hand-over") {
|
||||
!strings.Contains(r.Reason, "root, mode 755") {
|
||||
t.Fatalf("stated as: %+v", r)
|
||||
}
|
||||
// The path is the target, kept as evidence, and never in the reason: the operator's channel withholds a path
|
||||
// (novox/hq ADR 0234 §6), and the controller says the reason there.
|
||||
if !strings.Contains(r.Reason, "`nox node hand-over laptop <directory>` on the control-node") ||
|
||||
strings.Contains(r.Reason, "mesh-host") || strings.Contains(r.Reason, dir) {
|
||||
t.Fatalf("the way out is not the operator's line, or carries the path: %q", r.Reason)
|
||||
}
|
||||
foundNode.Store("")
|
||||
if r := withFoundDirectories(&link.Health{}, foundDirectories(known)).Resources[0]; !strings.Contains(r.Reason,
|
||||
"`nox node hand-over <node> <directory>`") {
|
||||
t.Fatalf("before enrolment the line names a placeholder: %q", r.Reason)
|
||||
}
|
||||
}
|
||||
|
||||
// `mesh-host hand-over` records a directory used as found, and refuses one that is not.
|
||||
// realDir is a temporary directory with every link in its path resolved, so a judgement of links is about the
|
||||
// links the test makes.
|
||||
func realDir(t *testing.T) string {
|
||||
t.Helper()
|
||||
dir, err := filepath.EvalSymlinks(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// A hand-over records a directory used as found, and refuses one that is not — through the engine's own
|
||||
// subcommand and through the controller's ask alike, which are one judgement.
|
||||
func TestHandOverRecordsOnlyAFoundDirectory(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dir := realDir(t)
|
||||
statePath := filepath.Join(dir, "state.json")
|
||||
found := filepath.Join(dir, "found")
|
||||
if err := os.Mkdir(found, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(dir, "mine"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := store.Save(statePath, store.State{Resources: []store.Applied{
|
||||
{ID: "notes.data", Type: "directory", Target: found, AsFound: true, Declared: "root, mode 755"},
|
||||
{ID: "notes.mine", Type: "directory", Target: filepath.Join(dir, "mine")},
|
||||
@@ -52,7 +84,7 @@ func TestHandOverRecordsOnlyAFoundDirectory(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
all, err := store.LoadHandedOver(statePath)
|
||||
if err != nil || len(all) != 1 || all[0].Path != found {
|
||||
if err != nil || len(all) != 1 || all[0].Path != found || !strings.HasSuffix(all[0].By, " at the machine") {
|
||||
t.Fatalf("recorded: %+v %v", all, err)
|
||||
}
|
||||
handedOverFrom(statePath)
|
||||
@@ -62,5 +94,83 @@ func TestHandOverRecordsOnlyAFoundDirectory(t *testing.T) {
|
||||
if !strings.Contains(out.String(), "root, mode 755") {
|
||||
t.Fatalf("said: %s", out.String())
|
||||
}
|
||||
_ = os.Remove(statePath)
|
||||
|
||||
// The controller's ask is the same judgement, recording who asked in the controller's words.
|
||||
_ = os.Remove(store.HandedOverPath(statePath))
|
||||
said, err := handOverFrom(statePath)(found, "jo through mesh-cli on anchor")
|
||||
if err != nil || !strings.Contains(said, "by jo through mesh-cli on anchor") || !strings.Contains(said, "root, mode 755") {
|
||||
t.Fatalf("the ask answered %q, %v", said, err)
|
||||
}
|
||||
all, _ = store.LoadHandedOver(statePath)
|
||||
if len(all) != 1 || all[0].By != "jo through mesh-cli on anchor" || all[0].ID != "notes.data" {
|
||||
t.Fatalf("recorded: %+v", all)
|
||||
}
|
||||
}
|
||||
|
||||
// Every value of a hand-over is judged before anything is recorded, and each refusal records nothing: a relative
|
||||
// path, a path that is not the engine's statement of the directory (`..`, a doubled or trailing separator), one
|
||||
// not used as found, a link at the path, a link in the path, a file, a directory that is gone.
|
||||
func TestAHandOverIsRefusedForEveryPathThatIsNotTheFoundDirectory(t *testing.T) {
|
||||
dir := realDir(t)
|
||||
statePath := filepath.Join(dir, "state.json")
|
||||
found := filepath.Join(dir, "found")
|
||||
if err := os.Mkdir(found, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// A link at the path of a second found directory, and a found directory below a linked directory.
|
||||
linked := filepath.Join(dir, "linked")
|
||||
if err := os.Symlink(found, linked); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
through := filepath.Join(dir, "via", "data")
|
||||
if err := os.Symlink(dir, filepath.Join(dir, "via")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(dir, "data"), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file := filepath.Join(dir, "file")
|
||||
if err := os.WriteFile(file, nil, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gone := filepath.Join(dir, "gone")
|
||||
if err := store.Save(statePath, store.State{Resources: []store.Applied{
|
||||
{ID: "notes.data", Type: "directory", Target: found, AsFound: true},
|
||||
{ID: "notes.linked", Type: "directory", Target: linked, AsFound: true},
|
||||
{ID: "notes.through", Type: "directory", Target: through, AsFound: true},
|
||||
{ID: "notes.file", Type: "directory", Target: file, AsFound: true},
|
||||
{ID: "notes.gone", Type: "directory", Target: gone, AsFound: true},
|
||||
}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
refused := map[string]string{
|
||||
"a relative path": "found",
|
||||
"a path with ..": dir + "/x/../found",
|
||||
"a doubled separator": dir + "//found",
|
||||
"a trailing separator": found + "/",
|
||||
"a directory not used as found": filepath.Join(dir, "other"),
|
||||
"a link at the path": linked,
|
||||
"a link in the path": through,
|
||||
"a file": file,
|
||||
"a directory that is gone": gone,
|
||||
}
|
||||
for name, path := range refused {
|
||||
said, err := handOverFound(statePath, path, "jo")
|
||||
if err == nil || said != "" {
|
||||
t.Fatalf("%s (%s) was handed over: %q", name, path, said)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "nothing was handed over") {
|
||||
t.Fatalf("%s's refusal does not say nothing was handed over: %v", name, err)
|
||||
}
|
||||
}
|
||||
if all, err := store.LoadHandedOver(statePath); err != nil || len(all) != 0 {
|
||||
t.Fatalf("a refusal recorded something: %+v %v", all, err)
|
||||
}
|
||||
// The found directory itself, stated exactly, is taken.
|
||||
if _, err := handOverFound(statePath, found, "jo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if all, _ := store.LoadHandedOver(statePath); len(all) != 1 || all[0].Path != found {
|
||||
t.Fatalf("recorded: %+v", all)
|
||||
}
|
||||
}
|
||||
|
||||
+18
-6
@@ -94,7 +94,12 @@ func versionAt(self, stamped string) string {
|
||||
return stamped
|
||||
}
|
||||
|
||||
const usage = `mesh-host — the node host
|
||||
const usage = `nox-mesh-host — the node-engine
|
||||
|
||||
A daemon: its launcher runs it, and it applies what the mesh declares for this machine. The
|
||||
operator's command is nox (mesh-cli), on any node, from their own account (novox/hq ADR 0272);
|
||||
the subcommands here are for installing a machine, joining it, and rescuing one no mesh can
|
||||
reach, run as root at the machine.
|
||||
|
||||
profile what this machine can be asked to do
|
||||
inventory what this machine is, and what it holds
|
||||
@@ -108,7 +113,9 @@ const usage = `mesh-host — the node host
|
||||
overlay take take over the tunnel found here (novox/hq ADR 0105): its key becomes this
|
||||
node's overlay key and the mesh is told, signed; --tunnel <iface> when several are up
|
||||
owned what this host has applied and still owns
|
||||
hand-over <dir> hand a directory used as found to the mesh: the next apply gives it its declared owner and mode
|
||||
hand-over <dir> hand a directory used as found to the mesh: the next apply gives it its declared
|
||||
owner and mode. The operator's line is nox node hand-over <node> <dir> on the
|
||||
control-node; this is the same act for a machine with no mesh to reach
|
||||
version
|
||||
|
||||
--json machine-readable output
|
||||
@@ -221,7 +228,7 @@ func parseArgs(args []string) (string, options, error) {
|
||||
// Anything left over was neither the command nor a flag. Refused rather than ignored: a
|
||||
// mistyped argument that changes nothing and reports success is worse than an error.
|
||||
if len(positionals) > 0 {
|
||||
return "", opts, fmt.Errorf("unexpected argument %q — try `mesh-host help`", positionals[0])
|
||||
return "", opts, fmt.Errorf("unexpected argument %q — try `nox-mesh-host help`", positionals[0])
|
||||
}
|
||||
return command, opts, nil
|
||||
}
|
||||
@@ -327,7 +334,7 @@ func run(ctx context.Context, command string, opts options) error {
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown command %q — try `mesh-host help`", command)
|
||||
return fmt.Errorf("unknown command %q — try `nox-mesh-host help`", command)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1077,6 +1084,8 @@ func runLink(ctx context.Context, opts options) error {
|
||||
// password, say — and restarting the host (design 28, task 5.2). Same file, same check.
|
||||
say := func(line string) { fmt.Println(line) }
|
||||
adoptDeliveredMembership(identity.Path(opts.state), &mine, say)
|
||||
// So every statement about a directory used as found names the operator's line for this node.
|
||||
foundNode.Store(mine.Node)
|
||||
|
||||
fmt.Printf("node %s, linking to %s\n", mine.Node, mine.Membership.Broker)
|
||||
|
||||
@@ -1181,8 +1190,11 @@ func runLink(ctx context.Context, opts options) error {
|
||||
},
|
||||
Unsaid: unsaidBeside(opts.state),
|
||||
Numbers: numbersBeside(opts.state),
|
||||
Say: say,
|
||||
Timeout: opts.timeout,
|
||||
// And the operator's hand-over of a directory used as found, carried by the controller from the
|
||||
// terminal (novox/hq issue 356): judged and recorded here, applied by the next apply.
|
||||
HandOver: handOverFrom(opts.state),
|
||||
Say: say,
|
||||
Timeout: opts.timeout,
|
||||
}
|
||||
worker := make(chan struct{})
|
||||
go func() {
|
||||
|
||||
@@ -70,10 +70,32 @@ func (p *proving) heard(r link.Report) {
|
||||
if len(retired) > 0 {
|
||||
p.say(fmt.Sprintf("host %s is proved; retired the host version(s) %s", p.version, strings.Join(retired, ", ")))
|
||||
}
|
||||
renewFallback(p.version, p.say)
|
||||
renewLauncher(p.say)
|
||||
})
|
||||
}
|
||||
|
||||
// renewFallback makes the engine placed by hand — the launcher's fallback, and the `nox-mesh-host` on this
|
||||
// machine's PATH — a copy of this proved build when it is not one (novox/hq issue 356). On the same
|
||||
// evidence known-good is written on, and said when it happens or cannot: a stale command that answers
|
||||
// for a build that is not running would otherwise stay until somebody typed it.
|
||||
func renewFallback(version string, say func(string)) {
|
||||
self, err := os.Executable()
|
||||
if err != nil {
|
||||
say("the engine placed by hand could not be renewed: this build does not know its own path: " + err.Error())
|
||||
return
|
||||
}
|
||||
fallback := upgrade.FallbackPath()
|
||||
renewed, err := upgrade.RenewFallback(self, fallback)
|
||||
switch {
|
||||
case err != nil:
|
||||
say(fmt.Sprintf("the engine placed by hand at %s is not this build, and could not be renewed: %v", fallback, err))
|
||||
case renewed:
|
||||
say(fmt.Sprintf("renewed the engine placed by hand at %s to this build, %s: the command on this machine is the "+
|
||||
"engine that runs it", fallback, version))
|
||||
}
|
||||
}
|
||||
|
||||
// The launcher, as the service manager runs it.
|
||||
const (
|
||||
launcherPath = upgrade.DefaultLibexec + "/launch"
|
||||
|
||||
Reference in New Issue
Block a user