Skip to content

Commit a802fdb

Browse files
committed
save progress
1 parent 653637e commit a802fdb

6 files changed

Lines changed: 168 additions & 61 deletions

File tree

pkg/cmd/deregister/deregister.go

Lines changed: 74 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -100,16 +100,43 @@ func runDeregister(ctx context.Context, t *terminal.Terminal, s DeregisterStore,
100100
return fmt.Errorf("sudo issue: %w", err)
101101
}
102102

103+
// Ensure user is logged in before any steps (login/email prompt happens here if needed).
104+
if _, err := s.GetCurrentUser(); err != nil {
105+
return breverrors.WrapAndTrace(err)
106+
}
107+
103108
reg, err := deps.registrationStore.Load()
104109
if err != nil {
105-
return breverrors.WrapAndTrace(err)
110+
return err
106111
}
107112

113+
orgName := reg.OrgName
114+
if orgName == "" {
115+
orgName = "(unknown)"
116+
}
117+
osUser, _ := user.Current()
118+
linuxUser := "(unknown)"
119+
if osUser != nil {
120+
linuxUser = osUser.Username
121+
}
122+
123+
t.Vprint("")
124+
t.Vprint(t.White("══════════════════════════════════════════════════"))
125+
t.Vprint(t.White(" Deregistering your device from Brev"))
126+
t.Vprint(t.White("══════════════════════════════════════════════════"))
127+
t.Vprint("")
128+
t.Vprint(t.Green(" Please confirm before continuing:"))
108129
t.Vprint("")
109-
t.Vprint(t.Green("Deregistering device"))
130+
t.Vprintf(" %s %s\n", t.Green(fmt.Sprintf("%-14s", "Device name:")), t.BoldBlue(reg.DisplayName))
131+
t.Vprintf(" %s %s\n", t.Green(fmt.Sprintf("%-14s", "Organization:")), t.BoldBlue(orgName))
132+
t.Vprintf(" %s %s\n", t.Green(fmt.Sprintf("%-14s", "Node ID:")), t.BoldBlue(reg.ExternalNodeID))
133+
t.Vprintf(" %s %s\n", t.Green(fmt.Sprintf("%-14s", "Linux user:")), t.BoldBlue(linuxUser))
110134
t.Vprint("")
111-
t.Vprintf(" Node ID: %s\n", reg.ExternalNodeID)
112-
t.Vprintf(" Name: %s\n", reg.DisplayName)
135+
t.Vprint(t.Yellow(" This will:"))
136+
t.Vprint(" 1. Remove this node from Brev")
137+
t.Vprint(" 2. Remove Brev SSH keys from this machine (if any)")
138+
t.Vprint(" 3. Uninstall the Brev tunnel")
139+
t.Vprint(" 4. Delete local registration data")
113140
t.Vprint("")
114141

115142
confirm := deps.prompter.Select(
@@ -121,28 +148,40 @@ func runDeregister(ctx context.Context, t *terminal.Terminal, s DeregisterStore,
121148
return nil
122149
}
123150

151+
const clearLine = "\033[2K\n"
152+
124153
t.Vprint("")
125-
t.Vprint(t.Yellow("Removing node from Brev..."))
154+
sp := t.NewSpinner()
155+
sp.Suffix = " [Step 1/4] Removing node from Brev..."
156+
sp.Start()
126157
client := deps.nodeClients.NewNodeClient(s, config.GlobalConfig.GetBrevPublicAPIURL())
127-
if _, err := client.RemoveNode(ctx, connect.NewRequest(&nodev1.RemoveNodeRequest{
158+
_, err = client.RemoveNode(ctx, connect.NewRequest(&nodev1.RemoveNodeRequest{
128159
ExternalNodeId: reg.ExternalNodeID,
129-
})); err != nil {
160+
}))
161+
sp.FinalMSG = clearLine
162+
sp.Stop()
163+
if err != nil {
130164
return fmt.Errorf("failed to deregister node: %w", err)
131165
}
132-
t.Vprint(t.Green(" Node removed from Brev."))
166+
t.Vprintf("%s Node removed from Brev.\n", t.Green(" ✓"))
133167
t.Vprint("")
134168

135-
// Remove Brev SSH keys from authorized_keys.
136-
osUser, err := user.Current()
137-
if err != nil {
138-
t.Vprintf(" Warning: could not determine current user for SSH key cleanup: %v\n", err)
169+
sp = t.NewSpinner()
170+
sp.Suffix = " [Step 2/4] Removing Brev SSH keys..."
171+
sp.Start()
172+
if osUser == nil {
173+
sp.FinalMSG = clearLine
174+
sp.Stop()
175+
t.Vprintf(" %s\n", t.Yellow("Skipped: could not determine current user"))
139176
} else {
140177
removed, kerr := deps.sshKeys.RemoveBrevKeys(osUser)
178+
sp.FinalMSG = clearLine
179+
sp.Stop()
141180
switch {
142181
case kerr != nil:
143-
t.Vprintf(" Warning: failed to remove Brev SSH keys: %v\n", kerr)
182+
t.Vprintf(" %s\n", t.Yellow(fmt.Sprintf("Warning: failed to remove Brev SSH keys: %v", kerr)))
144183
case len(removed) > 0:
145-
t.Vprint(t.Green(" Brev SSH keys removed from authorized_keys:"))
184+
t.Vprintf("%s Brev SSH keys removed from authorized_keys:\n", t.Green(" ✓"))
146185
for _, key := range removed {
147186
t.Vprintf(" - %s\n", key)
148187
}
@@ -152,21 +191,33 @@ func runDeregister(ctx context.Context, t *terminal.Terminal, s DeregisterStore,
152191
}
153192
t.Vprint("")
154193

155-
t.Vprint("Removing Brev tunnel...")
156-
if err := deps.netbird.Uninstall(); err != nil {
157-
t.Vprintf(" Warning: failed to remove Brev tunnel: %v\n", err)
194+
sp = t.NewSpinner()
195+
sp.Suffix = " [Step 3/4] Removing Brev tunnel..."
196+
sp.Start()
197+
err = deps.netbird.Uninstall()
198+
sp.FinalMSG = clearLine
199+
sp.Stop()
200+
if err != nil {
201+
t.Vprintf(" %s\n", t.Yellow(fmt.Sprintf("Warning: failed to remove Brev tunnel: %v", err)))
158202
} else {
159-
t.Vprint(t.Green(" Brev tunnel removed."))
203+
t.Vprintf("%s Brev tunnel removed.\n", t.Green(" ✓"))
160204
}
161205
t.Vprint("")
162206

163-
t.Vprint("Removing registration data...")
164-
if err := deps.registrationStore.Delete(); err != nil {
165-
t.Vprintf(" Warning: failed to remove local registration file: %v\n", err)
207+
sp = t.NewSpinner()
208+
sp.Suffix = " [Step 4/4] Removing registration data..."
209+
sp.Start()
210+
err = deps.registrationStore.Delete()
211+
sp.FinalMSG = clearLine
212+
sp.Stop()
213+
if err != nil {
214+
t.Vprintf(" %s\n", t.Yellow(fmt.Sprintf("Warning: failed to remove local registration file: %v", err)))
166215
t.Vprint(" You can manually remove it with: rm /etc/brev/device_registration.json")
216+
} else {
217+
t.Vprintf("%s Registration data removed.\n", t.Green(" ✓"))
167218
}
168-
169-
t.Vprint(t.Green("Deregistration complete."))
219+
t.Vprint("")
220+
t.Vprintf("%s Deregistration complete.\n", t.Green(""))
170221
t.Vprint("")
171222

172223
return nil

pkg/cmd/enablessh/enablessh.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func enableSSH(
9898
t.Vprintf(" Linux user: %s\n", u.Username)
9999
t.Vprint("")
100100

101+
t.Vprint("")
101102
port, err := register.PromptSSHPort(t)
102103
if err != nil {
103104
return fmt.Errorf("SSH port: %w", err)

pkg/cmd/register/device_registration_store.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type DeviceRegistration struct {
2626
ExternalNodeID string `json:"external_node_id"`
2727
DisplayName string `json:"display_name"`
2828
OrgID string `json:"org_id"`
29+
OrgName string `json:"org_name"`
2930
DeviceID string `json:"device_id"`
3031
RegisteredAt string `json:"registered_at"`
3132
HardwareProfile HardwareProfile `json:"hardware_profile"`
@@ -79,7 +80,7 @@ func (s *FileRegistrationStore) Load() (*DeviceRegistration, error) {
7980
if err != nil {
8081
return nil, breverrors.WrapAndTrace(err)
8182
}
82-
return nil, breverrors.WrapAndTrace(breverrors.New("device registration not found, run 'brev register' first"))
83+
return nil, breverrors.New("device registration not found, run 'brev register' first")
8384
}
8485
var reg DeviceRegistration
8586
if err := files.ReadJSON(files.AppFs, path, &reg); err != nil {

pkg/cmd/register/register.go

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
nodev1 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/devplaneapi/v1"
1212
"connectrpc.com/connect"
13+
"github.com/briandowns/spinner"
1314
"github.com/google/uuid"
1415

1516
"github.com/brevdev/brev-cli/pkg/config"
@@ -141,28 +142,51 @@ func runRegister(ctx context.Context, t *terminal.Terminal, s RegisterStore, nam
141142
// runRegisterSteps performs netbird install, hardware profile, AddNode, save registration, and runSetup.
142143
// It does not prompt or enable SSH. Used by both flag-driven and prompt-driven flows.
143144
func runRegisterSteps(ctx context.Context, t *terminal.Terminal, s RegisterStore, name string, org *entity.Organization, deps registerDeps) (*DeviceRegistration, error) {
145+
const clearLine = "\033[2K\n"
144146
t.Vprint("")
147+
148+
// Stop spinner (and clear its line) before any of our prints so stdout and stderr don't interleave.
149+
var sp *spinner.Spinner
150+
stopSpinner := func() {
151+
if sp != nil {
152+
sp.FinalMSG = clearLine
153+
sp.Stop()
154+
sp = nil
155+
}
156+
}
157+
defer stopSpinner()
158+
145159
t.Vprint(t.Yellow("[Step 1/3] Setting up Brev tunnel..."))
146-
if err := deps.netbird.Install(); err != nil {
160+
sp = t.NewSpinner()
161+
sp.Suffix = " Registering device..."
162+
sp.Start()
163+
err := deps.netbird.Install()
164+
stopSpinner()
165+
if err != nil {
147166
return nil, fmt.Errorf("brev tunnel setup failed: %w", err)
148167
}
149-
t.Vprint(t.Green(" Brev tunnel ready."))
168+
t.Vprintf("%s Brev tunnel ready.\n", t.Green(" ✓"))
150169

151170
t.Vprint("")
152171
t.Vprint(t.Yellow("[Step 2/3] Collecting hardware profile..."))
153-
t.Vprint("")
154-
172+
sp = t.NewSpinner()
173+
sp.Suffix = " Registering device..."
174+
sp.Start()
155175
hwProfile, err := deps.hardwareProfiler.Profile()
176+
stopSpinner()
156177
if err != nil {
157178
return nil, fmt.Errorf("failed to collect hardware profile: %w", err)
158179
}
159-
180+
t.Vprintf("%s Hardware profile collected.\n", t.Green(" ✓"))
181+
t.Vprint("")
160182
t.Vprint(" Hardware profile:")
161183
t.Vprint(FormatHardwareProfile(hwProfile))
162184

163185
t.Vprint("")
164186
t.Vprint(t.Yellow("[Step 3/3] Registering with Brev..."))
165-
187+
sp = t.NewSpinner()
188+
sp.Suffix = " Registering device..."
189+
sp.Start()
166190
deviceID := uuid.New().String()
167191
client := deps.nodeClients.NewNodeClient(s, config.GlobalConfig.GetBrevPublicAPIURL())
168192
addResp, err := client.AddNode(ctx, connect.NewRequest(&nodev1.AddNodeRequest{
@@ -172,6 +196,7 @@ func runRegisterSteps(ctx context.Context, t *terminal.Terminal, s RegisterStore
172196
NodeSpec: toProtoNodeSpec(hwProfile),
173197
}))
174198
if err != nil {
199+
stopSpinner()
175200
return nil, fmt.Errorf("failed to register node: %w", err)
176201
}
177202

@@ -180,21 +205,25 @@ func runRegisterSteps(ctx context.Context, t *terminal.Terminal, s RegisterStore
180205
ExternalNodeID: node.GetExternalNodeId(),
181206
DisplayName: name,
182207
OrgID: org.ID,
208+
OrgName: org.Name,
183209
DeviceID: deviceID,
184210
RegisteredAt: time.Now().UTC().Format(time.RFC3339),
185211
HardwareProfile: *hwProfile,
186212
}
187213
if err := deps.registrationStore.Save(reg); err != nil {
214+
stopSpinner()
188215
return nil, fmt.Errorf("node registered but failed to save locally: %w", err)
189216
}
190217

191-
t.Vprint(t.Green(" Registration complete."))
192218
runSetup(node, t, deps)
219+
stopSpinner()
220+
t.Vprintf("%s Node registered.\n", t.Green(" ✓"))
221+
t.Vprintf("%s Registration complete.\n", t.Green(" ✓"))
193222
return reg, nil
194223
}
195224

196225
// resolveOrgPromptDriven resolves organization for prompt-driven flow: by name if --org given, else always list and select with arrow keys.
197-
func resolveOrgPromptDriven(s RegisterStore, orgName string, deps registerDeps) (*entity.Organization, error) {
226+
func resolveOrgPromptDriven(t *terminal.Terminal, s RegisterStore, orgName string, deps registerDeps) (*entity.Organization, error) {
198227
if orgName != "" {
199228
orgs, err := s.GetOrganizationsByName(orgName)
200229
if err != nil {
@@ -217,6 +246,7 @@ func resolveOrgPromptDriven(s RegisterStore, orgName string, deps registerDeps)
217246
return nil, fmt.Errorf("no organization found; please create or join an organization first")
218247
}
219248

249+
t.Vprint("")
220250
names := make([]string, len(list))
221251
for i := range list {
222252
names[i] = list[i].Name
@@ -239,6 +269,11 @@ func runRegisterPromptDriven(ctx context.Context, t *terminal.Terminal, s Regist
239269
return fmt.Errorf("sudo issue: %w", err)
240270
}
241271

272+
// Ensure user is logged in before any other prompts (email/login happens here if needed).
273+
if _, err := s.GetCurrentUser(); err != nil {
274+
return breverrors.WrapAndTrace(err)
275+
}
276+
242277
alreadyRegistered, err := deps.registrationStore.Exists()
243278
if err != nil {
244279
return breverrors.WrapAndTrace(err)
@@ -248,6 +283,7 @@ func runRegisterPromptDriven(ctx context.Context, t *terminal.Terminal, s Regist
248283
}
249284

250285
if name == "" {
286+
t.Vprint("")
251287
name = terminal.PromptGetInput(terminal.PromptContent{
252288
Label: "Device name",
253289
ErrorMsg: "name is required",
@@ -259,7 +295,8 @@ func runRegisterPromptDriven(ctx context.Context, t *terminal.Terminal, s Regist
259295
return breverrors.WrapAndTrace(err)
260296
}
261297

262-
org, err := resolveOrgPromptDriven(s, orgName, deps)
298+
t.Vprint("")
299+
org, err := resolveOrgPromptDriven(t, s, orgName, deps)
263300
if err != nil {
264301
return err
265302
}
@@ -274,16 +311,20 @@ func runRegisterPromptDriven(ctx context.Context, t *terminal.Terminal, s Regist
274311
}
275312

276313
t.Vprint("")
277-
t.Vprint(t.Green("Registering your device with Brev"))
314+
t.Vprint(t.White("══════════════════════════════════════════════════"))
315+
t.Vprint(t.White(" Registering your device with Brev"))
316+
t.Vprint(t.White("══════════════════════════════════════════════════"))
317+
t.Vprint("")
318+
t.Vprint(t.Green(" Please confirm before continuing:"))
278319
t.Vprint("")
279-
t.Vprintf(" Name: %s\n", t.Yellow(name))
280-
t.Vprintf(" Organization: %s\n", org.Name)
281-
t.Vprintf(" Registering for Linux user: %s\n", osUser.Username)
320+
t.Vprintf(" %s %s\n", t.Green(fmt.Sprintf("%-14s", "Device name:")), t.BoldBlue(name))
321+
t.Vprintf(" %s %s\n", t.Green(fmt.Sprintf("%-14s", "Organization:")), t.BoldBlue(org.Name))
322+
t.Vprintf(" %s %s\n", t.Green(fmt.Sprintf("%-14s", "Linux user:")), t.BoldBlue(osUser.Username))
282323
t.Vprint("")
283-
t.Vprint("This will perform the following steps:")
284-
t.Vprint(" 1. Set up Brev tunnel")
285-
t.Vprint(" 2. Collect hardware profile")
286-
t.Vprint(" 3. Register this machine with Brev")
324+
t.Vprint(t.Yellow(" This will:"))
325+
t.Vprint(" 1. Set up Brev tunnel")
326+
t.Vprint(" 2. Collect hardware profile")
327+
t.Vprint(" 3. Register this machine with Brev")
287328
t.Vprint("")
288329

289330
if !deps.prompter.ConfirmYesNo("Proceed with registration?") {
@@ -465,22 +506,28 @@ func runSetup(node *nodev1.ExternalNode, t *terminal.Terminal, deps registerDeps
465506
// grantSSHAccessWithPort enables SSH; if port is 0, prompts for port (prompt-driven). Otherwise uses the given port (flag-driven).
466507
func grantSSHAccessWithPort(ctx context.Context, t *terminal.Terminal, deps registerDeps, tokenProvider externalnode.TokenProvider, reg *DeviceRegistration, brevUser *entity.User, osUser *user.User, port int32) error {
467508
t.Vprint("")
468-
t.Vprint(t.Green("Enabling SSH access on this device"))
509+
t.Vprint(t.White("══════════════════════════════════════════════════"))
510+
t.Vprint(t.White(" Enabling SSH access on this device"))
511+
t.Vprint(t.White("══════════════════════════════════════════════════"))
469512
t.Vprint("")
470-
t.Vprintf(" Node: %s (%s)\n", reg.DisplayName, reg.ExternalNodeID)
471-
t.Vprintf(" Brev user: %s\n", brevUser.ID)
472-
t.Vprintf(" Linux user: %s\n", osUser.Username)
513+
t.Vprint(t.Green(" Please confirm before continuing:"))
473514
t.Vprint("")
515+
t.Vprintf(" %s %s\n", t.Green(fmt.Sprintf("%-14s", "Device name:")), t.BoldBlue(reg.DisplayName))
516+
t.Vprintf(" %s %s\n", t.Green(fmt.Sprintf("%-14s", "Node ID:")), t.BoldBlue(reg.ExternalNodeID))
517+
t.Vprintf(" %s %s\n", t.Green(fmt.Sprintf("%-14s", "Brev user:")), t.BoldBlue(brevUser.ID))
518+
t.Vprintf(" %s %s\n", t.Green(fmt.Sprintf("%-14s", "Linux user:")), t.BoldBlue(osUser.Username))
474519

475520
var err error
476521
if port == 0 {
522+
t.Vprint("")
477523
port, err = PromptSSHPort(t)
478524
if err != nil {
479525
return fmt.Errorf("SSH port: %w", err)
480526
}
481527
} else {
482-
t.Vprintf(" SSH port: %d\n", port)
528+
t.Vprintf(" %s %s\n", t.Green(fmt.Sprintf("%-14s", "SSH port:")), t.BoldBlue(fmt.Sprintf("%d", port)))
483529
}
530+
t.Vprint("")
484531

485532
if err := OpenSSHPort(ctx, t, deps.nodeClients, tokenProvider, reg, port); err != nil {
486533
return fmt.Errorf("allocate SSH port failed: %w", err)
@@ -491,6 +538,7 @@ func grantSSHAccessWithPort(ctx context.Context, t *terminal.Terminal, deps regi
491538
return fmt.Errorf("grant SSH failed: %w", err)
492539
}
493540

541+
t.Vprint("")
494542
t.Vprint(t.Green(fmt.Sprintf("SSH access enabled. You can now SSH to this device via: brev shell %s", reg.DisplayName)))
495543
return nil
496544
}

0 commit comments

Comments
 (0)