Skip to content

Commit 1fa1593

Browse files
authored
Merge pull request #540 from duckietown:fix-error-loading-webview-and-security-issue-in-dts-code-editor-on-mac-devcontainer-DTSW-7328
Fix error loading lxs from devcontainer
2 parents 5904383 + c8dbb1d commit 1fa1593

File tree

8 files changed

+47
-899
lines changed

8 files changed

+47
-899
lines changed

devel/run/command.py

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import glob
22

3+
import argparse
34
import json
45
import os
56
import shutil
@@ -17,6 +18,7 @@
1718
CANONICAL_ARCH
1819
)
1920
from dtproject.types import LayerContainers, ContainerConfiguration
21+
from utils.cli_utils import ensure_command_is_installed
2022
from utils.docker_utils import (
2123
DEFAULT_MACHINE,
2224
DOCKER_INFO,
@@ -34,17 +36,6 @@
3436
REMOTE_USER = "duckie"
3537
REMOTE_GROUP = "duckie"
3638

37-
# Default ignore patterns for Mutagen sync
38-
MUTAGEN_DEFAULT_IGNORE = [
39-
".git/",
40-
".cache/",
41-
"**/__pycache__/",
42-
"build/",
43-
"install/",
44-
"log/",
45-
".vscode-server/",
46-
]
47-
4839

4940
class DTCommand(DTCommandAbs):
5041
help = "Runs the current project"
@@ -246,10 +237,10 @@ def command(shell, args: list, **kwargs):
246237
local_srcs, destination_srcs = proj.code_paths(root)
247238
# compile mountpoints
248239
for local_src, destination_src in zip(local_srcs, destination_srcs):
249-
if parsed.read_only:
250-
cc_mountpoints.append((local_src, destination_src, "ro"))
251-
else:
240+
if parsed.read_write:
252241
cc_mountpoints.append((local_src, destination_src, "rw"))
242+
else:
243+
cc_mountpoints.append((local_src, destination_src, "ro"))
253244

254245
# mount launchers
255246
if not parsed.no_mount_launchers:
@@ -264,21 +255,15 @@ def command(shell, args: list, **kwargs):
264255
if not os.path.isdir(local_launch):
265256
continue
266257

267-
# respect read-only setting for launcher mounts
268-
if parsed.read_only:
269-
cc_mountpoints.append((local_launch, destination_launch, "ro"))
270-
else:
271-
cc_mountpoints.append((local_launch, destination_launch, "rw"))
258+
cc_mountpoints.append((local_launch, destination_launch, "rw"))
272259
# make sure the launchers are executable (local only)
273260
if local:
274261
# noinspection PyBroadException
275262
try:
276263
_run_cmd(["chmod", "a+x", os.path.join(local_launch, "*")], shell=True)
277264
except Exception:
278-
dtslogger.warning(
279-
"An error occurred while making the launchers executable. "
280-
"Things might not work as expected."
281-
)
265+
dtslogger.warning("An error occurred while making the launchers executable. "
266+
"Things might not work as expected.")
282267

283268
# mount libraries explicitly to support symlinks (local only)
284269
if not parsed.no_mount_libraries and local:
@@ -294,10 +279,7 @@ def command(shell, args: list, **kwargs):
294279
os.makedirs(local_mountpoint, exist_ok=True)
295280
real_local_lib = os.path.realpath(local_lib)
296281
destination_lib: str = os.path.join(destination_src, "libraries", f"__{lib_name}")
297-
if parsed.read_only:
298-
cc_mountpoints.append((real_local_lib, destination_lib, "ro"))
299-
else:
300-
cc_mountpoints.append((real_local_lib, destination_lib, "rw"))
282+
cc_mountpoints.append((real_local_lib, destination_lib, "rw"))
301283

302284
# create image name
303285
cc_image = project.image(
@@ -430,27 +412,28 @@ def command(shell, args: list, **kwargs):
430412

431413
# sync
432414
if parsed.sync:
433-
# route to `devel.sync` for managing Mutagen sessions
415+
# TODO: this can just become a call to devel.sync
416+
# only allowed when mounting remotely
434417
if parsed.machine == DEFAULT_MACHINE:
435418
dtslogger.error("The option -s/--sync can only be used together with -H/--machine")
436419
exit(2)
437-
sync_args: List[str] = [
438-
"-H", parsed.machine,
439-
"-C", parsed.workdir,
440-
]
441-
# propagate mounts
442-
if parsed.mount is True:
443-
sync_args += ["-M"]
444-
elif isinstance(parsed.mount, str):
445-
sync_args += ["-M", parsed.mount]
446-
# propagate include-git for Mutagen
447-
if getattr(parsed, "sync_include_git", False):
448-
sync_args += ["--include-git"]
449-
# propagate optional flush direction
450-
if getattr(parsed, "sync_flush_direction", None):
451-
sync_args += ["--flush-direction", parsed.sync_flush_direction]
452-
# call devel.sync
453-
shell.include.devel.sync.command(shell, sync_args)
420+
# make sure rsync is installed
421+
ensure_command_is_installed("rsync", dependant="dts devel run")
422+
dtslogger.info(f"Syncing code with {parsed.machine.replace('.local', '')}...")
423+
remote_path = f"{parsed.sync_user}@{parsed.machine}:{parsed.sync_destination.rstrip('/')}/"
424+
# get projects' locations
425+
projects_to_sync = [parsed.workdir] if parsed.mount is True else []
426+
# sync secondary projects
427+
if isinstance(parsed.mount, str):
428+
projects_to_sync.extend(
429+
[os.path.abspath(os.path.join(os.getcwd(), p.strip())) for p in parsed.mount.split(",")]
430+
)
431+
# run rsync
432+
for project_path in projects_to_sync:
433+
cmd = (f"rsync --archive --delete --copy-links --chown={REMOTE_USER}:{REMOTE_GROUP} "
434+
f"\"{project_path}\" \"{remote_path}\"")
435+
_run_cmd(cmd, shell=True)
436+
dtslogger.info(f"Code synced!")
454437

455438
# run
456439
if parsed.configuration is None:

devel/run/configuration.py

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -179,19 +179,6 @@ def parser(cls, *args, **kwargs) -> Optional[argparse.ArgumentParser]:
179179
default=DEFAULT_REMOTE_SYNC_LOCATION,
180180
help="Location of the synced code on the remote server"
181181
)
182-
parser.add_argument(
183-
"--sync-flush-direction",
184-
default=None,
185-
choices=("alpha-to-beta", "beta-to-alpha"),
186-
help="After ensuring Mutagen sessions, perform a one-shot flush in this direction",
187-
)
188-
parser.add_argument(
189-
"--sync-include-git",
190-
dest="sync_include_git",
191-
default=False,
192-
action="store_true",
193-
help="Include the .git directory in remote sync",
194-
)
195182
parser.add_argument(
196183
"--net",
197184
"--network_mode",
@@ -213,20 +200,11 @@ def parser(cls, *args, **kwargs) -> Optional[argparse.ArgumentParser]:
213200
default=None,
214201
help="Overrides 'version' (usually taken to be branch name)"
215202
)
216-
# Mount mode: read-only available, but default remains read-write
217203
parser.add_argument(
218-
"-RO",
219-
"--read-only",
220-
dest="read_only",
204+
"-RW",
205+
"--read_write",
221206
default=False,
222207
action="store_true",
223-
help="Mount the project in read-only mode",
224-
)
225-
parser.add_argument(
226-
"--read-write",
227-
"--rw",
228-
dest="read_only",
229-
action="store_false",
230208
help="Mount the project in read-write mode",
231209
)
232210
parser.add_argument("docker_args", nargs="*", default=[])

devel/sync/command.py

Lines changed: 12 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,15 @@
11
import argparse
22
import os
33
import subprocess
4-
from typing import List, Optional
54

65
from dt_shell import DTCommandAbs, dtslogger
76
from utils.cli_utils import ensure_command_is_installed
87
from utils.docker_utils import DEFAULT_MACHINE
98
from utils.misc_utils import sanitize_hostname
109
from utils.multi_command_utils import MultiCommand
11-
from utils.mutagen_sync import MutagenSync, MutagenError, ensure_min_version, sanitize_session_name
12-
from utils.ssh_setup import ensure_ssh_for_host
13-
from devel.run.command import REMOTE_USER as DEFAULT_REMOTE_USER
1410

15-
# Default host path on the robot to mirror code into (bind-mount this in containers)
16-
REMOTE_SYNC_CODE_LOCATION = "/code"
17-
18-
# Default ignore patterns for sync (by default .git is ignored; can be included via --include-git)
19-
DEFAULT_IGNORE: List[str] = [
20-
".git/",
21-
".cache/",
22-
"**/__pycache__/",
23-
"build/",
24-
"install/",
25-
"log/",
26-
".vscode-server/",
27-
]
11+
DEFAULT_REMOTE_USER = "duckie"
12+
REMOTE_RSYNC_CODE_LOCATION = "/tmp/code"
2813

2914

3015
class DTCommand(DTCommandAbs):
@@ -53,97 +38,25 @@ def command(shell, args: list, **kwargs):
5338
# ---
5439
# sync
5540
if parsed.machine == DEFAULT_MACHINE:
56-
# only allowed when targeting a remote machine
57-
dtslogger.error("This command requires -H/--machine to specify the remote host")
41+
# only allowed when mounting remotely
42+
dtslogger.error("The option -s/--sync can only be used together with -H/--machine")
5843
exit(2)
59-
# Ensure SSH key-based auth is configured for the target
60-
robot_name: Optional[str] = None
61-
# If the given machine is like NAME.local, derive NAME
62-
try:
63-
if parsed.machine.endswith('.local'):
64-
robot_name = parsed.machine.split('.', 1)[0]
65-
elif '.' not in parsed.machine:
66-
# likely MagicDNS short name
67-
robot_name = parsed.machine
68-
except Exception:
69-
pass
70-
try:
71-
ensure_ssh_for_host(parsed.machine, user=DEFAULT_REMOTE_USER, robot_name=robot_name)
72-
except Exception as e:
73-
# Non-fatal: we proceed; SSH may still prompt if needed
74-
dtslogger.warning(f"SSH setup encountered an issue: {e}")
75-
# make sure Mutagen is installed
76-
ensure_command_is_installed(
77-
"mutagen",
78-
dependant="dts devel run",
79-
custom_msg=(
80-
"Please install it with:\n"
81-
" curl -sS https://webi.sh/mutagen | sh\n"
82-
" source ~/.config/envman/PATH.env\n"
83-
"and try again."
84-
),
85-
)
86-
# pre-flight version check
87-
try:
88-
ensure_min_version("0.17.0")
89-
except MutagenError as e:
90-
# Older Mutagen versions work with plain-text parsing; continue with a warning
91-
dtslogger.warning(str(e))
92-
dtslogger.info(f"Ensuring Mutagen sync to {parsed.machine.replace('.local', '')}...")
44+
# make sure rsync is installed
45+
ensure_command_is_installed("rsync")
46+
dtslogger.info(f"Syncing code with {parsed.machine.replace('.local', '')}...")
47+
remote_path = f"{DEFAULT_REMOTE_USER}@{parsed.machine}:{REMOTE_RSYNC_CODE_LOCATION}/"
9348
# get projects' locations
9449
projects_to_sync = [parsed.workdir] if parsed.mount is True else []
9550
# sync secondary projects
9651
if isinstance(parsed.mount, str):
9752
projects_to_sync.extend(
9853
[os.path.abspath(os.path.join(os.getcwd(), p.strip())) for p in parsed.mount.split(",")]
9954
)
100-
# create or reuse sessions
101-
sessions = []
55+
# run rsync
10256
for project_path in projects_to_sync:
103-
project_path = os.path.abspath(project_path)
104-
project_name = os.path.basename(project_path.rstrip("/"))
105-
session_name = sanitize_session_name(f"dts-sync-{project_name}-{parsed.machine}")
106-
# ensure remote directory exists
107-
remote_host_dir = os.path.join(REMOTE_SYNC_CODE_LOCATION, project_name)
108-
_run_cmd([
109-
"ssh",
110-
f"{DEFAULT_REMOTE_USER}@{parsed.machine}",
111-
f"mkdir -p '{remote_host_dir}'"
112-
])
113-
# build Mutagen endpoints
114-
alpha = project_path
115-
beta = f"ssh://{DEFAULT_REMOTE_USER}@{parsed.machine}//{remote_host_dir.lstrip('/')}"
116-
try:
117-
sync = MutagenSync(name=session_name)
118-
# Determine ignore paths (optionally include .git)
119-
ignore_paths = list(DEFAULT_IGNORE)
120-
if getattr(parsed, "include_git", False):
121-
ignore_paths = [p for p in ignore_paths if p != ".git/"]
122-
session = sync.ensure_session(
123-
alpha=alpha,
124-
beta=beta,
125-
ignore_paths=ignore_paths,
126-
max_staging_file_size="64MiB",
127-
)
128-
dtslogger.info(f"Session ready: {session.name} ({session.identifier})")
129-
sessions.append((sync, session))
130-
except MutagenError as e:
131-
dtslogger.error(str(e))
132-
exit(2)
133-
# optional flush
134-
if parsed.flush_direction:
135-
for sync, _ in sessions:
136-
try:
137-
sync.flush(parsed.flush_direction)
138-
except MutagenError as e:
139-
dtslogger.warning(f"Flush failed for {sync.name}: {e}")
140-
dtslogger.info(f"One-shot flush requested: {parsed.flush_direction}")
141-
# optional monitor (monitor the first session)
142-
if parsed.monitor and sessions:
143-
dtslogger.info("Monitoring Mutagen session. Press Ctrl-C to stop...")
144-
sessions[0][0].monitor()
145-
else:
146-
dtslogger.info("Mutagen sync configured. Use 'mutagen sync monitor' to watch events.")
57+
cmd = f"rsync --archive {project_path} {remote_path}"
58+
_run_cmd(cmd, shell=True)
59+
dtslogger.info(f"Code synced!")
14760

14861
@staticmethod
14962
def complete(shell, word, line):

devel/sync/configuration.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,5 @@ def parser(cls, *args, **kwargs) -> Optional[argparse.ArgumentParser]:
3636
help="Whether to mount the current project into the container. "
3737
"Pass a comma-separated list of paths to mount multiple projects",
3838
)
39-
parser.add_argument(
40-
"--monitor",
41-
default=False,
42-
action="store_true",
43-
help="After ensuring sessions, monitor sync activity (Ctrl-C to stop)",
44-
)
45-
parser.add_argument(
46-
"--flush-direction",
47-
default=None,
48-
choices=("alpha-to-beta", "beta-to-alpha"),
49-
help="After ensuring sessions, perform a one-shot flush in this direction",
50-
)
51-
parser.add_argument(
52-
"--include-git",
53-
dest="include_git",
54-
default=False,
55-
action="store_true",
56-
help="Include the .git directory in the sync",
57-
)
5839
# ---
5940
return parser

setup/mkcert/command.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
MKCERT_VERSION = "1.4.4"
2121
LOCAL_DOMAIN = "localhost"
22+
LOCAL_IP = "127.0.0.1"
2223

2324

2425
class DTCommand(DTCommandAbs):
@@ -146,9 +147,9 @@ def command(shell: DTShell, args):
146147

147148
# - make domain certificate
148149
if not ssl_exists:
149-
dtslogger.info(f"Creating local certificate for the domain '{LOCAL_DOMAIN}'...")
150+
dtslogger.info(f"Creating local certificate for the domain '{LOCAL_DOMAIN}' and {LOCAL_IP} ...")
150151
cmd: List[str] = DTCommand._mkcert_command(
151-
"-cert-file", ssl_cert, "-key-file", ssl_key, LOCAL_DOMAIN
152+
"-cert-file", ssl_cert, "-key-file", ssl_key, LOCAL_DOMAIN, LOCAL_IP
152153
)
153154
dtslogger.debug(f"Running command:\n\t$ {cmd}\n\tenv: {cmd_env}\n")
154155
out = subprocess.check_output(cmd, env=env, stderr=STDOUT).decode("utf-8")
@@ -159,7 +160,7 @@ def command(shell: DTShell, args):
159160
assert exists(ssl_cert)
160161
assert exists(ssl_key)
161162
# ---
162-
dtslogger.info(f"A new certificate for the domain '{LOCAL_DOMAIN}' was created.")
163+
dtslogger.info(f"A new certificate for the domains '{LOCAL_DOMAIN}' and '{LOCAL_IP}' was created.")
163164
else:
164165
dtslogger.info(f"Existing domain certificate found in [{ssl_dir}]")
165166

utils/cli_utils.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def ask_confirmation(message, default="n", question="Do you confirm?", choices=N
7676
return r
7777

7878

79-
def ensure_command_is_installed(command, dependant: Optional[str] = None, custom_msg = None):
79+
def ensure_command_is_installed(command, dependant: Optional[str] = None):
8080
command_path: Optional[str] = which(command)
8181
if command_path is None:
8282
extra: str = ""
@@ -87,8 +87,6 @@ def ensure_command_is_installed(command, dependant: Optional[str] = None, custom
8787
The command '{command}' is required{extra}. Please, install it before continuing.
8888
8989
"""
90-
if msg:
91-
msg += "\n" + custom_msg + "\n"
9290
raise UserError(msg)
9391
else:
9492
dtslogger.debug(f"Command '{command}' found: {command_path}")

0 commit comments

Comments
 (0)