Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HTTPS to Sidecar for deployment #8

Merged
merged 52 commits into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
51d13c5
add traefik config and update cli
eriktaubeneck Feb 15, 2024
ff09d45
cannot use env variables in traefik.yaml, use static config env varia…
eriktaubeneck Feb 15, 2024
512dd30
must call traefik with sudo to run on these ports
eriktaubeneck Feb 15, 2024
ce75e4c
use traefik cli args instead of env for key/cert
eriktaubeneck Feb 15, 2024
01eab14
move key/cert into dynamic_conf
eriktaubeneck Feb 15, 2024
da0e501
use different env formatting
eriktaubeneck Feb 15, 2024
9ad2140
move tls config into a file created in cli command
eriktaubeneck Feb 16, 2024
c23b5cd
fix bug in dynamic_config
eriktaubeneck Feb 16, 2024
f9fa873
fix bug in dynamic_config
eriktaubeneck Feb 16, 2024
45512b5
use different env format
eriktaubeneck Feb 16, 2024
49b785b
move dynamic config into cli
eriktaubeneck Feb 16, 2024
e087366
make sure to wrap single quotes around double quotes when needed
eriktaubeneck Feb 16, 2024
6459150
remove single/double quotes, add backticks
eriktaubeneck Feb 16, 2024
4cfac07
fix ports
eriktaubeneck Feb 16, 2024
6eb0be1
use adjacent subdomains, not nested
eriktaubeneck Feb 16, 2024
05416d5
add draft-mpc.vercel.app to CORS domains
eriktaubeneck Feb 16, 2024
1ac44f7
make test data directory before generating it
eriktaubeneck Feb 16, 2024
6aed89e
adjust ports, not inferred from network.toml
eriktaubeneck Feb 16, 2024
89458c4
use https not ws for checking status
eriktaubeneck Feb 17, 2024
bffedef
use http not https for checking status
eriktaubeneck Feb 17, 2024
48684ff
turn off verify for status check temporarily
eriktaubeneck Feb 17, 2024
d3e7324
use https for status check
eriktaubeneck Feb 17, 2024
a303c0c
turn off verification for terminate posts
eriktaubeneck Feb 17, 2024
2f7e64a
use https for terminate posts
eriktaubeneck Feb 17, 2024
7e8c5d8
fix traefik bug
eriktaubeneck Feb 17, 2024
ec40bcb
remove tls from helper traefik config
eriktaubeneck Feb 17, 2024
f40f955
readd tls from helper traefik config
eriktaubeneck Feb 17, 2024
6ead567
try a different approach to not using tls for helpers
eriktaubeneck Feb 17, 2024
9273e54
local traefik working. helpers still not working with domains
eriktaubeneck Mar 4, 2024
bc772fc
server updates, use localhost for ipa connections
eriktaubeneck Mar 4, 2024
0a87b6d
remove unneeded helper_domain from cli
eriktaubeneck Mar 5, 2024
5956af0
Update README.md
eriktaubeneck Mar 5, 2024
609a16e
use sidecar0 instead of sidecar-coordinator
eriktaubeneck Mar 5, 2024
656a7e4
removed signed call to /stop. needs to be handled differently
eriktaubeneck Mar 6, 2024
136d5be
add multi-threading to compile features for IPA
eriktaubeneck Mar 7, 2024
9660d9f
add a step to generate the MPC steps file
eriktaubeneck Mar 7, 2024
a093803
typo
eriktaubeneck Mar 8, 2024
cfecf75
fix script path
eriktaubeneck Mar 8, 2024
f727f47
add env option to command
eriktaubeneck Mar 8, 2024
d5873a8
add -m flag to collect_steps
eriktaubeneck Mar 8, 2024
f815f57
add cwd to subclasses of Command
eriktaubeneck Mar 8, 2024
66ccf83
fix pylint errors
eriktaubeneck Mar 12, 2024
0dc32d8
use mkcert CA with httpx
eriktaubeneck Mar 15, 2024
1153d1d
update github.tsx to warn if OCTOKIT_GITHUB_API_KEY isn't present
eriktaubeneck Mar 15, 2024
2dfb717
avoid race condition with getting a query that may be being created
eriktaubeneck Mar 15, 2024
3103593
add -f to git checkout command, as producing steps.txt causes an over…
eriktaubeneck Mar 15, 2024
37984f0
remove verify=False from httpx requests
eriktaubeneck Mar 15, 2024
896820a
update readme for first use of mkcert
eriktaubeneck Mar 15, 2024
6db365a
refresh IPA self signed local_dev keys
eriktaubeneck Mar 16, 2024
180abe2
fix bug with test directory not existing, wrap query run in exception…
eriktaubeneck Mar 16, 2024
158d810
fix pylint and grammer error
eriktaubeneck Mar 16, 2024
591d4b0
update TODO in readme
eriktaubeneck Mar 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ IGNORE-ME*

# local env files
.env*

# local certs
local_dev/config/cert.pem
local_dev/config/key.pem
107 changes: 105 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,40 @@ SUPABASE_AUTH_GITHUB_CLIENT_ID="<CLIENT_ID>"
SUPABASE_AUTH_GITHUB_SECRET="<CLIENT_SECRET>"
```

**Traefik**

install traefik

```
brew install traefik
```

update /etc/hosts with (requires sudo)

```
127.0.0.1 draft.test
127.0.0.1 sidecar0.draft.test
127.0.0.1 sidecar1.draft.test
127.0.0.1 sidecar2.draft.test
127.0.0.1 sidecar3.draft.test
```

make local certs

install mkcert with

```
brew install mkcert
```

make the cert with

```
mkcert -cert-file "local_dev/config/cert.pem" -key-file "local_dev/config/key.pem" "draft.test" "*.draft.test"
```

**Run local dev**

You're now ready to install, run, and develop on `draft`!

To start the local development environment:
Expand All @@ -113,7 +147,7 @@ draft start-local-dev

If needed, clone this repo:
```
git clone https://github.com/eriktaubeneck/draft.git
git clone https://github.com/private-attribution/draft.git
cd draft
```

Expand All @@ -125,5 +159,74 @@ pip install --editable .
```


## Credit
## Deployment

### Requirements

*Instructions for AWS Linux 2023*

1. **Python3.11**: Install with `sudo yum install python3.11`
2. **git**: Install with `sudo yum install git`
3. **draft** (this package):
1. Clone with `git clone https://github.com/private-attribution/draft.git`
2. Enter directory `cd draft`.
3. Create virtualenv: `python3.11 -m venv .venv`
4. Use virtualeenv: `source .venv/bin/activate`
5. Upgrade pip: `pip install --upgrade pip`
6. Install: `pip install --editable .`
4. **traefik**:
1. Download version 2.11: `wget https://github.com/traefik/traefik/releases/download/v2.11.0/traefik_v2.11.0_linux_amd64.tar.gz`
2. Validate checksum: `sha256sum traefik_v2.11.0_linux_amd64.tar.gz` should print `7f31f1cc566bd094f038579fc36e354fd545cf899523eb507c3cfcbbdb8b9552 traefik_v2.11.0_linux_amd64.tar.gz`
3. Extract the binary: `tar -zxvf traefik_v2.11.0_linux_amd64.tar.gz`
5. **tmux**: `sudo yum install tmux`


### Generating TLS certs with Let's Encrypt
eriktaubeneck marked this conversation as resolved.
Show resolved Hide resolved

You will need a domain name and TLS certificates for the sidecar to properly run over HTTPS. The following instructions assume your domain is `example.com`, please replace with the domain you'd like to use. You will need to create two sub-domains, `sidecar.example.com` and `helper.example.com`. (Note, you could also use a sub-domain as your base domain, e.g., `test.example.com` with two sub-domains of that: `sidecar.test.example.com` and `helper.test.example.com`.)

1. Set up DNS records for `sidecar.example.com` and `helper.example.com` pointing to a server you control.
2. Make sure you've installed the requirements above, and are using the virtual environment.
3. Install `certbot`: `pip install certbot`
4. `sudo .venv/bin/certbot certonly --standalone -m [email protected] -d "sidecar.example.com,helper.example.com"`
1. Note that you must point directly to `.venv/bin/certbot` as `sudo` does not operate in the virtualenv.
5. Accept the [Let's Encrypt terms](https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf).


### Make Configuration
Comment on lines +204 to +214
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instructions for generating TLS certificates with Let's Encrypt are well-detailed. However, there's a potential security concern with the use of sudo in the command for running certbot. It's recommended to use a system-wide installation of certbot rather than invoking it through the virtual environment with sudo. This approach reduces the risk associated with elevated privileges and ensures that certbot operates in a more controlled environment.

- sudo .venv/bin/certbot certonly --standalone -m [email protected] -d "sidecar.example.com,helper.example.com"
+ sudo certbot certonly --standalone -m [email protected] -d "sidecar.example.com,helper.example.com"

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
You will need a domain name and TLS certificates for the sidecar to properly run over HTTPS. The following instructions assume your domain is `example.com`, please replace with the domain you'd like to use. You will need to create two sub-domains, `sidecar.example.com` and `helper.example.com`. (Note, you could also use a sub-domain as your base domain, e.g., `test.example.com` with two sub-domains of that: `sidecar.test.example.com` and `helper.test.example.com`.)
1. Set up DNS records for `sidecar.example.com` and `helper.example.com` pointing to a server you control.
2. Make sure you've installed the requirements above, and are using the virtual environment.
3. Install `certbot`: `pip install certbot`
4. `sudo .venv/bin/certbot certonly --standalone -m [email protected] -d "sidecar.example.com,helper.example.com"`
1. Note that you must point directly to `.venv/bin/certbot` as `sudo` does not operate in the virtualenv.
5. Accept the [Let's Encrypt terms](https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf).
### Make Configuration
You will need a domain name and TLS certificates for the sidecar to properly run over HTTPS. The following instructions assume your domain is `example.com`, please replace with the domain you'd like to use. You will need to create two sub-domains, `sidecar.example.com` and `helper.example.com`. (Note, you could also use a sub-domain as your base domain, e.g., `test.example.com` with two sub-domains of that: `sidecar.test.example.com` and `helper.test.example.com`.)
1. Set up DNS records for `sidecar.example.com` and `helper.example.com` pointing to a server you control.
2. Make sure you've installed the requirements above, and are using the virtual environment.
3. Install `certbot`: `pip install certbot`
4. `sudo certbot certonly --standalone -m [email protected] -d "sidecar.example.com,helper.example.com"`
1. Note that you must point directly to `.venv/bin/certbot` as `sudo` does not operate in the virtualenv.
5. Accept the [Let's Encrypt terms](https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf).
### Make Configuration


For this stage, you'll need to know a few things about the other parties involved:
1. Their root domain
2. Their public keys
3. Everyone's *identity* (e.g., 0, 1, 2, 3)


One you know these:
1. Make a config directory `mkdir config`
2. Copy the default network config: `cp local_dev/config/network.toml config/.`
3. Update that file.
1. Replace `helper0.draft.test` and `sidecar0.draft.test` with the respective domains for party with identity=0.
2. Repeat for identity= 1, 2, and 3.
3. Replace respective certificates with their public keys.
4. Move your Let's Encrypt key and cert into place: `sudo ln -s /etc/letsencrypt/live/sidecar.example.com/fullchain.pem config/cert.pem` and `sudo ln -s /etc/letsencrypt/live/sidecar.example.com/privkey.pem key.pem`
5. Generate IPA specific keys:
1. TODO
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TODO under "Generate IPA specific keys" is left unaddressed in the README. It's important to either complete this section with the necessary instructions or remove the placeholder if it's not applicable. Leaving TODOs in documentation can lead to confusion for users trying to follow the setup instructions.

Would you like assistance in completing this section, or should it be removed?



### Run draft

You'll want this to continue to run, even if you disconnect from the host, so it's a good idea to start a tmux session:

```
tmux new -s draft-session
```

```
draft start-helper-sidecar --identity <identity> --root_domain example.com --config_path config
```




# Credit
[Beer tap icons created by wanicon - Flaticon]("https://www.flaticon.com/free-icons/beer-tap")
8 changes: 4 additions & 4 deletions local_dev/config/network.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ N0Gz2XisE0JNL5f0tEyrJf/PwSlnazeMxw==
-----END CERTIFICATE-----
"""
url = "localhost:7431"
sidecar_port = "17431"
sidecar_url = "sidecar1.draft.test"

[peers.hpke]
public_key = "fde0d0c958db9f49d3f1b49cb6830b867cc810bff9e7d0cbf17c777969f3c23e"
Expand All @@ -31,7 +31,7 @@ RwAwRAIgaX95X9bgeZHgbTCl73N2j61AnljyS8DXQ7mWb6fsQXECIFgvumh8TASD
-----END CERTIFICATE-----
"""
url = "localhost:7432"
sidecar_port = "17432"
sidecar_url = "sidecar2.draft.test"

[peers.hpke]
public_key = "4e8f1cd4114a8ee8adc58a33050782e2f8ded3336a9c65725f35998e765c4e2d"
Expand All @@ -50,7 +50,7 @@ B6Bgc2gw5JC/G6ahPglwIkjO2ew02/ax6g==
-----END CERTIFICATE-----
"""
url = "localhost:7433"
sidecar_port = "17433"
sidecar_url = "sidecar3.draft.test"

[peers.hpke]
public_key = "ebedcfa02354a1d17aed80b0ed55028d0616152d5f8971291e030231dc92063d"
Expand All @@ -61,7 +61,7 @@ version = "http2"

[coordinator]
url = "localhost:7430"
sidecar_port = "17430"
sidecar_url = "sidecar0.draft.test"
certificate = """
-----BEGIN CERTIFICATE-----
MIIBHDCBwqADAgECAghMfLQt7MF1IDAKBggqhkjOPQQDAjAUMRIwEAYDVQQDDAls
Expand Down
9 changes: 8 additions & 1 deletion server/app/auth/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ import { NextResponse } from "next/server";
import { createServerClient, type CookieOptions } from "@supabase/ssr";

export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url);
const { searchParams } = new URL(request.url);
const code = searchParams.get("code");
// if "next" is in param, use it as the redirect URL
const next = searchParams.get("next") ?? "/";

let origin =
process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env.
process?.env?.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel.
"https://draft.test/";
// Make sure to include `https://`
origin = origin.includes("https://") ? origin : `https://${origin}`;

if (code) {
const cookieStore = cookies();
const supabase = createServerClient(
Expand Down
6 changes: 3 additions & 3 deletions server/app/login/GitHubOAuthComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export default function GitHubOAuthComponent() {
let url =
process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env.
process?.env?.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel.
"http://localhost:3000/";
// Make sure to include `https://` when not localhost.
url = url.includes("http") ? url : `https://${url}`;
"https://draft.test/";
// Make sure to include `https://`
url = url.includes("https://") ? url : `https://${url}`;
// Make sure to include a trailing `/`.
url = url.charAt(url.length - 1) === "/" ? url : `${url}/`;

Expand Down
21 changes: 14 additions & 7 deletions server/app/query/servers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,19 @@ export class RemoteServer {

logsWebSocketURL(id: string): URL {
const webSocketURL = new URL(`/ws/logs/${id}`, this.baseURL);
webSocketURL.protocol = "ws";
webSocketURL.protocol = "wss";
return webSocketURL;
}

statusWebSocketURL(id: string): URL {
const webSocketURL = new URL(`/ws/status/${id}`, this.baseURL);
webSocketURL.protocol = "ws";
webSocketURL.protocol = "wss";
return webSocketURL;
}

statsWebSocketURL(id: string): URL {
const webSocketURL = new URL(`/ws/stats/${id}`, this.baseURL);
webSocketURL.protocol = "ws";
webSocketURL.protocol = "wss";
return webSocketURL;
}

Expand Down Expand Up @@ -271,19 +271,26 @@ export const IPARemoteServers: RemoteServersType = {
[RemoteServerNames.Coordinator]: new IPACoordinatorRemoteServer(
RemoteServerNames.Coordinator,
new URL(
process?.env?.NEXT_PUBLIC_COORDINATOR_URL ?? "http://localhost:17430",
process?.env?.NEXT_PUBLIC_COORDINATOR_URL ??
"https://sidecar0.draft.test",
),
),
[RemoteServerNames.Helper1]: new IPAHelperRemoteServer(
RemoteServerNames.Helper1,
new URL(process?.env?.NEXT_PUBLIC_HELPER1_URL ?? "http://localhost:17431"),
new URL(
process?.env?.NEXT_PUBLIC_HELPER1_URL ?? "https://sidecar1.draft.test",
),
),
[RemoteServerNames.Helper2]: new IPAHelperRemoteServer(
RemoteServerNames.Helper2,
new URL(process?.env?.NEXT_PUBLIC_HELPER2_URL ?? "http://localhost:17432"),
new URL(
process?.env?.NEXT_PUBLIC_HELPER2_URL ?? "https://sidecar2.draft.test",
),
),
[RemoteServerNames.Helper3]: new IPAHelperRemoteServer(
RemoteServerNames.Helper3,
new URL(process?.env?.NEXT_PUBLIC_HELPER3_URL ?? "http://localhost:17433"),
new URL(
process?.env?.NEXT_PUBLIC_HELPER3_URL ?? "https://sidecar3.draft.test",
),
),
};
10 changes: 5 additions & 5 deletions server/supabase/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ file_size_limit = "50MiB"
[auth]
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
# in emails.
site_url = "http://localhost:3000"
site_url = "https://draft.test"
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
additional_redirect_urls = ["https://localhost:3000/auth/callback"]
additional_redirect_urls = ["https://draft.test/auth/callback"]
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one
# week).
jwt_expiry = 3600
Expand All @@ -59,11 +59,11 @@ secret = "env(SUPABASE_AUTH_GITHUB_SECRET)"
redirect_uri = "http://localhost:54321/auth/v1/callback"

[analytics]
enabled = true
enabled = false
port = 54327
vector_port = 54328
# Setup BigQuery project to enable log viewer on local development stack.
# See: https://supabase.com/docs/guides/getting-started/local-development#enabling-local-logging
gcp_project_id = ""
gcp_project_number = ""
gcp_project_id = "null"
gcp_project_number = "null"
gcp_jwt_path = "supabase/gcloud.json"
39 changes: 10 additions & 29 deletions sidecar/app/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,10 @@ class Role(IntEnum):
@dataclass
class Helper:
role: Role
hostname: str
sidecar_port: int
helper_port: int
helper_url: ParseResult
sidecar_url: ParseResult
public_key: EllipticCurvePublicKey

@property
def sidecar_url(self) -> ParseResult:
return urlparse(f"http://{self.hostname}:{self.sidecar_port}")

@property
def helper_url(self) -> ParseResult:
return urlparse(f"http://{self.hostname}:{self.helper_port}")


def load_helpers_from_network_config(network_config_path: Path) -> dict[Role, Helper]:
with network_config_path.open("rb") as f:
Expand All @@ -39,40 +30,30 @@ def load_helpers_from_network_config(network_config_path: Path) -> dict[Role, He
helper_roles = list(r for r in Role if r != Role.COORDINATOR)
helpers = {}
for helper_config, role in zip(helper_configs, helper_roles):
url = urlparse(f"http://{helper_config['url']}")
hostname = str(url.hostname)
helper_port = int(url.port or 0)
sidecar_port = int(helper_config.get("sidecar_port", 0))
if not hostname or not helper_port or not sidecar_port:
raise Exception(f"{network_data=} missing data.")
helper_url = urlparse(f"http://{helper_config['url']}")
sidecar_url = urlparse(f"http://{helper_config['sidecar_url']}")
public_key_pem_data = helper_config.get("certificate")
cert = load_pem_x509_certificate(public_key_pem_data.encode("utf8"))
public_key = cert.public_key()
assert isinstance(public_key, EllipticCurvePublicKey)
helpers[role] = Helper(
role=role,
hostname=hostname,
helper_port=helper_port,
sidecar_port=sidecar_port,
helper_url=helper_url,
sidecar_url=sidecar_url,
public_key=public_key,
)

url = urlparse(f"http://{network_data['coordinator']['url']}")
hostname = str(url.hostname)
helper_port = int(url.port or 0)
sidecar_port = int(network_data["coordinator"].get("sidecar_port", 0))
if not hostname or not helper_port or not sidecar_port:
raise Exception(f"{network_data=} missing data.")
helper_url = urlparse(f"http://{network_data['coordinator']['url']}")
sidecar_url = urlparse(f"http://{network_data['coordinator']['sidecar_url']}")
public_key_pem_data = network_data["coordinator"].get("certificate")
cert = load_pem_x509_certificate(public_key_pem_data.encode("utf8"))
public_key = cert.public_key()
assert isinstance(public_key, EllipticCurvePublicKey)

helpers[Role.COORDINATOR] = Helper(
role=Role.COORDINATOR,
hostname=hostname,
helper_port=helper_port,
sidecar_port=sidecar_port,
helper_url=helper_url,
sidecar_url=sidecar_url,
public_key=public_key,
)
return helpers
4 changes: 1 addition & 3 deletions sidecar/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
app.include_router(start.router)
app.include_router(stop.router)

origins = [
"http://localhost:3000",
]
origins = ["https://draft.test", "https://draft-mpc.vercel.app"]

app.add_middleware(
CORSMiddleware,
Expand Down
4 changes: 4 additions & 0 deletions sidecar/app/query/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
class Command:
cmd: str
env: Optional[dict] = field(default_factory=lambda: {**os.environ}, repr=False)
cwd: Optional[Path] = field(default=None, repr=True)
process: Optional[subprocess.Popen] = field(init=False, default=None, repr=True)

@property
Expand Down Expand Up @@ -65,6 +66,7 @@ def build_process(self):
return subprocess.Popen(
shlex.split(self.cmd),
env=self.env,
cwd=self.cwd,
)

def start(self):
Expand Down Expand Up @@ -99,6 +101,7 @@ def build_process(self):
shlex.split(self.cmd),
stdout=self.output_file,
env=self.env,
cwd=self.cwd,
)

def start(self):
Expand All @@ -114,6 +117,7 @@ def build_process(self):
return subprocess.Popen(
shlex.split(self.cmd),
env=self.env,
cwd=self.cwd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
Expand Down
Loading
Loading