Skip to content

Commit 0d5d039

Browse files
committed
Merge branch 'dm/byon' into byon
2 parents 9246b0e + 4cb2941 commit 0d5d039

14 files changed

Lines changed: 1474 additions & 29 deletions

File tree

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@ build-darwin-amd:
180180
echo ${VERSION}
181181
GOOS=darwin GOARCH=amd64 go build -o brev -ldflags "-X github.com/brevdev/brev-cli/pkg/cmd/version.Version=${VERSION}"
182182

183+
.PHONY: build-linux
184+
build-linux:
185+
$(call print-target)
186+
echo ${VERSION}
187+
GOOS=linux GOARCH=amd64 go build -o brev_linux_amd64 -ldflags "-X github.com/brevdev/brev-cli/pkg/cmd/version.Version=${VERSION}"
188+
GOOS=linux GOARCH=arm64 go build -o brev_linux_arm64 -ldflags "-X github.com/brevdev/brev-cli/pkg/cmd/version.Version=${VERSION}"
183189

184190
.PHONY: setup-workspace-repo
185191
setup-workspace-repo: build-linux-amd

byon.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
BREV_AUTH_URL=https://api.stg.ngc.nvidia.com
2+
BREV_AUTH_ISSUER_URL=https://stg.login.nvidia.com
3+
BREV_API_URL=https://bd.dev2.brev.nvidia.com
4+
BREV_GRPC_URL=api.dev2.brev.nvidia.com:443

pkg/cmd/cmd.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ func NewBrevCommand() *cobra.Command { //nolint:funlen,gocognit,gocyclo // defin
223223
cobra.AddTemplateFunc("workspaceCommands", workspaceCommands)
224224
cobra.AddTemplateFunc("hasProviderDependentCommands", hasProviderDependentCommands)
225225
cobra.AddTemplateFunc("providerDependentCommands", providerDependentCommands)
226+
cobra.AddTemplateFunc("hasRegisterCommands", hasRegisterCommands)
227+
cobra.AddTemplateFunc("registerCommands", registerCommands)
228+
cobra.AddTemplateFunc("subTreeCommands", subTreeCommands)
229+
cobra.AddTemplateFunc("isSubTreeCommand", isSubTreeCommand)
226230
cobra.AddTemplateFunc("hasAccessCommands", hasAccessCommands)
227231
cobra.AddTemplateFunc("accessCommands", accessCommands)
228232
cobra.AddTemplateFunc("hasOrganizationCommands", hasOrganizationCommands)
@@ -287,7 +291,8 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor
287291
cmd.AddCommand(reset.NewCmdReset(t, loginCmdStore, noLoginCmdStore))
288292
cmd.AddCommand(profile.NewCmdProfile(t, loginCmdStore, noLoginCmdStore))
289293
cmd.AddCommand(refresh.NewCmdRefresh(t, loginCmdStore))
290-
cmd.AddCommand(register.NewCmdRegister(t))
294+
cmd.AddCommand(register.NewCmdRegister(t, loginCmdStore))
295+
cmd.AddCommand(register.NewCmdUnregister(t, loginCmdStore))
291296
cmd.AddCommand(runtasks.NewCmdRunTasks(t, noLoginCmdStore))
292297
cmd.AddCommand(proxy.NewCmdProxy(t, noLoginCmdStore))
293298
cmd.AddCommand(healthcheck.NewCmdHealthcheck(t, noLoginCmdStore))
@@ -296,7 +301,6 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor
296301
cmd.AddCommand(recreate.NewCmdRecreate(t, loginCmdStore))
297302
cmd.AddCommand(writeconnectionevent.NewCmdwriteConnectionEvent(t, loginCmdStore))
298303
cmd.AddCommand(updatemodel.NewCmdupdatemodel(t, loginCmdStore))
299-
// cmd.AddCommand(spark.NewCmdSpark(t, loginCmdStore, noLoginCmdStore))
300304
}
301305

302306
func hasWorkspaceCommands(cmd *cobra.Command) bool {
@@ -327,6 +331,10 @@ func hasProviderDependentCommands(cmd *cobra.Command) bool {
327331
return len(providerDependentCommands(cmd)) > 0
328332
}
329333

334+
func hasRegisterCommands(cmd *cobra.Command) bool {
335+
return len(registerCommands(cmd)) > 0
336+
}
337+
330338
func workspaceCommands(cmd *cobra.Command) []*cobra.Command {
331339
cmds := []*cobra.Command{}
332340
for _, sub := range cmd.Commands() {
@@ -397,6 +405,28 @@ func providerDependentCommands(cmd *cobra.Command) []*cobra.Command {
397405
return cmds
398406
}
399407

408+
func registerCommands(cmd *cobra.Command) []*cobra.Command {
409+
cmds := []*cobra.Command{}
410+
for _, sub := range cmd.Commands() {
411+
if isRegisterCommand(sub) {
412+
cmds = append(cmds, sub)
413+
}
414+
}
415+
return cmds
416+
}
417+
418+
// SubTreeCommands are commands that themselves have a tree of subcommands. Use of the 'sub-tree' annotation
419+
// will mark commands as this way, adding the 'Available Commands' section to the hep text.
420+
func subTreeCommands(cmd *cobra.Command) []*cobra.Command {
421+
cmds := []*cobra.Command{}
422+
for _, sub := range cmd.Commands() {
423+
if isSubTreeCommand(sub) {
424+
cmds = append(cmds, sub)
425+
}
426+
}
427+
return cmds
428+
}
429+
400430
func isWorkspaceCommand(cmd *cobra.Command) bool {
401431
_, ok := cmd.Annotations["workspace"]
402432
return ok
@@ -432,6 +462,16 @@ func isProviderDependentCommand(cmd *cobra.Command) bool {
432462
return ok
433463
}
434464

465+
func isRegisterCommand(cmd *cobra.Command) bool {
466+
_, ok := cmd.Annotations["register"]
467+
return ok
468+
}
469+
470+
func isSubTreeCommand(cmd *cobra.Command) bool {
471+
_, ok := cmd.Annotations["sub-tree"]
472+
return ok
473+
}
474+
435475
var usageTemplate = `Usage:{{if .Runnable}}
436476
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
437477
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
@@ -490,8 +530,18 @@ Debug Commands:
490530
{{rpad .Name .NamePadding }} {{.Short}}
491531
{{- end}}{{- end}}
492532
493-
{{- end}}{{if .HasAvailableLocalFlags}}
533+
{{- end}}
534+
535+
{{- if hasRegisterCommands . }}
494536
537+
Register Commands:
538+
{{- range registerCommands . }}
539+
{{rpad .Name .NamePadding }} {{.Short}}
540+
{{- end}}{{- end}}
541+
{{if isSubTreeCommand . }}
542+
{{$cmds := .Commands}}Available Commands:{{range $cmds}}
543+
{{rpad .Name .NamePadding }} {{.Short}}
544+
{{- end}}{{- end}}{{if .HasAvailableLocalFlags}}
495545
Flags:
496546
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
497547

pkg/cmd/register/agent.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package register
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/spf13/cobra"
8+
9+
"github.com/brevdev/brev-cli/pkg/terminal"
10+
)
11+
12+
const (
13+
agentShort = "Run the Brev agent daemon on the Spark node"
14+
agentLong = "Runs the Brev agent daemon in a continuous loop, reporting status and handling workloads."
15+
)
16+
17+
func NewCmdSparkAgent(t *terminal.Terminal) *cobra.Command {
18+
cmd := &cobra.Command{
19+
Use: "agent",
20+
Short: agentShort,
21+
Long: agentLong,
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
return runSparkAgent(t)
24+
},
25+
}
26+
return cmd
27+
}
28+
29+
func runSparkAgent(t *terminal.Terminal) error {
30+
t.Vprint(t.Green("Starting Brev agent daemon...\n"))
31+
32+
ticker := time.NewTicker(1 * time.Minute)
33+
defer ticker.Stop()
34+
35+
// Print immediately on startup
36+
t.Vprint(fmt.Sprintf("[%s] Brev agent running\n", time.Now().Format(time.RFC3339)))
37+
38+
// TODO this should call the logic in pkg/brevdaemon/agent.go
39+
for {
40+
select {
41+
case <-ticker.C:
42+
t.Vprint(fmt.Sprintf("[%s] Brev agent heartbeat\n", time.Now().Format(time.RFC3339)))
43+
}
44+
}
45+
}

pkg/cmd/register/install-binary.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env bash
2+
set -eo pipefail
3+
4+
# Installs the latest brev-cli binary as "brevd" from GitHub releases
5+
6+
# Detect OS and architecture
7+
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
8+
ARCH="$(uname -m)"
9+
case "${ARCH}" in
10+
x86_64) ARCH="amd64" ;;
11+
aarch64) ARCH="arm64" ;;
12+
esac
13+
14+
# Get the appropriate download URL for this platform
15+
DOWNLOAD_URL="$(curl -s https://api.github.com/repos/brevdev/brev-cli/releases/latest | grep "browser_download_url.*${OS}.*${ARCH}" | cut -d '"' -f 4)"
16+
17+
# Verify we found a suitable release
18+
if [ -z "${DOWNLOAD_URL}" ]; then
19+
echo "Error: Could not find release for ${OS} ${ARCH}" >&2
20+
exit 1
21+
fi
22+
23+
# Create temporary directory and ensure cleanup
24+
TMP_DIR="$(mktemp -d)"
25+
trap 'rm -rf "${TMP_DIR}"' EXIT
26+
27+
# Download and extract the release
28+
curl -sL "${DOWNLOAD_URL}" -o "${TMP_DIR}/brev.tar.gz"
29+
tar -xzf "${TMP_DIR}/brev.tar.gz" -C "${TMP_DIR}"
30+
31+
# Install the binary as "brevd" to /usr/local/bin/brevd
32+
sudo mv "${TMP_DIR}/brev" /usr/local/bin/brevd
33+
sudo chmod +x /usr/local/bin/brevd
34+
35+
echo "Successfully installed brevd to /usr/local/bin/brevd"
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env bash
2+
set -eo pipefail
3+
4+
STATE_DIR="${STATE_DIR:-/home/brevcloud/.brev-agent}"
5+
6+
# Create systemd service file
7+
sudo tee /etc/systemd/system/brevd.service > /dev/null <<'EOF'
8+
[Unit]
9+
Description=Brev Daemon
10+
After=network-online.target
11+
Wants=network-online.target
12+
13+
[Service]
14+
Type=simple
15+
EnvironmentFile=-/etc/default/brevd
16+
ExecStart=/usr/local/bin/brevd spark agent
17+
Restart=on-failure
18+
RestartSec=10s
19+
User=brevcloud
20+
Group=brevcloud
21+
22+
[Install]
23+
WantedBy=multi-user.target
24+
EOF
25+
26+
# Create default environment file if it doesn't exist
27+
if [ ! -f /etc/default/brevd ]; then
28+
sudo tee /etc/default/brevd > /dev/null <<EOF
29+
# Env vars consumed by brevd. These will be populated during enrollment.
30+
BREV_AGENT_BREV_CLOUD_NODE_ID=""
31+
BREV_AGENT_BREV_CLOUD_URL=""
32+
BREV_AGENT_REGISTRATION_TOKEN=""
33+
BREV_AGENT_CLOUD_CRED_ID=""
34+
BREV_AGENT_STATE_DIR="${STATE_DIR}"
35+
EOF
36+
fi
37+
38+
sudo chmod 600 /etc/default/brevd
39+
40+
# Reload systemd
41+
sudo systemctl daemon-reload
42+
43+
echo "Successfully installed brevd systemd service"

pkg/cmd/register/install-user.sh

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Creates the brev service user with passwordless sudo and an SSH directory.
5+
# This script is idempotent and can be run multiple times safely.
6+
7+
BREV_USER="${BREV_USER:-brevcloud}"
8+
BREV_HOME="${BREV_HOME:-/home/${BREV_USER}}"
9+
SUDOERS_FILE="/etc/sudoers.d/${BREV_USER}"
10+
11+
echo "Configuring user ${BREV_USER}..."
12+
13+
# Create or update the BREV_USER
14+
if ! id -u "${BREV_USER}" >/dev/null 2>&1; then
15+
# If the BREV_USER does not exist, create it and set the home directory and shell to /bin/bash
16+
echo "Creating user '${BREV_USER}' with home directory '${BREV_HOME}' and shell '/bin/bash'..."
17+
sudo useradd -m -d "${BREV_HOME}" -s /bin/bash "${BREV_USER}"
18+
else
19+
# If the BREV_USER exists, ensure the shell is set to /bin/bash if it is not already
20+
echo "User '${BREV_USER}' already exists"
21+
current_shell=$(getent passwd "${BREV_USER}" | cut -d: -f7)
22+
if [ "${current_shell}" != "/bin/bash" ]; then
23+
echo "Updating user '${BREV_USER}'s shell to '/bin/bash'..."
24+
sudo usermod -s /bin/bash "${BREV_USER}"
25+
else
26+
echo "User '${BREV_USER}'s shell is already '/bin/bash'"
27+
fi
28+
fi
29+
30+
# Ensure the home directory exists with the right permissions.
31+
if [ ! -d "${BREV_HOME}" ]; then
32+
echo "Creating home directory ${BREV_HOME}..."
33+
sudo install -d -m 700 -o "${BREV_USER}" -g "${BREV_USER}" "${BREV_HOME}"
34+
else
35+
# Directory exists, ensure correct permissions and ownership
36+
echo "Home directory '${BREV_HOME}' already exists, ensuring correct permissions and ownership..."
37+
sudo chown "${BREV_USER}:${BREV_USER}" "${BREV_HOME}"
38+
sudo chmod 700 "${BREV_HOME}"
39+
fi
40+
41+
# Grant passwordless sudo to the brev user (idempotent).
42+
sudoers_content="${BREV_USER} ALL=(ALL) NOPASSWD:ALL"
43+
if sudo test -f "${SUDOERS_FILE}"; then
44+
existing_content=$(sudo cat "${SUDOERS_FILE}" 2>/dev/null || echo "")
45+
if [ "${existing_content}" = "${sudoers_content}" ]; then
46+
echo "Sudoers file already configured correctly"
47+
else
48+
echo "Updating sudoers file..."
49+
echo "${sudoers_content}" | sudo tee "${SUDOERS_FILE}" >/dev/null
50+
sudo chmod 0440 "${SUDOERS_FILE}"
51+
sudo visudo -c -f "${SUDOERS_FILE}"
52+
fi
53+
else
54+
echo "Creating sudoers file..."
55+
echo "${sudoers_content}" | sudo tee "${SUDOERS_FILE}" >/dev/null
56+
sudo chmod 0440 "${SUDOERS_FILE}"
57+
sudo visudo -c -f "${SUDOERS_FILE}"
58+
fi
59+
60+
# Prepare SSH directory and authorized_keys.
61+
ssh_dir="${BREV_HOME}/.ssh"
62+
authorized_keys="${ssh_dir}/authorized_keys"
63+
64+
if ! sudo test -d "${ssh_dir}"; then
65+
echo "Creating SSH directory..."
66+
sudo install -d -m 700 -o "${BREV_USER}" -g "${BREV_USER}" "${ssh_dir}"
67+
else
68+
# Directory exists, ensure correct permissions and ownership
69+
echo "SSH directory '${ssh_dir}' already exists, ensuring correct permissions and ownership..."
70+
sudo chown "${BREV_USER}:${BREV_USER}" "${ssh_dir}"
71+
sudo chmod 700 "${ssh_dir}"
72+
fi
73+
74+
if ! sudo test -f "${authorized_keys}"; then
75+
echo "Creating authorized_keys file..."
76+
sudo touch "${authorized_keys}"
77+
else
78+
echo "Authorized keys file '${authorized_keys}' already exists"
79+
fi
80+
81+
# Ensure correct permissions and ownership for authorized_keys
82+
echo "Ensuring correct permissions and ownership for authorized_keys..."
83+
sudo chown "${BREV_USER}:${BREV_USER}" "${authorized_keys}"
84+
sudo chmod 600 "${authorized_keys}"
85+
86+
# Final ownership consistency check (avoid unnecessary recursive chown).
87+
# Only fix ownership if something is wrong to avoid touching all files unnecessarily.
88+
if [ "$(stat -c '%U' "${BREV_HOME}" 2>/dev/null || stat -f '%Su' "${BREV_HOME}" 2>/dev/null)" != "${BREV_USER}" ]; then
89+
echo "Fixing home directory ownership..."
90+
sudo chown -R "${BREV_USER}:${BREV_USER}" "${BREV_HOME}"
91+
fi
92+
93+
echo "User ${BREV_USER} is ready at ${BREV_HOME}"

0 commit comments

Comments
 (0)