We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Remove external 'zip' dependency from DistributionBuilder (zip_password=True)
Hi there, first off I want to acknowledge your efforts towards making an incredible tool, thank you!

The issue: I faced the exception *FileNotFoundError: [Errno 2] No such file or directory: 'zip'* when trying to specify `zip_password: true` for the DistributionBuilder args while running `provision -p project.yml`, and tracked the cause to `zip` not being installed on the local machine, which was a problem since installing additional software is not allowed on that machine.

## Replicate
```bash
# Launch a docker container with image python:3.8 running interactive bash
docker run -it --rm python:3.8 bash

# === Within docker container ===
pip install nvflare
provision # create example project.yml

# Set zip_password to true
sed -i 's/zip_password: false/zip_password: true/' project.yml

provision -p project.yml
```
We will find the following output:
```
Project yaml file: /project.yml.
Exception raised during provision.  Incomplete prod_n folder removed.
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/nvflare/lighter/spec.py", line 166, in provision
    b.build(study, ctx)
  File "/usr/local/lib/python3.8/site-packages/nvflare/lighter/impl/workspace.py", line 109, in build
    subprocess.run(run_args)
  File "/usr/local/lib/python3.8/subprocess.py", line 493, in run
    with Popen(*popenargs, **kwargs) as process:
  File "/usr/local/lib/python3.8/subprocess.py", line 858, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/local/lib/python3.8/subprocess.py", line 1704, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'zip'
```
Whereas it'll work if we install zip
```bash
# === Within docker container ===
apt-get update && apt-get install -y zip
provision -p project.yml
```

## Fix
The build function of the DistributionBuilder uses `shutil` to make the non-password protected zip but relies on `subprocess.run(['zip', ...])` in order to create password-protected files, since shutil doesn't support passwords.

I'd propose adding a package requirement of something like `pyminizip` or `pyzipper` to handle password protection.

https://github.com/NVIDIA/NVFlare/blob/c8d51bfb534c02eff874a760c47086c87cbff59f/nvflare/lighter/impl/workspace.py#L100-L113

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nvflare/lighter/impl/workspace.py`
Content:
```
1 # Copyright (c) 2021-2022, NVIDIA CORPORATION.  All rights reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import os
16 import pathlib
17 import shutil
18 import subprocess
19 
20 from nvflare.lighter.spec import Builder, Project
21 from nvflare.lighter.utils import generate_password
22 
23 
24 class WorkspaceBuilder(Builder):
25     def __init__(self, template_file):
26         """Manages the folder structure for provisioned projects.
27 
28         Sets the template_file containing scripts and configs to put into startup folders, creates directories for the
29         participants, and moves the provisioned project to the final location at the end
30         ($WORKSPACE/$PROJECT_NAME/prod_XX). WorkspaceBuilder manages and sets the number in prod_XX by incrementing from
31         the last time provision was run for this project in this workspace, starting with 00 to a max of 99.
32 
33         Each time the provisioning tool runs, it requires a workspace folder in the local file system.  The workspace
34         will have the following folder structure:
35 
36         .. code-block:: text
37 
38           $WORKSPACE/    <--- this is assigned by -w option of provision command (default is workspace)
39             $PROJECT_NAME/  <--- this is the name value in the project.yml file
40               prod_00/   <--- a new prod_NN folder is created if provision does not have any errors.
41               prod_01/
42               ...
43               resources/ <--- this folder stores resources for other builders to load
44               state/     <--- this folder stores persistent information (such as certificates) so subsequent runs of the provision command can load the state back.
45               wip/  <--- this is only used during runtime, and will be removed when the provision command exits
46 
47         Args:
48             template_file: name of template file containing scripts and configs to put into startup folders
49         """
50         self.template_file = template_file
51 
52     def _make_dir(self, dirs):
53         for dir in dirs:
54             if not os.path.exists(dir):
55                 os.makedirs(dir)
56 
57     def initialize(self, ctx):
58         workspace_dir = ctx["workspace"]
59         prod_dirs = [_ for _ in os.listdir(workspace_dir) if _.startswith("prod_")]
60         last = -1
61         for dir in prod_dirs:
62             stage = int(dir.split("_")[-1])
63             if stage > last:
64                 last = stage
65         ctx["last_prod_stage"] = last
66         template_file_full_path = os.path.join(self.get_resources_dir(ctx), self.template_file)
67         file_path = pathlib.Path(__file__).parent.absolute()
68         shutil.copyfile(os.path.join(file_path, self.template_file), template_file_full_path)
69         ctx["template_file"] = self.template_file
70 
71     def build(self, project: Project, ctx: dict):
72         dirs = [self.get_kit_dir(p, ctx) for p in project.participants]
73         self._make_dir(dirs)
74 
75     def finalize(self, ctx: dict):
76         if ctx["last_prod_stage"] >= 99:
77             print(f"Please clean up {ctx['workspace']} by removing prod_N folders")
78             print("After clean-up, rerun the provision command.")
79         else:
80             current_prod_stage = str(ctx["last_prod_stage"] + 1).zfill(2)
81             current_prod_dir = os.path.join(ctx["workspace"], f"prod_{current_prod_stage}")
82             shutil.move(self.get_wip_dir(ctx), current_prod_dir)
83             ctx.pop("wip_dir", None)
84             print(f"Generated results can be found under {current_prod_dir}.  Builder's wip folder removed.")
85             ctx["current_prod_dir"] = current_prod_dir
86 
87 
88 class DistributionBuilder(Builder):
89     def __init__(self, zip_password=False):
90         """Build the zip files for each folder.
91 
92         Creates the zip files containing the archives for each startup kit. It will add password protection if the
93         argument (zip_password) is true.
94 
95         Args:
96             zip_password: if true, will create zipped packages with passwords
97         """
98         self.zip_password = zip_password
99 
100     def build(self, project: Project, ctx: dict):
101         wip_dir = self.get_wip_dir(ctx)
102         dirs = [name for name in os.listdir(wip_dir) if os.path.isdir(os.path.join(wip_dir, name))]
103         for dir in dirs:
104             dest_zip_file = os.path.join(wip_dir, f"{dir}")
105             if self.zip_password:
106                 pw = generate_password()
107                 run_args = ["zip", "-rq", "-P", pw, dest_zip_file + ".zip", ".", "-i", "startup/*"]
108                 os.chdir(dest_zip_file)
109                 subprocess.run(run_args)
110                 os.chdir(os.path.join(dest_zip_file, ".."))
111                 print(f"Password {pw} on {dir}.zip")
112             else:
113                 shutil.make_archive(dest_zip_file, "zip", root_dir=os.path.join(wip_dir, dir), base_dir="startup")
114
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/nvflare/lighter/impl/workspace.py b/nvflare/lighter/impl/workspace.py
--- a/nvflare/lighter/impl/workspace.py
+++ b/nvflare/lighter/impl/workspace.py
@@ -98,6 +98,15 @@
         self.zip_password = zip_password
 
     def build(self, project: Project, ctx: dict):
+        """Create a zip for each individual folder.
+        Note that if zip_password is True, the zip command will be used to encrypt zip files.  Users have to to
+        install this zip utility before provisioning.  In Ubuntu system, use this command to install zip utility:
+        sudo apt-get install zip
+
+        Args:
+            project (Project): project instance
+            ctx (dict): the provision context
+        """
         wip_dir = self.get_wip_dir(ctx)
         dirs = [name for name in os.listdir(wip_dir) if os.path.isdir(os.path.join(wip_dir, name))]
         for dir in dirs:
@@ -106,8 +115,12 @@
                 pw = generate_password()
                 run_args = ["zip", "-rq", "-P", pw, dest_zip_file + ".zip", ".", "-i", "startup/*"]
                 os.chdir(dest_zip_file)
-                subprocess.run(run_args)
-                os.chdir(os.path.join(dest_zip_file, ".."))
-                print(f"Password {pw} on {dir}.zip")
+                try:
+                    subprocess.run(run_args)
+                    print(f"Password {pw} on {dir}.zip")
+                except FileNotFoundError as e:
+                    raise RuntimeError("Unable to zip folders with password.  Maybe the zip utility is not installed.")
+                finally:
+                    os.chdir(os.path.join(dest_zip_file, ".."))
             else:
                 shutil.make_archive(dest_zip_file, "zip", root_dir=os.path.join(wip_dir, dir), base_dir="startup")

{"golden_diff": "diff --git a/nvflare/lighter/impl/workspace.py b/nvflare/lighter/impl/workspace.py\n--- a/nvflare/lighter/impl/workspace.py\n+++ b/nvflare/lighter/impl/workspace.py\n@@ -98,6 +98,15 @@\n         self.zip_password = zip_password\n \n     def build(self, project: Project, ctx: dict):\n+        \"\"\"Create a zip for each individual folder.\n+        Note that if zip_password is True, the zip command will be used to encrypt zip files.  Users have to to\n+        install this zip utility before provisioning.  In Ubuntu system, use this command to install zip utility:\n+        sudo apt-get install zip\n+\n+        Args:\n+            project (Project): project instance\n+            ctx (dict): the provision context\n+        \"\"\"\n         wip_dir = self.get_wip_dir(ctx)\n         dirs = [name for name in os.listdir(wip_dir) if os.path.isdir(os.path.join(wip_dir, name))]\n         for dir in dirs:\n@@ -106,8 +115,12 @@\n                 pw = generate_password()\n                 run_args = [\"zip\", \"-rq\", \"-P\", pw, dest_zip_file + \".zip\", \".\", \"-i\", \"startup/*\"]\n                 os.chdir(dest_zip_file)\n-                subprocess.run(run_args)\n-                os.chdir(os.path.join(dest_zip_file, \"..\"))\n-                print(f\"Password {pw} on {dir}.zip\")\n+                try:\n+                    subprocess.run(run_args)\n+                    print(f\"Password {pw} on {dir}.zip\")\n+                except FileNotFoundError as e:\n+                    raise RuntimeError(\"Unable to zip folders with password.  Maybe the zip utility is not installed.\")\n+                finally:\n+                    os.chdir(os.path.join(dest_zip_file, \"..\"))\n             else:\n                 shutil.make_archive(dest_zip_file, \"zip\", root_dir=os.path.join(wip_dir, dir), base_dir=\"startup\")\n", "issue": "Remove external 'zip' dependency from DistributionBuilder (zip_password=True)\nHi there, first off I want to acknowledge your efforts towards making an incredible tool, thank you!\r\n\r\nThe issue: I faced the exception *FileNotFoundError: [Errno 2] No such file or directory: 'zip'* when trying to specify `zip_password: true` for the DistributionBuilder args while running `provision -p project.yml`, and tracked the cause to `zip` not being installed on the local machine, which was a problem since installing additional software is not allowed on that machine.\r\n\r\n## Replicate\r\n```bash\r\n# Launch a docker container with image python:3.8 running interactive bash\r\ndocker run -it --rm python:3.8 bash\r\n\r\n# === Within docker container ===\r\npip install nvflare\r\nprovision # create example project.yml\r\n\r\n# Set zip_password to true\r\nsed -i 's/zip_password: false/zip_password: true/' project.yml\r\n\r\nprovision -p project.yml\r\n```\r\nWe will find the following output:\r\n```\r\nProject yaml file: /project.yml.\r\nException raised during provision.  Incomplete prod_n folder removed.\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python3.8/site-packages/nvflare/lighter/spec.py\", line 166, in provision\r\n    b.build(study, ctx)\r\n  File \"/usr/local/lib/python3.8/site-packages/nvflare/lighter/impl/workspace.py\", line 109, in build\r\n    subprocess.run(run_args)\r\n  File \"/usr/local/lib/python3.8/subprocess.py\", line 493, in run\r\n    with Popen(*popenargs, **kwargs) as process:\r\n  File \"/usr/local/lib/python3.8/subprocess.py\", line 858, in __init__\r\n    self._execute_child(args, executable, preexec_fn, close_fds,\r\n  File \"/usr/local/lib/python3.8/subprocess.py\", line 1704, in _execute_child\r\n    raise child_exception_type(errno_num, err_msg, err_filename)\r\nFileNotFoundError: [Errno 2] No such file or directory: 'zip'\r\n```\r\nWhereas it'll work if we install zip\r\n```bash\r\n# === Within docker container ===\r\napt-get update && apt-get install -y zip\r\nprovision -p project.yml\r\n```\r\n\r\n## Fix\r\nThe build function of the DistributionBuilder uses `shutil` to make the non-password protected zip but relies on `subprocess.run(['zip', ...])` in order to create password-protected files, since shutil doesn't support passwords.\r\n\r\nI'd propose adding a package requirement of something like `pyminizip` or `pyzipper` to handle password protection.\r\n\r\nhttps://github.com/NVIDIA/NVFlare/blob/c8d51bfb534c02eff874a760c47086c87cbff59f/nvflare/lighter/impl/workspace.py#L100-L113\n", "before_files": [{"content": "# Copyright (c) 2021-2022, NVIDIA CORPORATION.  All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport pathlib\nimport shutil\nimport subprocess\n\nfrom nvflare.lighter.spec import Builder, Project\nfrom nvflare.lighter.utils import generate_password\n\n\nclass WorkspaceBuilder(Builder):\n    def __init__(self, template_file):\n        \"\"\"Manages the folder structure for provisioned projects.\n\n        Sets the template_file containing scripts and configs to put into startup folders, creates directories for the\n        participants, and moves the provisioned project to the final location at the end\n        ($WORKSPACE/$PROJECT_NAME/prod_XX). WorkspaceBuilder manages and sets the number in prod_XX by incrementing from\n        the last time provision was run for this project in this workspace, starting with 00 to a max of 99.\n\n        Each time the provisioning tool runs, it requires a workspace folder in the local file system.  The workspace\n        will have the following folder structure:\n\n        .. code-block:: text\n\n          $WORKSPACE/    <--- this is assigned by -w option of provision command (default is workspace)\n            $PROJECT_NAME/  <--- this is the name value in the project.yml file\n              prod_00/   <--- a new prod_NN folder is created if provision does not have any errors.\n              prod_01/\n              ...\n              resources/ <--- this folder stores resources for other builders to load\n              state/     <--- this folder stores persistent information (such as certificates) so subsequent runs of the provision command can load the state back.\n              wip/  <--- this is only used during runtime, and will be removed when the provision command exits\n\n        Args:\n            template_file: name of template file containing scripts and configs to put into startup folders\n        \"\"\"\n        self.template_file = template_file\n\n    def _make_dir(self, dirs):\n        for dir in dirs:\n            if not os.path.exists(dir):\n                os.makedirs(dir)\n\n    def initialize(self, ctx):\n        workspace_dir = ctx[\"workspace\"]\n        prod_dirs = [_ for _ in os.listdir(workspace_dir) if _.startswith(\"prod_\")]\n        last = -1\n        for dir in prod_dirs:\n            stage = int(dir.split(\"_\")[-1])\n            if stage > last:\n                last = stage\n        ctx[\"last_prod_stage\"] = last\n        template_file_full_path = os.path.join(self.get_resources_dir(ctx), self.template_file)\n        file_path = pathlib.Path(__file__).parent.absolute()\n        shutil.copyfile(os.path.join(file_path, self.template_file), template_file_full_path)\n        ctx[\"template_file\"] = self.template_file\n\n    def build(self, project: Project, ctx: dict):\n        dirs = [self.get_kit_dir(p, ctx) for p in project.participants]\n        self._make_dir(dirs)\n\n    def finalize(self, ctx: dict):\n        if ctx[\"last_prod_stage\"] >= 99:\n            print(f\"Please clean up {ctx['workspace']} by removing prod_N folders\")\n            print(\"After clean-up, rerun the provision command.\")\n        else:\n            current_prod_stage = str(ctx[\"last_prod_stage\"] + 1).zfill(2)\n            current_prod_dir = os.path.join(ctx[\"workspace\"], f\"prod_{current_prod_stage}\")\n            shutil.move(self.get_wip_dir(ctx), current_prod_dir)\n            ctx.pop(\"wip_dir\", None)\n            print(f\"Generated results can be found under {current_prod_dir}.  Builder's wip folder removed.\")\n            ctx[\"current_prod_dir\"] = current_prod_dir\n\n\nclass DistributionBuilder(Builder):\n    def __init__(self, zip_password=False):\n        \"\"\"Build the zip files for each folder.\n\n        Creates the zip files containing the archives for each startup kit. It will add password protection if the\n        argument (zip_password) is true.\n\n        Args:\n            zip_password: if true, will create zipped packages with passwords\n        \"\"\"\n        self.zip_password = zip_password\n\n    def build(self, project: Project, ctx: dict):\n        wip_dir = self.get_wip_dir(ctx)\n        dirs = [name for name in os.listdir(wip_dir) if os.path.isdir(os.path.join(wip_dir, name))]\n        for dir in dirs:\n            dest_zip_file = os.path.join(wip_dir, f\"{dir}\")\n            if self.zip_password:\n                pw = generate_password()\n                run_args = [\"zip\", \"-rq\", \"-P\", pw, dest_zip_file + \".zip\", \".\", \"-i\", \"startup/*\"]\n                os.chdir(dest_zip_file)\n                subprocess.run(run_args)\n                os.chdir(os.path.join(dest_zip_file, \"..\"))\n                print(f\"Password {pw} on {dir}.zip\")\n            else:\n                shutil.make_archive(dest_zip_file, \"zip\", root_dir=os.path.join(wip_dir, dir), base_dir=\"startup\")\n", "path": "nvflare/lighter/impl/workspace.py"}], "after_files": [{"content": "# Copyright (c) 2021-2022, NVIDIA CORPORATION.  All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport pathlib\nimport shutil\nimport subprocess\n\nfrom nvflare.lighter.spec import Builder, Project\nfrom nvflare.lighter.utils import generate_password\n\n\nclass WorkspaceBuilder(Builder):\n    def __init__(self, template_file):\n        \"\"\"Manages the folder structure for provisioned projects.\n\n        Sets the template_file containing scripts and configs to put into startup folders, creates directories for the\n        participants, and moves the provisioned project to the final location at the end\n        ($WORKSPACE/$PROJECT_NAME/prod_XX). WorkspaceBuilder manages and sets the number in prod_XX by incrementing from\n        the last time provision was run for this project in this workspace, starting with 00 to a max of 99.\n\n        Each time the provisioning tool runs, it requires a workspace folder in the local file system.  The workspace\n        will have the following folder structure:\n\n        .. code-block:: text\n\n          $WORKSPACE/    <--- this is assigned by -w option of provision command (default is workspace)\n            $PROJECT_NAME/  <--- this is the name value in the project.yml file\n              prod_00/   <--- a new prod_NN folder is created if provision does not have any errors.\n              prod_01/\n              ...\n              resources/ <--- this folder stores resources for other builders to load\n              state/     <--- this folder stores persistent information (such as certificates) so subsequent runs of the provision command can load the state back.\n              wip/  <--- this is only used during runtime, and will be removed when the provision command exits\n\n        Args:\n            template_file: name of template file containing scripts and configs to put into startup folders\n        \"\"\"\n        self.template_file = template_file\n\n    def _make_dir(self, dirs):\n        for dir in dirs:\n            if not os.path.exists(dir):\n                os.makedirs(dir)\n\n    def initialize(self, ctx):\n        workspace_dir = ctx[\"workspace\"]\n        prod_dirs = [_ for _ in os.listdir(workspace_dir) if _.startswith(\"prod_\")]\n        last = -1\n        for dir in prod_dirs:\n            stage = int(dir.split(\"_\")[-1])\n            if stage > last:\n                last = stage\n        ctx[\"last_prod_stage\"] = last\n        template_file_full_path = os.path.join(self.get_resources_dir(ctx), self.template_file)\n        file_path = pathlib.Path(__file__).parent.absolute()\n        shutil.copyfile(os.path.join(file_path, self.template_file), template_file_full_path)\n        ctx[\"template_file\"] = self.template_file\n\n    def build(self, project: Project, ctx: dict):\n        dirs = [self.get_kit_dir(p, ctx) for p in project.participants]\n        self._make_dir(dirs)\n\n    def finalize(self, ctx: dict):\n        if ctx[\"last_prod_stage\"] >= 99:\n            print(f\"Please clean up {ctx['workspace']} by removing prod_N folders\")\n            print(\"After clean-up, rerun the provision command.\")\n        else:\n            current_prod_stage = str(ctx[\"last_prod_stage\"] + 1).zfill(2)\n            current_prod_dir = os.path.join(ctx[\"workspace\"], f\"prod_{current_prod_stage}\")\n            shutil.move(self.get_wip_dir(ctx), current_prod_dir)\n            ctx.pop(\"wip_dir\", None)\n            print(f\"Generated results can be found under {current_prod_dir}.  Builder's wip folder removed.\")\n            ctx[\"current_prod_dir\"] = current_prod_dir\n\n\nclass DistributionBuilder(Builder):\n    def __init__(self, zip_password=False):\n        \"\"\"Build the zip files for each folder.\n\n        Creates the zip files containing the archives for each startup kit. It will add password protection if the\n        argument (zip_password) is true.\n\n        Args:\n            zip_password: if true, will create zipped packages with passwords\n        \"\"\"\n        self.zip_password = zip_password\n\n    def build(self, project: Project, ctx: dict):\n        \"\"\"Create a zip for each individual folder.\n        Note that if zip_password is True, the zip command will be used to encrypt zip files.  Users have to to\n        install this zip utility before provisioning.  In Ubuntu system, use this command to install zip utility:\n        sudo apt-get install zip\n\n        Args:\n            project (Project): project instance\n            ctx (dict): the provision context\n        \"\"\"\n        wip_dir = self.get_wip_dir(ctx)\n        dirs = [name for name in os.listdir(wip_dir) if os.path.isdir(os.path.join(wip_dir, name))]\n        for dir in dirs:\n            dest_zip_file = os.path.join(wip_dir, f\"{dir}\")\n            if self.zip_password:\n                pw = generate_password()\n                run_args = [\"zip\", \"-rq\", \"-P\", pw, dest_zip_file + \".zip\", \".\", \"-i\", \"startup/*\"]\n                os.chdir(dest_zip_file)\n                try:\n                    subprocess.run(run_args)\n                    print(f\"Password {pw} on {dir}.zip\")\n                except FileNotFoundError as e:\n                    raise RuntimeError(\"Unable to zip folders with password.  Maybe the zip utility is not installed.\")\n                finally:\n                    os.chdir(os.path.join(dest_zip_file, \"..\"))\n            else:\n                shutil.make_archive(dest_zip_file, \"zip\", root_dir=os.path.join(wip_dir, dir), base_dir=\"startup\")\n", "path": "nvflare/lighter/impl/workspace.py"}]}