diff --git a/ansible/inventory/group_vars/all.yaml b/ansible/inventory/group_vars/all.yaml
index 1062359..fb3a2f0 100644
--- a/ansible/inventory/group_vars/all.yaml
+++ b/ansible/inventory/group_vars/all.yaml
@@ -160,6 +160,84 @@ services:
http: "3000" # app
dependency:
web: "app-public"
+ openwebui:
+ domain:
+ public: "openwebui"
+ internal: "openwebui.app"
+ ports:
+ http: "8002"
+ dependency:
+ web: "app-public"
+ db: "postgresql"
+ llama:
+ ports:
+ http: "10000"
+ dependency:
+ models:
+ default: |
+ n-gpu-layers = all
+ kv-unified = true
+ cache-prompt = true
+ cache-type-k = q8_0
+ cache-type-v = q8_0
+ flash-attn = auto
+ reasoning = off
+ jinja = true
+ lazy-mode = off
+ Nemotron3.5-Lightning-30B-A3B: |
+ model = /models/Nemotron3.5-Lightning-30B-A3B/Nemotron-3.5-Lightning-30B-A3B-Heretic-Uncensored-Q4_K_M-GGUF.gguf
+ c = 65536
+ parallel = 2
+ load-on-startup = true
+ # version: mradermacher/LFM2-24B-A2B-heretic-ara-GGUF
+ # LFM2-24B-A2B: |
+ # model = /models/LFM2-24B-A2B/LFM2-24B-A2B-heretic-ara.Q4_K_M.gguf
+ # c = 65536
+ # parallel = 2
+ # load-on-startup = true
+ # # version: SC117/LFM2.5-VL-3B-Uncensored-GGUF
+ # LFM2-VL-3B: |
+ # model = /models/LFM2-VL-3B/LFM2.5-VL-3B-Uncensored-Q4_K_M.gguf
+ # mmproj = /models/LFM2-VL-3B/mmproj-LFM2.5-VL-3B-BF16.gguf
+ # c = 65536
+ # parallel = 1
+ # load-on-startup = true
+ # version: HauhauCS/Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q4_K_M
+ # Qwen3.6: |
+ # model = /models/Qwen3.6-35B-A3B/Qwen3.6-35B-A3B-Q4_K_M.gguf
+ # mmproj = /models/Qwen3.6-35B-A3B/mmproj-Qwen3.6-35B-BF16.gguf
+ # c = 65536
+ # parallel = 2
+ # load-on-startup = true
+ # image-min-tokens = 1024
+ # # version: HauhauCS/Qwen3.5-2B-Uncensored-HauhauCS-Aggressiv-Q8_0
+ # Qwen3.5-image: |
+ # model = /models/Qwen3.5-2B/Qwen3.5-2B-Q8_0.gguf
+ # c = 32768
+ # parallel = 1
+ # load-on-startup = true
+ # version: LiquidAI/LFM2.5-2.6B-GGUF
+ # LFM2.5: |
+ # model = /models/LFM2.5-2.6B/LFM2.5-2.6B-Q8_0.gguf
+ # c = 32768
+ # parallel = 2
+ # load-on-startup = true
+ stablediffusion:
+ ports:
+ http: "10001"
+ dependency:
+ models:
+ active: "zImage"
+ zImage: |
+ --diffusion-model /models/zImage/moodyRealMix_zitV7Global-Q8_0.gguf \
+ --llm /models/zImage/Qwen3-4B-Instruct-2507-Q4_K_M.gguf \
+ --vae /models/zImage/ae.safetensors \
+ --backend gpu \
+ --cfg-scale 1.0 \
+ --steps 9 \
+ --vae-tiling \
+ --vae-tile-size 32x32 \
+ --vae-tile-overlap 0.5
snappymail:
domain:
public: "webmail"
@@ -212,6 +290,28 @@ services:
dependency:
web: "app-public"
db: "postgresql"
+ groupoffice:
+ domain:
+ public: "groupoffice"
+ internal: "groupoffice.app"
+ ports:
+ http: "8005"
+ subuid: "100032"
+ route:
+ dav: true
+ dependency:
+ web: "app-public"
+ db: "mariadb"
+ kaneo:
+ domain:
+ public: "kaneo"
+ internal: "kaneo.app"
+ ports:
+ http: "8006"
+ subuid: "101000"
+ dependency:
+ web: "app-public"
+ db: "postgresql"
nextcloud:
domain:
public: "nextcloud"
@@ -318,9 +418,9 @@ version:
# infra
ca: "0.30.2"
## Postgresql
- postgresql: "18.4"
+ postgresql: "18.6"
## Mariadb
- mariadb: "12.3.2"
+ mariadb: "12.3.3"
### For immich - https://github.com/immich-app/base-images/blob/main/postgres/versions.yaml
vectorchord: "1.1.1"
## lldap
@@ -328,25 +428,34 @@ version:
## maddy
maddy: "0.9.5"
x509-exporter: "4.1.0"
- prometheus: "v3.13.2"
- loki: "3.7.4"
- grafana: "13.1.1"
+ prometheus: "v3.14.0"
+ loki: "3.7.7"
+ grafana: "13.2.1"
# Auth
authelia: "4.39.20"
# App
- vaultwarden: "1.37.1"
- gitea: "1.27.2"
- redis: "8.10.0"
+ vaultwarden: "1.37.2"
+ gitea: "1.27.3"
+ redis: "8.10.1"
immich: "v3.1.0"
nextcloud: "34.0.3"
- collabora: "26.04.3.1.1"
- sure: "0.7.3"
- outline: "1.9.2"
+ collabora: "26.04.3.2.1"
+ sure: "0.7.4"
+ outline: "1.10.0"
homebox: "0.26.2"
homepage: "v2.0.0"
snappymail: "v2.38.2"
- mastodon: "v4.6.6"
+ mastodon: "v4.6.7"
papra: "26.6.1-rootless"
tandoor: "2.6.13"
- bookstack: "26.5.3"
+ bookstack: "26.5.4"
linkding: "1.46.2"
+ openwebui: "v0.11.3"
+ # https://github.com/ggml-org/llama.cpp/pkgs/container/llama.cpp/versions
+ llama: "server-vulkan-b10830"
+ # https://github.com/leejet/stable-diffusion.cpp/pkgs/container/stable-diffusion.cpp
+ # master-sycl@sha256:f27ebe3fea7dcb5978241425d3a115ba6736cfc33c42e75233f22f7411449e9c has problem in it, it cannot get vRAM from UMA properly
+ # Pin due to a SYCL/UMA false-OOM regression in newer stable-diffusion.cpp ModelManager memory accounting.
+ stablediffusion: "master-sycl@sha256:67ccb5d11bf55578f7938daeb7c9fee1d0dd1344c089c090b9065714d7960fd5"
+ groupoffice: "26.0.43"
+ kaneo: "2.23.2"
diff --git a/ansible/inventory/host_vars/auth.yaml b/ansible/inventory/host_vars/auth.yaml
index 50abbff..68521da 100644
--- a/ansible/inventory/host_vars/auth.yaml
+++ b/ansible/inventory/host_vars/auth.yaml
@@ -16,7 +16,7 @@ vm:
PDO::exec() executes an SQL statement in a single function call, returning the number of rows affected by the statement. PDO::exec() does not return results from a SELECT statement. For a SELECT statement that you only need to issue once during your program, consider issuing The SQL statement to prepare and execute. Data inside the query should be properly escaped. PDO::exec() returns the number of rows that were modified or deleted by the SQL statement you issued. If no rows were affected, PDO::exec() returns 0. Warning This function may return Boolean The following example incorrectly relies on the return value of PDO::exec(), wherein a statement that affected 0 rows results in a call to PDO::query(). For a statement that you need to issue multiple times, prepare a PDOStatement object with PDO::prepare() and issue the statement with PDOStatement::execute().FALSE, but may also return a non-Boolean value which evaluates to FALSE. Please read the section on Booleans for more information. Use the === operator for testing the return value of this function.die(): <?php
+ * @link http://php.net/manual/en/pdo.exec.php
+ *
+ * @throws DbException
+ */
+ public function exec(string $sql): int
+ {
+ if($this->debug) {
+ go()->getDebugger()->debug($sql, 1, true);
+ }
+ try {
+ return $this->getPdo()->exec($sql);
+ }
+ catch(PDOException $e) {
+ throw new DbException($e, $sql);
+ }
+ }
+
+
+ /**
+ * UNLOCK TABLES explicitly releases any table locks held by the current session
+ */
+ public function unlockTables(): bool
+ {
+ return $this->getPdo()->exec("UNLOCK TABLES") !== false;
+ }
+
+ private $transactionSavePointLevel = 0;
+
+ /**
+ * Start a database transation
+ *
+ * @return boolean
+ */
+ public function beginTransaction(): bool
+ {
+ if($this->transactionSavePointLevel == 0) {
+ $ret = $this->getPdo()->beginTransaction();
+
+ }else
+ {
+ $ret = true;
+ }
+
+ $this->transactionSavePointLevel++;
+
+ if($this->debug) {
+ go()->debug("beginTransaction " . $this->transactionSavePointLevel, 1);
+ }
+
+ return $ret;
+ }
+
+ private $resumeLevels = 0;
+
+ public function isPaused(): bool
+ {
+ return $this->resumeLevels > 0;
+ }
+
+ /**
+ * Commits the transaction but remembers the nesting of transaction nesting level. Must be resumed with resumeTransactions().
+ * This is used by custom fields that do database structure changes. MySQL commits transactions automatically when the database
+ * structure changes.
+ *
+ */
+ public function pauseTransactions() {
+ if($this->isPaused()) {
+ return;
+ }
+
+ $this->resumeLevels = $this->transactionSavePointLevel;
+ while($this->transactionSavePointLevel > 0) {
+ $this->commit();
+ }
+ }
+
+ /**
+ * @see pauseTransactions()
+ */
+ public function resumeTransactions() {
+ while($this->resumeLevels > 0) {
+ $this->beginTransaction();
+ $this->resumeLevels--;
+ }
+ }
+
+ /**
+ * Rollback the DB transaction
+ *
+ * @return boolean
+ */
+ public function rollBack(): bool
+ {
+ if($this->transactionSavePointLevel == 0) {
+ throw new LogicException("Not in transaction!");
+ }
+
+ $this->transactionSavePointLevel--;
+
+ if($this->debug) {
+ go()->warn("ROLLBACK DB TRANSACTION " . $this->transactionSavePointLevel, 1);
+ }
+
+ if($this->transactionSavePointLevel == 0) {
+ return $this->getPdo()->rollBack();
+ }else
+ {
+ return true;
+ }
+ }
+
+ /**
+ * Commit the database transaction
+ *
+ * @return boolean
+ * @throws PDOException
+ */
+ public function commit(): bool
+ {
+ if($this->transactionSavePointLevel == 0) {
+ throw new PDOException("Not in transaction!");
+ }
+
+ $this->transactionSavePointLevel--;
+
+ if($this->debug) {
+ go()->debug("COMMIT DB TRANSACTION " . $this->transactionSavePointLevel, 1);
+ }
+
+
+ if($this->transactionSavePointLevel == 0) {
+ return $this->getPdo()->commit();
+ }else
+ {
+ return true;
+ }
+ }
+
+ /**
+ * Check if a transaction is active
+ *
+ * @return boolean
+ */
+ public function inTransaction(): bool
+ {
+ return $this->getPdo()->inTransaction();
+ }
+
+ /**
+ * Lock the table of this model and also for the 't' alias.
+ *
+ * The locks array should be indexed by model name and the value is an array with two optional values.
+ * THe first is a boolean that enables a write-lock and the second is a table alias.
+ *
+ * @param array $locks eg. [go\cores\Users\Model\User::tableName() => [true, 't']]
+ *
+ * @throws PDOException
+ */
+ public function lock(array $locks)
+ {
+ $sql = "LOCK TABLES ";
+
+ foreach ($locks as $tableName => $lockInfo) {
+ $sql .= $tableName . " ";
+
+ if (isset($lockInfo[1])) {
+ $sql .= ' AS ' . $lockInfo[1] . ' ';
+ }
+ $sql .= empty($lockInfo[0]) ? 'READ' : 'WRITE';
+
+ $sql .= ', ';
+ }
+
+ $sql = rtrim($sql, ', ');
+
+ App::get()->getDbConnection()->exec($sql);
+ }
+
+ /**
+ * Create a delete statement
+ *
+ * @param string $tableName
+ * @param Query|array|string|null $query
+ * @return Statement
+ * @example
+ * ```
+ * $success = App::get()
+ * ->getDbConnection()
+ * ->delete("test_a", ['id' => 1])
+ * ->execute();
+ * ```
+ */
+ public function delete(string $tableName, $query = null): Statement
+ {
+ $query = Query::normalize($query);
+
+ $queryBuilder = new QueryBuilder($this);
+ $build = $queryBuilder->buildDelete($tableName, $query);
+
+ return $this->createStatement($build);
+ }
+
+ /**
+ * Create an insert statement
+ *
+ * @param string $tableName
+ * @param array|Query $data Key value array, a numeric array with key value arrays or select query
+ * @param string[] $columns If $data is a query object then you can supply the
+ * selected columns with this parameter. If not given all columns must be
+ * selected in the correct order.
+ *
+ * @return Statement
+ * @throws PDOException
+ * @example Single record
+ * ```
+ * $data = [
+ * "propA" => "string 1",
+ * "createdAt" => new \DateTime(),
+ * "modifiedAt" => new \DateTime()
+ * ];
+ *
+ * $result = App::get()
+ * ->getDbConnection()
+ * ->insert("test_a", $data)
+ * ->execute();
+ * ```
+ *
+ * Get the ID if it has an auto increment column:
+ * ```
+ * $id = App::get()->getDbConnection()->getPDO()->lastInsertId();
+ * ```
+ *
+ *
+ * @example Multiple records
+ * ```
+ * $data = [[
+ * "propA" => "string 1",
+ * "createdAt" => new \DateTime(),
+ * "modifiedAt" => new \DateTime()
+ * ],[
+ * "propA" => "string 2",
+ * "createdAt" => new \DateTime(),
+ * "modifiedAt" => new \DateTime()
+ * ]];
+ *
+ * $result = App::get()
+ * ->getDbConnection()
+ * ->insert("test_a", $data)
+ * ->execute();
+ * ```
+ *
+ * Or with an expression:
+ *
+ * ```
+ * App::get()->getDbConnection()
+ * ->update("core_state", new \go\core\db\Expression("highestModSeq = highestModSeq + 1"), $query);
+ * ```
+ *
+ */
+ public function insert(string $tableName, $data, array $columns = []): Statement
+ {
+
+ $queryBuilder = new QueryBuilder($this);
+ $build = $queryBuilder->buildInsert($tableName, $data, $columns);
+
+ return $this->createStatement($build);
+ }
+
+ /**
+ * Insert data in the database and ignore if the records exist
+ *
+ * @param string $tableName
+ * @param array|Query $data Key value array or select query
+ * @param string[] $columns If $data is a query object then you can supply the
+ * selected columns with this parameter. If not given all columns must be
+ * selected in the correct order.
+ *
+ * @return Statement
+ * @throws DbException
+ * @see insert()
+ */
+ public function insertIgnore(string $tableName, Query|array $data, array $columns = []): Statement
+ {
+
+ $queryBuilder = new QueryBuilder($this);
+ $build = $queryBuilder->buildInsert($tableName, $data, $columns, "INSERT IGNORE");
+
+ return $this->createStatement($build);
+ }
+
+ /**
+ * Replace data in the database
+ *
+ * @param string $tableName
+ * @param array|Query $data Key value array, array of key value arrays for multi insert or select query
+ * @param string[] $columns If $data is a query object then you can supply the
+ * selected columns with this parameter. If not given all columns must be
+ * selected in the correct order.
+ *
+ * @return Statement
+ * @throws Throwable
+ * @see insert()
+ */
+ public function replace(string $tableName, Query|array $data, array $columns = []): Statement
+ {
+
+ $queryBuilder = new QueryBuilder($this);
+ $build = $queryBuilder->buildInsert($tableName, $data, $columns, "REPLACE");
+
+ return $this->createStatement($build);
+ }
+
+ /**
+ * Create an update statement
+ *
+ * @param string $tableName
+ * @param $data
+ * @param Query|string|array|null $query {@see Query::normalize()}
+ * @return Statement
+ * @throws PDOException
+ * @example with join
+ * ```
+ * go()->getDbConnection()->update(
+ * 'core_acl',
+ * [
+ * 'acl.entityTypeId' => $entityTypeId,
+ * 'acl.entityId' => new Expression('ab.id')], // Use go\core\db\Expression for references to tables
+ * (new Query())
+ * ->tableAlias('acl') // set alias for core_acl table
+ * ->join('addressbook_addressbook, 'ab', 'ab.aclId = acl.id'))
+ * ->execute();
+ * ```
+ *
+ *
+ * @example
+ * ```
+ * $data = [
+ * "propA" => "string 3",
+ * "count" => new \go\core\db\Expression('count + 1'), //Example for expression
+ * ];
+ *
+ * $stmt = App::get()->getDbConnection()->update("test_a", $data, ['id' => 1]);
+ * $stmt->execute();
+ * ```
+ *
+ */
+ public function update(string $tableName, $data, Criteria|array|string|null $query = null): Statement
+ {
+ $query = Query::normalize($query);
+
+ $queryBuilder = new QueryBuilder($this);
+ $build = $queryBuilder->buildUpdate($tableName, $data, $query);
+
+ return $this->createStatement($build);
+ }
+
+ /**
+ * Update but with ignore
+ *
+ * @param $tableName
+ * @param $data
+ * @param Criteria|array|string|null $query
+ * @return Statement
+ * @throws Exception
+ * @see update()
+ */
+ public function updateIgnore($tableName, $data, Criteria|array|string|null $query = null): Statement
+ {
+ $query = Query::normalize($query);
+
+ $queryBuilder = new QueryBuilder($this);
+ $build = $queryBuilder->buildUpdate($tableName, $data, $query, "UPDATE IGNORE");
+
+ return $this->createStatement($build);
+ }
+
+ /**
+ * Create a select statement.
+ *
+ * @param string|string[] $select
+ * @return Query
+ * @example
+ * ```
+ * $query = go()->getDbConnection()
+ * ->select('*')
+ * ->from('test_a')
+ * ->where('id', '=', 1);
+ *
+ * $stmt = $query->execute();
+ *
+ * ```
+ *
+ * @see Query
+ */
+ public function select(array|string $select = "*"): Query
+ {
+ $query = new Query();
+ return $query->setDbConnection($this)->select($select);
+ }
+
+ /**
+ * Select a single column or count(*) for example.
+ *
+ * Shortcut for:
+ * $query->fetchMode(\PDO::FETCH_COLUMN,0)->select($select)
+ *
+ * @param string $select
+ * @return Query
+ */
+ public function selectSingleValue(string $select): Query
+ {
+ $query = new Query();
+ return $query->setDbConnection($this)->selectSingleValue($select);
+ }
+
+ /**
+ * Create a statement from a QueryBuilder result.
+ *
+ * For internal use of the API.
+ *
+ * @param array $build
+ * @return Statement
+ * @throws Exception
+ */
+ public function createStatement(array &$build): Statement
+ {
+ try {
+ if($this->debug) {
+ $build['start'] = go()->getDebugger()->getMicroTime();
+ }
+
+ $pdoStmt = $this->getPDO()->prepare($build['sql']);
+
+ $stmt = new Statement($pdoStmt);
+ $stmt->setBuild($build);
+
+ for ($i =0, $l = count($build['params']); $i < $l; $i++) {
+ $stmt->bindValue($i + 1, $build['params'][$i]['value'], $build['params'][$i]['pdoType']);
+ }
+
+ return $stmt;
+ } catch(PDOException $e) {
+ throw new DbException($e, go()->getDebugger()->enabled ? QueryBuilder::debugBuild($build) : null);
+ } catch(Exception $e) {
+ if(go()->getDebugger()->enabled) {
+ go()->debug(QueryBuilder::debugBuild($build));
+ }
+ throw $e;
+ }
+ }
+
+
+ /**
+ * Get or set foreign key checks
+ *
+ * @param bool|null $value Provide to set new value
+ * @return bool The current or old value
+ * @throws DbException
+ */
+ public function foreignKeyChecks(bool|null $value = null): bool
+ {
+ $stmt = $this->query("SELECT @@SESSION.foreign_key_checks");
+ $current = !!$stmt->fetch(PDO::FETCH_COLUMN, 0);
+
+ if(isset($value) && $value != $current) {
+ $this->exec("set foreign_key_checks = " . ($value ? "1" : "0") . ";");
+ }
+
+ return $current;
+ }
+
+
+}
diff --git a/config/services/containers/app/groupoffice/config/config.php.j2 b/config/services/containers/app/groupoffice/config/config.php.j2
new file mode 100644
index 0000000..4b3b9cf
--- /dev/null
+++ b/config/services/containers/app/groupoffice/config/config.php.j2
@@ -0,0 +1,36 @@
+
+ array (
+ 'package' => 'studio',
+ )
+);
diff --git a/config/services/containers/app/groupoffice/config/configfile.php.j2 b/config/services/containers/app/groupoffice/config/configfile.php.j2
new file mode 100644
index 0000000..ac8311c
--- /dev/null
+++ b/config/services/containers/app/groupoffice/config/configfile.php.j2
@@ -0,0 +1,252 @@
+createDsn($config['db_name'] ?? "groupoffice", $config);
+ /*
+ * Database TLS
+ */
+ $options = [];
+
+ if (!empty($config['db_ssl_ca'])) {
+ $verifyServerCert =
+ $config['db_ssl_verify_server_cert'] ?? true;
+
+ if (version_compare(PHP_VERSION, '8.5.0') >= 0) {
+ $options[PDO\Mysql::ATTR_SSL_CA] =
+ $config['db_ssl_ca'];
+
+ $options[PDO\Mysql::ATTR_SSL_VERIFY_SERVER_CERT] =
+ $verifyServerCert;
+ } else {
+ $options[PDO::MYSQL_ATTR_SSL_CA] =
+ $config['db_ssl_ca'];
+
+ $options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] =
+ $verifyServerCert;
+ }
+ }
+
+
+ try {
+ $pdo = new PDO(
+ $dsn,
+ $config['db_user'],
+ $config['db_pass'],
+ $options
+ );
+ }
+ /*
+ * Database TLS
+ */
+ catch(Exception $e){
+ $dbConnectError = "Could not connect to the database with DSN = '" . $dsn . "' with username '" . ($config['db_user'] ?? "groupoffice") . " and " . (empty($config['db_pass']) ? "NOT using password" : "USING a password") . ". The database returned this error:
$db->exec() or die(print_r($db->errorInfo(), true)); // incorrect
?>
".$e->getMessage();
+ }
+
+ if($pdo) {
+ $clientVersion = $pdo->getAttribute(PDO::ATTR_CLIENT_VERSION);
+ if (strpos($clientVersion, 'mysqlnd') === false) {
+ $dbConnectError = "PDO is not using the mysqlnd driver. Please make sure PDO uses mysqlnd. It's now using: " . $clientVersion;
+ $pdo = null;
+ }
+ }
+
+ return $pdo;
+}
+
+
+
+$configFile = App::findConfigFile();
+if(!$configFile) {
+ $configFile = '/etc/groupoffice/config.php';
+} else
+{
+ require($configFile);
+}
+
+$mustSave = false;
+
+if($_SERVER['REQUEST_METHOD'] != 'POST')
+{
+
+ $config['file_storage_path'] = $config['file_storage_path'] ?? "/var/lib/groupoffice";
+ $config['tmpdir'] = $config['tmpdir'] ?? "/tmp/groupoffice";
+
+ //check if config.ini is already prefilled with usable data (Docker)
+ $tmpFolder = new Folder($config['tmpdir']);
+ $dataFolder = new Folder($config['file_storage_path']);
+ if(!$dataFolder->isWritable() && isset($_SERVER['DOCUMENT_ROOT'])) {
+ $dataFolder = new Folder(dirname($_SERVER['DOCUMENT_ROOT']) . '/groupoffice');
+ $mustSave = true;
+ }
+
+ if(dbConnect($config) && $dataFolder->isWritable() && $tmpFolder->isWritable() && !$mustSave) {
+ header("Location: install.php");
+ exit();
+ }
+
+ $_POST['dbPort'] = $config['db_port'] ?? 3306;
+ $_POST['dbHostname'] = $config['db_host'] ?? 'localhost';
+ $_POST['dbName'] = $config['db_name'] ?? "groupoffice";
+
+ $_POST['dbUsername'] = $config['db_user'] ?? "groupoffice";
+ //$_POST['dbPassword'] = $config['db']['password'];
+} else
+{
+// $config['general']['dataPath'] = $_POST['dataPath'];
+// $config['general']['tmpPath'] = $_POST['tmpPath'];
+// $config['db']['dsn'] = 'mysql:host='.$_POST['dbHostname'].';port='.$_POST['dbPort'].';dbname='.$_POST['dbName'];
+// $config['db']['username'] = $_POST['dbUsername'];
+// $config['db']['password'] = $_POST['dbPassword'];
+
+ $config['file_storage_path'] = $_POST['dataPath'];
+ $config['tmpdir'] = $_POST['tmpPath'];
+ $config['db_host'] =$_POST['dbHostname'];
+ $config['db_name'] =$_POST['dbName'];
+ $config['db_user'] =$_POST['dbUsername'];
+ $config['db_pass'] =$_POST['dbPassword'];
+ $config['db_port'] =$_POST['dbPort'];
+
+ $tmpFolder = new Folder($config['tmpdir']);
+ $dataFolder = new Folder($config['file_storage_path']);
+
+
+ if($dataFolder->isWritable() && $tmpFolder->isWritable() && dbConnect($config)) {
+ $cFile = new \go\core\fs\File($configFile);
+
+ if(!$cFile->putContents("getPath());
+ }
+
+ header('Location: install.php');
+ exit();
+ }
+ }
+}
+
+
+
+
+require('header.php');
+?>
+
+
+
+
+ + " required /> +
++ + " required /> +
++ + " required /> +
++ + " required /> +
++ + +
+ + + + + + + + + + + diff --git a/config/services/containers/app/groupoffice/config/groupoffice-ca.ini.j2 b/config/services/containers/app/groupoffice/config/groupoffice-ca.ini.j2 new file mode 100644 index 0000000..095b3bb --- /dev/null +++ b/config/services/containers/app/groupoffice/config/groupoffice-ca.ini.j2 @@ -0,0 +1 @@ +openssl.cafile=/etc/ssl/groupoffice/{{ root_cert_filename }} diff --git a/config/services/containers/app/groupoffice/groupoffice.container.j2 b/config/services/containers/app/groupoffice/groupoffice.container.j2 new file mode 100644 index 0000000..1dc26f9 --- /dev/null +++ b/config/services/containers/app/groupoffice/groupoffice.container.j2 @@ -0,0 +1,41 @@ +[Quadlet] +DefaultDependencies=false + +[Unit] +Description=GroupOffice + +[Container] +Image=docker.io/intermesh/groupoffice:{{ version['containers']['groupoffice'] }} + +ContainerName=groupoffice +HostName=groupoffice + +PublishPort={{ services['groupoffice']['ports']['http'] }}:80/tcp + +Volume=%h/data/containers/groupoffice/data:/var/lib/groupoffice:rw +Volume=%h/containers/groupoffice/ssl/{{ root_cert_filename }}:/etc/ssl/groupoffice/{{ root_cert_filename }}:ro +# Keep the official image intact. +Volume=%h/containers/groupoffice/config/config.php:/etc/groupoffice/config.php:ro +Volume=%h/containers/groupoffice/config/groupoffice-ca.ini:/usr/local/etc/php/conf.d/99-groupoffice-ca.ini:ro +Volume=%h/containers/groupoffice/config/Authenticator.php:/usr/local/share/groupoffice/go/modules/community/imapauthenticator/model/Authenticator.php:ro +Volume=%h/containers/groupoffice/config/db_Connection.php:/usr/local/share/groupoffice/go/core/db/Connection.php:ro +Volume=%h/containers/groupoffice/config/configfile.php:/usr/local/share/groupoffice/install/configfile.php:ro + +Environment="TZ={{ timezone }}" + +# GroupOffice official Docker DB configuration +Environment="MYSQL_HOST={{ services['mariadb']['domain']['internal'] }}.{{ domain['internal'] }}" +Environment="MYSQL_DATABASE=groupoffice_db" +Environment="MYSQL_USER=groupoffice" +Secret=GROUPOFFICE_DB_PASSWORD,type=env,target=MYSQL_PASSWORD + +[Service] +ExecStartPre=/usr/bin/nc -zv {{ services['mariadb']['domain']['internal'] }}.{{ domain['internal'] }} {{ services['mariadb']['ports']['tcp'] }} +ExecStartPre=/usr/bin/nc -zv {{ services['maddy']['domain']['internal'] }}.{{ domain['internal'] }} 993 +ExecStartPost=-/usr/bin/podman exec -u www-data groupoffice php /usr/local/share/groupoffice/cli.php core/System/upgrade +Restart=always +RestartSec=10s +TimeoutStopSec=120 + +[Install] +WantedBy=default.target diff --git a/config/services/containers/app/groupoffice/services/groupoffice-cron.service b/config/services/containers/app/groupoffice/services/groupoffice-cron.service new file mode 100644 index 0000000..2c28d82 --- /dev/null +++ b/config/services/containers/app/groupoffice/services/groupoffice-cron.service @@ -0,0 +1,9 @@ +[Unit] +Description=GroupOffice cron.php +Requires=groupoffice.service +After=groupoffice.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/podman exec -u www-data groupoffice \ + php /usr/local/share/groupoffice/cron.php diff --git a/config/services/containers/app/groupoffice/services/groupoffice-cron.timer b/config/services/containers/app/groupoffice/services/groupoffice-cron.timer new file mode 100644 index 0000000..f23556a --- /dev/null +++ b/config/services/containers/app/groupoffice/services/groupoffice-cron.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Run GroupOffice cron every 1 minutes + +[Timer] +OnBootSec=1min +OnUnitActiveSec=1min +Unit=groupoffice-cron.service + +[Install] +WantedBy=timers.target diff --git a/config/services/containers/app/homepage/config/services.yaml.j2 b/config/services/containers/app/homepage/config/services.yaml.j2 index 5d7f040..3cf4089 100644 --- a/config/services/containers/app/homepage/config/services.yaml.j2 +++ b/config/services/containers/app/homepage/config/services.yaml.j2 @@ -1,6 +1,7 @@ {% set icon_map = { + 'ldap': 'lldap', 'tandoor': 'tandoor-recipes', - 'ldap': 'lldap' + 'openwebui': 'open-webui' } %} - Services: diff --git a/config/services/containers/app/kaneo/kaneo.container.j2 b/config/services/containers/app/kaneo/kaneo.container.j2 new file mode 100644 index 0000000..6d36e72 --- /dev/null +++ b/config/services/containers/app/kaneo/kaneo.container.j2 @@ -0,0 +1,48 @@ +[Quadlet] +DefaultDependencies=false + +[Unit] +Description=Kaneo + +[Container] +Image=ghcr.io/usekaneo/kaneo:{{ version['containers']['kaneo'] }} + +ContainerName=kaneo +HostName=kaneo + +PublishPort={{ services['kaneo']['ports']['http'] }}:5173/tcp + +Volume=%h/containers/kaneo/ssl:/etc/ssl/kaneo:ro + +Environment="TZ={{ timezone }}" +Environment="DISABLE_PASSWORD_REGISTRATION=true" +Environment="DISABLE_REGISTRATION=false" +Environment="DISABLE_GUEST_ACCESS=true" +Environment="KANEO_CLIENT_URL=https://{{ services['kaneo']['domain']['public'] }}.{{ domain['public'] }}" +Secret=KANEO_AUTH_SECRET,type=env,target=AUTH_SECRET + +# PostgreSQL +Environment="POSTGRES_HOST={{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }}" +Environment="POSTGRES_PORT={{ services['postgresql']['ports']['tcp'] }}" +Environment="POSTGRES_DB=kaneo_db" +Environment="POSTGRES_USER=kaneo" +Environment="PGSSLMODE=verify-full" +Environment="NODE_EXTRA_CA_CERTS=/etc/ssl/kaneo/{{ root_cert_filename }}" +Secret=KANEO_DB_PASSWORD,type=env,target=POSTGRES_PASSWORD + +Environment="CUSTOM_OAUTH_CLIENT_ID=kaneo" +Environment="CUSTOM_OAUTH_DISCOVERY_URL=https://{{ services['authelia']['domain']['public'] }}.{{ domain['public'] }}/.well-known/openid-configuration" +Environment="CUSTOM_OAUTH_SCOPES=openid,profile,email" +Environment="CUSTOM_AUTH_PKCE=true" +Secret=KANEO_OIDC_CLIENT_SECRET,type=env,target=CUSTOM_OAUTH_CLIENT_SECRET + + +[Service] +ExecStartPre=/usr/bin/nc -zv {{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }} {{ services['postgresql']['ports']['tcp'] }} + +Restart=always +RestartSec=10s +TimeoutStopSec=120 + +[Install] +WantedBy=default.target diff --git a/config/services/containers/app/llama/config/models.ini.j2 b/config/services/containers/app/llama/config/models.ini.j2 new file mode 100644 index 0000000..5be2350 --- /dev/null +++ b/config/services/containers/app/llama/config/models.ini.j2 @@ -0,0 +1,13 @@ +version = 1 + +{% for models in (services['llama']['dependency']['models'] | dict2items | list) %} +{% if models['key'] == 'default' %} +[*] +{{ models['value'] }} + +{% else %} +[{{ models['key'] }}] +{{ models['value'] }} + +{% endif %} +{% endfor %}{{ '' }} diff --git a/config/services/containers/app/llama/llama.container.j2 b/config/services/containers/app/llama/llama.container.j2 new file mode 100644 index 0000000..d8a8b84 --- /dev/null +++ b/config/services/containers/app/llama/llama.container.j2 @@ -0,0 +1,34 @@ +[Quadlet] +DefaultDependencies=false + +[Unit] +Description=llama.cpp Model Server + +[Container] +Image=ghcr.io/ggml-org/llama.cpp:{{ version['containers']['llama'] }} + +ContainerName=llama +HostName=llama +PublishPort={{ services['llama']['ports']['http'] }}:9931/tcp + +# iGPU access for Vulkan +AddDevice=/dev/dri:/dev/dri +PodmanArgs=--group-add keep-groups + +Volume=%h/containers/llama/models:/models:ro +Volume=%h/containers/llama/config:/config:ro + +Environment="TZ={{ timezone }}" + +Exec=--models-preset /config/models.ini \ + --models-max 2 \ + --host 0.0.0.0 \ + --port 9931 + +[Service] +Restart=always +RestartSec=10s +TimeoutStopSec=300 + +[Install] +WantedBy=default.target diff --git a/config/services/containers/app/openwebui/openwebui.container.j2 b/config/services/containers/app/openwebui/openwebui.container.j2 new file mode 100644 index 0000000..80f6c5c --- /dev/null +++ b/config/services/containers/app/openwebui/openwebui.container.j2 @@ -0,0 +1,84 @@ +[Quadlet] +DefaultDependencies=false + +[Unit] +Description=Open WebUI + +[Container] +Image=ghcr.io/open-webui/open-webui:{{ version['containers']['openwebui'] }} +ContainerName=openwebui +HostName=openwebui +PublishPort={{ services['openwebui']['ports']['http'] }}:8080/tcp + +Volume=%h/data/containers/openwebui/data:/app/backend/data:rw +Volume=%h/containers/openwebui/ssl:/etc/ssl/openwebui:ro + +# General +Environment="TZ={{ timezone }}" +Environment="WEBUI_URL=https://{{ services['openwebui']['domain']['public'] }}.{{ domain['public'] }}" +Environment="CORS_ALLOW_ORIGIN=https://{{ services['openwebui']['domain']['public'] }}.{{ domain['public'] }}" +Environment="ENABLE_PERSISTENT_CONFIG=false" +Environment="ENABLE_SIGNUP=false" +Environment="ENABLE_LOGIN_FORM=false" +Environment="ENABLE_PASSWORD_AUTH=false" +Environment="ENABLE_PASSWORD_CHANGE_FORM=false" +Secret=OPEN_WEBUI_SECRET_KEY,type=env,target=WEBUI_SECRET_KEY +# We use OpenAI-compatible backends, not Ollama +Environment="ENABLE_OLLAMA_API=false" + +# PostgreSQL +Environment="DATABASE_TYPE=postgresql" +Environment="DATABASE_HOST={{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }}" +Environment="DATABASE_PORT={{ services['postgresql']['ports']['tcp'] }}" +Environment="DATABASE_NAME=openwebui_db" +Environment="DATABASE_USER=openwebui" +Environment="PGSSLMODE=verify-full" +Environment="PGSSLROOTCERT=/etc/ssl/openwebui/{{ root_cert_filename }}" +Environment="PGSSLCERTMODE=disable" +Secret=OPEN_WEBUI_POSTGRES_PASSWORD,type=env,target=DATABASE_PASSWORD + +# OIDC +Environment="ENABLE_OAUTH_PERSISTENT_CONFIG=false" +Environment="ENABLE_OAUTH_SIGNUP=true" +Environment="OAUTH_AUTO_REDIRECT=false" +Environment="OAUTH_MERGE_ACCOUNTS_BY_EMAIL=true" +Environment="OAUTH_CLIENT_ID=openwebui" +Environment="OPENID_PROVIDER_URL=https://{{ services['authelia']['domain']['public'] }}.{{ domain['public'] }}/.well-known/openid-configuration" +Environment="OAUTH_PROVIDER_NAME=Authelia" +Environment="OAUTH_SCOPES=openid email profile groups" +Environment="ENABLE_OAUTH_ROLE_MANAGEMENT=true" +Environment="OAUTH_ALLOWED_ROLES=admins,users" +Environment="OAUTH_ADMIN_ROLES=admins" +Environment="OAUTH_ROLES_CLAIM=groups" +Environment="OAUTH_CODE_CHALLENGE_METHOD=S256" +Secret=OPEN_WEBUI_OAUTH_CLIENT_SECRET,type=env,target=OAUTH_CLIENT_SECRET + +# AI backend server +# llama.cpp +Environment="OPENAI_API_BASE_URL=http://host.containers.internal:{{ services['llama']['ports']['http'] }}/v1" +Environment="OPENAI_API_KEY=none" +# stable-diffusion.cpp +Environment="ENABLE_IMAGE_GENERATION=true" +Environment="IMAGE_GENERATION_ENGINE=openai" +Environment="IMAGES_OPENAI_API_BASE_URL=http://host.containers.internal:{{ services['stablediffusion']['ports']['http'] }}/v1" +Environment="IMAGES_OPENAI_API_KEY=none" +Environment="IMAGE_GENERATION_MODEL=sd-cpp-local" +Environment="IMAGE_SIZE=1024x1024" +Environment="AIOHTTP_CLIENT_TIMEOUT=3600" +# Disable background LLM tasks +Environment="ENABLE_TITLE_GENERATION=false" +Environment="ENABLE_TAGS_GENERATION=false" +Environment="ENABLE_FOLLOW_UP_GENERATION=false" +Environment="ENABLE_AUTOCOMPLETE_GENERATION=false" +Environment="DEFAULT_MODEL_METADATA={\"builtinTools\":{\"user_input\":false}}" + + +[Service] +MemoryKSM=yes +ExecStartPre=/usr/bin/nc -zv {{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }} {{ services['postgresql']['ports']['tcp'] }} +Restart=always +RestartSec=10s +TimeoutStopSec=120 + +[Install] +WantedBy=default.target diff --git a/config/services/containers/app/stablediffusion/stablediffusion.container.j2 b/config/services/containers/app/stablediffusion/stablediffusion.container.j2 new file mode 100644 index 0000000..a81bdc3 --- /dev/null +++ b/config/services/containers/app/stablediffusion/stablediffusion.container.j2 @@ -0,0 +1,36 @@ +[Quadlet] +DefaultDependencies=false + +[Unit] +Description=stable-diffusion.cpp Image Generation Server + +[Container] +Image=ghcr.io/leejet/stable-diffusion.cpp:{{ version['containers']['stablediffusion'] }} + +ContainerName=stablediffusion +HostName=stablediffusion +PublishPort={{ services['stablediffusion']['ports']['http'] }}:1234/tcp + +# iGPU access for container +AddDevice=/dev/dri:/dev/dri +PodmanArgs=--group-add keep-groups + +Volume=%h/containers/stablediffusion/models:/models:ro + +# General +Environment="TZ={{ timezone }}" +# Prevent Fence expiration time out f9r CYCL +Environment="SYCL_PI_LEVEL_ZERO_USE_IMMEDIATE_COMMANDLISTS=1" + +Entrypoint=/sd-server +Exec=--listen-ip 0.0.0.0 \ + --listen-port 1234 \ + {{ services['stablediffusion']['dependency']['models'][stabeldiffusion_active_model] }} + +[Service] +Restart=always +RestartSec=10s +TimeoutStopSec=300 + +[Install] +WantedBy=default.target diff --git a/config/services/containers/app/sure/sure-web.container.j2 b/config/services/containers/app/sure/sure-web.container.j2 index 3be2a4b..1329e77 100644 --- a/config/services/containers/app/sure/sure-web.container.j2 +++ b/config/services/containers/app/sure/sure-web.container.j2 @@ -57,6 +57,17 @@ Environment="WEBAUTHN_ALLOWED_ORIGINS=https://{{ services['sure']['domain']['pub Environment="EXCHANGE_RATE_PROVIDER=yahoo_finance" Environment="SECURITIES_PROVIDER=yahoo_finance" +# AI backend +Environment="ASSISTANT_TYPE=builtin" +Environment="OPENAI_URI_BASE=http://host.containers.internal:10000/v1" +Environment="OPENAI_ACCESS_TOKEN=local" +Environment="OPENAI_MODEL={{ services['llama']['dependency']['models'] | dict2items | rejectattr('key', 'equalto', 'default') | map(attribute='key') | last }}" +Environment="LLM_CONTEXT_WINDOW=8192" +Environment="OPENAI_REQUEST_TIMEOUT=600" +Environment="ASSISTANT_MAX_TOOL_CALL_ITERATIONS=2" +Environment="AI_RESPONSE_TIMEOUT=2400" + + [Service] MemoryKSM=yes ExecStartPre=/usr/bin/nc -zv {{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }} {{ services['postgresql']['ports']['tcp'] }} diff --git a/config/services/containers/app/sure/sure-worker.container.j2 b/config/services/containers/app/sure/sure-worker.container.j2 index 73a0b36..6555498 100644 --- a/config/services/containers/app/sure/sure-worker.container.j2 +++ b/config/services/containers/app/sure/sure-worker.container.j2 @@ -33,6 +33,16 @@ Secret=SURE_POSTGRES_PASSWORD,type=env,target=POSTGRES_PASSWORD # Redis Environment="REDIS_URL=redis://host.containers.internal:{{ services['sure']['ports']['redis'] }}/1" +# AI backend +Environment="ASSISTANT_TYPE=builtin" +Environment="OPENAI_URI_BASE=http://host.containers.internal:10000/v1" +Environment="OPENAI_ACCESS_TOKEN=local" +Environment="OPENAI_MODEL={{ services['llama']['dependency']['models'] | dict2items | rejectattr('key', 'equalto', 'default') | map(attribute='key') | last }}" +Environment="LLM_CONTEXT_WINDOW=8192" +Environment="OPENAI_REQUEST_TIMEOUT=600" +Environment="ASSISTANT_MAX_TOOL_CALL_ITERATIONS=2" +Environment="AI_RESPONSE_TIMEOUT=2400" + # Provider ## Currency Environment="EXCHANGE_RATE_PROVIDER=yahoo_finance" diff --git a/config/services/containers/auth/authelia/config/authelia.yaml.j2 b/config/services/containers/auth/authelia/config/authelia.yaml.j2 index 4bce4f9..e20b439 100644 --- a/config/services/containers/auth/authelia/config/authelia.yaml.j2 +++ b/config/services/containers/auth/authelia/config/authelia.yaml.j2 @@ -361,4 +361,48 @@ identity_providers: - 'profile' access_token_signed_response_alg: 'none' userinfo_signed_response_alg: 'none' - token_endpoint_auth_method: 'client_secret_post' \ No newline at end of file + token_endpoint_auth_method: 'client_secret_post' + # https://www.authelia.com/integration/openid-connect/clients/open-webui/ + - client_id: 'openwebui' + client_name: 'openwebui' + consent_mode: 'implicit' + client_secret: '{{ hostvars['console']['openwebui']['oidc']['hash'] }}' + public: false + authorization_policy: 'one_factor' + require_pkce: true + pkce_challenge_method: 'S256' + redirect_uris: + - 'https://{{ services['openwebui']['domain']['public'] }}.{{ domain['public'] }}/oauth/oidc/callback' + scopes: + - 'openid' + - 'profile' + - 'groups' + - 'email' + response_types: + - 'code' + grant_types: + - 'authorization_code' + access_token_signed_response_alg: 'none' + userinfo_signed_response_alg: 'none' + token_endpoint_auth_method: 'client_secret_basic' + - client_id: 'kaneo' + client_name: 'kaneo' + consent_mode: 'implicit' + client_secret: '{{ hostvars['console']['kaneo']['oidc']['hash'] }}' + public: false + authorization_policy: 'one_factor' + require_pkce: true + pkce_challenge_method: 'S256' + redirect_uris: + - 'https://{{ services['kaneo']['domain']['public'] }}.{{ domain['public'] }}/api/auth/oauth2/callback/custom' + scopes: + - 'openid' + - 'profile' + - 'email' + response_types: + - 'code' + grant_types: + - 'authorization_code' + access_token_signed_response_alg: 'none' + userinfo_signed_response_alg: 'none' + token_endpoint_auth_method: 'client_secret_post' diff --git a/config/services/containers/common/caddy/etc/Caddyfile.j2 b/config/services/containers/common/caddy/etc/Caddyfile.j2 index d9f3765..fbf0c04 100644 --- a/config/services/containers/common/caddy/etc/Caddyfile.j2 +++ b/config/services/containers/common/caddy/etc/Caddyfile.j2 @@ -60,6 +60,10 @@ import crowdsec_log route { crowdsec +{% if service['route'] is defined and service['route']['dav'] is defined and service['route']['dav'] %} + redir /.well-known/caldav /dav 301 + redir /.well-known/carddav /dav 301 +{% endif %} {% if web_type == 'auth-public' %} reverse_proxy host.containers.internal:{{ service['ports']['http'] }} {% else %} diff --git a/docs/archives/services/app/ovms/group_vars.yaml b/docs/archives/services/app/ovms/group_vars.yaml new file mode 100644 index 0000000..2e42495 --- /dev/null +++ b/docs/archives/services/app/ovms/group_vars.yaml @@ -0,0 +1,8 @@ +services: + ovms: + ports: + http: "5000" + subuid: "104999" +version: + containers: + ovms: "2026.3-gpu" diff --git a/docs/archives/services/app/ovms/ovms.container.j2 b/docs/archives/services/app/ovms/ovms.container.j2 new file mode 100644 index 0000000..9732907 --- /dev/null +++ b/docs/archives/services/app/ovms/ovms.container.j2 @@ -0,0 +1,35 @@ +[Quadlet] +DefaultDependencies=false + +[Unit] +Description=OpenVINO Model Server + +[Container] +Image=docker.io/openvino/model_server:{{ version['containers']['ovms'] }} + +ContainerName=ovms +HostName=ovms +PublishPort={{ services['ovms']['ports']['http'] }}:8000/tcp + +# iGPU access for OpenVINO +AddDevice=/dev/dri:/dev/dri +PodmanArgs=--group-add keep-groups + +Volume=%h/containers/ovms/models:/models:ro +Volume=%h/containers/ovms/config:/config:ro +Volume=%h/containers/ovms/cache:/opt/cache:rw + +Environment="TZ={{ timezone }}" + +Exec=--rest_port 8000 \ + --config_path /config/config.json \ + --cache_dir /opt/cache \ + --file_system_poll_wait_seconds 0 + +[Service] +Restart=always +RestartSec=10s +TimeoutStopSec=300 + +[Install] +WantedBy=default.target diff --git a/docs/archives/services/app/ovms/set_ovms.yaml b/docs/archives/services/app/ovms/set_ovms.yaml new file mode 100644 index 0000000..8efdc12 --- /dev/null +++ b/docs/archives/services/app/ovms/set_ovms.yaml @@ -0,0 +1,106 @@ +--- +- name: Add user in video, render group + ansible.builtin.user: + name: "{{ ansible_user }}" + state: "present" + groups: "video, render" + append: true + become: true + +- name: Check model directory in data volume + ansible.builtin.find: + path: "{{ node['home_path'] }}/data/containers/ovms/models" + file_type: any + hidden: true + register: ovms_models + +- name: Check config directory in data volume + ansible.builtin.stat: + path: "{{ node['home_path'] }}/data/containers/ovms/config/config.json" + register: ovms_config + +- name: Failure when models directory or config file in data volume + ansible.builtin.fail: + msg: "There are no models or config file in data directory, please prepare the models and config before set the ovms" + when: (ovms_models.matched == 0) or (not ovms_config.stat.exists) + +- name: Create ovms directory + ansible.builtin.file: + path: "{{ node['home_path'] }}/{{ item }}" + state: "directory" + owner: "{{ services['ovms']['subuid'] }}" + group: "{{ node['group'] }}" + mode: "0770" + loop: + - "containers/ovms" + - "containers/ovms/models" + - "containers/ovms/config" + - "containers/ovms/cache" + become: true + +- name: Sync model data (--delete=true) + delegate_to: "{{ inventory_hostname }}" + ansible.posix.synchronize: + src: "{{ node['home_path'] }}/data/containers/ovms/models/" + dest: "{{ node['home_path'] }}/containers/ovms/models/" + owner: true + group: true + perms: true + delete: true + become: true + notify: "notification_restart_ovms" + +- name: Copy configuration file + ansible.builtin.copy: + remote_src: true + src: "{{ node['home_path'] }}/data/containers/ovms/config/config.json" + dest: "{{ node['home_path'] }}/containers/ovms/config/config.json" + owner: "{{ services['ovms']['subuid'] }}" + group: "{{ node['group'] }}" + mode: "0640" + become: true + notify: "notification_restart_ovms" + +- name: Pull image + containers.podman.podman_image: + name: "docker.io/openvino/model_server" + tag: "{{ version['containers']['ovms'] }}" + state: "present" + pull: true + +- name: Deploy container file + ansible.builtin.template: + src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/ovms/ovms.container.j2" + dest: "{{ node['home_path'] }}/.config/containers/systemd/ovms.container" + owner: "{{ ansible_user }}" + group: "{{ node['group'] }}" + mode: "0644" + notify: "notification_restart_ovms" + +- name: Enable ovms.service + ansible.builtin.systemd: + name: "ovms.service" + state: "started" + enabled: true + daemon_reload: true + scope: "user" + +- name: Check container archive images + ansible.builtin.stat: + path: "{{ node['home_path'] }}/archives/containers/docker.io_openvino_model_server_{{ version['containers']['ovms'] }}.tar" + register: container_archive_images + +- name: Save container archive images + containers.podman.podman_save: + image: + - "docker.io/openvino/model_server:{{ version['containers']['ovms'] }}" + dest: "{{ node['home_path'] }}/archives/containers/docker.io_openvino_model_server_{{ version['containers']['ovms'] }}.tar" + format: "oci-archive" + force: false + when: not container_archive_images.stat.exists + +- name: Fetch container archive images + ansible.builtin.fetch: + src: "{{ node['home_path'] }}/archives/containers/docker.io_openvino_model_server_{{ version['containers']['ovms'] }}.tar" + dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/" + flat: true