Hi Tanguy,

happy to hear you've finally fixed it.

Again, %ZSTART is not at all mandatory ; as soon as your ENV variables are available in your container you should be able to get it either from $system.Util.GetEnviron(env) or ##class(%SYS.Python).Import("os").getenv(env)

With these variables : 

{"Env": [
			"API_CLIENT_SECRET=ea5663885513e5b00df120fa4b4da8e1150398cde9d41ee27b5a8c6f1898dfa63ae711b82bf06b36475b646453a9092f5653895ddd2c3bb067d9a4f562a6b625",
			"TZ=Europe/Paris",
			"ISC_DATA_DIRECTORY=/irisdata",
			"ISC_CPF_MERGE_FILE=/app/merge.cpf",
			"API_CLIENT_ID=3e726f42daaa06a8",
			"ISC_PACKAGE_IRISGROUP=irisowner",
			"ISC_PACKAGE_IRISUSER=irisowner",
			"ISC_PACKAGE_MGRGROUP=irisowner",
			"ISC_PACKAGE_MGRUSER=irisowner",
			"IRISSYS=/home/irisowner/irissys",
			"TINI_VERSION=v0.19.0",
			"ISC_PACKAGE_INSTANCENAME=IRIS",
			"ISC_PACKAGE_INSTALLDIR=/usr/irissys",
			"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/irisowner/bin",
			"PYTHONPATH=/usr/irissys/mgr/python",
			"LANG=en_US.UTF-8",
			"LANGUAGE=en_US.UTF-8",
			"LC_ALL=en_US.UTF-8"
		]}

You should get :
 

write $SYSTEM.Util.GetEnviron("API_CLIENT_ID")
3e726f42daaa06a8
write $SYSTEM.Util.GetEnviron("API_CLIENT_SECRET")
ea5663885513e5b00df120fa4b4da8e1150398cde9d41ee27b5a8c6f1898dfa63ae711b82bf06b36475b646453a9092f5653895ddd2c3bb067d9a4f562a6b625
write $SYSTEM.Util.GetEnviron("TZ")
Europe/Paris
write ##class(%SYS.Python).Import("os").getenv("API_CLIENT_ID")
3e726f42daaa06a8
write ##class(%SYS.Python).Import("os").getenv("API_CLIENT_SECRET")
ea5663885513e5b00df120fa4b4da8e1150398cde9d41ee27b5a8c6f1898dfa63ae711b82bf06b36475b646453a9092f5653895ddd2c3bb067d9a4f562a6b625
write ##class(%SYS.Python).Import("os").getenv("TZ")
Europe/Paris

Or, with %ZSTART containing :
 

ROUTINE %ZSTART
  QUIT ; Prevents direct execution without a label

SYSTEM ; Subroutine called at system startup
  ; Initialize the IRIS application secrets
  Set ^Secrets("API_CLIENT_ID") = $System.Util.GetEnviron("API_CLIENT_ID")
  Set ^Secrets("API_CLIENT_ID","LastUpdated") = $ZDATETIME($HOROLOG,3)
  Set ^Secrets("API_CLIENT_SECRET") = $System.Util.GetEnviron("API_CLIENT_SECRET")
  Set ^Secrets("API_CLIENT_SECRET","LastUpdated") = $ZDATETIME($HOROLOG,3)
  QUIT

you should obtain :

IRISAPP>zw ^Secrets
^Secrets("API_CLIENT_ID")="3e726f42daaa06a8"
^Secrets("API_CLIENT_ID","LastUpdated")="2025-08-07 17:16:19"
^Secrets("API_CLIENT_SECRET")="ea5663885513e5b00df120fa4b4da8e1150398cde9d41ee27b5a8c6f1898dfa63ae711b82bf06b36475b646453a9092f5653895ddd2c3bb067d9a4f562a6b625"
^Secrets("API_CLIENT_SECRET","LastUpdated")="2025-08-07 17:16:19"

IRISAPP>d SYSTEM^%ZSTART

IRISAPP>zw ^Secrets
^Secrets("API_CLIENT_ID")="3e726f42daaa06a8"
^Secrets("API_CLIENT_ID","LastUpdated")="2025-08-07 17:24:02"
^Secrets("API_CLIENT_SECRET")="ea5663885513e5b00df120fa4b4da8e1150398cde9d41ee27b5a8c6f1898dfa63ae711b82bf06b36475b646453a9092f5653895ddd2c3bb067d9a4f562a6b625"
^Secrets("API_CLIENT_SECRET","LastUpdated")="2025-08-07 17:24:02"

Hi Tanguy,

%ZSTART routine is not mandatory. It's just an example of a way to retrieve and store data at instance startup. If it works you should see the results in   System  > Globals  > View Global Data > ^IRIS.Temp global.

If $System.Util.GetEnviron("APIclientid") works, it means that your environment variables are well configured.

Could you please share your business operation code ?

Hi @TAZ.R 
Environment variables set in the Docker container are only visible to processes that inherit the environment. 

You can rapidly check it from a terminal session : 

write $system.Util.GetEnviron("TZ")
Europe/Paris
write ##class(%SYS.Python).Import("os").getenv("TZ")
Europe/Paris

Or from a simple Business Operation :

 
BusinessOperation env

You could also store the content in a global at IRIS startup by creating the %ZSTART routine 

ROUTINE %ZSTART
  QUIT ; Prevents direct execution without a label

SYSTEM ; Subroutine called at system startup
  Set ^IRIS.Temp("Secrets", $System.Util.GetEnviron("API_CLIENT_ID")) = $System.Util.GetEnviron("API_CLIENT_SECRET")
  QUIT
  

That way, you can retrieve your secret by executing this code:

$Get(^IRIS.Temp("Secrets", "your-client-id"), "")
 
Business Operation secret

Thank you @Anastasia Dyubaylo for these guidelines which are always good to remember.

I don't know if this is intentional on your part to ensure that your article is properly read by the community, but a typo has crept in: %SYSTEM.JSON isn't an existing class in IRIS.

Or is this simply an unverified hallucination, which would prove that you yourself should be applying your recommendations for the proper use of generative AI?

I'd go with the first option 😊

You can also use the ":sql" alias to launch the SQL Shell :

USER>:sql
SQL Command Line Shell
----------------------------------------------------

The command prefix is currently set to: <<nothing>>.
Enter <command>, 'q' to quit, '?' for help.
[SQL]USER>>select sysdate
2.      select sysdate

| Expression_1 |
| -- |
| 2025-02-18 13:39:24 |

1 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0928s/35,642/155,490/1ms
          execute time(s)/globals/cmds/disk: 0.0001s/3/408/0ms
                                query class: %sqlcq.USER.cls6
---------------------------------------------------------------------------
[SQL]USER>>quit

USER>

And just type ":?" to get the list of all the alias :

USER>:?
 :<number>    Recall command # <number>
 :py          Do $system.Python.Shell()
 :mdx         Do $system.DeepSee.Shell()
 :sql         Do $system.SQL.Shell()
 :tsql        Do $system.SQL.TSQLShell()
 :alias       Create/display aliases
 :unalias     Remove aliases
 :history     Display command history
 :clear       Clear history buffer
 :?           Display help
 Ctrl+R       Reverse incremental search

USER>

You can schedule the following task which removes any file from a directory, based on its age, using Python:

Class admin.purge Extends %SYS.Task.Definition
{

Property Directory As %String(MAXLEN = 2000) [ InitialExpression = "/usr/irissys/mgr/Backup" ];

Property DaysToKeep As %Integer(VALUELIST = ",0,1,2,3,4,5") [ InitialExpression = "1" ];

Method OnTask() As %Status
{
    set sc = $$$OK
    Try {
        do ..purge(..Directory,..DaysToKeep)
    }
    Catch ex {
        Set sc=ex.AsStatus()
    }
    return sc
}

ClassMethod purge(path As %String, daysToKeep As %Integer) As %Status [ Language = python ]
{
import iris
import os
import time
from datetime import datetime, timedelta

event = "[TASK PURGE OLD BACKUP FILES]"

class FileDeletionError(Exception):
    """Custom exception for file deletion errors."""
    pass

def delete_old_files(path, days_limit):
    limit_date = datetime.now() - timedelta(days=int(days_limit))

    for file in os.listdir(path):
        file_path = os.path.join(path, file)
        if os.path.isfile(file_path):
            creation_date = datetime.fromtimestamp(os.path.getctime(file_path))
            if creation_date < limit_date:
                try:
                    os.remove(file_path)
                    iris.cls("%SYS.System").WriteToConsoleLog(f"{event} Deleted: {str(file_path)}")
                except PermissionError:
                    raise FileDeletionError(f"Permission error: Unable to delete {file}")
                except FileNotFoundError:
                    raise FileDeletionError(f"File not found: {file}")
                except Exception as e:
                    raise FileDeletionError(f"Unexpected error while deleting {file}: {str(e)}")

try:
    iris.cls("%SYS.System").WriteToConsoleLog(f"{event} Executing task to delete backup files from directory {path} created more than {str(daysToKeep)} days ago")
    days_limit = daysToKeep
    delete_old_files(path, days_limit)
except FileDeletionError as e:
    iris.cls("%SYS.System").WriteToConsoleLog(f"{event} Error during deletion: {str(e)}",0,1)
except Exception as e:
    iris.cls("%SYS.System").WriteToConsoleLog(f"{event} Unexpected error: {str(e)}",0,1)
}

}
01/13/25-18:21:00:549 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Executing task to delete backup files from directory /usr/irissys/mgr/Backup created more than 0 days ago
01/13/25-18:21:00:550 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Deleted: /usr/irissys/mgr/Backup/FullAllDatabases_20250113_009.cbk
01/13/25-18:21:00:550 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Deleted: /usr/irissys/mgr/Backup/FullAllDatabases_20250113_008.log
01/13/25-18:21:00:598 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Deleted: /usr/irissys/mgr/Backup/FullAllDatabases_20250113_008.cbk
01/13/25-18:21:00:598 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Deleted: /usr/irissys/mgr/Backup/FullAllDatabases_20250113_009.log

You can schedule the following task which removes any file from a directory, based on its age, using Python:

Class admin.purge Extends %SYS.Task.Definition
{

Property Directory As %String(MAXLEN = 2000) [ InitialExpression = "/usr/irissys/mgr/Backup" ];

Property DaysToKeep As %Integer(VALUELIST = ",0,1,2,3,4,5") [ InitialExpression = "1" ];

Method OnTask() As %Status
{
    set sc = $$$OK
    Try {
        do ..purge(..Directory,..DaysToKeep)
    }
    Catch ex {
        Set sc=ex.AsStatus()
    }
    return sc
}

ClassMethod purge(path As %String, daysToKeep As %Integer) As %Status [ Language = python ]
{
import iris
import os
import time
from datetime import datetime, timedelta

event = "[TASK PURGE OLD BACKUP FILES]"

class FileDeletionError(Exception):
    """Custom exception for file deletion errors."""
    pass

def delete_old_files(path, days_limit):
    limit_date = datetime.now() - timedelta(days=int(days_limit))

    for file in os.listdir(path):
        file_path = os.path.join(path, file)
        if os.path.isfile(file_path):
            creation_date = datetime.fromtimestamp(os.path.getctime(file_path))
            if creation_date < limit_date:
                try:
                    os.remove(file_path)
                    iris.cls("%SYS.System").WriteToConsoleLog(f"{event} Deleted: {str(file_path)}")
                except PermissionError:
                    raise FileDeletionError(f"Permission error: Unable to delete {file}")
                except FileNotFoundError:
                    raise FileDeletionError(f"File not found: {file}")
                except Exception as e:
                    raise FileDeletionError(f"Unexpected error while deleting {file}: {str(e)}")

try:
    iris.cls("%SYS.System").WriteToConsoleLog(f"{event} Executing task to delete backup files from directory {path} created more than {str(daysToKeep)} days ago")
    days_limit = daysToKeep
    delete_old_files(path, days_limit)
except FileDeletionError as e:
    iris.cls("%SYS.System").WriteToConsoleLog(f"{event} Error during deletion: {str(e)}",0,1)
except Exception as e:
    iris.cls("%SYS.System").WriteToConsoleLog(f"{event} Unexpected error: {str(e)}",0,1)
}

}

The task logs its actions in messages.log :

01/13/25-18:21:00:549 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Executing task to delete backup files from directory /usr/irissys/mgr/Backup created more than 0 days ago
01/13/25-18:21:00:550 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Deleted: /usr/irissys/mgr/Backup/FullAllDatabases_20250113_009.cbk
01/13/25-18:21:00:550 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Deleted: /usr/irissys/mgr/Backup/FullAllDatabases_20250113_008.log
01/13/25-18:21:00:598 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Deleted: /usr/irissys/mgr/Backup/FullAllDatabases_20250113_008.cbk
01/13/25-18:21:00:598 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Deleted: /usr/irissys/mgr/Backup/FullAllDatabases_20250113_009.log