office --host with the host table and self-check; board --reset; startup commands written down
This commit is contained in:
21
bin/board
21
bin/board
@@ -16,6 +16,8 @@ renumbers the rest. Run it twice and nothing is duplicated.
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import time
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -28,7 +30,8 @@ BIN = os.path.join(os.path.dirname(HERE), "bin", "harness")
|
||||
|
||||
|
||||
def parse(argv):
|
||||
opts = {"folder": os.getcwd(), "replace": "--replace" in argv}
|
||||
opts = {"folder": os.getcwd(), "replace": "--replace" in argv,
|
||||
"reset": "--reset" in argv}
|
||||
for i, a in enumerate(argv):
|
||||
if a == "--folder" and i + 1 < len(argv):
|
||||
opts["folder"] = argv[i + 1]
|
||||
@@ -76,6 +79,22 @@ def main():
|
||||
h.say("board: %s (session %s, folder %s)" % (cfg["paperclip"]["project"], session, folder))
|
||||
h.say("watching: %s" % task)
|
||||
|
||||
if opts["reset"]:
|
||||
# Empty the session first, then build. --replace alone can run out of
|
||||
# workspace slots on a half-built session and leave pages missing.
|
||||
h.say("reset: closing every pane in %s" % session)
|
||||
for w in tuios.windows(session):
|
||||
subprocess.run(["tuios", "close-window", w["window_id"], "-s", session],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
for _ in range(30):
|
||||
if not tuios.windows(session):
|
||||
break
|
||||
time.sleep(0.5)
|
||||
for w in tuios.workspaces(session):
|
||||
subprocess.run(["tuios", "set-workspace-name", str(w["workspace"]), "",
|
||||
"-s", session], capture_output=True, text=True, timeout=20)
|
||||
h.say("reset: %s" % ("empty" if not tuios.windows(session) else "panes remain, building anyway"))
|
||||
|
||||
if opts["replace"]:
|
||||
for name, _, _ in page_list:
|
||||
if tuios.close_pane(name, session):
|
||||
|
||||
56
bin/office
56
bin/office
@@ -24,6 +24,7 @@ producing a second copy of the same project.
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
@@ -167,6 +168,41 @@ exit 0
|
||||
|
||||
|
||||
|
||||
HOSTS = {
|
||||
"nixos-desktop": "192.168.20.13", ".13": "192.168.20.13", "13": "192.168.20.13",
|
||||
"sam-4screen-desktop": "192.168.20.27", "sam-4screen-desktop-1": "192.168.20.27",
|
||||
".27": "192.168.20.27", "27": "192.168.20.27",
|
||||
"sam-thinkpad": "192.168.20.51", ".51": "192.168.20.51", "51": "192.168.20.51",
|
||||
}
|
||||
|
||||
|
||||
def local_addresses():
|
||||
"""The addresses and names that mean this machine."""
|
||||
names = {socket.gethostname(), socket.gethostname().split(".")[0]}
|
||||
try:
|
||||
out = subprocess.run(["ip", "-4", "-o", "addr", "show", "scope", "global"],
|
||||
capture_output=True, text=True, timeout=10).stdout
|
||||
for line in out.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) >= 4:
|
||||
names.add(parts[3].split("/")[0])
|
||||
except Exception:
|
||||
pass
|
||||
return names
|
||||
|
||||
|
||||
def resolve_host(name):
|
||||
"""A host name, or the address ssh can actually reach for it."""
|
||||
if not name:
|
||||
return ""
|
||||
return HOSTS.get(name, name)
|
||||
|
||||
|
||||
def is_this_machine(name):
|
||||
addr = resolve_host(name)
|
||||
return addr in local_addresses()
|
||||
|
||||
|
||||
def run_on_remote(host, folder, opts):
|
||||
"""Adopt a folder that lives on another machine, from where you are sitting.
|
||||
|
||||
@@ -175,7 +211,13 @@ def run_on_remote(host, folder, opts):
|
||||
over ssh on that machine, and only the last step — showing the session — is
|
||||
the client's own business.
|
||||
"""
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
addr = resolve_host(host)
|
||||
probe = subprocess.run(["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=8", addr, "true"],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
if probe.returncode != 0:
|
||||
raise h.Refused(3, "cannot log in to %s (%s). Set up a key first: ssh-copy-id %s"
|
||||
% (host, addr, addr))
|
||||
here = os.path.dirname(os.path.realpath(__file__))
|
||||
inner = [os.path.join(here, "office"), folder, "--no-switch"]
|
||||
for flag, value in (("--project", opts["project"]), ("--session", opts["session"]), ("--new", opts["new"])):
|
||||
if value:
|
||||
@@ -184,7 +226,7 @@ def run_on_remote(host, folder, opts):
|
||||
inner.append("--dry")
|
||||
cmd = " ".join(shlex.quote(a) for a in inner)
|
||||
h.say("on %s: %s" % (host, cmd))
|
||||
out = subprocess.run(["ssh", "-o", "StrictHostKeyChecking=accept-new", host, cmd],
|
||||
out = subprocess.run(["ssh", "-o", "StrictHostKeyChecking=accept-new", addr, cmd],
|
||||
capture_output=True, text=True, timeout=600)
|
||||
sys.stdout.write(out.stdout)
|
||||
if out.returncode != 0:
|
||||
@@ -201,6 +243,10 @@ def main():
|
||||
raise h.Refused(2, "no folder at %s" % folder)
|
||||
|
||||
host = opts["host"] or pj.this_machine()
|
||||
if opts["host"] and not is_this_machine(opts["host"]):
|
||||
return run_on_remote(opts["host"], folder, opts)
|
||||
if opts["host"]:
|
||||
h.say("host: %s (this machine)" % opts["host"])
|
||||
|
||||
# An adopted project already knows its session. Only a new one needs a
|
||||
# default, and a default that names a session nobody made is worse than an
|
||||
@@ -218,7 +264,11 @@ def main():
|
||||
# the verb for exactly this; asking the person to run `tuios new` first was
|
||||
# the step everyone forgot.
|
||||
if opts["remote"]:
|
||||
return run_on_remote(opts["remote"], folder, opts)
|
||||
if is_this_machine(opts["remote"]):
|
||||
h.say("host: %s (this machine)" % opts["remote"])
|
||||
opts["remote"] = ""
|
||||
else:
|
||||
return run_on_remote(opts["remote"], folder, opts)
|
||||
|
||||
if opts["dry"]:
|
||||
h.say("would create a Paperclip project %r for %s" % (opts["new"] or opts["project"], folder))
|
||||
|
||||
@@ -24,6 +24,7 @@ it is a pointer and should be deleted or replaced.
|
||||
| `docs/UI-LAYOUT.md` | this project | what each page must show, where each fact comes from, and the three candidate layouts |
|
||||
| `docs/PLAN-INTERFACE.md` | this project | the plan: Paperclip object → screen, what exists, what is missing, and in what order |
|
||||
| `docs/SESSIONS.md` | this project | the two kinds of session, where Pi's conversations are, and the plan for the junk TUIOS sessions |
|
||||
| `docs/STARTUP.md` | this project | starting a project: the three commands, the rule about machine and session, and the rebuild levels |
|
||||
| `docs/TEST-PLAN.md` | this project | the test plan: every step, every key, every expected result |
|
||||
| `docs/CLEAN-TESTS.md` | this project | the two clean tests: a new project from nothing, and adopting an existing folder |
|
||||
| `docs/RUNS-AND-MERGE.md` | this project | what a task, a run and a merge are, with the measured run counts |
|
||||
|
||||
68
docs/STARTUP.md
Normal file
68
docs/STARTUP.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Starting a project: the three commands and the rule
|
||||
|
||||
Replaces the previous project's four commands. There are three now, and one rule that removes
|
||||
most of the confusion.
|
||||
|
||||
## The rule
|
||||
|
||||
> **The folder and the session live on the same machine.**
|
||||
> If you are typing inside a session, on the machine that owns the folder, you type
|
||||
> `bin/office <folder>` and nothing else.
|
||||
|
||||
`--host` exists for exactly one case: the folder is on a different machine from the shell you are
|
||||
typing in. It is stated once; after that the project file remembers it.
|
||||
|
||||
## The three commands
|
||||
|
||||
| What you want | Command |
|
||||
|---|---|
|
||||
| adopt a folder | `bin/office <folder> --project "<Paperclip project name>"` |
|
||||
| adopt a folder Paperclip has never seen | `bin/office <folder> --new "<Name>"` |
|
||||
| adopt a folder that lives on another machine | `bin/office <folder> --project "<Name>" --host nixos-desktop` |
|
||||
| repair or rebuild the panes | `bin/board`, `bin/board --replace`, `bin/board --reset` |
|
||||
| close everything and start again | `bin/project-reset <folder>` |
|
||||
|
||||
Nothing else is needed. In particular you do **not** create the session by hand any more: the
|
||||
previous design needed `tuios new --host …` because `office` could not create a session. This one
|
||||
can, through the daemon's own verb, so the second command is gone.
|
||||
|
||||
## What `bin/office` does, in order
|
||||
|
||||
1. Reads the folder. If another machine is named, checks first whether that name is *this*
|
||||
machine, and runs locally if it is. (The previous version once tried to `ssh` to itself.)
|
||||
2. Resolves a host name to an address ssh can reach: `nixos-desktop`/`.13` → `192.168.20.13`,
|
||||
`sam-4screen-desktop`/`.27` → `192.168.20.27`, `sam-thinkpad`/`.51` → `192.168.20.51`.
|
||||
3. If it really is another machine: tests the login with `ssh -o BatchMode=yes <address> true`,
|
||||
then runs the same command there, and prints how to attach.
|
||||
4. On the machine that owns the folder: creates the Paperclip project if `--new` was given, or
|
||||
adopts the existing one; writes `.harness/harness.toml`, `check.sh`, `NOTES.md` — never
|
||||
overwriting anything that exists.
|
||||
5. Creates the session if it is missing, on that machine's daemon.
|
||||
6. Builds the eight panes, in order, each pane claimed by workspace **name**.
|
||||
|
||||
The last step it cannot do is show you the session. A program may not move your client: the
|
||||
daemon refuses `switch-session` from a pane. Switch with TUIOS's own session switcher, or
|
||||
`tuios attach --host <machine> <session>`.
|
||||
|
||||
## The three rebuild levels
|
||||
|
||||
| Command | What it does |
|
||||
|---|---|
|
||||
| `bin/board` | creates what is missing, closes nothing |
|
||||
| `bin/board --replace` | closes this project's panes, then builds all eight again |
|
||||
| `bin/board --reset` | closes **every** pane in the session, waits for none to remain, clears the workspace names, then builds |
|
||||
|
||||
`--reset` exists because `--replace` on a half-built session can run out of workspace slots and
|
||||
leave pages missing.
|
||||
|
||||
Inside a pane, `R` in `Repair>repair` rebuilds everything; `B` only adds what is missing.
|
||||
|
||||
## Traps this design keeps from the previous one
|
||||
|
||||
- A pane's command must carry the project, or a rebuild shows the wrong one.
|
||||
- Compare a host name against this machine's own names and addresses before ssh, or you log in to
|
||||
yourself.
|
||||
- Never store a workspace number; TUIOS deletes an emptied workspace and renumbers the rest.
|
||||
- A pane that never exits keeps a workspace alive. `Shell>shell` is that pane here.
|
||||
- Scripts resolve their own directory with `realpath`, so a symlinked wrapper still finds its
|
||||
siblings.
|
||||
Reference in New Issue
Block a user