This is an automated email from the ASF dual-hosted git repository.

msciabarra pushed a commit to branch 0.1.0
in repository https://gitbox.apache.org/repos/asf/openserverless-task.git


The following commit(s) were added to refs/heads/0.1.0 by this push:
     new 2b396ae  updated to latest main
2b396ae is described below

commit 2b396ae289289fa74751eb228f0ea1329851d8a7
Author: Michele Sciabarra <“[email protected]”>
AuthorDate: Tue Feb 24 07:50:52 2026 +0000

    updated to latest main
---
 admin/docopts.md                                   |   3 +-
 admin/opsfile.yml                                  |   7 +-
 config/docopts.md                                  |  11 +-
 config/opsfile.yml                                 |  64 ++++-
 ide/deploy/bun.lockb                               | Bin 17094 -> 17804 bytes
 ide/deploy/client.js                               |  56 ++---
 ide/deploy/deploy.js                               |  48 ++--
 ide/deploy/env_utils.js                            |   2 +-
 ide/deploy/index.js                                |  54 ++++-
 ide/deploy/info.js                                 |  21 +-
 ide/deploy/package.json                            |   3 +-
 ide/deploy/scan.js                                 |  48 ++--
 ide/deploy/serve.js                                | 120 +++++++---
 ide/deploy/syncDeployInfo.js                       | 124 ++++++++++
 ide/deploy/undeploy.js                             | 122 ++++++++++
 ide/docopts.md                                     |  15 +-
 ide/nodejs/build-node.sh                           |   3 +-
 ide/opsfile.yml                                    | 169 ++++++++------
 ide/python/build-venv.sh                           |   8 +-
 ide/python/installer.code-workspace                |  17 ++
 ide/util/opsfile.yml                               |   4 +-
 opsroot.json                                       |   4 +-
 prereq.yml                                         |  72 +++---
 setup/docker/ingress-deploy.yaml                   | 258 +--------------------
 setup/docker/kind.yaml                             |  76 ++----
 setup/docker/opsfile.yml                           |  96 ++++----
 setup/docopts.md                                   |   6 +-
 setup/kubernetes/crds/whisk-crd.yaml               |  57 ++++-
 setup/kubernetes/operator.yaml                     |  14 +-
 setup/kubernetes/opsfile.yml                       |  76 +++---
 setup/kubernetes/whisk.yaml                        |  17 +-
 setup/nuvolaris/opsfile.yml                        |  44 ++--
 setup/nuvolaris/streamer/nginx-template.yaml       |  11 +
 setup/nuvolaris/streamer/opsfile.yml               |  48 ++--
 setup/nuvolaris/streamer/traefik-template.yaml     |  16 +-
 .../system-api/deploy/buildkit/buildkitd.toml      |  40 ++--
 setup/nuvolaris/system-api/opsfile.yml             |  73 ++++--
 setup/opsfile.yml                                  |  76 +++---
 update/opsfile.yml                                 |   6 +-
 util/docopts.md                                    |   8 +-
 util/freeze/opsfile.yml                            | 176 ++++++++++++++
 util/kube/opsfile.yml                              |  18 +-
 util/opsfile.yml                                   |  60 +++--
 43 files changed, 1314 insertions(+), 837 deletions(-)

diff --git a/admin/docopts.md b/admin/docopts.md
index dffa362..19c836f 100644
--- a/admin/docopts.md
+++ b/admin/docopts.md
@@ -27,7 +27,7 @@ You can create namespaces and choose which services to enable.
 
 ```text
 Usage:
-  admin adduser <username> <email> <password> [--all] [--redis] [--mongodb] 
[--minio] [--postgres] [--milvus] [--storagequota=<quota>|auto]
+  admin adduser <username> <email> <password> [--all] [--redis] [--mongodb] 
[--minio|--seaweedfs] [--postgres] [--milvus] [--storagequota=<quota>|auto] 
   admin deleteuser <username>
   admin listuser [<username>]
   admin compact [--ttl=<ttl>|10]
@@ -49,6 +49,7 @@ Usage:
   --redis                 enable redis
   --mongodb               enable mongodb
   --minio                 enable minio
+  --seaweedfs             enable seaweedfs
   --postgres              enable postgres
   --milvus                enable milvus vector db
   --storagequota=<quota>
diff --git a/admin/opsfile.yml b/admin/opsfile.yml
index 0898a4b..415699c 100644
--- a/admin/opsfile.yml
+++ b/admin/opsfile.yml
@@ -119,17 +119,18 @@ tasks:
           fi
         fi
 
-        if {{.__minio}} || {{.__all}}
+        if {{.__minio}} || {{.__seaweedfs}} || {{.__all}}
         then 
-          if $OPERATOR_COMPONENT_MINIO
+          if $OPERATOR_COMPONENT_MINIO || $OPERATOR_COMPONENT_SEAWEEDFS
           then 
             MINIO_DATA_ENABLED=true
             MINIO_STATIC_ENABLED=true
           else 
-            die "Error! Minio is not enabled in OpenServerless"
+            die "Error! Minio or Seaweedfs are not enabled in OpenServerless"
           fi
         fi
 
+
         if test -n "{{.__storagequota}}"
         then MINIO_STORAGE_QUOTA={{.__storagequota}}
         fi         
diff --git a/config/docopts.md b/config/docopts.md
index a41be77..54b69bd 100644
--- a/config/docopts.md
+++ b/config/docopts.md
@@ -24,13 +24,14 @@ Configure OpenServerless
 
 ```text
 Usage:
-  config (enable|disable) [--all] [--redis] [--mongodb] [--minio] [--cron] 
[--static] [--postgres] [--prometheus] [--slack] [--mail] [--affinity] 
[--tolerations] [--quota] [--milvus] [--registry]
+  config (enable|disable) [--all] [--redis] [--mongodb] [--minio] [--cron] 
[--static] [--postgres] [--prometheus] [--slack] [--mail] [--affinity] 
[--tolerations] [--quota] [--milvus] [--registry] [--seaweedfs]
   config apihost (<apihost>|auto) [--tls=<email>] 
[--protocol=<http/https>|auto]
   config runtimes [<runtimesjson>]  
   config slack [--apiurl=<slackapiurl>] [--channel=<slackchannel>]
   config mail  [--mailuser=<mailuser>] [--mailpwd=<mailpwd>] 
[--mailfrom=<mailfrom>] [--mailto=<mailto>]
-  config volumes [--couchdb=<couchdb>] [--kafka=<kafka>] [--pgvol=<postgres>] 
[--storage=<storage>] [--alerting=<alerting>] [--zookeeper=<zookeeper>] 
[--redisvol=<redis>] [--mongodbvol=<mongodb>] [--etcdvol=<etcd>] 
[--mvvol=<milvus>] [--mvzookvol=<milvuszook>] 
[--pulsarjournalvol=<pulsarjournal>] [--pulsarledgelvol=<pulsarledge>]  
+  config volumes [--couchdb=<couchdb>] [--kafka=<kafka>] [--pgvol=<postgres>] 
[--storage=<storage>] [--alerting=<alerting>] [--zookeeper=<zookeeper>] 
[--redisvol=<redis>] [--mongodbvol=<mongodb>] [--etcdvol=<etcd>] 
[--mvvol=<milvus>] [--mvzookvol=<milvuszook>] 
[--pulsarjournalvol=<pulsarjournal>] [--pulsarledgelvol=<pulsarledge>] 
[--seaweedfsvol=<seaweedfsvol>]  
   config controller [--javaopts=<javaopts>] [--loglevel=<loglevel>] 
[--replicas=<replicas>]
+  config ingress [--class=<auto|nginx|traefik|public>]
   config invoker [--javaopts=<javaopts>] [--poolmemory=<poolmemory>] 
[--timeoutsrun=<timeoutsrun>] [--timeoutslogs=<timeoutslogs>] 
[--loglevel=<loglevel>] [--replicas=<replicas>]
   config limits [--time=<time>] [--memory=<memory>] 
[--sequencelength=<sequencelength>] [--perminute=<perminute>] 
[--concurrent=<concurrent>] [--triggerperminute=<triggerperminute>] 
[--activation_max_payload=<activation_max_payload>] 
[--blackbox_fraction=<blackbox_fraction>]
   config storage [--class=<storage_class>] 
[--provisioner=<storage_provisioner>]
@@ -45,7 +46,8 @@ Usage:
   config azcloud [--project=<project>] [--region=<region>] [--vm=<vm>] 
[--disk=<disk>] [--key=<key>] [--image=<image>]
   config aks [--project=<project>] [--name=<name>] [--region=<region>] 
[--count=<count>]  [--vm=<vm>] [--disk=<disk>] [--key=<key>]
   config externalregistry [--regurl=<regurl>] [--reguser=<reguser>] 
[--regpassword=<regpassword>]
-  config registry [--disk=<disk>] [--ingress] 
+  config registry [--disk=<disk>] [--ingress]
+  config seaweedfs [--s3] [--console]
   config (status|export|reset)
   config use [<n>] [--delete] [--rename=<rename>]
   config minimal
@@ -63,6 +65,7 @@ Usage:
   config mail             configure Alert Manager over a gmail account
   config volumes          configure the volume size distinguished in 3 
categories (openwhisk couchdb & kafka, database, minio storage, alerting, 
milvus)
   config controller       configure Openwhisk enterprise controller java 
options
+  config ingress          configure OpenServerless ingress class
   config invoker          configure Openwhisk enterprise invoker options
   config limits           configure Openwhisk actions limits
   config storage          allows to customize storage persistence class and 
provider
@@ -120,4 +123,6 @@ Usage:
   --quota               select quota checker module
   --milvus              select MILVUS vector database
   --registry            activate the support for a private image registry for 
custom actions (by default it will install one)
+  --class               specify the ingress class. It can be auto, traefik, 
nginx. On microk8s it should be public.
+  --seaweedfs           activate the support of SEAWEEDFS as S3 api and 
buckets provider
 ```
diff --git a/config/opsfile.yml b/config/opsfile.yml
index d8f92f0..9cb5c90 100644
--- a/config/opsfile.yml
+++ b/config/opsfile.yml
@@ -124,7 +124,12 @@ tasks:
         then 
           config OPERATOR_COMPONENT_REGISTRY=true
           config REGISTRY_CONFIG_SECRET_PUSH_PULL="$(random --str 12)"
-        fi                 
+        fi  
+      - | 
+        if {{.__seaweedfs}} || {{.__all}}
+        then 
+          config OPERATOR_COMPONENT_SEAWEEDFS=true          
+        fi                         
       - config CONFIGURED=true
       - task: status
 
@@ -197,14 +202,19 @@ tasks:
       - | 
         if {{.__milvus}} || {{.__all}}
         then 
-          config OPERATOR_CONFIG_MILVUS=false
-          config OPERATOR_CONFIG_ETCD=false
+          config OPERATOR_COMPONENT_MILVUS=false
+          config OPERATOR_COMPONENT_ETCD=false
         fi  
       - | 
         if {{.__registry}} || {{.__all}}
         then 
-          config OPERATOR_CONFIG_REGISTRY=false
-        fi                                  
+          config OPERATOR_COMPONENT_REGISTRY=false
+        fi
+      - | 
+        if {{.__seaweedfs}} || {{.__all}}
+        then 
+          config OPERATOR_COMPONENT_SEAWEEDFS=false
+        fi                                            
       - task: status
 
   status:
@@ -870,7 +880,16 @@ tasks:
         HINT: | 
               Specifies an integer value
         VAL: "{{.__pulsarledgelvol}}"
-        DEF: "20"                                               
+        DEF: "20" 
+    - task: read
+      vars:
+        MSG: "SEAWEEDFS Volume size"
+        VAR: "STORAGE_SIZE_SEAWEEDFS"
+        HINT: | 
+              Specifies an integer value
+        VAL: "{{.__seaweedfsvol}}"
+        DEF: "60"         
+
 
   controller:
     silent: true
@@ -1080,7 +1099,6 @@ tasks:
       then config MINIO_CONFIG_INGRESS_CONSOLE=true
       fi 
 
-
   etcd:
     silent: true
     cmds:
@@ -1158,7 +1176,18 @@ tasks:
             Specifies the volume size assigned to the image registry
           VAL: "{{.__disk}}"
           DEF: "50"
-                                      
+
+  seaweefs:
+    silent: true
+    cmds:
+    - |
+      if {{.__s3}}
+      then config SEAWEEDFS_CONFIG_INGRESS_S3_ENABLED=true
+      fi    
+    - |
+      if {{.__console}}
+      then config SEAWEEDFS_CONFIG_INGRESS_CONSOLE_ENABLED=true
+      fi           
 
   minimal:
     silent: true
@@ -1169,7 +1198,7 @@ tasks:
     - |
       config OPERATOR_COMPONENT_MONGODB=true
     - |
-      config OPERATOR_COMPONENT_MINIO=true
+      config OPERATOR_COMPONENT_SEAWEEDFS=true
     - |
       config OPERATOR_COMPONENT_CRON=true  
     - |
@@ -1188,13 +1217,14 @@ tasks:
       config OPERATOR_COMPONENT_KAFKA=false  
       config OPERATOR_COMPONENT_REDIS=true
       config OPERATOR_COMPONENT_MONGODB=true
-      config OPERATOR_COMPONENT_MINIO=true
+      config OPERATOR_COMPONENT_MINIO=false
+      config OPERATOR_COMPONENT_SEAWEEDFS=true
       config OPERATOR_COMPONENT_CRON=true
       config OPERATOR_COMPONENT_STATIC=true
       config OPERATOR_COMPONENT_POSTGRES=true
       config OPERATOR_COMPONENT_ETCD=true
       config OPERATOR_COMPONENT_MILVUS=true      
-      config MINIO_CONFIG_INGRESS_S3=true
+      config SEAWEEDFS_CONFIG_INGRESS_S3_ENABLED=true
       config ETCD_CONFIG_REPLICAS=1
       config POSTGRES_CONFIG_REPLICAS=1
       config OPERATOR_CONFIG_SLIM=true
@@ -1205,3 +1235,15 @@ tasks:
       config OPENWHISK_INVOKER_CONTAINER_POOL_MEMORY=6g
     - task: status
     - config CONFIGURED=true
+
+  ingress:
+    silent: true
+    cmds:
+    - task: read
+      vars:
+        MSG: "Ingress Class"
+        VAR: "OPERATOR_CONFIG_INGRESSCLASS"
+        HINT: | 
+              Specify the ingress class.
+        VAL: "{{.__class}}"
+        DEF: "auto"
\ No newline at end of file
diff --git a/ide/deploy/bun.lockb b/ide/deploy/bun.lockb
old mode 100644
new mode 100755
index 47fe67b..526bb27
Binary files a/ide/deploy/bun.lockb and b/ide/deploy/bun.lockb differ
diff --git a/ide/deploy/client.js b/ide/deploy/client.js
index 307f7ed..e9ce791 100644
--- a/ide/deploy/client.js
+++ b/ide/deploy/client.js
@@ -18,12 +18,27 @@
 import {readFile} from 'fs/promises';
 import {resolve} from 'path';
 import process from 'process';
-import {createInterface} from 'readline';
-import {globalWatcher} from './watch';
-import {expandEnv} from './env_utils';
+import {globalWatcher} from './watch.js';
+import {expandEnv} from './env_utils.js';
 const { parse } = await import('shell-quote');
 export let globalProc = undefined;
 
+async function signalHandler() {
+    if (globalProc) {
+        console.log(`🔥Killing process pid ${globalProc.pid}`);
+        globalProc.kill();
+    }
+    if (globalWatcher) {
+        console.log("☠️ Stopping watcher");
+        await globalWatcher.close();
+    }
+    process.exit(0);
+}
+
+// Register signal handlers once at module level
+process.on('SIGINT', signalHandler);
+process.on('SIGTERM', signalHandler);
+
 /**
  * Read a key from OpenServerless configuration added to a `package.json` file 
in the
  * root of the project
@@ -44,20 +59,7 @@ export async function getOpenServerlessConfig(key, 
defaultValue) {
 }
 
 /**
- * This function creates a line-by-line interface over the provided input 
stream
- * and logs each line to the console as it is received.
- * @param {ReadableStream} inp - The input stream to read lines from.
- * @returns {void}
- */
-function readlines(inp) {
-    const rl = createInterface({input: inp, terminal: false});
-    rl.on('line', (line) => {
-        console.log(line);
-    });
-}
-
-/**
- * laungh a process with the command taken from OpenServerless Config
+ * launch a process with the command taken from OpenServerless Config
  * (see `getOpenServerlessConfig`)
  * @param key
  * @param defaultValue
@@ -65,7 +67,7 @@ function readlines(inp) {
 export async function launch(key, defaultValue) {
     const cmd = await getOpenServerlessConfig(key, defaultValue);
     const cmdArgs = parse(cmd).filter(arg => typeof arg === "string");
-    const proc = Bun.spawn(cmdArgs, {
+    Bun.spawn(cmdArgs, {
         shell: true,
         env: process.env,
         cwd: process.env.OPS_PWD,
@@ -78,7 +80,7 @@ export async function launch(key, defaultValue) {
  * through `getOpenServerlessConfig` mechanism
  */
 export async function serve() {
-    await launch('devel', 'ops ide serve');
+    launch('devel', 'ops ide serve');    
 }
 
 /**
@@ -86,17 +88,7 @@ export async function serve() {
  * through `getOpenServerlessConfig` mechanism
  */
 export async function logs() {
-    await launch('logs', 'ops activation poll');
-}
-
-async function signalHandler() {
-    console.log(`🔥Killing process pid ${globalProc.pid}`);
-    if (globalWatcher) {
-        console.log("☠️ Stopping watcher");
-        await globalWatcher.close();
-    }
-    globalProc.kill();
-    process.exit(0);
+    launch('logs', 'ops activation poll');
 }
 
 /**
@@ -122,10 +114,6 @@ export async function build() {
 
     console.log(`✈️ Deploy Child process: ${deploy} has PID: 
${globalProc.pid}`);
 
-    // Register signal handlers
-    process.on('SIGINT', signalHandler);
-    process.on('SIGTERM', signalHandler);
-
     // Await its completion
     try {
         const code = await globalProc.exited;
diff --git a/ide/deploy/deploy.js b/ide/deploy/deploy.js
index 293ed50..28f16bc 100644
--- a/ide/deploy/deploy.js
+++ b/ide/deploy/deploy.js
@@ -15,9 +15,9 @@
 // specific language governing permissions and limitations
 // under the License.
 
-import fs from 'fs/promises';
-import { expandEnv } from './env_utils';
-const { parse } = await import('shell-quote');
+import fs from "fs/promises";
+import { expandEnv } from "./env_utils";
+const { parse } = await import("shell-quote");
 
 const MAINS = ["__main__.py", "index.js", "index.php", "main.go"];
 
@@ -33,13 +33,13 @@ export function setDryRun(b) {
 async function exec(cmd) {
   console.log("$", cmd);
   cmd = expandEnv(cmd);
-  const cmdArgs = parse(cmd).filter(arg => typeof arg === "string");
-  
+  const cmdArgs = parse(cmd).filter((arg) => typeof arg === "string");
+
   const proc = Bun.spawn(cmdArgs, {
     shell: true,
     env: process.env,
     cwd: process.env.OPS_PWD,
-    stdio: ['inherit', 'inherit', 'inherit']
+    stdio: ["inherit", "inherit", "inherit"],
   });
   await proc.exited;
 }
@@ -51,11 +51,13 @@ async function extractArgs(files) {
       const fileContent = await fs.readFile(file, "utf-8");
       const lines = fileContent.split("\n");
       for (const line of lines) {
-        if (line.startsWith("#-")) {
-          res.push(line.trim().substring(1));
+        // python style comment
+        if (line.match(/^#[ ]?-{1,2}[^\s-].+/)) {
+          res.push(line.trim().substring(1).trim());
         }
-        if (line.startsWith("//-")) {
-          res.push(line.trim().substring(2));
+        // js style comment
+        if (line.match(/^\/\/[ ]?-{1,2}[^\s-].+/)) {
+          res.push(line.trim().substring(2).trim());
         }
       }
     }
@@ -87,7 +89,9 @@ export async function buildAction(pkg, action) {
 }
 
 export async function deployAction(artifact) {
-  let pkg = '', name='', typ = '';
+  let pkg = "",
+    name = "",
+    typ = "";
 
   if (activeDeployments.has(artifact)) {
     queue.push(artifact);
@@ -96,7 +100,7 @@ export async function deployAction(artifact) {
 
   activeDeployments.set(artifact, true);
   const indexInQueue = queue.indexOf(artifact);
-  if (indexInQueue>-1) {
+  if (indexInQueue > -1) {
     console.log(`⚙️ Deploying ${artifact} (from queue: ${indexInQueue})`);
   }
 
@@ -106,11 +110,12 @@ export async function deployAction(artifact) {
     name = spData[0];
     typ = spData[1];
     pkg = sp[1];
-  } catch {
-    console.log("❌ cannot deploy", artifact);
+  } catch(error) {
+    
+    console.log("❌ cannot deploy", artifact, "Error:", error.message);
     return;
   }
-  
+
   await deployPackage(pkg);
 
   let toInspect;
@@ -134,7 +139,6 @@ export async function deployAction(artifact) {
   }
 }
 
-
 /**
  * Deploy a `file`
  * @param file
@@ -146,7 +150,7 @@ export async function deploy(file) {
   // const file = "packages/deploy/multi/__main__.py";
   // const file = "packages/deploy/multi/requirements.txt";
 
-  const stat = await fs.stat(file)
+  const stat = await fs.stat(file);
 
   if (stat.isDirectory()) {
     for (const start of MAINS) {
@@ -157,10 +161,10 @@ export async function deploy(file) {
       }
     }
   }
-  
+
   const sp = file.split("/");
   if (sp.length > 3) {
-    buildZip(sp[1], sp[2]);
+    await buildZip(sp[1], sp[2]);
     file = await buildAction(sp[1], sp[2]);
   }
   console.log(`Deploying ${file}`);
@@ -174,10 +178,12 @@ export async function deploy(file) {
 export async function deployProject(artifact) {
   if (await fs.exists(artifact)) {
     const manifestContent = await Bun.file(artifact).text();
-    if (manifestContent.indexOf('packages:')!==-1) {
+    if (manifestContent.indexOf("packages:") !== -1) {
       await exec(`ops -wsk project deploy --manifest ${artifact}`);
     } else {
-      console.log(`⚠️ Warning: it seems that the ${artifact} file is not a 
valid manifest file. Skipping`);
+      console.log(
+        `⚠️ Warning: it seems that the ${artifact} file is not a valid 
manifest file. Skipping`
+      );
     }
   }
 }
diff --git a/ide/deploy/env_utils.js b/ide/deploy/env_utils.js
index e793551..4670b14 100644
--- a/ide/deploy/env_utils.js
+++ b/ide/deploy/env_utils.js
@@ -47,7 +47,7 @@ export function expandEnv(str, env = process.env) {
           const name = varMatch[1];
           let val;
           if (name === "$") val = process.pid;
-          else if (name === "?") val = "0"; // impossibile sapere senza 
contesto
+          else if (name === "?") val = "0"; // Cannot determine actual exit 
code without execution context
           else if (name === "#") val = "0";
           else if (name === "!") val = "";
           else val = env[name] ?? "";
diff --git a/ide/deploy/index.js b/ide/deploy/index.js
index 2889f02..d4c7dde 100644
--- a/ide/deploy/index.js
+++ b/ide/deploy/index.js
@@ -22,6 +22,7 @@ import {program} from 'commander';
 import {scan} from './scan.js';
 import {watchAndDeploy, globalWatcher} from './watch.js';
 import {setDryRun, deploy} from './deploy.js';
+import {undeploy, setDryRun as setUndeployDryRun} from './undeploy.js';
 import {build} from './client.js';
 
 
@@ -34,8 +35,11 @@ async function signalHandler() {
         await globalWatcher.close();
     }
 
-    unlinkSync(expanduser('~/.ops/tmp/deploy.pid'));
-    process.kill(process.getpgrp(), 'SIGKILL');
+    const pidPath = expanduser('~/.ops/tmp/deploy.pid');
+    if (existsSync(pidPath)) {
+        unlinkSync(pidPath);
+    }
+    process.kill(process.pid, 'SIGTERM');
     process.exit(0); // should not be reached but just in case...
 }
 
@@ -44,7 +48,7 @@ function checkPort() {
     server.listen(8080, '127.0.0.1');
     server.on('error', () => {
         console.log('deployment mode already active (or something listening in 
127.0.0.1:8080)');
-        server.close();
+        server.close();        
     });
     server.on('listening', () => server.close());
 }
@@ -59,7 +63,6 @@ async function main() {
 
 
     process.on('SIGTERM', signalHandler);
-    process.on('SIGKILL', signalHandler);
     const pidfile = expanduser('~/.ops/tmp/deploy.pid');
     console.log('PID', pid);
 
@@ -74,15 +77,52 @@ async function main() {
         .option('-d, --deploy', 'Deploy')
         .option('-w, --watch', 'Watch for changes')
         .option('-s, --single <string>', 'Deploy a single action, either a 
single file or a directory.', '')
+        .option('-u, --undeploy', 'Undeploy actions and packages from the 
current project')
         .parse(process.argv);
 
     const options = program.opts();
     const directory = program.args[0];
 
     setDryRun(options.dryRun);
+    setUndeployDryRun(options.dryRun);
+
+    if (!existsSync(directory)) {
+        console.error(`❌ Error: Directory ${directory} does not exist`);
+        process.exit(1);
+    }
     process.chdir(directory);
 
-    if (options.watch) {
+    if (options.undeploy) {
+        // Undeploy actions and packages from the current project
+        let success;
+
+        if (options.single !== '') {
+            // If a single action is specified, undeploy just that action
+            let action = options.single;
+            if (!action.startsWith('packages/')) {
+                action = `packages/${action}`;
+            }
+
+            // Extract the package and action name
+            const parts = action.split('/');
+            if (parts.length >= 3) {
+                const pkg = parts[1];
+                const actionName = parts[2].split('.')[0]; // Remove file 
extension if present
+                success = undeploy(`${pkg}/${actionName}`);
+            } else {
+                // If the format is already package/action
+                success = undeploy(action);
+            }
+        } else {
+            // Otherwise, undeploy all actions and packages from the current 
project
+            success = undeploy();
+        }
+
+        if (!success) {
+            process.exit(1);
+        }
+        process.exit(0);
+    } else if (options.watch) {
         checkPort();
         if (!options.fast) {
             await scan();
@@ -92,7 +132,7 @@ async function main() {
 
     } else if (options.deploy) {
         await scan();
-        await build()
+        await build();
         process.exit(0);
     } else if (options.single !== '') {
         let action = options.single;
@@ -115,5 +155,3 @@ main().catch(err => {
     console.error(err);
     process.exit(1);
 });
-
-setInterval(() => {}, 1000);
\ No newline at end of file
diff --git a/ide/deploy/info.js b/ide/deploy/info.js
index 757ba9b..ee7025d 100644
--- a/ide/deploy/info.js
+++ b/ide/deploy/info.js
@@ -1,11 +1,28 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
 import { getOpenServerlessConfig} from './client.js';
 import {program} from "commander";
 import process from "process";
 
 program
     .description('Deployer info')
-    .argument('<param>', 'The key to query')
-    .argument('<param>', 'The default value')
+    .argument('<key>', 'The key to query')
+    .argument('[default]', 'The default value')
     .parse(process.argv);
 
 const key = program.args[0];
diff --git a/ide/deploy/package.json b/ide/deploy/package.json
index 264d8d3..c490dc7 100644
--- a/ide/deploy/package.json
+++ b/ide/deploy/package.json
@@ -4,7 +4,8 @@
     "commander": "=13.1.0",
     "dotenv": "^16.4.7",
     "glob": "=11.0.1",
-    "shell-quote": "^1.8.3"
+    "shell-quote": "^1.8.3",
+    "mime-types": "^2.1.35"
   },
   "devDependencies": {
     "@types/bun": "=1.2.2"
diff --git a/ide/deploy/scan.js b/ide/deploy/scan.js
index 8962819..484089d 100644
--- a/ide/deploy/scan.js
+++ b/ide/deploy/scan.js
@@ -19,6 +19,7 @@ import {glob} from 'glob';
 import {buildAction, buildZip, deployAction, deployPackage, deployProject} 
from './deploy.js';
 import {getOpenServerlessConfig} from './client.js';
 import {config} from "dotenv";
+import {syncDeployInfo} from "./syncDeployInfo";
 
 /**
  * This function will prepare and deploy the functions in `packages` directory.
@@ -52,21 +53,19 @@ export async function scan() {
     // load user defined requirements or use `defaultReqsGlobs`
     const packageGlobs = await getOpenServerlessConfig("requirements", 
defaultReqsGlobs);
     // requirements
-    const reqs = [];
+    const reqsSet = new Set();
 
     // Check for additional packages from each package
-    // and add them to the requirements array
+    // and add them to the requirements set
     for (const pkgGlob of packageGlobs) {
         const items = await glob(pkgGlob);
         for (const item of items) {
-            if (!reqs.includes(item)) {
-                reqs.push(item);
-            }
+            reqsSet.add(item);
         }
     }
 
     // let's package the functions that have requirements
-    for (const req of reqs) {
+    for (const req of reqsSet) {
         console.log(">> Requirements:", req);
         const sp = req.split("/");
         const act = await buildZip(sp[1], sp[2]);
@@ -83,18 +82,16 @@ export async function scan() {
     ];
     // load user defined main functions or use `defaultMainsGlobs`
     const mainsGlobs = await getOpenServerlessConfig("mains", 
defaultMainsGlobs);
-    const mains = [];
+    const mainsSet = new Set();
 
     for (const mainGlob of mainsGlobs) {
         const items = await glob(mainGlob);
         for (const item of items) {
-            if (!mains.includes(item)) {
-                mains.push(item);
-            }
+            mainsSet.add(item);
         }
     }
 
-    for (const main of mains) {
+    for (const main of mainsSet) {
         console.log(">> Main:", main);
         const sp = main.split("/");
         const act = await buildAction(sp[1], sp[2]);
@@ -110,18 +107,16 @@ export async function scan() {
         "packages/*/*.go"
     ];
     const singlesGlobs = await getOpenServerlessConfig("singles", 
defaultSinglesGlobs);
-    const singles = [];
+    const singlesSet = new Set();
 
     for (const singleGlob of singlesGlobs) {
         const items = await glob(singleGlob);
         for (const item of items) {
-            if (!singles.includes(item)) {
-                singles.push(item);
-            }
+            singlesSet.add(item);
         }
     }
 
-    for (const single of singles) {
+    for (const single of singlesSet) {
         console.log(">> Action:", single);
         const sp = single.split("/");
         deployments.add(single);
@@ -146,25 +141,30 @@ export async function scan() {
         "packages/*/*.yaml",
     ];
     const manifestsGlobs = await getOpenServerlessConfig("manifests", 
defaultProjectGlobs);
-    const manifests = [];
+    const manifestsSet = new Set();
 
     for (const manifestGlobs of manifestsGlobs) {
         const items = await glob(manifestGlobs);
         for (const item of items) {
-            if (!manifests.includes(item)) {
-                manifests.push(item);
-            }
+            manifestsSet.add(item);
         }
-        manifests.sort((a, b) => a.localeCompare(b));
     }
-    if (manifests.length >0 ) {
-        if (Bun.file('packeges/.env')) {
+    const manifests = Array.from(manifestsSet).sort((a, b) => 
a.localeCompare(b));
+    if (manifests.length > 0) {
+        if (await Bun.file('packages/.env').exists()) {
             console.log("Found packages .env file. Reading it");
-            config({ path: "./package/.env" });
+            config({ path: "./packages/.env" });
         }
         for (const manifest of manifests) {
             console.log(">>> Manifest:", manifest);
             await deployProject(manifest);
         }
     }
+
+    try {
+        // Save deployment information with the new structure
+        syncDeployInfo(packages, deployments);
+    } catch (error) {
+        console.error("Error saving deployment information:", error);
+    }
 }
diff --git a/ide/deploy/serve.js b/ide/deploy/serve.js
index 549e89d..7d22e8e 100644
--- a/ide/deploy/serve.js
+++ b/ide/deploy/serve.js
@@ -17,6 +17,7 @@
 
 import {stat} from "fs/promises";
 import {resolve, extname} from "path";
+import {lookup as lookupMime} from 'mime-types';
 import {parse} from "url";
 import process from 'process';
 import { file } from "bun";
@@ -54,16 +55,44 @@ async function findAvailablePort(host, startPort = 8080) {
  * @returns {Promise<Buffer<ArrayBuffer>>}
  */
 async function toBuffer(stream) {
-    const list = []
-    const reader = stream.getReader();
-    while (true) {
-        const {value, done} = await reader.read();
-        if (value)
-            list.push(value)
-        if (done)
-            break
+    // If there's no body (e.g. OPTIONS requests or empty bodies), return null
+    if (!stream) return null;
+
+    // Some runtimes provide a ReadableStream with getReader(), others may 
give a Node-style stream
+    if (typeof stream.getReader === 'function') {
+        const list = [];
+        const reader = stream.getReader();
+        while (true) {
+            const {value, done} = await reader.read();
+            if (value)
+                list.push(value);
+            if (done)
+                break;
+        }
+        return Buffer.concat(list);
+    }
+
+    // If it's already a Buffer or ArrayBuffer-like
+    if (Buffer.isBuffer(stream)) return stream;
+    if (stream instanceof ArrayBuffer) return Buffer.from(new 
Uint8Array(stream));
+
+    // If it's an async iterator (Node readable), consume it
+    if (stream[Symbol.asyncIterator]) {
+        const list = [];
+        for await (const chunk of stream) {
+            list.push(Buffer.from(chunk));
+        }
+        return Buffer.concat(list);
     }
-    return Buffer.concat(list)
+
+    // Unknown type: try to stringify
+    try {
+        const s = String(stream);
+        return Buffer.from(s, 'utf8');
+    } catch (e) {
+        return null;
+    }
+
 }
 
 //
@@ -73,20 +102,10 @@ async function toBuffer(stream) {
  * @returns {*|string}
  */
 function getMimeType(filePath) {
-    const ext = extname(filePath).toLowerCase();
-    const mimeTypes = {
-        ".html": "text/html",
-        ".css": "text/css",
-        ".js": "application/javascript",
-        ".json": "application/json",
-        ".png": "image/png",
-        ".jpg": "image/jpeg",
-        ".gif": "image/gif",
-        ".svg": "image/svg+xml",
-        ".txt": "text/plain",
-        // Add more mimetypes as needed
-    };
-    return mimeTypes[ext] || "application/octet-stream";
+    // Use the mime-types package to resolve mimetypes for a broad set of 
extensions.
+    // Fallback to application/octet-stream when unknown.
+    const m = lookupMime(filePath);
+    return m || "application/octet-stream";
 }
 
 const excludedAssets = ['favicon.ico'];
@@ -154,15 +173,45 @@ async function main() {
 
                 if (fileStats.isFile()) {
                     const mimeType = flags.mimeType || getMimeType(filePath);
+
+                    // Compute caching headers
+                    const lastModified = fileStats.mtime.toUTCString();
+                    // Simple ETag based on inode/size/mtime if available
+                    const etag = `W/"${fileStats.size}-${fileStats.mtimeMs}"`;
+
+                    // Handle conditional requests
+                    const ifNoneMatch = req.headers.get('if-none-match');
+                    const ifModifiedSince = 
req.headers.get('if-modified-since');
+                    if (ifNoneMatch && ifNoneMatch === etag) {
+                        return new Response(null, {status: 304, headers: 
{"ETag": etag}});
+                    }
+                    if (ifModifiedSince) {
+                        const since = new Date(ifModifiedSince);
+                        if (!isNaN(since.getTime()) && fileStats.mtime <= 
since) {
+                            return new Response(null, {status: 304, headers: 
{"Last-Modified": lastModified, "ETag": etag}});
+                        }
+                    }
+
                     const headers = {
                         "Content-Type": mimeType,
+                        "Content-Length": String(fileStats.size),
+                        "Last-Modified": lastModified,
+                        "ETag": etag,
                     };
                     if (flags.cacheTime) {
                         headers["Cache-Control"] = 
`max-age=${flags.cacheTime}`;
                     }
                     console.log(`[200] - Serving file ${pathname} from 
filesystem`);
-                    const data = await Bun.file(filePath).text();
-                    return new Response(data, {headers});
+                    // Stream the file to avoid loading the whole file into 
memory
+                    // Bun.file(filePath).stream() returns a ReadableStream
+                    try {
+                        const stream = Bun.file(filePath).stream();
+                        return new Response(stream, {headers});
+                    } catch (e) {
+                        // Fallback: read as arrayBuffer if stream isn't 
available
+                        const ab = await Bun.file(filePath).arrayBuffer();
+                        return new Response(ab, {headers});
+                    }
                 }
             } catch (err) {
                 let shouldExclude = (excludedAssets.indexOf(pathname) !== -1);
@@ -189,12 +238,21 @@ async function main() {
                         method: req.method,
                         headers: newHeaders,
                     };
-                    if (req.method.toLowerCase() !== 'get') {
-                        let body = await toBuffer(req.body);
-                        body = body.toString('utf8');
-
-                        if (body !== undefined) {
-                            init['body'] = body;
+                    // Only forward a body when the method permits it and a 
body exists
+                    const method = req.method.toLowerCase();
+                    const canHaveBody = (method !== 'get' && method !== 'head' 
&& method !== 'options');
+                    if (canHaveBody && req.body) {
+                        const buf = await toBuffer(req.body);
+                        if (buf && buf.length > 0) {
+                            // prefer sending string when it's valid UTF-8 text
+                            let bodyToSend = buf;
+                            try {
+                                const text = buf.toString('utf8');
+                                bodyToSend = text;
+                            } catch (e) {
+                                bodyToSend = buf;
+                            }
+                            init['body'] = bodyToSend;
                         }
                     }
                     const respP = await fetch(destProxyUrl, init);
diff --git a/ide/deploy/syncDeployInfo.js b/ide/deploy/syncDeployInfo.js
new file mode 100644
index 0000000..6865019
--- /dev/null
+++ b/ide/deploy/syncDeployInfo.js
@@ -0,0 +1,124 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import {existsSync, mkdirSync, writeFileSync, readFileSync} from "fs";
+
+/**
+ * Synchronizes deployment information by saving the provided package and 
deployment data
+ * to a persisted `.ops/deployment.json` file. If the directory `.ops` does 
not exist, it
+ * is created before saving the information.
+ *
+ * The deployment information is organized by packages, with each package 
containing an array
+ * of its actions. This allows for more granular control when undeploying 
specific actions.
+ *
+ * @param {Set<string>} packages - A set of package names to include in the 
deployment data.
+ * @param {Set<string>} deployments - A set of deployment identifiers to 
include in the deployment data.
+ * @return {void}
+ */
+export function syncDeployInfo(packages, deployments) {
+    if (!existsSync('.ops')) {
+        mkdirSync('.ops', { recursive: true });
+    }
+
+    // Create a structured object with packages as keys and arrays of actions 
as values
+    const packageActions = {};
+
+    // Initialize packages with empty arrays
+    for (const pkg of packages) {
+        packageActions[pkg] = [];
+    }
+
+    // Add actions to their respective packages
+    for (const deployment of deployments) {
+        try {
+            const sp = deployment.split("/");
+            const spData = sp[sp.length - 1].split(".");
+            const name = spData[0];
+            const pkg = sp[1];
+
+            // If the package exists in our structure, add the action to it
+            if (packageActions[pkg]) {
+                packageActions[pkg].push(name);
+            }
+        } catch (error) {
+            console.error(`Error parsing deployment path ${deployment}:`, 
error);
+        }
+    }
+
+    const deploymentInfo = {
+        packages: Array.from(packages),
+        packageActions: packageActions
+    };
+
+    writeFileSync('.ops/deployment.json', JSON.stringify(deploymentInfo, null, 
2));
+    console.log("> Saved deployment information to .ops/deployment.json");
+}
+
+/**
+ * Removes a specific action from the deployment information.
+ * 
+ * @param {string} actionName - The name of the action to remove in the format 
"package/action".
+ * @return {boolean} - True if the action was found and removed, false 
otherwise.
+ */
+export function removeActionFromDeployInfo(actionName) {
+    if (!existsSync('.ops/deployment.json')) {
+        console.error('Error: No deployment information found.');
+        return false;
+    }
+
+    try {
+        const deploymentInfo = JSON.parse(readFileSync('.ops/deployment.json', 
'utf8'));
+        const [pkg, action] = actionName.split('/');
+
+        if (!deploymentInfo.packageActions || 
!deploymentInfo.packageActions[pkg]) {
+            console.error(`❌ Error: Package ${pkg} not found in deployment 
information.`);
+            return false;
+        }
+
+        const actionIndex = deploymentInfo.packageActions[pkg].indexOf(action);
+        if (actionIndex === -1) {
+            console.error(`❌ Error: Action ${action} not found in package 
${pkg}.`);
+            return false;
+        }
+
+        // Remove the action from the package
+        deploymentInfo.packageActions[pkg].splice(actionIndex, 1);
+
+        // If the package has no more actions, remove it from the packages list
+        if (deploymentInfo.packageActions[pkg].length === 0) {
+            const packageIndex = deploymentInfo.packages.indexOf(pkg);
+            if (packageIndex !== -1) {
+                deploymentInfo.packages.splice(packageIndex, 1);
+            }
+            delete deploymentInfo.packageActions[pkg];
+        }
+
+        writeFileSync('.ops/deployment.json', JSON.stringify(deploymentInfo, 
null, 2));
+        console.log(`> Removed ${actionName} from deployment information.`);
+        return true;
+    } catch (error) {
+        console.error("❌ Error updating deployment information:", error);
+        return false;
+    }
+}
+
+/**
+ * Cleans up deployment information by resetting and synchronizing deployment 
data.
+ */
+export function cleanupDeployInfo() {
+    syncDeployInfo(new Set(), new Set());
+}
diff --git a/ide/deploy/undeploy.js b/ide/deploy/undeploy.js
new file mode 100644
index 0000000..74aa9c3
--- /dev/null
+++ b/ide/deploy/undeploy.js
@@ -0,0 +1,122 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { existsSync, readFileSync } from 'fs';
+import { spawnSync } from 'child_process';
+import { cleanupDeployInfo, removeActionFromDeployInfo } from 
"./syncDeployInfo.js";
+import { expandEnv } from './env_utils.js';
+
+let dryRun = false;
+
+export function setDryRun(b) {
+  dryRun = b;
+}
+
+function exec(cmd) {
+  console.log("$", cmd);
+  if (!dryRun) {
+    cmd = expandEnv(cmd);
+    const result = spawnSync(cmd, { shell: true, env: process.env, stdio: 
"inherit" });
+    if (result.error) {
+      console.error(`Command failed: ${cmd}`, result.error);
+      throw result.error;
+    }
+    return result;
+  }
+}
+
+/**
+ * Undeploy a specific action and update the deployment information
+ * @param {string} actionName - The name of the action to undeploy in the 
format "package/action"
+ * @returns {boolean} true if successful, false if error
+ */
+export function undeployAction(actionName) {
+  console.log(`> Undeploying action: ${actionName}`);
+
+  try {
+    // Execute the undeploy command
+    exec(`$OPS action delete ${actionName}`);
+
+    // Update the deployment information
+    const success = removeActionFromDeployInfo(actionName);
+    if (success) {
+      console.log(`> Action ${actionName} successfully undeployed and removed 
from deployment information.`);
+      return true;
+    } else {
+      console.error(`> Action ${actionName} was undeployed but could not be 
removed from deployment information.`);
+      return false;
+    }
+  } catch (error) {
+    console.error(`Error undeploying action ${actionName}:`, error);
+    return false;
+  }
+}
+
+/**
+ * Undeploy actions and packages based on the deployment information in 
.ops/deployment.json
+ * If no deployment information is found, return an error
+ * @param {string} [specificAction] - Optional specific action to undeploy
+ * @returns {boolean} true if successful, false if error
+ */
+export function undeploy(specificAction) {
+  // If a specific action is provided, undeploy just that action
+  if (specificAction) {
+    return undeployAction(specificAction);
+  }
+
+  // Otherwise, undeploy all actions and packages from the current project
+  if (!existsSync('.ops/deployment.json')) {
+    console.error('❌ Error: No OpenServerless project found in the current 
directory.');
+    console.error('❌ Please run this command in a directory with an 
OpenServerless project.');
+    return false;
+  }
+
+  try {
+    const deploymentInfo = JSON.parse(readFileSync('.ops/deployment.json', 
'utf8'));
+    const { packages, packageActions } = deploymentInfo;
+
+    if (!packages || !packageActions || packages.length === 0) {
+      console.error('❌ Error: No deployment information found.');
+      return false;
+    }
+
+    console.log("> Undeploy actions and packages from the current project:");
+
+    // Undeploy actions
+    for (const pkg of packages) {
+      const actions = packageActions[pkg] || [];
+      for (const action of actions) {
+        const actionName = `${pkg}/${action}`;
+        console.log(`>> Undeploy action: ${actionName}`);
+        exec(`$OPS action delete ${actionName}`);
+      }
+    }
+
+    // Undeploy packages
+    for (const pkg of packages) {
+      console.log(`>> Undeploy package: ${pkg}`);
+      exec(`$OPS package delete ${pkg}`);
+    }
+
+    cleanupDeployInfo();
+    console.log("> Undeployment completed successfully.");
+    return true;
+  } catch (error) {
+    console.error("❌ Error undeploy:", error);
+    return false;
+  }
+}
diff --git a/ide/docopts.md b/ide/docopts.md
index 5b4d4b6..8b35feb 100644
--- a/ide/docopts.md
+++ b/ide/docopts.md
@@ -24,12 +24,12 @@ OpenServerless Ide Development Utilities.
 
 ```text
 Usage:
-    ide login [<username>] [<apihost>]
+    ide login [<username>] [<apihost>] [--pin]
     ide devel [--fast] [--dry-run]
-    ide deploy [<action>] [--dry-run]
+    ide deploy [<action>|--packages|--web] [--dry-run]
     ide undeploy [<action>] [--dry-run]
     ide clean
-    ide setup 
+    ide setup
     ide serve
     ide poll
     ide shell
@@ -45,7 +45,7 @@ Usage:
     ide login               login in openserverless
     ide devel               activate development mode
     ide deploy              deploy everything or just one action
-    ide undeploy            undeploy everything or just one action
+    ide undeploy            undeploy actions and packages from the current 
project or just one action
     ide clean               clean the temporay files
     ide setup               setup the ide
     ide serve               serve web area
@@ -60,6 +60,9 @@ Usage:
 ## Options
 
 ```
+--dry-run       Simulates the execution without making any actual changes
+--packages      Only deploy packages, skip web upload
+--web           Only deploy web folder, skip packages
 --fast          Skip the initial deployment step and go in incremental update 
mode
---dry-run       Simulates the execution without making any actual changes 
-```
\ No newline at end of file
+--pin           Pin the current auth in the .env to check you are not 
deploying on the wrong user
+```
diff --git a/ide/nodejs/build-node.sh b/ide/nodejs/build-node.sh
index 9235046..a8f77d5 100644
--- a/ide/nodejs/build-node.sh
+++ b/ide/nodejs/build-node.sh
@@ -19,8 +19,7 @@
 DIR="${1:?directory}"
 ZIP="${2:?zip file}"
 cd "$DIR"
-npm install
-touch node_modules/.package-lock.json
+bun install
 if test -f "$ZIP"
 then rm "$ZIP"
 fi
diff --git a/ide/opsfile.yml b/ide/opsfile.yml
index 25f4ad8..75d28e8 100644
--- a/ide/opsfile.yml
+++ b/ide/opsfile.yml
@@ -22,7 +22,7 @@ vars:
   API_HOST:
     sh: |
       echo "http://localhost:80";
-  
+
   DEPLOY_CURRENT_HASH:
     sh: |
       if test -e "$OPS_ROOT/ide/deploy/bun.lockb"
@@ -36,22 +36,18 @@ vars:
       then cat "$OPS_ROOT/ide/deploy/hash.lock"
       else echo "0"
       fi
-      
+
 tasks:
 
   prereq:
     silent: true
-    vars: 
+    vars:
       MSG: |
             This command must be run inside a devcontainer.
             Please use 'ops ide devcontainer' to create a configuration and 
enter in a devcontainer.
             You can then use 'ops ide devel' and 'ops ide deploy'.
             You can also reopen the project with VSCode and use the command 
'Reopen in Container'.
     cmds:
-      - test -e "/.dockerenv" || die "{{.MSG}}"
-      - test "$(python3 -V | awk -F. '{print $2}')" -ge 10 || die "python 3.10 
or greater not available"
-      - test -x "$(which virtualenv)" || die "you need python virtualenv"
-      - test "$(node -v | awk -F. '{print substr($1,2) }')" -ge 18 || die 
"nodejs 18 or greater not available"
       - test "$(printf '%s\n' "$(bun -v)" "1.1.18" | sort -V | head -n 1)" = 
"1.1.18" || die "bun 1.1.18 or greater not available"
       - test -d "$OPS_PWD/packages" || die "no packages in current directory"
       - test -e ~/.wskprops || die "please run 'ops ide login' first"
@@ -59,7 +55,7 @@ tasks:
       - test "$(ops -wsk property get --namespace | awk '{ print $3 }')" = 
"$OPSDEV_USERNAME" || die "Repeat the login"
       - |
         if ! test -d "$OPS_ROOT/ide/deploy/node_modules" || [ 
"{{.DEPLOY_CURRENT_HASH}}" != "{{.DEPLOY_PREVIOUS_HASH}}" ]
-          then 
+          then
             cd $OPS_ROOT/ide/deploy && bun install && rm -f 
$OPS_ROOT/ide/deploy/hash.lock && bun $OPS_ROOT/ide/deploy/bun.lockb --hash > 
$OPS_ROOT/ide/deploy/hash.lock
         fi
       - |
@@ -67,9 +63,9 @@ tasks:
         then if ! test -d "$OPS_PWD/node_modules"
              then cd $OPS_PWD ; bun install
              fi
-        fi      
+        fi
       - task: kill
-  
+
   devcontainer:
     silent: true
     desc: add a devcontainer to your project
@@ -77,13 +73,13 @@ tasks:
       - |
         if test -e "$OPS_PWD/.devcontainer/devcontainer.json"
         then echo "a .devcontainer already exists"
-        else 
+        else
           mkdir -p "$OPS_PWD/.devcontainer"
           cp devcontainer.json "$OPS_PWD/.devcontainer/"
           echo "devcontainer created in .devcontainer - please use VSCode and 
the command 'Reopen in Container'"
         fi
       - |
-        if echo "$OPS_PWD/" | rg ' ' 
+        if echo "$OPS_PWD/" | rg ' '
         then die "please place your workspace in a folder WITHOUT SPACES IN 
THE FOLDER NAME!!!!"
         fi
       - bun x @devcontainers/cli up --workspace-folder "$OPS_PWD"
@@ -98,22 +94,22 @@ tasks:
         PIDFILE=$(ops -opspath ~/.ops/tmp/deploy.pid)
         echo $PIDFILE
         if test -e $PIDFILE
-        then          
+        then
           PID=$(cat $PIDFILE)
-          
+
           if [ ! -z "$PID" ]; then
             if ps -p "$PID" > /dev/null;
             then
               echo "Found previous deploy pid: $PID"
               kill "$PID"
-              # PGRP=$(ps -o 'pgid=' -p $PID | xargs)              
+              # PGRP=$(ps -o 'pgid=' -p $PID | xargs)
               # if [ ! -z "$PGRP" ];
-              # then 
+              # then
               #   echo "Terminating deploy process group $PGRP"
               #   kill "$PGRP"
               # else
               #   echo "Terminating deploy process $PID"
-              #   kill "$PID" 
+              #   kill "$PID"
               # fi
             fi
           fi
@@ -141,40 +137,48 @@ tasks:
 
 
   login:
-    silent: true
+    silent: false
     desc: login in you OpenServerless host
     cmds:
       - |
+        set -a
         if test -e "$OPS_PWD/.env"
         then source "$OPS_PWD/.env"
         fi
+        if test -n "$OPS_USERNAME"
+        then echo "Please use OPS_USER, OPS_USERNAME is deprecated"
+        fi
         if test -n "{{._apihost_}}"
-        then OPSDEV_APIHOST="{{._apihost_}}"
+        then export OPSDEV_APIHOST="{{._apihost_}}"
         fi
         echo "*** Configuring Access to OpenServerless ***"
-        if test -z "{{._apihost_}}"
-        then 
-            if test -z "$OPS_APIHOST"
+        if test -z "{{._username_}}"
+        then
+            if test -n "$OPS_USER"
             then
-                echo -n "Enter Apihost: "
-                read OPSDEV_APIHOST
-                OPSDEV_APIHOST="$(echo $OPSDEV_APIHOST | tr -d '\r')"
+                export OPSDEV_USERNAME="$OPS_USER"
             else
-                OPSDEV_APIHOST="$OPS_APIHOST"
+              echo -n "Enter Username: "
+              read OPSDEV_USERNAME
+              export OPSDEV_USERNAME="$(echo $OPSDEV_USERNAME | tr -d '\r')"
+            fi
+        else
+            export OPSDEV_USERNAME="{{._username_}}"
+            if test -e "$OPS_PWD/.env.{{._username_}}"
+            then source "$OPS_PWD/.env.{{._username_}}"
             fi
-        else OPSDEV_APIHOST="{{._apihost_}}"
         fi
-        if test -z "{{._username_}}"
+        if test -z "{{._apihost_}}"
         then
-            if test -n "$OPS_USERNAME"
+            if test -z "$OPS_APIHOST"
             then
-                OPSDEV_USERNAME="$OPS_USERNAME"
+                echo -n "Enter Apihost: "
+                read OPSDEV_APIHOST
+                export OPSDEV_APIHOST="$(echo $OPSDEV_APIHOST | tr -d '\r')"
             else
-              echo -n "Enter Username: "
-              read OPSDEV_USERNAME
-              OPSDEV_USERNAME="$(echo $OPSDEV_USERNAME | tr -d '\r')"
+                export OPSDEV_APIHOST="$OPS_APIHOST"
             fi
-        else OPSDEV_USERNAME="{{._username_}}"
+        else export OPSDEV_APIHOST="{{._apihost_}}"
         fi
         echo "Logging user $OPSDEV_USERNAME in $OPSDEV_APIHOST"
         if test -n "$OPS_PASSWORD"
@@ -188,10 +192,17 @@ tasks:
           config OPSDEV_USERNAME="$OPSDEV_USERNAME"
           config 
OPSDEV_HOST="$OPSDEV_HOST_PROT://$OPSDEV_USERNAME.$OPSDEV_HOST_URL"
           source ~/.wskprops
-        else 
-          false
         fi
-  
+        if {{.__pin}}
+        then
+          touch $OPS_PWD/.env
+          mv $OPS_PWD/.env $OPS_PWD/.env.$$
+          cat $OPS_PWD/.env.$$ | rg -v "AUTH_CHECK=" >$OPS_PWD/.env || true
+          echo "AUTH_CHECK=$AUTH" >>$OPS_PWD/.env
+        fi
+
+
+
   poll:
     silent: true
     desc: poll activation logs
@@ -223,73 +234,86 @@ tasks:
         then source $OPS_PWD/packages/.env
         fi
         if test -n "$AUTH_CHECK"
-        then if test "$AUTH_CHECK" != "$AUTH" 
-             then echo "WARNING: wrong deploy! You are logged in a different 
user than your configured AUTH_CHECK" ; exit 1
+        then if test "$AUTH_CHECK" != "$AUTH"
+             then echo "WARNING: wrong deploy! You are logged in a different 
user than your pinned one and configured in .env as AUTH_CHECK" ; exit 1
              fi
         fi
+
         if {{.__dry_run}}
         then DRY="--dry-run" ; ECHO='echo'
         else DRY="" ; ECHO=""
         fi
-        if test -z "{{._action_}}"
+
+        if {{.__web}}
+        then deploy_packages=""
+        else deploy_packages="1"
+        fi
+
+        if {{.__packages}}
+        then deploy_web=""
+        else deploy_web="1"
+        fi
+
+        if test -n "{{._action_}}"
         then
-            bun {{.TASKFILE_DIR}}/deploy/index.js "$OPS_PWD" -d $DRY
-            OPS_UPLOAD_FOLDER=`bun {{.TASKFILE_DIR}}/deploy/info.js upload web`
-            echo "UPLOAD ASSETS FROM ${OPS_UPLOAD_FOLDER}"
-            $ECHO $OPS util upload ${OPS_UPLOAD_FOLDER:-web}
-            echo "URL: $OPSDEV_HOST"
+            bun {{.TASKFILE_DIR}}/deploy/index.js "$OPS_PWD" -s 
"{{._action_}}" $DRY
         else
-            bun {{.TASKFILE_DIR}}/deploy/index.js "$OPS_PWD" -s 
"{{._action_}}" $DRY            
+            if [ -n "$deploy_packages" ]
+            then
+                bun {{.TASKFILE_DIR}}/deploy/index.js "$OPS_PWD" -d $DRY
+            fi
+            if [ -n "$deploy_web" ]
+            then
+              OPS_UPLOAD_FOLDER=`bun {{.TASKFILE_DIR}}/deploy/info.js upload 
web`
+              echo "UPLOAD ASSETS FROM ${OPS_UPLOAD_FOLDER}"
+              $ECHO $OPS util upload ${OPS_UPLOAD_FOLDER:-web}
+              echo "URL: $OPSDEV_HOST"
+            fi
         fi
 
   undeploy:
-    desc: undeploy all the actions
-    prompt: "are you sure you want to remove all actions and packages?"
+    desc: undeploy actions and packages from the current project
     silent: true
     cmds:
       - task: prereq
-      - >
-        $OPS action list
-        | awk  'NR>1 { print $1}' 
-        | while read action ;
-        do  if {{.__dry_run}}
-            then echo '$' $OPS action delete "$action"
-            else $OPS action delete "$action"
-            fi
-        done
-      - >
-        $OPS package list
-        | awk  'NR>1 { print $1}'
-        | while read package ;
-        do if {{.__dry_run}}
-           then echo '$' $OPS package delete "$package" 
-           else $OPS package delete "$package" 
-           fi
-        done
+      - |
+        if {{.__dry_run}}
+        then DRY="--dry-run"
+        else DRY=""
+        fi
+        # Check if an action argument is provided
+        if test -n "{{._action_}}"
+        then
+          # Undeploy a specific action
+          echo "Undeploying specific action: {{._action_}}"
+          bun {{.TASKFILE_DIR}}/deploy/index.js "$OPS_PWD" -u -s 
"{{._action_}}" $DRY
+        else
+          # Undeploy all actions and packages from the current project
+          bun {{.TASKFILE_DIR}}/deploy/index.js "$OPS_PWD" -u $DRY
+        fi
       - >
         if {{.__dry_run}}
           then echo '$' $OPS util clean
           else $OPS util clean
         fi
-        
+
 
   clean:
     silent: true
     cmds:
       - task: kill
       - |
-        if test -d "$OPS_PWD/packages" 
-        then 
+        if test -d "$OPS_PWD/packages"
+        then
            echo "*** removing virtualenv"
            /bin/rm -rvf "$OPS_PWD"/packages/*/*/virtualenv/
            echo "*** removing node_modules"
            /bin/rm -rvf "$OPS_PWD"/packages/*/*/node_modules/
            echo "*** removing .zip"
-           /bin/rm -vf "$OPS_PWD"/packages/*/*.zip 
+           /bin/rm -vf "$OPS_PWD"/packages/*/*.zip
         else die "no packages in current directory"
         fi
 
-
   devel:
     interactive: true
     silent: true
@@ -305,7 +329,7 @@ tasks:
         then source $OPS_PWD/packages/.env
         fi
         if test -n "$AUTH_CHECK"
-        then if test "$AUTH_CHECK" != "$AUTH" 
+        then if test "$AUTH_CHECK" != "$AUTH"
              then echo "WARNING: wrong deploy! You are logged in a different 
user than your configured AUTH_CHECK" ; exit 1
              fi
         fi
@@ -334,7 +358,6 @@ tasks:
         bun {{.TASKFILE_DIR}}/deploy/index.js "$OPS_PWD" -w $FAST $DRY
         true
 
-
   shell:
     desc: open a bash shell with the current environment
     interactive: true
diff --git a/ide/python/build-venv.sh b/ide/python/build-venv.sh
index cf94ce5..2a806a1 100644
--- a/ide/python/build-venv.sh
+++ b/ide/python/build-venv.sh
@@ -20,13 +20,9 @@ DIR="${1:?directory}"
 ZIP="${2:?zip file}"
 cd "$DIR"
 if ! test -d virtualenv
-  then virtualenv virtualenv
+then uv venv virtualenv --python=/usr/bin/python3
 fi
-source virtualenv/bin/activate
-pip install --upgrade pip
-pip install -r  requirements.txt
-virtualenv/bin/python -m pip uninstall -y -q setuptools wheel pip
-touch virtualenv/bin/activate
+uv pip install --python=virtualenv/bin/python -r requirements.txt
 if test -f "$ZIP"
   then rm "$ZIP"
 fi
diff --git a/ide/python/installer.code-workspace 
b/ide/python/installer.code-workspace
new file mode 100644
index 0000000..0a4554e
--- /dev/null
+++ b/ide/python/installer.code-workspace
@@ -0,0 +1,17 @@
+{
+       "folders": [
+               {
+                       "path": "../../.."
+               },
+               {
+                       "path": "../../../../python-starter"
+               }
+       ],
+       "settings": {
+               "workbench.colorCustomizations": {
+                       "activityBar.background": "#5A0F2E",
+                       "titleBar.activeBackground": "#7E1541",
+                       "titleBar.activeForeground": "#FEFBFD"
+               }
+       }
+}
\ No newline at end of file
diff --git a/ide/util/opsfile.yml b/ide/util/opsfile.yml
index 0d30e1b..7ff0c9d 100644
--- a/ide/util/opsfile.yml
+++ b/ide/util/opsfile.yml
@@ -30,11 +30,11 @@ tasks:
       then $OPS ide python zip DIR="$($OPS -opspath "packages/{{.A}}")"
       elif test -e "packages/{{.A}}/package.json"
       then $OPS ide nodejs zip DIR="$($OPS -opspath "packages/{{.A}}")"
-      else echo "no zip environment"
+      else true #echo "no zip environment"
       fi
 
   action:
-    desc: build an action filling its zip file 
+    desc: build an action filling its zip file
     silent: true
     requires: { vars: [A]}
     cmds:
diff --git a/opsroot.json b/opsroot.json
index ac25685..677fac3 100644
--- a/opsroot.json
+++ b/opsroot.json
@@ -5,11 +5,11 @@
             "coreutils": "arch b2sum b3sum base32 basename basenc cat chgrp 
chmod chown chroot cksum comm cp csplit cut date dd df dir dircolors dirname du 
env expand expr factor fmt fold groups hashsum head hostid hostname id install 
join kill link ln logname ls md5sum mkdir mkfifo mknod mktemp more mv nice nl 
nohup nproc numfmt od paste pathchk pinky pr printenv printf ptx pwd readlink 
realpath rm rmdir seq sha1sum sha224sum sha256sum sha3-224sum sha3-256sum 
sha3-384sum sha3-512sum sha384sum [...]
        },
        "images": {
-         "operator": 
"registry.hub.docker.com/apache/openserverless-operator:0.1.0-incubating.2510012114",
+         "operator": 
"registry.hub.docker.com/apache/openserverless-operator:0.1.0-incubating.2512151437",
          "controller": 
"ghcr.io/nuvolaris/openwhisk-controller:3.1.0-mastrogpt.2402101445",
          "invoker": 
"ghcr.io/nuvolaris/openwhisk-invoker:3.1.0-mastrogpt.2402101445",
          "streamer": 
"registry.hub.docker.com/apache/openserverless-streamer:0.1.0-incubating.2505031325",
-         "systemapi": 
"registry.hub.docker.com/apache/openserverless-admin-api:0.1.0-incubating.2508161629"
+         "systemapi": 
"registry.hub.docker.com/apache/openserverless-admin-api:0.1.0-incubating.2512101357"
         }
     }
 }
diff --git a/prereq.yml b/prereq.yml
index 6bc8edd..e5bac6d 100644
--- a/prereq.yml
+++ b/prereq.yml
@@ -8,15 +8,15 @@ vars:
   DRY: ""
 
 tasks:
- 
+
   download-and-extract:
      requires:
-       vars: 
+       vars:
        - FILE
        - URL
        - OUT
      cmds:
-     - echo "{{.OUT}} {{.FILE}} {{.URL}} {{.FILETAR}}" 
+     - echo "{{.OUT}} {{.FILE}} {{.URL}} {{.FILETAR}}"
      - curl -sL "{{.URL}}" -o {{.FILE}}
      - extract "{{.FILE}}" "{{.OUT}}{{.EXE}}"
      - remove "{{.FILE}}"
@@ -48,7 +48,7 @@ tasks:
     cmds:
     - curl -sL "{{.URL}}"  -o "{{.FILE}}"
     - |
-      if test "{{.OS}}" == "windows" 
+      if test "{{.OS}}" == "windows"
       then rename 7zr.exe 7zz.exe
       else extract "{{.FILE}}" 7zz
       fi
@@ -66,11 +66,11 @@ tasks:
             linux-amd64) echo "x86_64-unknown-linux-gnu" ;;
             linux-arm64) echo "aarch64-unknown-linux-gnu" ;;
             darwin-amd64) echo "x86_64-apple-darwin" ;;
-            darwin-arm64) echo "aarch64-apple-darwin" ;; 
+            darwin-arm64) echo "aarch64-apple-darwin" ;;
             windows-*) echo "x86_64-pc-windows-gnu" ;;
             *) echo "unknown" ;;
             esac
-      URL: 
"https://github.com/uutils/coreutils/releases/download/{{.VERSION}}/coreutils-{{.VERSION}}-{{.SUFFIX}}{{.ARC}}";
 
+      URL: 
"https://github.com/uutils/coreutils/releases/download/{{.VERSION}}/coreutils-{{.VERSION}}-{{.SUFFIX}}{{.ARC}}";
       FILE: "{{base .URL}}"
       OUT: coreutils
     cmds:
@@ -106,7 +106,7 @@ tasks:
   kind:
     desc: download kind
     vars:
-      VERSION: 0.29.0
+      VERSION: 0.30.0
     cmds:
       - task: download
         vars:
@@ -118,7 +118,7 @@ tasks:
     vars:
       VERSION: 0.13.6
       BASE: "https://github.com/alexellis/k3sup/releases/download/{{.VERSION}}";
-      FILE: 
+      FILE:
         sh: |
             case {{.OS}}-{{.ARCH}} in
             (linux-amd64)   echo k3sup ;;
@@ -132,7 +132,7 @@ tasks:
         vars:
           URL: "{{.BASE}}/{{.FILE}}"
           OUT: k3sup
-  
+
   rg:
     desc: download ripgrep
     vars:
@@ -145,11 +145,11 @@ tasks:
             linux-amd64) echo "x86_64-unknown-linux-musl" ;;
             linux-arm64) echo "aarch64-unknown-linux-gnu" ;;
             darwin-amd64) echo "x86_64-apple-darwin" ;;
-            darwin-arm64) echo "aarch64-apple-darwin" ;; 
+            darwin-arm64) echo "aarch64-apple-darwin" ;;
             windows-*) echo "x86_64-pc-windows-msvc" ;;
             *) echo "unknown" ;;
             esac
-      URL: 
"https://github.com/BurntSushi/ripgrep/releases/download/{{.VERSION}}/ripgrep-{{.VERSION}}-{{.SUFFIX}}{{.ARC}}";
 
+      URL: 
"https://github.com/BurntSushi/ripgrep/releases/download/{{.VERSION}}/ripgrep-{{.VERSION}}-{{.SUFFIX}}{{.ARC}}";
       FILE: "{{base .URL}}"
       OUT: rg
     cmds:
@@ -280,7 +280,7 @@ tasks:
       fi
       EOF
         executable zip
-      ;; 
+      ;;
       windows-*)
          curl -sL "{{.ZIP_EXE}}" -o zip.exe
          executable zip.exe
@@ -290,31 +290,31 @@ tasks:
   #kubefwd:
   #  desc: download kubefwd
   #  vars:
-  #    VERSION: "1.25.8"
+  #    VERSION: "1.25.3"
   #    SUFFIX:
   #      sh: |
   #          case "{{.OS}}-{{.ARCH}}" in
-  #         linux-amd64) echo "_Linux_x86_64" ;;
-  #         linux-arm64) echo "_Linux_arm64" ;;
-  #         darwin-amd64) echo "_Darwin_x86_64" ;;
-  #         darwin-arm64) echo "_Darwin_arm64" ;; 
-  #         windows-*) echo "_Windows_x86_64" ;;
-  #         *) echo "unknown" ;;
-  #         esac
-  #         
#https://github.com/txn2/kubefwd/releases/download/1.22.5/kubefwd_Darwin_x86_64.tar.gz
-  #   URL: 
"https://github.com/txn2/kubefwd/releases/download/{{.VERSION}}/kubefwd{{.SUFFIX}}{{.ARC}}";
 
-  #   FILE: "{{base .URL}}"
-  #   OUT: kubefwd
-  # cmds:
-  # - task: download-and-extract
-  #   vars:
-  #     URL: "{{.URL}}"
-  #     FILE: "{{.FILE}}"
-  #     OUT: kubefwd
+  #          linux-amd64) echo "_Linux_x86_64" ;;
+  #          linux-arm64) echo "_Linux_arm64" ;;
+  #          darwin-amd64) echo "_Darwin_x86_64" ;;
+  #          darwin-arm64) echo "_Darwin_arm64" ;;
+  #          windows-*) echo "_Windows_x86_64" ;;
+  #          *) echo "unknown" ;;
+  #          esac
+  #          
#https://github.com/txn2/kubefwd/releases/download/1.22.5/kubefwd_Darwin_x86_64.tar.gz
+  #    URL: 
"https://github.com/txn2/kubefwd/releases/download/{{.VERSION}}/kubefwd{{.SUFFIX}}{{.ARC}}";
+  #    FILE: "{{base .URL}}"
+  #    OUT: kubefwd
+  #  cmds:
+  #  - task: download-and-extract
+  #    vars:
+  #      URL: "{{.URL}}"
+  #      FILE: "{{.FILE}}"
+  #      OUT: kubefwd
 
   all:
     - task: 7zz
-    - task: coreutils    
+    - task: coreutils
     - task: bun
     - task: kind
     - task: k3sup
@@ -322,14 +322,14 @@ tasks:
     - task: rg
     - task: uv
     - task: helm
-    #- task: kubefwd
     - task: yq
     # do not test zip as it is a special case
     #- task: zip
     # commented out for now
+    #- task: kubefwd
     #- task: hugo
     #- task: k3d
-    
+
   test:
     vars:
       DIR: "{{.OPS_PWD}}/bin/{{.OS}}-{{.ARCH}}"
@@ -340,7 +340,7 @@ tasks:
       cd "{{.DIR}}"
       {{.DRY}} ops -task -t ../../prereq.yml -d "{{.DIR}}" all
     - task: check
-      
+
   check:
     vars:
       DIR: "{{.OPS_PWD}}/bin/{{.OS}}-{{.ARCH}}"
@@ -359,11 +359,11 @@ tasks:
     cmds:
     - for: sources
       cmd: filetype -m "{{.ITEM}}" | rg {{.FILETYPE}}
- 
+
   tests:
    - __OS=linux   __ARCH=amd64 ops -task -t prereq.yml test
    - __OS=linux   __ARCH=arm64 ops -task -t prereq.yml test
    - __OS=darwin  __ARCH=amd64 ops -task -t prereq.yml test
    - __OS=darwin  __ARCH=arm64 ops -task -t prereq.yml test
    - __OS=windows __ARCH=amd64 ops -task -t prereq.yml test
-      
+
diff --git a/setup/docker/ingress-deploy.yaml b/setup/docker/ingress-deploy.yaml
index 55712e7..0e7a16c 100644
--- a/setup/docker/ingress-deploy.yaml
+++ b/setup/docker/ingress-deploy.yaml
@@ -36,18 +36,6 @@ metadata:
   name: ingress-nginx
   namespace: ingress-nginx
 ---
-apiVersion: v1
-kind: ServiceAccount
-metadata:
-  labels:
-    app.kubernetes.io/component: admission-webhook
-    app.kubernetes.io/instance: ingress-nginx
-    app.kubernetes.io/name: ingress-nginx
-    app.kubernetes.io/part-of: ingress-nginx
-    app.kubernetes.io/version: 1.7.0
-  name: ingress-nginx-admission
-  namespace: ingress-nginx
----
 apiVersion: rbac.authorization.k8s.io/v1
 kind: Role
 metadata:
@@ -139,26 +127,6 @@ rules:
   - get
 ---
 apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
-metadata:
-  labels:
-    app.kubernetes.io/component: admission-webhook
-    app.kubernetes.io/instance: ingress-nginx
-    app.kubernetes.io/name: ingress-nginx
-    app.kubernetes.io/part-of: ingress-nginx
-    app.kubernetes.io/version: 1.7.0
-  name: ingress-nginx-admission
-  namespace: ingress-nginx
-rules:
-- apiGroups:
-  - ""
-  resources:
-  - secrets
-  verbs:
-  - get
-  - create
----
-apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   labels:
@@ -240,25 +208,6 @@ rules:
   - get
 ---
 apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  labels:
-    app.kubernetes.io/component: admission-webhook
-    app.kubernetes.io/instance: ingress-nginx
-    app.kubernetes.io/name: ingress-nginx
-    app.kubernetes.io/part-of: ingress-nginx
-    app.kubernetes.io/version: 1.7.0
-  name: ingress-nginx-admission
-rules:
-- apiGroups:
-  - admissionregistration.k8s.io
-  resources:
-  - validatingwebhookconfigurations
-  verbs:
-  - get
-  - update
----
-apiVersion: rbac.authorization.k8s.io/v1
 kind: RoleBinding
 metadata:
   labels:
@@ -279,26 +228,6 @@ subjects:
   namespace: ingress-nginx
 ---
 apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
-metadata:
-  labels:
-    app.kubernetes.io/component: admission-webhook
-    app.kubernetes.io/instance: ingress-nginx
-    app.kubernetes.io/name: ingress-nginx
-    app.kubernetes.io/part-of: ingress-nginx
-    app.kubernetes.io/version: 1.7.0
-  name: ingress-nginx-admission
-  namespace: ingress-nginx
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: Role
-  name: ingress-nginx-admission
-subjects:
-- kind: ServiceAccount
-  name: ingress-nginx-admission
-  namespace: ingress-nginx
----
-apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
   labels:
@@ -316,25 +245,6 @@ subjects:
   name: ingress-nginx
   namespace: ingress-nginx
 ---
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  labels:
-    app.kubernetes.io/component: admission-webhook
-    app.kubernetes.io/instance: ingress-nginx
-    app.kubernetes.io/name: ingress-nginx
-    app.kubernetes.io/part-of: ingress-nginx
-    app.kubernetes.io/version: 1.7.0
-  name: ingress-nginx-admission
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: ingress-nginx-admission
-subjects:
-- kind: ServiceAccount
-  name: ingress-nginx-admission
-  namespace: ingress-nginx
----
 apiVersion: v1
 data:
   allow-snippet-annotations: "true"
@@ -381,29 +291,6 @@ spec:
     app.kubernetes.io/name: ingress-nginx
   type: NodePort
 ---
-apiVersion: v1
-kind: Service
-metadata:
-  labels:
-    app.kubernetes.io/component: controller
-    app.kubernetes.io/instance: ingress-nginx
-    app.kubernetes.io/name: ingress-nginx
-    app.kubernetes.io/part-of: ingress-nginx
-    app.kubernetes.io/version: 1.7.0
-  name: ingress-nginx-controller-admission
-  namespace: ingress-nginx
-spec:
-  ports:
-  - appProtocol: https
-    name: https-webhook
-    port: 443
-    targetPort: webhook
-  selector:
-    app.kubernetes.io/component: controller
-    app.kubernetes.io/instance: ingress-nginx
-    app.kubernetes.io/name: ingress-nginx
-  type: ClusterIP
----
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -443,9 +330,6 @@ spec:
         - --controller-class=k8s.io/ingress-nginx
         - --ingress-class=nginx
         - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
-        - --validating-webhook=:8443
-        - --validating-webhook-certificate=/usr/local/certificates/cert
-        - --validating-webhook-key=/usr/local/certificates/key
         - --watch-ingress-without-class=true
         - --publish-status-address=localhost
         env:
@@ -459,7 +343,7 @@ spec:
               fieldPath: metadata.namespace
         - name: LD_PRELOAD
           value: /usr/local/lib/libmimalloc.so
-        image: 
registry.k8s.io/ingress-nginx/controller:v1.7.0@sha256:7612338342a1e7b8090bef78f2a04fffcadd548ccaabe8a47bf7758ff549a5f7
+        image: registry.k8s.io/ingress-nginx/controller:v1.7.0
         imagePullPolicy: IfNotPresent
         lifecycle:
           preStop:
@@ -486,9 +370,6 @@ spec:
           hostPort: 443
           name: https
           protocol: TCP
-        - containerPort: 8443
-          name: webhook
-          protocol: TCP
         readinessProbe:
           failureThreshold: 3
           httpGet:
@@ -511,10 +392,6 @@ spec:
             drop:
             - ALL
           runAsUser: 101
-        volumeMounts:
-        - mountPath: /usr/local/certificates/
-          name: webhook-cert
-          readOnly: true
       dnsPolicy: ClusterFirst
       nodeSelector:
         ingress-ready: "true"
@@ -528,106 +405,6 @@ spec:
       - effect: NoSchedule
         key: node-role.kubernetes.io/control-plane
         operator: Equal
-      volumes:
-      - name: webhook-cert
-        secret:
-          secretName: ingress-nginx-admission
----
-apiVersion: batch/v1
-kind: Job
-metadata:
-  labels:
-    app.kubernetes.io/component: admission-webhook
-    app.kubernetes.io/instance: ingress-nginx
-    app.kubernetes.io/name: ingress-nginx
-    app.kubernetes.io/part-of: ingress-nginx
-    app.kubernetes.io/version: 1.7.0
-  name: ingress-nginx-admission-create
-  namespace: ingress-nginx
-spec:
-  template:
-    metadata:
-      labels:
-        app.kubernetes.io/component: admission-webhook
-        app.kubernetes.io/instance: ingress-nginx
-        app.kubernetes.io/name: ingress-nginx
-        app.kubernetes.io/part-of: ingress-nginx
-        app.kubernetes.io/version: 1.7.0
-      name: ingress-nginx-admission-create
-    spec:
-      containers:
-      - args:
-        - create
-        - 
--host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc
-        - --namespace=$(POD_NAMESPACE)
-        - --secret-name=ingress-nginx-admission
-        env:
-        - name: POD_NAMESPACE
-          valueFrom:
-            fieldRef:
-              fieldPath: metadata.namespace
-        image: 
registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230312-helm-chart-4.5.2-28-g66a760794@sha256:01d181618f270f2a96c04006f33b2699ad3ccb02da48d0f89b22abce084b292f
-        imagePullPolicy: IfNotPresent
-        name: create
-        securityContext:
-          allowPrivilegeEscalation: false
-      nodeSelector:
-        kubernetes.io/os: linux
-      restartPolicy: OnFailure
-      securityContext:
-        fsGroup: 2000
-        runAsNonRoot: true
-        runAsUser: 2000
-      serviceAccountName: ingress-nginx-admission
----
-apiVersion: batch/v1
-kind: Job
-metadata:
-  labels:
-    app.kubernetes.io/component: admission-webhook
-    app.kubernetes.io/instance: ingress-nginx
-    app.kubernetes.io/name: ingress-nginx
-    app.kubernetes.io/part-of: ingress-nginx
-    app.kubernetes.io/version: 1.7.0
-  name: ingress-nginx-admission-patch
-  namespace: ingress-nginx
-spec:
-  template:
-    metadata:
-      labels:
-        app.kubernetes.io/component: admission-webhook
-        app.kubernetes.io/instance: ingress-nginx
-        app.kubernetes.io/name: ingress-nginx
-        app.kubernetes.io/part-of: ingress-nginx
-        app.kubernetes.io/version: 1.7.0
-      name: ingress-nginx-admission-patch
-    spec:
-      containers:
-      - args:
-        - patch
-        - --webhook-name=ingress-nginx-admission
-        - --namespace=$(POD_NAMESPACE)
-        - --patch-mutating=false
-        - --secret-name=ingress-nginx-admission
-        - --patch-failure-policy=Fail
-        env:
-        - name: POD_NAMESPACE
-          valueFrom:
-            fieldRef:
-              fieldPath: metadata.namespace
-        image: 
registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230312-helm-chart-4.5.2-28-g66a760794@sha256:01d181618f270f2a96c04006f33b2699ad3ccb02da48d0f89b22abce084b292f
-        imagePullPolicy: IfNotPresent
-        name: patch
-        securityContext:
-          allowPrivilegeEscalation: false
-      nodeSelector:
-        kubernetes.io/os: linux
-      restartPolicy: OnFailure
-      securityContext:
-        fsGroup: 2000
-        runAsNonRoot: true
-        runAsUser: 2000
-      serviceAccountName: ingress-nginx-admission
 ---
 apiVersion: networking.k8s.io/v1
 kind: IngressClass
@@ -641,36 +418,3 @@ metadata:
   name: nginx
 spec:
   controller: k8s.io/ingress-nginx
----
-apiVersion: admissionregistration.k8s.io/v1
-kind: ValidatingWebhookConfiguration
-metadata:
-  labels:
-    app.kubernetes.io/component: admission-webhook
-    app.kubernetes.io/instance: ingress-nginx
-    app.kubernetes.io/name: ingress-nginx
-    app.kubernetes.io/part-of: ingress-nginx
-    app.kubernetes.io/version: 1.7.0
-  name: ingress-nginx-admission
-webhooks:
-- admissionReviewVersions:
-  - v1
-  clientConfig:
-    service:
-      name: ingress-nginx-controller-admission
-      namespace: ingress-nginx
-      path: /networking/v1/ingresses
-  failurePolicy: Fail
-  matchPolicy: Equivalent
-  name: validate.nginx.ingress.kubernetes.io
-  rules:
-  - apiGroups:
-    - networking.k8s.io
-    apiVersions:
-    - v1
-    operations:
-    - CREATE
-    - UPDATE
-    resources:
-    - ingresses
-  sideEffects: None
diff --git a/setup/docker/kind.yaml b/setup/docker/kind.yaml
index 4102b17..696eaa6 100644
--- a/setup/docker/kind.yaml
+++ b/setup/docker/kind.yaml
@@ -17,8 +17,14 @@
 
 kind: Cluster
 apiVersion: kind.x-k8s.io/v1alpha4
+containerdConfigPatches:
+- |-
+  [plugins."io.containerd.grpc.v1.cri".registry]
+    config_path = "/etc/containerd/certs.d"
+
 nodes:
 - role: control-plane
+  image: kindest/node:v1.33.1
   kubeadmConfigPatches:
   - |
     kind: InitConfiguration
@@ -32,71 +38,29 @@ nodes:
   - containerPort: 443
     hostPort: 443
     protocol: TCP
-- role: worker
+  - containerPort: 30222
+    hostPort: 2222
+    protocol: TCP
   extraMounts:
-  - hostPath: $DATADIR/data
+  - hostPath: "$DATADIR/data"
     containerPath: /data
-  - hostPath: $DATADIR/openwhisk/standalone
+  - hostPath: "$DATADIR/openwhisk/standalone"
     containerPath: /openwhisk/standalone
   - hostPath: $DATADIR/openwhisk/invoker
     containerPath: /openwhisk/invoker
-  - hostPath: $DATADIR/openwhisk/controller
+  - hostPath: "$DATADIR/openwhisk/controller"
     containerPath: /openwhisk/controller
-  - hostPath: $DATADIR/couchdb
+  - hostPath: "$DATADIR/couchdb"
     containerPath: /couchdb
-  - hostPath: $DATADIR/mongodb
+  - hostPath: "$DATADIR/mongodb"
     containerPath: /mongodb
-  - hostPath: $DATADIR/redis
+  - hostPath: "$DATADIR/redis"
     containerPath: /redis
-  - hostPath: $DATADIR/zookeper/data
+  - hostPath: "$DATADIR/zookeper/data"
     containerPath: /zookeeper/data
-  - hostPath: $DATADIR/zookeeper/log
+  - hostPath: "$DATADIR/zookeeper/log"
     containerPath: /zookeeper/log
-  - hostPath: $DATADIR/kafka
+  - hostPath: "$DATADIR/kafka"
     containerPath: /kafka
-  extraPortMappings:
-  - containerPort: 30232
-    hostPort: 3232
-    protocol: TCP
-  - containerPort: 30233
-    hostPort: 3233
-    protocol: TCP
-  - containerPort: 30896
-    hostPort: 7896
-    protocol: TCP  
-  - containerPort: 30984
-    hostPort: 5984
-    protocol: TCP  
-  - containerPort: 30992 
-    hostPort: 9092
-    protocol: TCP
-  - containerPort: 30644
-    hostPort: 9644
-    protocol: TCP    
-  - containerPort: 6379
-    hostPort: 30379
-    protocol: TCP
-  - containerPort: 28017
-    hostPort: 32817
-    protocol: TCP
-  - containerPort: 32717
-    hostPort: 27017
-    protocol: TCP
-  - containerPort: 32090
-    hostPort: 9000
-    protocol: TCP 
-  - containerPort: 32091
-    hostPort: 9090
-    protocol: TCP            
-  - containerPort: 9080
-    hostPort: 9080
-    protocol: TCP            
-  - containerPort: 9010
-    hostPort: 9010
-    protocol: TCP 
-  - containerPort: 32530
-    hostPort: 19530
-    protocol: TCP 
-  - containerPort: 32531
-    hostPort: 9091
-    protocol: TCP                    
+  - hostPath: "$OPS_HOME/workspace"
+    containerPath: /workspace
diff --git a/setup/docker/opsfile.yml b/setup/docker/opsfile.yml
index b40122c..e43b194 100644
--- a/setup/docker/opsfile.yml
+++ b/setup/docker/opsfile.yml
@@ -17,26 +17,28 @@
 version: '3'
 
 vars:
+  CLUSTER: nuvolaris
   INGRESS: 
"https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml";
 
 env:
   DATADIR:
-    sh: echo $OPS_TMP/kind
-  KUBECONFIG: 
-    sh: |- 
+    sh: echo "$OPS_TMP/kind"
+  WORKSPACE:
+    sh: echo "$OPS_HOME/workspace"
+  KUBECONFIG:
+    sh: |-
         if test -e "$OPS_TMP/kubeconfig"
         then echo "$OPS_TMP/kubeconfig"
         else echo ~/.kube/config
         fi
 
 tasks:
-
   status:
     silent: true
     desc: status of the Apache OpenServerless cluster in Docker
     cmds:
     - |
-      if kind get clusters | rg nuvolaris
+      if kind get clusters | rg {{.CLUSTER}}
       then echo Cluster Apache OpenServerless up and running
       else echo Cluster Apache OpenServerless not found
       fi
@@ -51,7 +53,7 @@ tasks:
       echo "=== Kind Version"
       kind version
       echo "=== Kind Clusters"
-      kind get clusters 
+      kind get clusters
       if test -e $KUBECONFIG
       then  echo "=== Nodes"
             kubectl get nodes
@@ -60,19 +62,32 @@ tasks:
   cluster:
     silent: true
     cmds:
-    - mkdir -p "$DATADIR"
+    - mkdir -p "$DATADIR" "$OPS_HOME/workspace"
     - envsubst -i kind.yaml -o _kind.yaml
-    - > 
-      kind create cluster 
-      --kubeconfig="$OPS_TMP/kubeconfig" 
-      --wait=1m --name=nuvolaris 
+    - >
+      kind create cluster
+      --wait=2m --name=nuvolaris
       --config=_kind.yaml
- 
+    - |
+      docker exec nuvolaris-control-plane mkdir -p 
/etc/containerd/certs.d/127.0.0.1:32000
+      docker exec nuvolaris-control-plane sh -c 'cat > 
/etc/containerd/certs.d/127.0.0.1:32000/hosts.toml << EOF
+      server = "http://127.0.0.1:32000";
+
+      [host."http://127.0.0.1:32000";]
+        capabilities = ["pull", "resolve", "push"]
+        skip_verify = true
+      EOF'
+      docker exec nuvolaris-control-plane systemctl restart containerd
+      kind export kubeconfig -n nuvolaris --kubeconfig "$OPS_TMP/kubeconfig"
+      echo "waiting for kind ready..."
+      retry -t 100 -m 600 test -e $OPS_TMP/kubeconfig
+      retry kubectl --kubeconfig $OPS_TMP/kubeconfig cluster-info
+
   ingress:
     silent: true
     cmds:
-    - > 
-      kubectl --kubeconfig="$OPS_TMP/kubeconfig" 
+    - >
+      retry kubectl --kubeconfig="$OPS_TMP/kubeconfig"
       apply -f ingress-deploy.yaml
 
   check-space:
@@ -81,38 +96,6 @@ tasks:
     cmds:
       - uv run space.py
 
-  preload:
-    silent: true
-    desc: preload the Apache OpenServerless images in docker to speed up the 
cluster creation
-    ignore_errors: true
-    status:
-      - config SKIP_PRELOAD_IMAGES
-    cmds:
-    - |
-      for image in "$IMAGES_STREAMER" "$IMAGES_OPERATOR"  "$IMAGES_CONTROLLER" 
  
-      do
-        docker pull "$image" --platform "linux/{{ARCH}}"
-        if ! kind load docker-image "$image" --name nuvolaris 
-        then 
-          echo "*** Cannot preload images. You can disable preload with ops 
-config SKIP_PRELOAD_IMAGES=1"
-          echo "*** Workaround on Docker Desktop: disable 'Use containerd for 
pulling and storing images' on General"
-          echo "*** WARNING! If you skip preload, it can take a lot of time 
before the system starts. Be patient."
-          exit 1
-        fi
-        docker rmi "$image"
-      done
-    - |
-      jq -r '.runtimes[][]
-      | select(.default == true)
-      | .image
-      | "\(.prefix)/\(.name):\(.tag)"' <$OPS_ROOT/runtimes.json \
-      | rg apache/openserverless-runtime \
-      | while read -r image; do
-          docker pull "$image" --platform "linux/{{ARCH}}"
-          kind load docker-image "$image" --name nuvolaris
-          docker rmi "$image"
-        done
-
   clean-all:
     silent: true
     desc: remove all images and containers from docker
@@ -121,7 +104,7 @@ tasks:
     - echo "Cleaning all docker images and containers"
     - docker ps -qa | while read a ; do docker rm -f "$a" ; done
     - docker images -qa | while read a ; do docker rmi -f "$a" ; done
-    - docker system prune -af 
+    - docker system prune -af
     - docker volume prune -af
     - docker system df
 
@@ -129,18 +112,23 @@ tasks:
     silent: true
     desc: create the Apache OpenServerless cluster in Docker
     cmds:
-    - task: cluster
-    - task: ingress
-    - cp "$OPS_TMP/kubeconfig" "$OPS_TMP/kind.kubeconfig"
-    status:
-    - kind get clusters | rg nuvolaris
+      - |
+        if  kind get clusters | rg {{.CLUSTER}}
+        then kind export kubeconfig --name {{.CLUSTER}} --kubeconfig 
"$OPS_TMP/kubeconfig"
+        else
+           ops util freeze docker-load
+           ops setup docker cluster
+           ops util freeze kind-load
+           ops setup docker ingress
+        fi
+      - cp "$OPS_TMP/kubeconfig" "$OPS_TMP/kind-{{.CLUSTER}}.kubeconfig"
 
   delete:
     silent: true
     desc: destroy the Apache OpenServerless cluster in Docker
     cmds:
-    - kind delete clusters nuvolaris
-    - rm -f "$OPS_TMP/kubeconfig" "$OPS_TMP/kind.kubeconfig"
+    - kind delete clusters {{.CLUSTER}}
+    - rm -f "$OPS_TMP/kubeconfig" "$OPS_TMP/kind-{{.CLUSTER}}.kubeconfig"
 
   kubeconfig:
     desc: export kubeconfig
diff --git a/setup/docopts.md b/setup/docopts.md
index 926ce40..55e412a 100644
--- a/setup/docopts.md
+++ b/setup/docopts.md
@@ -24,19 +24,18 @@ Manage installation
 
 ```text
 Usage:
-  setup mini
+  setup mini [<apihost>]
   setup devcluster [--uninstall|--status|[--skip-check-ports] 
[--skip-preload-images]]
   setup cluster [<context>] [--uninstall|--status]
   setup server <server> [<user>] [--uninstall|--status]
   setup status
   setup uninstall
-  setup prereq
 ```
 
 ## Commands
 
 ```
-  setup mini          deploy mini Apache OpenServerless, slim local 
installation available as http://devel.miniops.me 
+  setup mini          deploy mini Apache OpenServerless, slim local 
installation available as http://devel.miniops.me (or your local domain)
   setup cluster       deploy Apache OpenServerless in the Kubernetes cluster 
using the <context>, default the current
   setup devcluster    deploy Apache OpenServerless in a devcluster created 
locally
                       you need Docker Desktop available with at least 6G of 
memory assigned
@@ -44,7 +43,6 @@ Usage:
                       the server must be accessible with ssh using the <user> 
with sudo power, default root
   setup status        show the status of the last installation
   setup uninstall     uninstall the last installation
-  setup prereq        validate current configuration
 ```
 
 ## Options
diff --git a/setup/kubernetes/crds/whisk-crd.yaml 
b/setup/kubernetes/crds/whisk-crd.yaml
index dff969c..73dc3e7 100644
--- a/setup/kubernetes/crds/whisk-crd.yaml
+++ b/setup/kubernetes/crds/whisk-crd.yaml
@@ -149,7 +149,10 @@ spec:
                       type: boolean
                     registry:
                       description: deploys a private registry to load on the 
fly generated action runtimes (false by default)
-                      type: boolean                                            
  
+                      type: boolean
+                    seaweedfs:
+                      description: deploys an S3 compatible layer using a 
standalone deployment of seaweedfs
+                      type: boolean                                            
                         
                   required:
                     - openwhisk
                     - couchdb
@@ -835,7 +838,50 @@ spec:
                   - mode
                   - volume-size
                   - auth
-                  - hostname                                         
+                  - hostname
+                seaweedfs:
+                  description: used to configure the internal saweedfs data 
storage service
+                  type: object
+                  properties:
+                    volume-size:
+                      description: volume size in GB, default to 60GB
+                      type: integer
+                    default-bucket-quota:
+                      description: default bucket quota, default to 1024MB
+                      type: integer
+                    nuvolaris:
+                      description: used to configure the MINIO nuvolaris user 
used for non administrative purposes
+                      type: object
+                      properties:
+                        user:
+                          type: string
+                        password:
+                          type: string
+                      required:
+                      - user
+                      - password
+                    ingress:
+                      description: configuration option for global minio 
ingresses exposure
+                      type: object
+                      properties:
+                        s3-enabled:
+                          description: boolean flag to activate SEAWEEDFS S3 
compatible ingress. Default to false
+                          type: boolean
+                        s3-hostname:
+                          description: ingress hostname to be used if true 
(normally s3.<apihost>)
+                          type: string
+                        console-enabled:
+                          description: boolean flag to expose SEAWEEDFS Filer 
UI
+                          type: boolean
+                        console-hostname:
+                          description: ingress hostname to be used (nora=mally 
filer.<apihost>)
+                          type: string
+                      required:
+                      - s3-enabled
+                      - console-enabled                           
+                  required:
+                  - volume-size
+                  - nuvolaris                                                  
         
             status:
               x-kubernetes-preserve-unknown-fields: true
               # type: object
@@ -948,4 +994,9 @@ spec:
           type: string
           priority: 0
           jsonPath: .status.whisk_create.registry
-          description: Registry                                                
                                                              
\ No newline at end of file
+          description: Registry 
+        - name: Seaweedfs
+          type: string
+          priority: 0
+          jsonPath: .status.whisk_create.seaweedfs
+          description: Seaweedfs                                               
                                                                         
\ No newline at end of file
diff --git a/setup/kubernetes/operator.yaml b/setup/kubernetes/operator.yaml
index c2a4a9f..121296b 100644
--- a/setup/kubernetes/operator.yaml
+++ b/setup/kubernetes/operator.yaml
@@ -19,8 +19,8 @@ apiVersion: apps/v1
 kind: StatefulSet
 metadata:
   name: nuvolaris-operator
-  namespace: nuvolaris  
-spec:  
+  namespace: nuvolaris
+spec:
   replicas: 1
   selector:
     matchLabels:
@@ -67,18 +67,18 @@ spec:
         - key: "nuvolaris-role"
           operator: "Equal"
           value: core
-          effect: "NoSchedule"     
+          effect: "NoSchedule"
       securityContext:
           runAsUser: 1001
-          fsGroup: 1001       
+          fsGroup: 1001
       volumes:
       - name: deploy
         persistentVolumeClaim:
-          claimName: nuvolaris-operator-deploy-pvc       
+          claimName: nuvolaris-operator-deploy-pvc
       containers:
         - name: nuvolaris-operator
           image: ${IMAGES_OPERATOR}
-          imagePullPolicy: Always
+          imagePullPolicy: IfNotPresent
           command: ["./run.sh"]
           args: ["--verbose"]
           securityContext:
@@ -87,4 +87,4 @@ spec:
                 - ALL
           volumeMounts:
           - name: deploy
-            mountPath: "/home/nuvolaris/deploy"              
\ No newline at end of file
+            mountPath: "/home/nuvolaris/deploy"
\ No newline at end of file
diff --git a/setup/kubernetes/opsfile.yml b/setup/kubernetes/opsfile.yml
index 6b536e3..733eb8c 100644
--- a/setup/kubernetes/opsfile.yml
+++ b/setup/kubernetes/opsfile.yml
@@ -31,44 +31,44 @@ env:
       fi
 
 # sets memory parameter for the controller invoker and corresponding pods
-  OPENWHISK_CONTROLLER_JAVA_OPTS: 
+  OPENWHISK_CONTROLLER_JAVA_OPTS:
     sh: |
       if [ -z "$OPENWHISK_CONTROLLER_JVMGB" ]
       then echo -Xmx2G
       else echo -Xmx"$OPENWHISK_CONTROLLER_JVMGB"G
       fi
-  OPENWHISK_INVOKER_JAVA_OPTS: 
+  OPENWHISK_INVOKER_JAVA_OPTS:
     sh: |
       if [ -z "$OPENWHISK_INVOKER_JVMGB" ]
       then echo -Xmx2G
       else echo -Xmx"$OPENWHISK_INVOKER_JVMGB"G
       fi
-  OPENWHISK_INVOKER_CONTAINER_POOL_MEMORY: 
+  OPENWHISK_INVOKER_CONTAINER_POOL_MEMORY:
     sh: |
       if [ -z "$OPENWHISK_INVOKER_CONTAINER_POOLMEMORYGB" ]
       then echo 2048m
       else pool=$OPENWHISK_INVOKER_CONTAINER_POOLMEMORYGB; echo $((pool*1024))m
       fi
-  OPENWHISK_CONTROLLER_RES_MIN_MEM: 
+  OPENWHISK_CONTROLLER_RES_MIN_MEM:
     sh: |
       if [ -z "$OPENWHISK_CONTROLLER_JVMGB" ]
       then echo 2G
       else echo "$OPENWHISK_CONTROLLER_JVMGB"G
       fi
-  OPENWHISK_CONTROLLER_RES_MAX_MEM: 
+  OPENWHISK_CONTROLLER_RES_MAX_MEM:
     sh: |
       if [ -z "$OPENWHISK_CONTROLLER_JVMGB" ]
       then echo 3G
       else mem=$OPENWHISK_CONTROLLER_JVMGB; echo $((mem+1))G
       fi
-  OPENWHISK_INVOKER_RES_MIN_MEM: 
+  OPENWHISK_INVOKER_RES_MIN_MEM:
     sh: |
       if [ -z "$OPENWHISK_INVOKER_JVMGB" ]
-      then echo 3G 
+      then echo 3G
       else mem=$OPENWHISK_INVOKER_JVMGB; echo $((mem))G
       fi
-  OPENWHISK_INVOKER_RES_MAX_MEM: 
-    sh: | 
+  OPENWHISK_INVOKER_RES_MAX_MEM:
+    sh: |
       if [ -z "$OPENWHISK_INVOKER_JVMGB" ] && [ -z 
"$OPENWHISK_INVOKER_CONTAINER_POOLMEMORYGB" ]
       then echo 4G
       else mem=$OPENWHISK_INVOKER_JVMGB; 
pool=$OPENWHISK_INVOKER_CONTAINER_POOLMEMORYGB; echo $((mem+1))G
@@ -89,10 +89,10 @@ env:
       fi
 
 tasks:
-  
+
   streamer:
     desc: deploy streamer
-  
+
   status:
     desc: show nuvolaris cluster status
     silent: true
@@ -126,7 +126,7 @@ tasks:
     status:
       - test "{{.COND}}" != "true"
     env:
-      BAR: 
+      BAR:
         sh: printf "%*s" {{.RETRY}} "" | tr ' ' '#'
     cmds:
       - test -n "{{.OBJECT}}" || die "use OBJECT=<to-wait-for>"
@@ -146,7 +146,7 @@ tasks:
         N=0
         RES=false
         while [[ $N -lt {{.RETRY}} ]]
-        do 
+        do
           printf "\rwaiting for {{.OBJECT}} ready: [${BAR:0:$((N++))}]"
           if kubectl -n nuvolaris wait --for=condition=Ready "{{.OBJECT}}" 
--timeout={{.TIMEOUT}} >/dev/null 2>&1
           then RES=true ; break
@@ -154,7 +154,7 @@ tasks:
         done
         printf "\n"
         $RES
-  
+
   wait-pod:
     #desc: wait for a pod to be available - leave the result in FILE
     silent: true
@@ -166,7 +166,7 @@ tasks:
         - COND
         - FILE
     env:
-      BAR: 
+      BAR:
         sh: printf "%*s" {{.RETRY}} "" | tr ' ' '#'
     cmds:
       - |
@@ -214,7 +214,7 @@ tasks:
       - kubectl -n nuvolaris apply -f _runtimes.yaml
     vars:
       RUNTIMES:
-        sh: |          
+        sh: |
           if test -e "$OPS_TMP/runtimes.json"
           then echo "$OPS_TMP/runtimes.json"
           else echo "$OPS_ROOT/runtimes.json"
@@ -299,41 +299,53 @@ tasks:
       - task: wait
         vars:
           OBJECT: po/redis-0
-          COND: 
+          COND:
             sh: echo "$OPERATOR_COMPONENT_REDIS"
       - task: wait
         vars:
           OBJECT: po/nuvolaris-mongodb-0
-          COND: 
+          COND:
             sh: echo "$OPERATOR_COMPONENT_MONGODB"
       - task: wait
         vars:
           OBJECT: pod/nuvolaris-postgres-1-0
-          COND: 
+          COND:
             sh: echo "$OPERATOR_COMPONENT_POSTGRES"
       - task: wait-pod
         vars:
           SELECTOR: "-l app=minio"
-          COND: 
+          COND:
             sh: echo "$OPERATOR_COMPONENT_MINIO"
           FILE: _minio
+      #- task: wait
+      #  vars:
+      #    OBJECT:
+      #      sh: echo "pod/$(cat _minio)"
+      #    COND:
+      #      sh: echo "$OPERATOR_COMPONENT_MINIO"
+      - task: wait-pod
+        vars:
+          SELECTOR: "-l app=seaweedfs"
+          COND:
+            sh: echo "$OPERATOR_COMPONENT_SEAWEEDFS"
+          FILE: _seaweedfs
       - task: wait
         vars:
           OBJECT:
-            sh: echo "pod/$(cat _minio)"
-          COND: 
-            sh: echo "$OPERATOR_COMPONENT_MINIO"
+            sh: echo "pod/$(cat _seaweedfs)"
+          COND:
+            sh: echo "$OPERATOR_COMPONENT_SEAWEEDFS"
       - task: wait-pod
         vars:
           SELECTOR: "-l name=nuvolaris-milvus"
-          COND: 
+          COND:
             sh: echo "$OPERATOR_COMPONENT_MILVUS"
           FILE: _milvus
       - task: wait
         vars:
           OBJECT:
             sh: echo "pod/$(cat _milvus)"
-          COND: 
+          COND:
             sh: echo "$OPERATOR_COMPONENT_MILVUS"
 
   create:
@@ -357,7 +369,7 @@ tasks:
     - config SKIP_OPERATOR_LAUNCH=1
     - task: create
     - config -r SKIP_OPERATOR_LAUNCH
-  
+
   deploy:
     silent: true
     desc: deploy in namespace (use namespace admin)
@@ -376,7 +388,7 @@ tasks:
     desc: delete cluster
     cmds:
       - source forcedelete.src
-      - kubectl delete clusterissuers/letsencrypt-issuer 2>/dev/null      
+      - kubectl delete clusterissuers/letsencrypt-issuer 2>/dev/null
       - kubectl -n nuvolaris delete sts/nuvolaris-operator --grace-period=0
       - kubectl -n nuvolaris delete po,sts,job,deploy,svc --all 
--grace-period=0
       - kubectl -n nuvolaris delete cm,secret,sa,role --all
@@ -395,7 +407,7 @@ tasks:
     desc: "unlock locked deletion (removing a finalizers)"
     cmds:
       - >
-        kubectl -n nuvolaris patch wsk/controller --type=merge 
+        kubectl -n nuvolaris patch wsk/controller --type=merge
         --patch '{"metadata": {"finalizers":[] } }'
 
   operator-update:
@@ -405,7 +417,7 @@ tasks:
       - "kubectl apply -f crds"
       - task: operator
       - kubectl -n nuvolaris rollout restart statefulset nuvolaris-operator
-    preconditions:      
+    preconditions:
       - sh: '[ $ENT_IMAGES_OPERATOR != $(nuv debug operator:version) ]'
         msg: "Current OpenServerless operator it is already updated to newest 
version. Request ignored."
 
@@ -414,9 +426,9 @@ tasks:
     silent: true
     cmds:
       - ops setup kubernetes prepare
-      - ops setup kubernetes whiskcrd      
-      - |        
+      - ops setup kubernetes whiskcrd
+      - |
         kubectl apply -f _whisk.yaml
       - task: wait
         vars:
-          OBJECT: pod/controller-0    
\ No newline at end of file
+          OBJECT: pod/controller-0
\ No newline at end of file
diff --git a/setup/kubernetes/whisk.yaml b/setup/kubernetes/whisk.yaml
index de6b164..93beb40 100644
--- a/setup/kubernetes/whisk.yaml
+++ b/setup/kubernetes/whisk.yaml
@@ -31,6 +31,7 @@ spec:
       slim: ${OPERATOR_CONFIG_SLIM:-false}
       affinity: ${OPERATOR_CONFIG_AFFINITY:-false}
       tolerations: ${OPERATOR_CONFIG_TOLERATIONS:-false}
+      ingressclass: ${OPERATOR_CONFIG_INGRESSCLASS:-auto}
       preload:
         only-apache: ${OPERATOR_CONFIG_PRELOAD_ONLY_APACHE:-true}    
   components:
@@ -68,6 +69,8 @@ spec:
     milvus: ${OPERATOR_COMPONENT_MILVUS:-false}
     # REGISTRY enabled or not
     registry: ${OPERATOR_COMPONENT_REGISTRY:-false}
+    # REGISTRY enabled or not
+    seaweedfs: ${OPERATOR_COMPONENT_SEAWEEDFS:-false}    
   tls:
     acme-registered-email: ${OPERATOR_CONFIG_TLSEMAIL:[email protected]}
     acme-server-url: https://acme-v02.api.letsencrypt.org/directory
@@ -247,7 +250,17 @@ spec:
     volume-size: ${REGISTRY_CONFIG_VOLUME_SIZE:-50}
     auth:
       username: ${REGISTRY_CONFIG_USERNAME:-opsuser}
-      password: $REGISTRY_CONFIG_SECRET_PUSH_PULL
+      password: ${REGISTRY_CONFIG_SECRET_PUSH_PULL:-changeme-registry}
     hostname: ${REGISTRY_CONFIG_HOSTNAME:-auto}
     ingress:
-      enabled: ${REGISTRY_CONFIG_INGRESS_ENABLED:-false}       
+      enabled: ${REGISTRY_CONFIG_INGRESS_ENABLED:-false}
+  seaweedfs:
+    volume-size:  ${STORAGE_SIZE_SEAWEEDFS:-60}
+    nuvolaris:
+      user: nuvolaris
+      password: ${SECRET_SEAWEEDFS_NUVOLARIS:-changeme-seaweedfs}
+    ingress:
+      s3-enabled: ${SEAWEEDFS_CONFIG_INGRESS_S3_ENABLED:-true}
+      s3-hostname: ${SEAWEEDFS_CONFIG_INGRESS_S3_HOSTNAME:-auto}
+      console-enabled: ${SEAWEEDFS_CONFIG_INGRESS_CONSOLE_ENABLED:-false}
+      console-hostname: ${SEAWEEDFS_CONFIG_INGRESS_CONSOLE_HOSTNAME:-auto}     
         
diff --git a/setup/nuvolaris/opsfile.yml b/setup/nuvolaris/opsfile.yml
index 558beed..b03be40 100644
--- a/setup/nuvolaris/opsfile.yml
+++ b/setup/nuvolaris/opsfile.yml
@@ -23,14 +23,14 @@ vars:
   CAT: '{{if eq .D ""}}true{{else}}cat{{end}}'
   RETRY: 100
   TIMEOUT: 10
-  ETC: 
+  ETC:
     sh: if $WSK_INSECURE ; then echo "-i" ; else echo "" ; fi
   CFG: "kubectl -n nuvolaris get wsk/controller"
   SLEEP: "500"
 
 env:
-  KUBECONFIG: 
-    sh: | 
+  KUBECONFIG:
+    sh: |
         if test -e "$OPS_TMP/kubeconfig"
         then echo "$OPS_TMP/kubeconfig"
         else echo ~/.kube/config
@@ -42,7 +42,7 @@ tasks:
     #desc: wait for apihost available
     silent: true
     env:
-      BAR: 
+      BAR:
         sh: printf "%*s" {{.RETRY}} "" | tr ' ' '#'
     cmds:
     - |
@@ -61,10 +61,10 @@ tasks:
       echo $APIHOST
 
   wait-cm:
-    desc: wait for an annotation in nuvolaris cm/config 
+    desc: wait for an annotation in nuvolaris cm/config
     silent: true
     env:
-      BAR: 
+      BAR:
         sh: printf "%*s" {{.RETRY}} "" | tr ' ' '#'
     cmds:
       - test -n "{{.JSONPATH}}" || die "use 
JSONPATH=<cm/config-jsonpath-lookup>"
@@ -72,13 +72,13 @@ tasks:
         N=0
         RES=false
         while [[ $N -lt {{.RETRY}} ]]
-        do           
+        do
           ANNOT_VALUE="$(kubectl -n nuvolaris get cm/config -o 
jsonpath='{{.JSONPATH}}')"
           if [[ -z "$ANNOT_VALUE" ]]
-          then 
+          then
             printf "\rwaiting for completing system initialization: 
[${BAR:0:$((N++))}]"
             sleep 5
-          else  
+          else
             RES=true; break
           fi
         done
@@ -89,7 +89,7 @@ tasks:
     desc: wait for an annotation in nuvolaris cm/config to be set and have a 
specific value
     silent: true
     env:
-      BAR: 
+      BAR:
         sh: printf "%*s" {{.RETRY}} "" | tr ' ' '#'
     cmds:
       - test -n "{{.JSONPATH}}" || die "use 
JSONPATH=<cm/config-jsonpath-lookup>"
@@ -101,10 +101,10 @@ tasks:
         N=0
         RES=false
         while [[ $N -lt {{.RETRY}} ]]
-        do           
+        do
           ANNOT_VALUE="$(kubectl -n nuvolaris get cm/config -o 
jsonpath='{.metadata.annotations.system_action_status}')"
           if [[ -z "$ANNOT_VALUE" ]]
-          then 
+          then
             printf "\rwaiting for completing system initialization: 
[${BAR:0:$((N++))}]"
             sleep 5
           else
@@ -120,11 +120,11 @@ tasks:
     silent: true
     cmds:
     - |
-      export OPSDEV_APIHOST=$(kubectl --kubeconfig="$KUBECONFIG" -n nuvolaris 
get cm/config -o jsonpath='{.metadata.annotations.apihost}')
+      export OPSDEV_APIHOST=$(ops util kubeget cm/config 
.metadata.annotations.apihost)
       export OPSDEV_USERNAME=nuvolaris
-      export AUTH=$(kubectl --kubeconfig="$KUBECONFIG" -n nuvolaris get 
wsk/controller -ojsonpath='{.spec.openwhisk.namespaces.nuvolaris}')
+      export AUTH=$(ops util kubeget wsk/controller 
.spec.openwhisk.namespaces.nuvolaris)
       echo Checking connection...
-      retry -t 100 -m 600 $OPS -wsk {{.ETC}} --apihost "$OPSDEV_APIHOST" 
--auth "$AUTH" namespace list
+      retry -t 100 -m 600 ops -wsk {{.ETC}} --apihost "$OPSDEV_APIHOST" --auth 
"$AUTH" namespace list
       export OPS_PASSWORD=$(kubectl -n nuvolaris get wsk/controller -ojsonpath 
-ojsonpath='{.spec.nuvolaris.password}')
       ops -login "$OPSDEV_APIHOST" "$OPSDEV_USERNAME"
       OPSDEV_HOST_PROT="$(echo "$OPSDEV_APIHOST" | awk -F '://' '{print $1}')"
@@ -167,7 +167,7 @@ tasks:
       MONGODB_URL="$(kubectl -n nuvolaris get cm/config -o 
jsonpath='{.metadata.annotations.mongodb_url}')"
       {{.RUN}} wsk {{.ETC}} action update hello/mongodb mongodb.js -p 
mongodb_url "$MONGODB_URL"
     - "{{.RUN}} wsk {{.ETC}} action invoke hello/mongodb -r | rg 'hello'"
-  
+
   redis:
     silent: true
     desc: redis test
@@ -202,7 +202,7 @@ tasks:
   postgres:
     silent: true
     desc: postgres test
-    cmds:    
+    cmds:
     - "{{.RUN}} wsk {{.ETC}} package update hello"
     - |
       POSTGRES_URL="$(kubectl -n nuvolaris get cm/config -o 
jsonpath='{.metadata.annotations.postgres_url}')"
@@ -210,7 +210,7 @@ tasks:
     - "{{.RUN}} wsk {{.ETC}} action invoke hello/postgres -r"
 
   sleep:
-    desc: sleep 
+    desc: sleep
     silent: true
     cmds:
     - "{{.RUN}} wsk action update sleep sleep.js --web=true --timeout 300000"
@@ -259,10 +259,12 @@ tasks:
             then ops -random --str 8
             else echo "{{.P}}"
             fi
+      FLAGS: '{{or .F "--all"}}'
+
     env:
-      APIHOST: 
+      APIHOST:
         sh: echo "$OPERATOR_CONFIG_HOSTPROTOCOL://$OPERATOR_CONFIG_APIHOST"
-      HOST: 
+      HOST:
         sh: echo 
"$OPERATOR_CONFIG_HOSTPROTOCOL://{{.USR}}.$OPERATOR_CONFIG_APIHOST"
     cmds:
     #- test ! -e ~/.ops/{{.USR}}.password || die "User {{.USR}} already exists 
- check .ops/{{.USR}}.password in your home for password"
@@ -272,7 +274,7 @@ tasks:
       fi
     - echo "Creating user {{.USR}} in $APIHOST"
     - |
-      ops admin adduser "{{.USR}}" "{{.USR}}@example.com" '{{.PSW}}' --all
+      ops admin adduser "{{.USR}}" "{{.USR}}@example.com" '{{.PSW}}' {{.FLAGS}}
       sleep 15
     - |
       export OPS_PASSWORD="{{.PSW}}"
diff --git a/setup/nuvolaris/streamer/nginx-template.yaml 
b/setup/nuvolaris/streamer/nginx-template.yaml
index 7202022..beb7bbc 100644
--- a/setup/nuvolaris/streamer/nginx-template.yaml
+++ b/setup/nuvolaris/streamer/nginx-template.yaml
@@ -21,10 +21,21 @@ metadata:
     nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
     nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
     nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
+
+$T    cert-manager.io/cluster-issuer: letsencrypt-prod
+$T    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
+
   name: nuvolaris-streamer-api-ingress
   namespace: nuvolaris
+
 spec:
   ingressClassName: nginx
+
+$T  tls:
+$T  - hosts:
+$T      - stream.${STREAMER_API_HOSTNAME:-localhost}
+$T    secretName: streamer-api-tls
+
   rules:
   - host: stream.${STREAMER_API_HOSTNAME:-localhost}    
     http:
diff --git a/setup/nuvolaris/streamer/opsfile.yml 
b/setup/nuvolaris/streamer/opsfile.yml
index 97a7a8a..9a0b666 100644
--- a/setup/nuvolaris/streamer/opsfile.yml
+++ b/setup/nuvolaris/streamer/opsfile.yml
@@ -16,13 +16,14 @@
 # under the License.
 version: '3'
 
-vars: 
+vars:
   VAR: ""
   MSG: ""
   VAL: ""
   RETRY: 100
   TIMEOUT: 15s
   CONTEXT: ""
+  DRY: ""
 
 env:
 
@@ -33,47 +34,50 @@ env:
       else echo ~/.kube/config
       fi
 
-  STREAMER_API_HOSTNAME: 
+  STREAMER_API_HOSTNAME:
       sh: |
-        ops util kubectl -- -n nuvolaris get ingress/apihost -o 
jsonpath='{.spec.rules[0].host}'       
-  
+        ops util kubeget ingress/apihost .spec.rules[0].host
+
   OW_APIHOST:
     sh: |
-      HOST=$(ops util kubectl -- -n nuvolaris get ingress/apihost -o 
jsonpath='{.spec.rules[0].host}')
-      if [ $OPERATOR_COMPONENT_INVOKER = true ]; 
+      HOST=$(ops util kubeget ingress/apihost .spec.rules[0].host)
+      if [ $OPERATOR_COMPONENT_INVOKER = true ];
         then echo "$HOST"
         else echo "http://controller:3233";
-      fi 
+      fi
 
   INGRESS_TYPE:
     sh: ops util ingress-type
 
 tasks:
 
-  boh:
-    desc: echo a message
-    silent: true
-    cmds:
-      - echo "$host.Name"
-  
   deploy:
     silent: true
     ignore_error: false
-    desc: deploy the streamer 
+    desc: deploy the streamer
+    vars:
+      T:
+        sh: |
+            if ops util kubeget wsk/controller .status.whisk_create.tls | rg 
on >/dev/null
+            then echo ""
+            else echo "#"
+            fi
     cmds:
     - test -e ${INGRESS_TYPE}-template.yaml || die "No avalable template for 
ingress type ${INGRESS_TYPE}."
     - test -n "$IMAGES_STREAMER" || die "IMAGES_STREAMER is not set. Please 
set it to the desired image version."
     - envsubst -i streamer-template.yaml -o _streamer.yaml > /dev/null 2>&1
-    - envsubst -i "${INGRESS_TYPE}-template.yaml" -o _ingress.yaml > /dev/null 
2>&1
-    - kubectl -n nuvolaris apply -f _streamer.yaml
+    - |
+      export T="{{.T}}"
+      envsubst -i "${INGRESS_TYPE}-template.yaml" -o _ingress.yaml > /dev/null 
2>&1
+    - "{{.DRY}} kubectl -n nuvolaris apply -f _streamer.yaml"
     #- cat _ingress.yaml
-    - kubectl -n nuvolaris apply -f _ingress.yaml
+    - "{{.DRY}} kubectl -n nuvolaris apply -f _ingress.yaml"
     - |
       echo "Streamer API deployed with HOSTNAME: 
stream.${STREAMER_API_HOSTNAME} and OW_APIHOST: ${OW_APIHOST}"
 
   undeploy:
     silent: true
-    desc: undeploy the streamer 
+    desc: undeploy the streamer
     ignore_error: true
     cmds:
     - kubectl -n nuvolaris delete sts/nuvolaris-streamer-api 
ing/nuvolaris-streamer-api-ingress
@@ -82,16 +86,16 @@ tasks:
 
   update:
     silent: true
-    desc: update the streamer 
+    desc: update the streamer
     cmds:
       - task: deploy
       - kubectl -n nuvolaris rollout restart statefulset nuvolaris-streamer-api
-    preconditions:      
+    preconditions:
       - sh: '[ $IMAGES_STREAMER != $CURRENT_STREAMER_VERSION ]'
         msg: "Current nuvolaris streamer API stateful set it is already 
updated to newest version. Request ignored."
     env:
       CURRENT_STREAMER_VERSION:
         sh: |
           echo "$(kubectl -n nuvolaris get pod/nuvolaris-streamer-api-0 
-ojsonpath='{.spec.containers[0].image}')"
-                      
-  
+
+
diff --git a/setup/nuvolaris/streamer/traefik-template.yaml 
b/setup/nuvolaris/streamer/traefik-template.yaml
index bf85cd1..24c7cb0 100644
--- a/setup/nuvolaris/streamer/traefik-template.yaml
+++ b/setup/nuvolaris/streamer/traefik-template.yaml
@@ -17,14 +17,26 @@ apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
   annotations:
-    ingressClassName: traefik
     nginx.ingress.kubernetes.io/proxy-body-size: 1024m
     traefik.ingress.kubernetes.io/transport.respondingTimeouts.idleTimeout: 
"600"
     traefik.ingress.kubernetes.io/transport.respondingTimeouts.readTimeout: 
"600"
     traefik.ingress.kubernetes.io/transport.respondingTimeouts.writeTimeout: 
"600"
+
+$T    cert-manager.io/cluster-issuer: letsencrypt-prod
+$T    traefik.ingress.kubernetes.io/router.tls: "true"
+$T    traefik.ingress.kubernetes.io/router.entrypoints: websecure,web
+
   name: nuvolaris-streamer-api-ingress
   namespace: nuvolaris
+
 spec:
+  ingressClassName: traefik
+
+$T  tls:
+$T    - hosts:
+$T        - stream.${STREAMER_API_HOSTNAME:-localhost}
+$T      secretName: streamer-api-tls
+
   rules:
   - host: stream.${STREAMER_API_HOSTNAME:-localhost}
     http:
@@ -35,4 +47,4 @@ spec:
             port:
               number: 8080
         path: /
-        pathType: Prefix
\ No newline at end of file
+        pathType: Prefix
diff --git a/ide/python/build-venv.sh 
b/setup/nuvolaris/system-api/deploy/buildkit/buildkitd.toml
similarity index 60%
copy from ide/python/build-venv.sh
copy to setup/nuvolaris/system-api/deploy/buildkit/buildkitd.toml
index cf94ce5..37460c4 100644
--- a/ide/python/build-venv.sh
+++ b/setup/nuvolaris/system-api/deploy/buildkit/buildkitd.toml
@@ -1,4 +1,3 @@
-#!/bin/bash
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information
@@ -15,23 +14,28 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+#
+# =========================
+# Worker OCI (rootlesskit)
+# =========================
+[worker.oci]
+  enabled = true
+  rootless = true
+  no-process-sandbox = true
+  snapshotter = "overlayfs"   # usa overlayfs se il kernel lo consente
 
-DIR="${1:?directory}"
-ZIP="${2:?zip file}"
-cd "$DIR"
-if ! test -d virtualenv
-  then virtualenv virtualenv
-fi
-source virtualenv/bin/activate
-pip install --upgrade pip
-pip install -r  requirements.txt
-virtualenv/bin/python -m pip uninstall -y -q setuptools wheel pip
-touch virtualenv/bin/activate
-if test -f "$ZIP"
-  then rm "$ZIP"
-fi
-7zz a "$ZIP" -tzip virtualenv >/dev/null
-ls -l $ZIP
-date >virtualenv/date
+[worker.containerd]
+  enabled = false
 
+# =========================
+# Registry HTTP insicuro
+# =========================
+[registry."nuvolaris-registry-svc:5000"]
+  #insecure = true
+  http = true
 
+# =========================
+# Logging
+# =========================
+[log]
+  level = "trace"
\ No newline at end of file
diff --git a/setup/nuvolaris/system-api/opsfile.yml 
b/setup/nuvolaris/system-api/opsfile.yml
index 79a2768..f5e1767 100644
--- a/setup/nuvolaris/system-api/opsfile.yml
+++ b/setup/nuvolaris/system-api/opsfile.yml
@@ -36,54 +36,99 @@ env:
   SYS_API_CDB_HOST: couchdb
 
   SYS_API_CDB_USER:
-    sh: $OPS debug kube ctl CMD="-n nuvolaris get wsk/controller -o 
jsonpath='{.spec.couchdb.admin.user}'"
+    #sh: ops util kubectl -- --kubeconfig "$KUBECONFIG" -n nuvolaris get 
wsk/controller -o jsonpath='{.spec.couchdb.admin.user}'
+    sh: ops util kubeget wsk/controller .spec.couchdb.admin.user
 
   SYS_API_CDB_PASSWORD:
-    sh: $OPS debug kube ctl CMD="-n nuvolaris get wsk/controller -o 
jsonpath='{.spec.couchdb.admin.password}'"
+    #sh: ops util kubectl -- --kubeconfig "$KUBECONFIG" -n nuvolaris get 
wsk/controller -o jsonpath='{.spec.couchdb.admin.password}'
+    sh: ops util kubeget wsk/controller .spec.couchdb.admin.password
 
   SYS_API_HOSTNAME:
-    sh: $OPS debug kube ctl CMD="-n nuvolaris get ingress/apihost -o 
jsonpath='{.spec.rules[0].host}'"
+    #sh: ops util kubectl -- --kubeconfig "$KUBECONFIG" -n nuvolaris get 
ingress/apihost -o jsonpath='{.spec.rules[0].host}'
+    sh: ops util kubeget ingress/apihost .spec.rules[0].host
+
+  REGISTRY_PASS:
+    #sh: ops util kubectl -- --kubeconfig "$KUBECONFIG" -n nuvolaris get 
cm/config  -o jsonpath='{.metadata.annotations.registry_password}'
+    sh: ops util kubeget cm/config .metadata.annotations.registry_password
 
   INGRESS_TYPE:
-    sh: $OPS util ingress-type
+    sh: ops util ingress-type
 
 tasks:
+  install-toml:
+    desc: Update the buildkitd.toml file config map
+    silent: true
+    cmds:
+      - task: remove-toml
+      - kubectl -n nuvolaris create configmap nuvolaris-buildkitd-conf 
--from-file="$OPS_ROOT/setup/nuvolaris/system-api/deploy/buildkit/buildkitd.toml"
+
+  remove-toml:
+    desc: Remove the buildkitd.toml config map
+    silent: true
+    cmds:
+      - |
+        if kubectl -n nuvolaris get cm nuvolaris-buildkitd-conf >/dev/null 
2>&1;
+          then kubectl -n nuvolaris delete configmap nuvolaris-buildkitd-conf
+        fi
+
+  install-secret:
+    desc: Create secret to push to internal registry
+    silent: true
+    cmds:
+      - task: remove-secret
+      - |
+        if ! kubectl -n nuvolaris get secret registry-pull-secret-int 
>/dev/null 2>&1;
+        then kubectl -n nuvolaris create secret docker-registry 
registry-pull-secret-int --docker-server=http://nuvolaris-registry-svc:5000 
--docker-username=opsuser --docker-password=${REGISTRY_PASS}
+        fi
+
+  remove-secret:
+    desc: Delete secret to push to internal registry
+    silent: true
+    cmds:
+      - |
+        if kubectl -n nuvolaris get secret registry-pull-secret-int >/dev/null 
2>&1;
+        then kubectl -n nuvolaris delete secret registry-pull-secret-int
+        fi
 
   deploy:
     silent: true
     ignore_error: false
-    desc: deploy the admin api 
+    desc: deploy the admin api
     cmds:
     - test -e ${INGRESS_TYPE}-template.yaml || die "No avalable template for 
ingress type ${INGRESS_TYPE}."
     - test -n "$IMAGES_SYSTEMAPI" || die "IMAGES_SYSTEMAPI is not set. Please 
set it to the desired image version."
     - envsubst -i api-template.yaml -o _api.yaml > /dev/null 2>&1
     - envsubst -i ${INGRESS_TYPE}-template.yaml -o _ingress.yaml > /dev/null 
2>&1
-    - $OPS debug kube ctl CMD="-n nuvolaris apply -f 
$OPS_PWD/setup/nuvolaris/system-api/_api.yaml"
+    - kubectl -n nuvolaris apply -f 
"$OPS_ROOT/setup/nuvolaris/system-api/_api.yaml"
     #- cat _ingress.yaml
-    - $OPS debug kube ctl CMD="-n nuvolaris apply -f 
$OPS_PWD/setup/nuvolaris/system-api/_ingress.yaml"
+    - kubectl -n nuvolaris apply -f 
"$OPS_ROOT/setup/nuvolaris/system-api/_ingress.yaml"
+    - task: install-toml
+    - task: install-secret
     - |
       echo "Admin API deployed with HOSTNAME: ${SYS_API_HOSTNAME}/system"
-  
+
   undeploy:
     silent: true
     desc: undeploy the admin api
-    ignore_error: true
+    ignore_error: false
     cmds:
-    - $OPS debug kube ctl CMD="-n nuvolaris delete sts/nuvolaris-system-api 
ing/nuvolaris-system-api-ingress svc/nuvolaris-system-api"
+    - task: remove-secret
+    - task: remove-toml
+    - kubectl -n nuvolaris delete sts/nuvolaris-system-api 
ing/nuvolaris-system-api-ingress svc/nuvolaris-system-api
     - |
       echo "System Admin API undeployed"
-  
+
 
   update:
     silent: true
     desc: update the OpenServerless System Admin Api Stateful Set
     cmds:
       - task: deploy
-      - $OPS debug kube ctl CMD="-n nuvolaris rollout restart statefulset 
nuvolaris-system-api"
-    preconditions:      
+      - kubectl -n nuvolaris rollout restart statefulset nuvolaris-system-api
+    preconditions:
       - sh: '[ $IMAGES_SYSTEMAPI != $CURRENT_API_VERSION ]'
         msg: "Current OpenServerless System API stateful set it is already 
updated to newest version. Request ignored."
     env:
       CURRENT_API_VERSION:
         sh: |
-          echo $($OPS debug kube ctl CMD="-n nuvolaris get 
sts/nuvolaris-system-api 
-ojsonpath='{.spec.template.spec.containers[0].image}'")
\ No newline at end of file
+          echo $(kubectl -n nuvolaris get sts/nuvolaris-system-api 
-ojsonpath='{.spec.template.spec.containers[0].image}')
\ No newline at end of file
diff --git a/setup/opsfile.yml b/setup/opsfile.yml
index 44a1512..f4bd7f2 100644
--- a/setup/opsfile.yml
+++ b/setup/opsfile.yml
@@ -31,14 +31,14 @@ tasks:
     vars:
       PORTS: "{{ .PORTS | default \"\" }}"
     cmds:
-      - |        
+      - |
         {{if eq OS "windows"}}
         $ports = "{{ .PORTS }}".Split(" ")
         foreach ($entry in $ports) {
           $parts = $entry -split "/"
           $port = $parts[0]
           $protocol = $parts[1].ToLower()
-        
+
           if ($protocol -eq "tcp" -and (Get-NetTCPConnection -LocalPort $port 
-ErrorAction SilentlyContinue)) {
             Write-Host "TCP Port $port is in use!" -ForegroundColor Red
             exit 1
@@ -76,26 +76,39 @@ tasks:
         done
         {{end}}
 
-  prereq:
-    silent: true
-    cmds:
-    - exit 0
 
   mini:
     silent: true
     desc: install miniops
+    vars:
+       APIHOST: '{{or ._apihost_ "http://miniops.me"}}'
     cmds:
+      - echo '{{.APIHOST}}' | rg 'https?://(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]+/?' 
>/dev/null || die "please spefify an accessibile http://host";
+      - task: _mini
+        vars:
+          APIHOST: '{{.APIHOST}}'
+          PROTO:
+            sh:  echo '{{.APIHOST}}' | rg -o '(https?)://' -r '$1'
+          DOMAIN:
+            sh:  echo '{{.APIHOST}}' | rg -o 
'https?://((?:[a-zA-Z0-9-]+\.)+[a-zA-Z]+)/?' -r '$1'
+
+  _mini:
+    silent: true
+    cmds:
+      - echo APIHOST={{.APIHOST}}
+      - echo PROTO={{.PROTO}}
+      - echo DOMAIN={{.DOMAIN}}
       - docker -v || die "Docker is not installed. Please install Docker 
Desktop first."
       - docker info 2>/dev/null >/dev/null || die "Docker is not running. 
Please start Docker Desktop first."
       - |
         if test -z {{.__skip_check_cluster}}
-        then          
-          if curl -sL http://miniops.me >/dev/null 2>/dev/null
+        then
+          if curl -sL {{.APIHOST}} >/dev/null 2>/dev/null
           then
-            if curl -sL http://miniops.me/api/info 2>/dev/null | jq  -r 
.description 2>/dev/null | rg OpenWhisk >/dev/null 2>/dev/null
+            if curl -sL {{.APIHOST}}/api/info 2>/dev/null | jq  -r 
.description 2>/dev/null | rg OpenWhisk >/dev/null 2>/dev/null
             then
               echo "*** Apache OpenServerless mini is already installed."
-              echo "*** Login with: ops ide login devel http://miniops.me";
+              echo "*** Login with: ops ide login devel {{.APIHOST}}"
               echo "*** You can find the password in .ops/devel.password in 
your home directory"
               echo "*** You can can uninstall and restart with: ops setup 
docker delete"
               exit 1
@@ -109,9 +122,10 @@ tasks:
         fi
       - ops setup docker check-space
       - ops config slim
-      - ops config apihost miniops.me --protocol=http
+      - ops config apihost '{{.DOMAIN}}' --protocol={{.PROTO}}
       - ops setup devcluster
       - ops setup nuvolaris streamer deploy
+      - ops setup nuvolaris system-api deploy
       - ops setup nuvolaris add-user
 
   devcluster:
@@ -126,18 +140,14 @@ tasks:
       then  {{.RUN}} ops setup docker delete
       else
         if test -z {{.__skip_check_ports}}
-        then 
+        then
           task check-ports PORTS="{{ .PORTS }}"
         fi
         {{.RUN}} ops setup docker create
-        if test -z {{.__skip_preload_images}}
-        then
-          {{.RUN}} ops setup docker preload
-        fi
         {{.RUN}} ops setup kubernetes create
         {{.RUN}} ops setup nuvolaris login
         config STATUS_LAST=devcluster
-      fi   
+      fi
 
   cluster:
     silent: true
@@ -150,8 +160,8 @@ tasks:
       else
             {{.RUN}} ops setup kubernetes create CONTEXT="{{._context_}}"
             {{.RUN}} ops setup nuvolaris login
-            config STATUS_LAST=cluster 
-            if test -n "{{._context_}}" 
+            config STATUS_LAST=cluster
+            if test -n "{{._context_}}"
             then config STATUS_CONTEXT="{{._context_}}"
             fi
       fi
@@ -171,32 +181,32 @@ tasks:
           config STATUS_LAST=server STATUS_SERVER="{{._server_}}"
           if test -n "{{._user_}}"
           then config STATUS_USERNAME="{{._user_}}"
-          fi   
+          fi
       fi
 
   status:
     silent: true
-    cmds: 
+    cmds:
       - |
-        case "$STATUS_LAST" in 
+        case "$STATUS_LAST" in
         (devcluster) {{.RUN}} ops setup devcluster --status ;;
         (cluster)    {{.RUN}} ops setup cluster "$STATUS_CONTEXT" --status ;;
-        (server)     {{.RUN}} ops setup server "$STATUS_SERVER" 
"$STATUS_USERNAME" --status ;; 
+        (server)     {{.RUN}} ops setup server "$STATUS_SERVER" 
"$STATUS_USERNAME" --status ;;
         (*) echo nothing installed yet
         esac
 
   uninstall:
     silent: true
-    cmds: 
+    cmds:
       - |
-        case "$STATUS_LAST" in 
-        (devcluster) 
+        case "$STATUS_LAST" in
+        (devcluster)
           {{.RUN}} ops setup devcluster --uninstall
           config -r STATUS_LAST
         ;;
-        (cluster)   
-          if test -n "$STATUS_CONTEXT" 
-          then 
+        (cluster)
+          if test -n "$STATUS_CONTEXT"
+          then
               {{.RUN}} ops setup cluster "$STATUS_CONTEXT" --uninstall
               config -r STATUS_CONTEXT
           else
@@ -204,16 +214,16 @@ tasks:
           fi
           config -r STATUS_LAST
         ;;
-        (server)    
+        (server)
           if test -z "$STATUS_USERNAME"
-          then 
+          then
                 {{.RUN}} ops setup server "$STATUS_SERVER" --uninstall
           else
                 {{.RUN}} ops setup server "$STATUS_SERVER" "$STATUS_USERNAME" 
--uninstall
                 config -r STATUS_USERNAME
           fi
-          config -r STATUS_LAST STATUS_SERVER 
-        ;; 
+          config -r STATUS_LAST STATUS_SERVER
+        ;;
         (*) echo nothing installed yet
         esac
 
diff --git a/update/opsfile.yml b/update/opsfile.yml
index 0488394..24d7165 100644
--- a/update/opsfile.yml
+++ b/update/opsfile.yml
@@ -33,7 +33,6 @@ env:
       fi
 
 tasks:
-  hi: echo hi
 
   cli:
     desc: update the cli
@@ -61,6 +60,9 @@ tasks:
       - config SECRET_POSTGRES_REPLICA="$(kubectl -n nuvolaris get 
wsk/controller -o jsonpath='{.spec.postgres.admin.replica-password}')"
       - config SECRET_POSTGRES_NUVOLARIS="$(kubectl -n nuvolaris get 
wsk/controller -ojsonpath='{.spec.postgres.nuvolaris.password}')"
       - config SECRET_NUVOLARIS_METADATA="$(kubectl -n nuvolaris get 
wsk/controller -ojsonpath='{.spec.nuvolaris.password}')"
+      - config SECRET_MILVUS_ROOT="$(kubectl -n nuvolaris get wsk/controller 
-ojsonpath='{.spec.milvus.password.root}')"
+      - config SECRET_MILVUS_S3="$(kubectl -n nuvolaris get wsk/controller 
-ojsonpath='{.spec.milvus.password.s3}')"
+      - config REGISTRY_CONFIG_SECRET_PUSH_PULL="$(kubectl -n nuvolaris get 
wsk/controller -ojsonpath='{.spec.registry.auth.password}')"
 
   apply:
     desc: apply updated config
@@ -68,7 +70,7 @@ tasks:
       - $OPS update secrets
       - $OPS setup kubernetes runtimes
       - $OPS setup kubernetes instance
-      - $OPS setup kubernetes services
+      - $OPS setup kubernetes wait-services
       - $OPS setup nuvolaris login
 
   operator:
diff --git a/util/docopts.md b/util/docopts.md
index 3173608..1ab1629 100644
--- a/util/docopts.md
+++ b/util/docopts.md
@@ -24,6 +24,7 @@ OpenServerless Utilities
 
 ```text
 Usage:
+  util apihost
   util system
   util update-cli
   util check-operator-version <version>
@@ -33,6 +34,7 @@ Usage:
   util no-user-secrets <username>
   util kubectl <args>...
   util kubeconfig
+  util kubeget <object> <jsonpath>
   util config <configjson> [--override] [--showhelp]
   util clean
   util upload [<folder>] [--batchsize=<batchsize>] [--verbose] [--clean]
@@ -42,21 +44,22 @@ Usage:
   util ingress-type
   util edit [<plugin>]
   util link-plugin <plugin>
-  util docker-proxy [<port>] [<host>] [--remove]
 ```
 
 ## Commands
 
 ```
+-  apihost                 current apihost
 -  system                  system info (<os>-<arch> in Go format)
 -  update-cli              update the cli downloading the binary
 -  check-operator-version  check if you need to update the operator
--  secrets                 generate system secrets 
+-  secrets                 generate system secrets
 -  nosecrets               remove system secrets
 -  user-secrets            generate user secrets for the given user
 -  no-user-secrets         remove user secrets for the given user
 -  kubectl                 execute kubectl on current kubeconfig
 -  kubeconfig              export OVERWRITING current kubeconfig to 
~/.kube/config
+-  kubeget                 extract info from an object with a jsonpath
 -  config                  update configuration file interactively
 -  clean                   clean up the web bucket
 -  upload                  uploads a folder to the web bucket in 
OpenServerless.
@@ -66,7 +69,6 @@ Usage:
 -  ingress-type            return the ingress type
 -  edit                    edit (with vscode) the current tasks or a plugin
 -  link-plugin             link the <plugin> in current directory to the 
global list
--  docker-proxy            launch (or remove) in docker a proxy listening in 
public <port> (default 8080) to the <host> (default devel.miniops.me)
 ```
 
 ## Options
diff --git a/util/freeze/opsfile.yml b/util/freeze/opsfile.yml
new file mode 100644
index 0000000..5ebb6b4
--- /dev/null
+++ b/util/freeze/opsfile.yml
@@ -0,0 +1,176 @@
+version: 3
+
+vars:
+  CLUSTER: nuvolaris
+  KIND_CLUSTER: '{{.CLUSTER}}-control-plane'
+  BASE:
+    sh: echo ~/.ops
+  IMAGES:
+     sh: >
+        echo {{.BASE}}/{{OS}}-{{ARCH}}/images
+  KIND_IMAGE:
+    sh: >
+       awk '/image:/{print $2}' $OPS_ROOT/setup/docker/kind.yaml | tee 
_docker.lst
+
+tasks:
+
+  docker-list:
+    silent: true
+    desc: list docker images
+    cmds:
+    - docker inspect  {{.KIND_CLUSTER}} 2>/dev/null | jq -r '.[0].Config.Image'
+
+  kind-list:
+    silent: true
+    env:
+      CLU: "{{.KIND_CLUSTER}}"
+    desc: list images in kindes
+    cmds:
+    - awk '/image:/ {print $2}' $OPS_ROOT/setup/docker/ingress-deploy.yaml | 
tee _kind.lst
+    - >
+      docker exec -it "$CLU" crictl images 2>/dev/null
+      | awk 'NR > 1  && 
!/^(registry.k8s.io\/(kube-|coredns|etc|pause|ingress-nginx)|docker.io\/kindest\/)/
 {print  $1 ":" $2 }'
+      | tee _kind.lst
+
+  docker-save:
+    silent: true
+    desc: save images in docker
+    cmds:
+    - docker inspect  {{.KIND_CLUSTER}} | jq -r '.[0].Config.Image' 
>_docker.lst
+    - mkdir -p {{.IMAGES}}/docker
+    - |
+      cat _docker.lst | while read img
+      do
+        file={{.IMAGES}}/docker/$(base64 -e "$img")
+        if test -e "$file"
+        then echo "already saved $img.{{ARCH}} "
+        else echo ">>> Saving: $img"
+             if docker pull --platform linux/{{ARCH}} "$img"
+             then echo "OK: save $file"
+             else echo "*** FAIL: pull $img"
+             fi
+             if docker save "$img" -o "$file"
+             then echo "OK: save $file"
+             else echo "*** FAIL: save $file"
+             fi
+        fi
+      done
+
+  kind-save:
+    silent: true
+    desc: list images in kindes
+    env:
+      CLU: "{{.KIND_CLUSTER}}"
+    cmds:
+    - awk '/image:/ {print $2}' $OPS_ROOT/setup/docker/ingress-deploy.yaml > 
_kind.lst
+    - >
+      docker exec -it "$CLU" crictl images 2>/dev/null
+      | awk 'NR > 1  && 
!/^(registry.k8s.io\/(kube-|coredns|etc|pause|ingress-nginx)|docker.io\/kindest\/)/
 {print  $1 ":" $2 }'
+      >> _kind.lst
+    - mkdir -p {{.IMAGES}}/kind
+    - |
+      cat _kind.lst | while read img
+      do
+        file={{.IMAGES}}/kind/"$(base64 -e "$img")"
+        if test -e "$file"
+        then echo "already saved $img"
+        else echo ">>> Saving: {{ARCH}} $img"
+            if !  docker pull --platform linux/{{ARCH}} "$img"
+            then echo "*** FAIL pull $img" ; continue
+            fi
+            if ! docker save "$img" -o "$file"
+            then echo "*** FAIL save $file" ; continue
+            fi
+            echo "OK: save $file"
+        fi
+      done
+
+  image-save:
+    desc: save images
+    cmds:
+    - kind get clusters | grep {{.CLUSTER}} || die "please setup an ops 
cluster first"
+    - task: docker-save
+    - task: kind-save
+
+  image-list:
+    silent: true
+    desc: list images
+    cmds:
+      - rm -f _images.lst
+      - |
+        for dir in docker kind
+        do for file in {{.IMAGES}}/$dir/*
+           do if test -e "$file"
+              then
+                b64=$(basename "$file")
+                img=$(base64 -d "${b64}")
+                echo $dir/$img
+                echo $img >>_images.lst
+              fi
+           done
+        done
+
+  image-clean:
+    prompt: "are you sure you wnat to remote images fron disk"
+    silent: true
+    desc: clean images
+    cmds:
+    - rm -f -v  {{.IMAGES}}/docker/* {{.IMAGES}}/kind/*
+
+  docker-load:
+    silent: true
+    desc: load disk images in docker
+    cmds:
+      - |
+        for file in {{.IMAGES}}/docker/*
+        do
+          b64=$(basename "$file")
+          img=$(base64 -d "${b64#*-}")
+          echo Docker: loading "$img"
+          docker load -i "$file"
+          #docker tag $(awk '{print $4}' _image) $img
+          docker tag ${img#*@} ${img%@*}
+        done
+    status:
+      - test -z "$(ls {{.IMAGES}}/docker/* 2>/dev/null)"
+
+  kind-load:
+    silent: true
+    desc: load disk images in kind
+    cmds:
+      - |
+        for file in {{.IMAGES}}/kind/*
+        do
+           b64="$(basename "$file")"
+           img="$(base64 -d "${b64#*-}")"
+           echo "Kind: loading $img"
+           docker exec -i {{.CLUSTER}}-control-plane ctr --namespace=k8s.io 
images import --snapshotter=overlayfs - < "$file"
+        done
+        echo 'Images loaded:'
+      - task: kind-list
+    status:
+      - test -z "$(ls {{.IMAGES}}/kind/* 2>/dev/null)"
+
+  test-clean:
+  ignore_errors: true
+  cmds:
+  - task: docker-clean
+  - kind delete cluster --name=test
+
+  test-compare:
+  - task: docker-list
+  - task: kind-list
+  - task: image-list
+  - cat _docker.lst _kind.lst | sort >_loaded.lst
+  - sort <_images.lst >_saved.lst
+  - echo "Comparing differences loaded and saved"
+  - diff _loaded.lst _saved.lst
+
+  test:
+  - task: test-clean
+  - task: docker-load
+  - kind create cluster --name=test --image={{.KIND_IMAGE}}
+  - task: kind-load
+  - task: test-compare
+  - task: test-clean
+
diff --git a/util/kube/opsfile.yml b/util/kube/opsfile.yml
index 129f03c..5d16367 100644
--- a/util/kube/opsfile.yml
+++ b/util/kube/opsfile.yml
@@ -33,7 +33,7 @@ env:
 tasks:
 
   waitfor:
-    desc: wait for an object matching a condition 
+    desc: wait for an object matching a condition
     silent: true
     cmds:
       - test -n "{{.OBJ}}" || die "use OBJ=<pod/pod-name>"
@@ -42,14 +42,14 @@ tasks:
         N=0
         RES=false
         while [[ $N -lt {{.RETRY}} ]]
-        do           
+        do
           if kubectl -n nuvolaris wait --for="{{.FOR}}" "{{.OBJ}}" 
--timeout="{{.TIMEOUT}}s"
-          then RES=true; break            
+          then RES=true; break
           else echo "$((N++)) Waiting {{.OBJ}} for {{.FOR}}"
                sleep "{{.SLEEP}}s"
           fi
         done
-        $RES 
+        $RES
 
   wait:
     desc: wait for a value matching the given jsonpath on the specific 
resources under the namespace nuvolaris
@@ -61,17 +61,17 @@ tasks:
         N=0
         RES=false
         while [[ $N -lt {{.RETRY}} ]]
-        do           
+        do
           MATCHING_VALUE="$(kubectl -n nuvolaris get {{.OBJECT}} -o 
jsonpath='{{.JSONPATH}}')"
           if [[ -z "$MATCHING_VALUE" ]]
-          then 
+          then
             echo "$((N++)) Waiting for {{.JSONPATH}} to be non empty value"
             sleep 5
-          else  
+          else
             RES=true; break
           fi
         done
-        $RES 
+        $RES
 
   export:
     desc: exporting NAMEd kubeconfig to specified FILE
@@ -80,7 +80,7 @@ tasks:
     - test -e "$OPS_TMP/{{.NAME}}.kubeconfig" || die "no {{.NAME}} kubeconfig"
     - |
       FILE="$(realpath {{.FILE}})"
-      cp "$OPS_TMP/kubeconfig" "$FILE" 
+      cp "$OPS_TMP/kubeconfig" "$FILE"
 
   import:
     desc: importing FILE  to the NAMEd kubeconfig
diff --git a/util/opsfile.yml b/util/opsfile.yml
index eacf8af..3d7e00e 100644
--- a/util/opsfile.yml
+++ b/util/opsfile.yml
@@ -36,7 +36,7 @@ tasks:
         if {{.__showhelp}}
         then FLAGS="$FLAGS --showhelp"
         fi
-        if {{.__override}} 
+        if {{.__override}}
         then FLAGS="$FLAGS --override"
         fi
         bun config/configurator.js $(opspath {{._configjson_}}) $FLAGS
@@ -47,6 +47,12 @@ tasks:
     cmds:
       - echo "{{OS}}-{{ARCH}}"
 
+  apihost:
+    desc: current apihost
+    silent: true
+    cmds:
+      - ops -wsk property get | awk '/whisk API host/{print $4}'
+
   update-cli:
     desc: update CLI
     silent: true
@@ -70,7 +76,7 @@ tasks:
           else echo "cannot auto update ops - update it manually"
                echo "please run 'curl -sL bit.ly/get-ops | bash'"
           fi
-        else 
+        else
           echo "skipping rename $OPS_TMP/ops{{.EXE}} $OPS_CMD"
         fi
 
@@ -92,6 +98,7 @@ tasks:
       - config SECRET_POSTGRES_ADMIN="$(random --str 12)" 
SECRET_POSTGRES_REPLICA="$(random --str 12)" 
SECRET_POSTGRES_NUVOLARIS="$(random --str 12)"
       - config SECRET_ETCD_ROOT="$(random --str 12)" 
SECRET_MILVUS_ROOT="$(random --str 12)" SECRET_MILVUS_S3="$(random --str 12)" 
SECRET_MILVUS_NUVOLARIS="$(random --str 12)"
       - config SECRET_NUVOLARIS_METADATA="$(random --str 12)"
+      - config SECRET_SEAWEEDFS_NUVOLARIS="$(random --str 12)"
       - echo "Generated $(config -d | rg SECRET_ | wc -l) secrets."
 
   user-secrets:
@@ -144,7 +151,7 @@ tasks:
     cmds:
       - |
         # no quote here - syntax array generated by docps
-        args={{._args_}} 
+        args={{._args_}}
         # exanded and assigned to positional parameter
         set -- ${args[@]}
         # removing the '--' at the beginning required to pass extra flags
@@ -159,6 +166,21 @@ tasks:
           else echo ~/.kube/config
           fi
 
+  kubeget:
+    desc: extract informations from
+    silent: true
+    cmds:
+    - kubectl -n nuvolaris get "$OBJECT" -o jsonpath="{$JSONPATH}"
+    env:
+      JSONPATH: '{{._jsonpath_}}'
+      OBJECT: '{{._object_}}'
+      KUBECONFIG:
+        sh: |-
+          if test -e "$OPS_TMP/kubeconfig"
+          then echo "$OPS_TMP/kubeconfig"
+          else echo ~/.kube/config
+          fi
+
   kubeconfig:
     silent: true
     prompt: Are you sure? I am overwriting your ~/.kube/config
@@ -166,7 +188,7 @@ tasks:
     cmds:
       - |
         if test -e "$OPS_TMP/kubeconfig"
-        then  
+        then
           if test -e ~/.kube/config
           then BKP=~/.kube/config.$(datefmt -f ms)
                mv ~/.kube/config $(realpath $BKP)
@@ -199,7 +221,7 @@ tasks:
     env:
       FOLDER_PATH:
         sh: |
-          if test -n "{{._folder_}}" 
+          if test -n "{{._folder_}}"
           then opspath "{{._folder_}}"
           else opspath .
           fi
@@ -257,30 +279,6 @@ tasks:
           exit 1
         fi
 
-  docker-proxy:
-    desc: create in docker a proxy to expose miniops.me (or others)
-    silent: true
-    vars:
-      PORT: '{{or ._port_ "8080"}}'
-      HOST: '{{or ._host_ "devel.miniops.me"}}'
-      NGINX_CONF: >
-        server {
-           listen 80;
-           location / { 
-             proxy_pass http://nuvolaris-control-plane:80;
-             proxy_set_header Host {{.HOST}}; 
-           } 
-        }
-    cmds:
-    - |
-      if {{.__remove}}
-      then docker rm docker-proxy-{{.PORT}} -f
-      else
-        if docker run -d --name docker-proxy-{{.PORT}} -p {{.PORT}}:80 
--network kind nginx:alpine sh -c \
-           "echo '{{.NGINX_CONF}}' > /etc/nginx/conf.d/default.conf && nginx 
-g 'daemon off;'"
-        then echo "docker-proxy started on port {{.PORT}} to  http://{{.HOST}}";
-        else echo "Cannot start docker-proxy-{{.PORT}} - either the port is 
busy or another instance is running, remove it" 
-        fi       
-      fi
-
+  freeze:
+    desc: freeze util
 


Reply via email to