Ver código fonte

Merge branch 'iapt-platform:development' into development

visuddhinanda 2 meses atrás
pai
commit
92c7431e04
47 arquivos alterados com 1038 adições e 111 exclusões
  1. 1 1
      ai-translate/ai_translate/__main__.py
  2. 2 2
      ai-translate/pyproject.toml
  3. 0 8
      ai-translate/run.sh
  4. 2 0
      api-v8/README.md
  5. 2 1
      api-v8/app/Services/SummaryService.php
  6. 1 1
      api-v8/routes/api.php
  7. 1 0
      dashboard-v4/dashboard/.env.development
  8. 1 0
      dashboard-v4/dashboard/.env.orig
  9. 2 1
      dashboard-v4/dashboard/package.json
  10. 2 0
      dashboard-v4/dashboard/src/api/index.ts
  11. 1 0
      dashboard-v6/.env.development
  12. 4 27
      dashboard-v6/.gitignore
  13. 2 0
      dashboard-v6/src/api/index.ts
  14. 1 2
      dashboard-v6/vite.config.ts
  15. 16 11
      deploy/group_vars/all.yml
  16. 31 0
      deploy/mint-v2.2.yml
  17. 122 20
      deploy/mint.yml
  18. 0 0
      deploy/roles/mint-build-v2.3/files/.gitignore
  19. 136 0
      deploy/roles/mint-build-v2.3/tasks/main.yml
  20. 46 0
      deploy/roles/mint-v2.3/files/clean.py
  21. 9 0
      deploy/roles/mint-v2.3/files/php-fpm.sh
  22. 29 0
      deploy/roles/mint-v2.3/tasks/config.yml
  23. 13 0
      deploy/roles/mint-v2.3/tasks/laravel-scheduler.yml
  24. 74 0
      deploy/roles/mint-v2.3/tasks/main.yml
  25. 101 0
      deploy/roles/mint-v2.3/tasks/systemd.yml
  26. 15 0
      deploy/roles/mint-v2.3/templates/ai-translate.service.j2
  27. 11 0
      deploy/roles/mint-v2.3/templates/ai-translate.sh.j2
  28. 16 0
      deploy/roles/mint-v2.3/templates/ai-translate.toml.j2
  29. 13 0
      deploy/roles/mint-v2.3/templates/clean.service.j2
  30. 9 0
      deploy/roles/mint-v2.3/templates/clean.timer.j2
  31. 10 0
      deploy/roles/mint-v2.3/templates/docker.sh.j2
  32. 15 0
      deploy/roles/mint-v2.3/templates/laravel-job.service.j2
  33. 14 0
      deploy/roles/mint-v2.3/templates/laravel-scheduler.service.j2
  34. 9 0
      deploy/roles/mint-v2.3/templates/laravel-scheduler.timer.j2
  35. 63 0
      deploy/roles/mint-v2.3/templates/laravel.sh.j2
  36. 5 0
      deploy/roles/mint-v2.3/templates/open-ai-server.json.j2
  37. 15 0
      deploy/roles/mint-v2.3/templates/open-ai-server.service.j2
  38. 9 0
      deploy/roles/mint-v2.3/templates/open-ai-server.sh.j2
  39. 15 0
      deploy/roles/mint-v2.3/templates/php-fpm-8.1.service.j2
  40. 15 0
      deploy/roles/mint-v2.3/templates/php-fpm-8.4.service.j2
  41. 18 0
      deploy/roles/mint-v2.3/templates/v1/config.js.j2
  42. 67 0
      deploy/roles/mint-v2.3/templates/v1/config.php.j2
  43. 97 0
      deploy/roles/mint-v2.3/templates/v2/env.j2
  44. 2 2
      docker/mint/Dockerfile
  45. 21 1
      scripts/3rd-pack.sh
  46. 0 31
      scripts/container/mint.sh
  47. 0 3
      scripts/container/run.sh

+ 1 - 1
ai-translate/ai_translate/__main__.py

@@ -24,7 +24,7 @@ def main():
     parser.add_argument('-d', '--debug',
                         action='store_true', help='run on debug mode')
     parser.add_argument('-v', '--version',
-                        action='version', version='%(prog)s v2025.6.27')
+                        action='version', version='%(prog)s v2026.1.16')
     args = parser.parse_args()
 
     if args.debug:

+ 2 - 2
ai-translate/pyproject.toml

@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
 
 [project]
 name = "ai_translate"
-version = "2025.6.27"
-requires-python = ">= 3.13"
+version = "2026.1.16"
+requires-python = ">= 3.12"
 description = "An OpenAI consumer process"
 readme = "README.md"
 license = "MIT"

+ 0 - 8
ai-translate/run.sh

@@ -1,8 +0,0 @@
-#!/bin/bash
-
-if [ "$#" -lt 2 ]; then
-    echo "USAGE: $0 PYTHON_VERSION ARGS"
-    exit 1
-fi
-
-podman run --rm -it --events-backend=file --hostname=palm --network host -v $PWD:/srv:z "mint-python$1" bash -c "source ~/python3/bin/activate && mint-ai-translate-consumer ${@:2}"

+ 2 - 0
api-v8/README.md

@@ -295,6 +295,8 @@ php artisan mq:discussion
 php artisan mq:pr
 php artisan mq:progress
 php artisan mq:wbw.analyses
+php artisan mq:export.pali.chapter
+php artisan mq:export.article
 ```
 
 ## 运行dev server

+ 2 - 1
api-v8/app/Services/SummaryService.php

@@ -24,7 +24,8 @@ class SummaryService
     public function __construct(AIModelService $aiModels)
     {
         $models = $aiModels->getSysModels('summarize');
-        $this->modelId = $models[0]['uid'];
+        // FIXME
+        // $this->modelId = $models[0]['uid'];
         $this->apiUrl = config('mint.ai.proxy') . '/api/openai';
     }
 

+ 1 - 1
api-v8/routes/api.php

@@ -225,7 +225,7 @@ Route::group(['prefix' => 'v2'], function () {
     Route::get('relation-import', [RelationController::class, "import"]);
     Route::apiResource('term-vocabulary', TermVocabularyController::class);
     Route::apiResource('related-paragraph', RelatedParagraphController::class);
-    Route::apiResource('search', SearchController::class);
+    Route::apiResource('search-1', SearchController::class); // FIXME
     Route::get('search-book-list', [SearchController::class, 'book_list']);
     Route::apiResource('pali-word-index', WordIndexController::class);
     Route::apiResource('studio', StudioController::class);

+ 1 - 0
dashboard-v4/dashboard/.env.development

@@ -0,0 +1 @@
+REACT_APP_API_BASE=""

+ 1 - 0
dashboard-v4/dashboard/.env.orig

@@ -15,3 +15,4 @@ REACT_APP_API_SERVER=https://www.wikipali.org
 REACT_APP_ICP_CODE=
 REACT_APP_QUESTIONNAIRE_LINK=
 REACT_APP_OPENAI_PROXY=https://staging.ai.jp.wikipali.org/api/openai
+REACT_APP_API_BASE=""

+ 2 - 1
dashboard-v4/dashboard/package.json

@@ -1,7 +1,8 @@
 {
   "name": "dashboard",
-  "version": "0.1.0",
+  "version": "2026.1.20",
   "private": true,
+  "proxy": "http://localhost:8000",
   "dependencies": {
     "@ant-design/charts": "^1.4.2",
     "@ant-design/pro-components": "^2.3.32",

+ 2 - 0
dashboard-v4/dashboard/src/api/index.ts

@@ -0,0 +1,2 @@
+export const api_url = (path: string) =>
+  `${process.env.REACT_APP_API_BASE}${path}`;

+ 1 - 0
dashboard-v6/.env.development

@@ -0,0 +1 @@
+VITE_API_BASE=""

+ 4 - 27
dashboard-v6/.gitignore

@@ -1,27 +1,4 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-lerna-debug.log*
-
-node_modules
-dist
-dist-ssr
-*.local
-
-# Editor directories and files
-.vscode/*
-!.vscode/extensions.json
-.idea
-.DS_Store
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?
-
-# my add
-package-lock.json
+/node_modules/
+/dist/
+/package-lock.json
+/.env

+ 2 - 0
dashboard-v6/src/api/index.ts

@@ -0,0 +1,2 @@
+export const api_url = (path: string) =>
+  `${import.meta.env.VITE_API_BASE}${path}`;

+ 1 - 2
dashboard-v6/vite.config.ts

@@ -8,8 +8,7 @@ export default defineConfig({
     host: "127.0.0.1",
     port: 4000,
     proxy: {
-      "/api": "http://127.0.0.1:8080",
-      "/api-v2026": "http://127.0.0.1:8088",
+      "/api": "http://127.0.0.1:8000",
     },
   },
   plugins: [react()],

+ 16 - 11
deploy/group_vars/all.yml

@@ -2,19 +2,24 @@ ansible_user: "deploy"
 ansible_python_interpreter: /usr/bin/python3
 ansible_ssh_private_key_file: "{{ inventory_dir }}/.ssh/id_ed25519"
 
-app_deploy_target: "/srv/{{ app_domain }}/mint-{{ mint_version }}"
-app_build_target: "{{ ansible_env.HOME }}/build/{{ app_domain }}/mint-{{ mint_version }}"
-app_downloads: "{{ ansible_env.HOME }}/downloads"
+app_deploy_target: "/srv/{{ app_domain }}/{{ mint_version }}"
 app_debug: false
 
-app_dashboard_base_path: "/pcd"
-app_postgresql_version: "16"
-app_open_search_version: "2.19.1"
-app_python_version: "3.13"
-app_php_version: "8.1"
-app_php_fpm_port: 9081
+app_api_v8_php_base_path: "/api"
+app_api_v12_php_base_path: "/api-v2026"
+app_dashboard_v4_base_path: "/pcd"
+app_dashboard_v6_base_path: "/pcd-v2026"
+
+app_dashboard_token_key: "token.20260116"
 app_php_memory_limit: "128M"
-app_container_prefix: "mint"
 app_consumer_loop_limit_ai_translate: 16
+app_deploy_keeps: 7
+
+app_php_fpm81_port: 9810
+app_php_fpm84_port: 9840
 
-app_docker_image_name: "magnolia"
+app_php81_image: "wikipali/mint:php-8.1-20251225"
+app_php84_image: "wikipali/mint:php-8.4-20260108"
+app_python_image: "wikipali/mint:php-8.4-20260108"
+app_nodejs_image: "wikipali/mint:php-8.4-20260108"
+app_pull_images: false

+ 31 - 0
deploy/mint-v2.2.yml

@@ -0,0 +1,31 @@
+- name: Setup local build folder
+  hosts: localhost
+  tasks:
+    - name: Git checkout repo
+      ansible.builtin.git:
+        repo: "https://github.com/iapt-platform/mint.git"
+        dest: "{{ playbook_dir }}/tmp/mint"
+        version: "{{ mint_version }}"
+
+- name: Build on local
+  hosts: localhost
+  roles:
+    - mint-dashboard-v4-build
+    - mint-openai-proxy-build
+    - clove-build
+
+- name: Build mint package on remote
+  hosts: building
+  roles:
+    - mint-release-build
+
+- name: Setup deploy folder
+  hosts: all:!localhost
+  roles:
+    - mint-v2.2
+
+- name: Setup nginx
+  hosts:
+    - web
+  roles:
+    - mint-nginx

+ 122 - 20
deploy/mint.yml

@@ -1,31 +1,133 @@
-- name: Setup local build folder
+- name: Prepare the env
   hosts: localhost
   tasks:
-    - name: Git checkout repo
-      ansible.builtin.git:
-        repo: "https://github.com/iapt-platform/mint.git"
-        dest: "{{ playbook_dir }}/tmp/mint"
-        version: "{{ mint_version }}"
+    - name: Check if mint.tar.xz file exists
+      ansible.builtin.stat:
+        path: "{{ playbook_dir }}/tmp/{{ app_domain }}-{{ mint_version }}.tar.xz"
+      register: mint_release_file_status
 
-- name: Build on local
+- name: Building release package
   hosts: localhost
   roles:
-    - mint-dashboard-v4-build
-    - mint-openai-proxy-build
-    - clove-build
+    - role: mint-build-v2.3
+      when: not mint_release_file_status.stat.exists
+  vars:
+    app_workspace: "{{ ansible_facts['env']['HOME'] }}/build/mint"
 
-- name: Build mint package on remote
-  hosts: building
+- name: Upload and setup release package
+  hosts:    
+    - laravel_web
+    - laravel_task
+    - laravel_scheduler
+    - ai_translate
+    - open_ai_server
   roles:
-    - mint-release-build
+    - role: mint-v2.3
 
-- name: Setup deploy folder
-  hosts: all:!localhost
-  roles:
-    - mint-v2.2
+- name: Setup laravel framework
+  hosts:    
+    - laravel_web
+    - laravel_task
+    - laravel_scheduler
+  tasks:
+    - name: Setup api-v8
+      ansible.builtin.command: docker run --rm -it -v /srv:/srv:z {{ app_php81_image }} {{ app_deploy_target }}/scripts/laravel.sh setup      
+    - name: Setup api-v12
+      ansible.builtin.command: docker run --rm -it -v /srv:/srv:z {{ app_php84_image }} {{ app_deploy_target }}/scripts/laravel.sh setup
+
+- name: Start laravel scheduler
+  hosts:    
+    - laravel_scheduler
+  tasks:
+    - name: Enable laravel scheduler timer(v8)
+      become: true
+      ansible.builtin.systemd_service:
+        name: "{{ app_domain }}-laravel-scheduler-v8.timer"
+        state: restarted
+        enabled: true
+    - name: Enable laravel scheduler timer(v12)
+      become: true
+      ansible.builtin.systemd_service:
+        name: "{{ app_domain }}-laravel-scheduler-v12.timer"
+        state: restarted
+        enabled: true
+
+- name: Start laravel job
+  hosts:    
+    - laravel_task
+  tasks:
+    - name: Enable laravel job service
+      become: true
+      ansible.builtin.systemd_service:
+        name: "{{ app_domain }}-laravel-job-{{ item.name }}.service"
+        state: restarted
+        enabled: true
+      with_items:
+        - { name: "mq-discussion"}
+        - { name: "mq-pr"}
+        - { name: "mq-progress"}
+        - { name: "mq-wbw.analyses"}
+        - { name: "mq-export.pali.chapter"}
+        - { name: "mq-export.article"}
+    
+- name: Start ai-translate service
+  hosts:    
+    - ai_translate
+  tasks:
+    - name: Enable ai-translate service
+      become: true
+      ansible.builtin.systemd_service:
+        name: "{{ app_domain }}-ai-translate.service"
+        state: restarted
+        enabled: true
+
+- name: Start open-ai-server
+  hosts:    
+    - open_ai_server
+  tasks:
+    - name: Enable open-ai-server service
+      become: true
+      ansible.builtin.systemd_service:
+        name: "{{ app_domain }}-open-ai-server.service"
+        state: restarted
+        enabled: true
 
 - name: Setup nginx
   hosts:
-    - web
-  roles:
-    - mint-nginx
+    - nginx
+  tasks:
+    - name: Fix document root
+      become: true
+      ansible.builtin.lineinfile:
+        path: /etc/nginx/sites-enabled/{{ app_domain }}.conf
+        regexp: '^root '
+        line: "root {{ app_deploy_target }}/api-v8/public;"
+    - name: Fix {{ app_dashboard_v4_base_path }}
+      become: true
+      ansible.builtin.lineinfile:
+        path: /etc/nginx/sites-enabled/{{ app_domain }}.conf
+        search_string: 'dashboard-v4'
+        line: "alias {{ app_deploy_target }}/dashboard-v4/;"
+    - name: Fix {{ app_dashboard_v6_base_path }}
+      become: true
+      ansible.builtin.lineinfile:
+        path: /etc/nginx/sites-enabled/{{ app_domain }}.conf
+        search_string: 'dashboard-v6'
+        line: "alias {{ app_deploy_target }}/dashboard-v6/;"
+    - name: Create nginx logs folder
+      become: true
+      ansible.builtin.file:
+        path: "{{ app_deploy_target | dirname }}/logs"
+        state: directory
+        owner: www-data
+        mode: "0755"
+    - name: Test nginx configuration
+      become: true
+      ansible.builtin.command: nginx -t
+    - name: Restart nginx    
+      become: true
+      ansible.builtin.systemd_service:
+        name: nginx
+        state: restarted
+        enabled: true
+      

+ 0 - 0
deploy/roles/mint-build-v2.3/files/.gitignore


+ 136 - 0
deploy/roles/mint-build-v2.3/tasks/main.yml

@@ -0,0 +1,136 @@
+- name: Create build folder
+  ansible.builtin.file:
+    path: "{{ app_workspace | dirname }}"
+    state: directory
+    # owner: "{{ ansible_user }}"
+    mode: "0755"
+
+- name: Git checkout mint repo
+  ansible.builtin.git:
+    repo: "https://github.com/iapt-platform/mint.git"
+    dest: "{{ app_workspace }}"
+    version: "{{ mint_version }}"
+
+- name: Extract dashboard-v4
+  ansible.builtin.unarchive:
+    src: dashboard-v4-{{ ansible_facts['architecture'] }}-20260116065749.tar.xz
+    dest: "{{ app_workspace }}/dashboard-v4/dashboard"
+    creates: "{{ app_workspace }}/dashboard-v4/dashboard/yarn.lock"
+
+- name: Extract api-v8
+  ansible.builtin.unarchive:
+    src: api-v8-20260117.tar.xz
+    dest: "{{ app_workspace }}/api-v8"
+    creates: "{{ app_workspace }}/api-v8/composer.lock"
+
+- name: Extract dashboard-v6
+  ansible.builtin.unarchive:
+    src: dashboard-v6-{{ ansible_facts['architecture'] }}-20260116064312.tar.xz
+    dest: "{{ app_workspace }}/dashboard-v6"
+    creates: "{{ app_workspace }}/dashboard-v6/package-lock.json"
+
+- name: Extract api-v12
+  ansible.builtin.unarchive:
+    src: api-v12-{{ ansible_facts['architecture'] }}-20260116064312.tar.xz
+    dest: "{{ app_workspace }}/api-v12"
+    creates: "{{ app_workspace }}/api-v12/composer.lock"
+
+- name: Extract open-ai-server
+  ansible.builtin.unarchive:
+    src: open-ai-server-{{ ansible_facts['architecture'] }}-20260116064312.tar.xz
+    dest: "{{ app_workspace }}/open-ai-server"
+    creates: "{{ app_workspace }}/open-ai-server/package-lock.json"
+
+- name: Build dashboard-v4
+  ansible.builtin.shell: |
+    source ~/.nvm/nvm.sh && yarn build
+  args:
+    executable: /bin/bash
+    chdir: "{{ app_workspace }}/dashboard-v4/dashboard"
+    creates: "{{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}/dashboard-v4"
+  environment:
+    BUILD_PATH: "{{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}/dashboard-v4"
+    NODE_OPTIONS: "--max_old_space_size=5120"
+    PUBLIC_URL: "{{ app_dashboard_v4_base_path }}"
+    REACT_APP_DEFAULT_LOCALE: "zh-Hans"
+    REACT_APP_LANGUAGES: "en-US,zh-Hans,zh-Hant"
+    REACT_APP_ENABLE_LOCAL_TOKEN: "true"
+    REACT_APP_TOKEN_KEY: "{{ app_dashboard_token_key }}"
+    REACT_APP_DOCUMENTS_SERVER: "{{ app_documents_server }}"
+    REACT_APP_RPC_SERVER: "{{ app_grpc_web_server }}"
+    REACT_APP_ASSETS_SERVER: "{{ app_assets_server }}"
+    REACT_APP_API_SERVER: "https://{{ app_domain }}"
+    REACT_APP_ICP_CODE: "{{ app_icp_code }}"
+    REACT_APP_MPS_CODE: "{{ app_mps_code }}"
+    REACT_APP_QUESTIONNAIRE_LINK: "{{ app_questionnaire_link }}"
+    REACT_APP_OPENAI_PROXY: "{{ app_openai_proxy_server }}/api/openai"
+
+- name: Build dashboard-v6
+  ansible.builtin.shell: |
+    source ~/.nvm/nvm.sh && npm run build -- --base={{ app_dashboard_v6_base_path }} --outDir {{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}/dashboard-v6
+  args:
+    executable: /bin/bash
+    chdir: "{{ app_workspace }}/dashboard-v6"
+    creates: "{{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}/dashboard-v6"
+  environment:
+    NODE_ENV: production
+    VITE_DEFAULT_LOCALE: "zh-Hans"
+    VITE_LANGUAGES: "en-US,zh-Hans,zh-Hant"
+    VITE_ENABLE_LOCAL_TOKEN: "true"
+    VITE_TOKEN_KEY: "{{ app_dashboard_token_key }}"
+    VITE_DOCUMENTS_SERVER: "{{ app_documents_server }}"
+    VITE_RPC_SERVER: "{{ app_grpc_web_server }}"
+    VITE_ASSETS_SERVER: "{{ app_assets_server }}"
+    VITE_API_SERVER: "https://{{ app_domain }}"
+    VITE_ICP_CODE: "{{ app_icp_code }}"
+    VITE_MPS_CODE: "{{ app_mps_code }}"
+    VITE_QUESTIONNAIRE_LINK: "{{ app_questionnaire_link }}"
+    VITE_OPENAI_PROXY: "{{ app_openai_proxy_server }}/api/openai"
+
+- name: Build open-ai proxy server
+  ansible.builtin.shell: |
+    source ~/.nvm/nvm.sh && npm run build -- --output-path {{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}/open-ai-server
+  args:
+    executable: /bin/bash
+    chdir: "{{ app_workspace }}/open-ai-server"
+    creates: "{{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}/open-ai-server"
+  environment:
+    NODE_ENV: production
+
+- name: Copy api-v8 to release folder
+  ansible.posix.synchronize:
+    src: "{{ app_workspace }}/api-v8"
+    dest: "{{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}"
+    recursive: true
+
+- name: Copy api-v12 to release folder
+  ansible.posix.synchronize:
+    src: "{{ app_workspace }}/api-v12"
+    dest: "{{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}"
+    recursive: true
+
+- name: Copy ai-translate to release folder
+  ansible.posix.synchronize:
+    src: "{{ app_workspace }}/ai-translate"
+    dest: "{{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}"
+    recursive: true
+
+# - name: Archive release.tar.xz
+#   community.general.archive:
+#     path: "{{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}/*"
+#     dest: "{{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}.tar.xz"
+#     format: xz
+#     remove: true
+
+- name: Archive release.tar.xz
+  ansible.builtin.command: tar --remove-files -cJf {{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}.tar.xz -C {{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }} .
+  args:
+    creates: "{{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}.tar.xz"
+  environment:
+    XZ_OPT: "-9"
+
+- name: Download release.tar.xz
+  ansible.builtin.fetch:
+    src: "{{ app_workspace }}/tmp/{{ app_domain }}-{{ mint_version }}.tar.xz"
+    dest: "{{ playbook_dir }}/tmp/"
+    flat: true

+ 46 - 0
deploy/roles/mint-v2.3/files/clean.py

@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+
+import logging
+import argparse
+import os
+import sys
+import shutil
+
+logger = logging.getLogger(__name__)
+
+
+def launch(root, keeps, debug):
+    logger.warning("try to clean %s and keep recent %d items", root, keeps)
+    items = os.listdir(root)
+    logger.debug("found %d files", len(items))
+    if len(items) <= keeps:
+        return
+    items.sort(key=lambda it: os.path.getmtime(
+        os.path.join(root, it)), reverse=True)
+    items = items[keeps:]
+    for it in items:
+        it = os.path.join(root, it)
+        logger.warning("delete %s", it)
+        if not debug:
+            shutil.rmtree(it)
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        prog='mint',
+        description='Clean legacy files',
+        epilog='https://github.com/iapt-platform/mint')
+    parser.add_argument('-k', '--keep', type=int, default=7)
+    parser.add_argument('-d', '--debug', action='store_true')
+    parser.add_argument('-v', '--version', action='version',
+                        version='%(prog)s v2026.1.17')
+    args = parser.parse_args()
+    logging.basicConfig(
+        level=logging.DEBUG if args.debug else logging.INFO, format='%(asctime)s %(levelname).1s %(name)s: %(message)s')
+    logging.debug('run on debug mode')
+    if args.keep < 2:
+        logging.error("keeps must lager than 2")
+        sys.exit(1)
+    root = os.getcwd()
+    launch(root, args.keep, args.debug)
+    logger.info("done.")

+ 9 - 0
deploy/roles/mint-v2.3/files/php-fpm.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+set -e
+
+sed -i 's/^listen = .*/listen = 9000/g' /etc/php/$1/fpm/pool.d/www.conf
+
+/usr/sbin/php-fpm${1} --nodaemonize --fpm-config /etc/php/$1/fpm/php-fpm.conf
+
+exit 0

+ 29 - 0
deploy/roles/mint-v2.3/tasks/config.yml

@@ -0,0 +1,29 @@
+- name: Upload .env(v2)
+  ansible.builtin.template:
+    src: v2/env.j2
+    dest: "{{ app_deploy_target }}/api-v8/.env"
+    mode: "0444"
+
+- name: Upload config.php(v1)
+  ansible.builtin.template:
+    src: v1/config.php.j2
+    dest: "{{ app_deploy_target }}/api-v8/public/app/config.php"
+    mode: "0444"
+
+- name: Upload config.js(v1)
+  ansible.builtin.template:
+    src: v1/config.js.j2
+    dest: "{{ app_deploy_target }}/api-v8/public/app/config.js"
+    mode: "0444"
+
+- name: Upload ai-translate.toml
+  ansible.builtin.template:
+    src: ai-translate.toml.j2
+    dest: "{{ app_deploy_target }}/ai-translate/config.toml"
+    mode: "0444"
+
+- name: Upload open-ai-server.json
+  ansible.builtin.template:
+    src: open-ai-server.json.j2
+    dest: "{{ app_deploy_target }}/open-ai-server/config.json"
+    mode: "0444"

+ 13 - 0
deploy/roles/mint-v2.3/tasks/laravel-scheduler.yml

@@ -0,0 +1,13 @@
+- name: Upload laravel-scheduler.service({{ item.version }})
+  become: true
+  ansible.builtin.template:
+    src: laravel-scheduler.service.j2
+    dest: "/etc/systemd/system/{{ app_domain }}-laravel-scheduler-{{ item.version }}.service"
+    mode: "0444"
+
+- name: Upload laravel-scheduler.timer({{ item.version }})
+  become: true
+  ansible.builtin.template:
+    src: laravel-scheduler.timer.j2
+    dest: "/etc/systemd/system/{{ app_domain }}-laravel-scheduler-{{ item.version }}.timer"
+    mode: "0444"

+ 74 - 0
deploy/roles/mint-v2.3/tasks/main.yml

@@ -0,0 +1,74 @@
+- name: Pull docker image
+  ansible.builtin.command: docker pull {{ item }}
+  with_items:
+    - "{{ app_php81_image }}"
+    - "{{ app_php84_image }}"
+    - "{{ app_python_image }}"
+    - "{{ app_nodejs_image }}"
+  when: app_pull_images
+
+- name: Create /srv/bin folder
+  become: true
+  ansible.builtin.file:
+    path: /srv/bin
+    state: directory
+    owner: "{{ ansible_user }}"
+    mode: "0755"
+
+- name: Upload docker.sh
+  ansible.builtin.template:
+    src: docker.sh.j2
+    dest: "/srv/bin/docker.sh"
+    mode: "0555"
+
+- name: Create /srv/python3 folder
+  become: true
+  ansible.builtin.file:
+    path: /srv/python3
+    state: directory
+    owner: "{{ ansible_user }}"
+    mode: "0755"
+
+- name: Extract python3.tar.xz
+  ansible.builtin.unarchive:
+    src: python3-{{ ansible_facts['architecture'] }}-2026011614.tar.xz
+    dest: /srv
+    creates: /srv/python3/pyvenv.cfg
+
+- name: Create workspace folder
+  become: true
+  ansible.builtin.file:
+    path: "{{ app_deploy_target | dirname }}"
+    state: directory
+    owner: "{{ ansible_user }}"
+    mode: "0755"
+
+- name: Create scripts folder
+  ansible.builtin.file:
+    path: "{{ app_deploy_target }}"
+    state: directory
+    mode: "0755"
+
+- name: Extract release
+  ansible.builtin.unarchive:
+    src: "{{ playbook_dir }}/tmp/{{ app_domain }}-{{ mint_version }}.tar.xz"
+    dest: "{{ app_deploy_target }}"
+    creates: "{{ app_deploy_target }}/api-v12"
+
+- name: Create scripts folder
+  ansible.builtin.file:
+    path: "{{ app_deploy_target }}/scripts"
+    state: directory
+    mode: "0755"
+
+- name: Upload laravel.sh
+  ansible.builtin.template:
+    src: laravel.sh.j2
+    dest: "{{ app_deploy_target }}/scripts/laravel.sh"
+    mode: "0555"
+
+- name: Upload configurations
+  ansible.builtin.import_tasks: config.yml
+
+- name: Setup systemd services
+  ansible.builtin.import_tasks: systemd.yml

+ 101 - 0
deploy/roles/mint-v2.3/tasks/systemd.yml

@@ -0,0 +1,101 @@
+- name: Upload clean.py
+  ansible.builtin.copy:
+    src: clean.py
+    dest: "{{ app_deploy_target | dirname }}/"
+    mode: "0444"
+
+- name: Upload clean.service
+  become: true
+  ansible.builtin.template:
+    src: clean.service.j2
+    dest: "/etc/systemd/system/{{ app_domain }}-clean.service"
+    mode: "0444"
+
+- name: Upload clean.timer
+  become: true
+  ansible.builtin.template:
+    src: clean.timer.j2
+    dest: "/etc/systemd/system/{{ app_domain }}-clean.timer"
+    mode: "0444"
+
+- name: Upload php-fpm.sh
+  ansible.builtin.copy:
+    src: php-fpm.sh
+    dest: "/srv/bin/"
+    mode: "0555"
+
+- name: Upload php-fpm-v8.1.service
+  become: true
+  ansible.builtin.template:
+    src: php-fpm-8.1.service.j2
+    dest: "/etc/systemd/system/php-fpm-8.1.service"
+    mode: "0444"
+
+- name: Upload php-fpm-8.4.service
+  become: true
+  ansible.builtin.template:
+    src: php-fpm-8.4.service.j2
+    dest: "/etc/systemd/system/php-fpm-8.4.service"
+    mode: "0444"
+
+- name: Upload open-ai-server.sh
+  ansible.builtin.template:
+    src: open-ai-server.sh.j2
+    dest: "{{ app_deploy_target }}/scripts/open-ai-server.sh"
+    mode: "0555"
+
+- name: Upload open-ai-server.service
+  become: true
+  ansible.builtin.template:
+    src: open-ai-server.service.j2
+    dest: "/etc/systemd/system/{{ app_domain }}-open-ai-server.service"
+    mode: "0444"
+
+- name: Upload ai-translate.sh
+  ansible.builtin.template:
+    src: ai-translate.sh.j2
+    dest: "{{ app_deploy_target }}/scripts/ai-translate.sh"
+    mode: "0555"
+
+- name: Upload ai-translate.service
+  become: true
+  ansible.builtin.template:
+    src: ai-translate.service.j2
+    dest: "/etc/systemd/system/{{ app_domain }}-ai-translate.service"
+    mode: "0444"
+
+- name: Upload laravel-worker.service
+  become: true
+  ansible.builtin.template:
+    src: laravel-job.service.j2
+    dest: "/etc/systemd/system/{{ app_domain }}-laravel-job-{{ item.name }}.service"
+    mode: "0444"
+  with_items:
+    - { name: "mq-discussion", image: "{{ app_php81_image }}", args: "mq:discussion" }
+    - { name: "mq-pr", image: "{{ app_php81_image }}", args: "mq:pr" }
+    - { name: "mq-progress", image: "{{ app_php81_image }}", args: "mq:progress" }
+    - { name: "mq-wbw.analyses", image: "{{ app_php81_image }}", args: "mq:wbw.analyses" }
+    - { name: "mq-export.pali.chapter", image: "{{ app_php81_image }}", args: "mq:export.pali.chapter" }
+    - { name: "mq-export.article", image: "{{ app_php81_image }}", args: "mq:export.article" }
+
+- name: Upload laravel sceduler
+  ansible.builtin.include_tasks: laravel-scheduler.yml
+  with_items:
+    - { image: "{{ app_php81_image }}", version: "v8" }
+    - { image: "{{ app_php84_image }}", version: "v12" }
+
+# - name: Reload systemd profiles
+#   become: true
+#   ansible.builtin.command: systemctl daemon-reload
+
+- name: Reload systemd profiles
+  become: true
+  ansible.builtin.systemd_service:
+    daemon_reload: true
+
+- name: Enable clean timer
+  become: true
+  ansible.builtin.systemd_service:
+    name: "{{ app_domain }}-clean.timer"
+    state: restarted
+    enabled: true

+ 15 - 0
deploy/roles/mint-v2.3/templates/ai-translate.service.j2

@@ -0,0 +1,15 @@
+[Unit]
+Description=open-ai server({{ app_domain }}).
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=simple
+User={{ ansible_user }}
+ExecStart=/usr/bin/docker run --rm --name {{ app_domain }}-ai-translate -v /srv:/srv:z {{ app_python_image }} {{ app_deploy_target }}/scripts/ai-translate.sh
+ExecStop=/usr/bin/docker stop {{ app_domain }}-ai-translate
+WorkingDirectory={{ app_deploy_target }}
+Restart=always
+
+[Install]
+WantedBy=multi-user.target

+ 11 - 0
deploy/roles/mint-v2.3/templates/ai-translate.sh.j2

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+set -e
+
+. /srv/python3/bin/activate
+
+cd {{ app_deploy_target }}/ai-translate/
+# https://github.com/iapt-platform/mint/blob/df8e1cf7ade16d17add360e7a869540c1ddaf1b9/api-v8/config/mint.php#L129
+python3 -m ai_translate -c config.toml -n ai.translate -q ai_translate_v2
+
+exit 0

+ 16 - 0
deploy/roles/mint-v2.3/templates/ai-translate.toml.j2

@@ -0,0 +1,16 @@
+[rabbitmq]
+host = '{{ app_rabbitmq_host }}'
+port = {{ app_rabbitmq_port }}
+user = '{{ app_rabbitmq_user }}'
+password = '{{ app_rabbitmq_password }}'
+virtual-host = '{{ app_rabbitmq_virtual_host }}'
+customer-timeout = 3600
+
+[redis]
+namespace = '{{ app_domain }}://'
+host = '{{ app_redis_host }}'
+port = {{ app_redis_port }}
+
+[app]
+api-url = '{{ app_api_server }}/api'
+openai-proxy-url = '{{ app_openai_proxy_server }}/api/openai'

+ 13 - 0
deploy/roles/mint-v2.3/templates/clean.service.j2

@@ -0,0 +1,13 @@
+[Unit]
+Description=Clean legacy files({{ app_domain }}).
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+User=root
+ExecStart=/usr/bin/python3 clean.py -d -k {{ app_deploy_keeps }}
+WorkingDirectory={{ app_deploy_target | dirname }}
+
+[Install]
+WantedBy=multi-user.target

+ 9 - 0
deploy/roles/mint-v2.3/templates/clean.timer.j2

@@ -0,0 +1,9 @@
+[Unit]
+Description=Clean legacy files daily({{ app_domain }}).e.
+
+[Timer]
+OnCalendar=*-*-* 3:00:00
+Persistent=false
+
+[Install]
+WantedBy=timers.target

+ 10 - 0
deploy/roles/mint-v2.3/templates/docker.sh.j2

@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ "$#" -eq 1 && "$1" == "php81" ]]; then
+    docker run --rm -it -v {{ app_php_fpm81_port }}
+elif [[ "$#" -eq 1 && "$1" == "php84" ]]; then
+    docker run --rm -it -v {{ app_php_fpm84_port }}
+else
+    echo "Usage: $0 php81 | php84"
+    exit 1
+fi

+ 15 - 0
deploy/roles/mint-v2.3/templates/laravel-job.service.j2

@@ -0,0 +1,15 @@
+[Unit]
+Description=laravel job({{ app_domain }}-{{ item.name }}).
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=simple
+User={{ ansible_user }}
+ExecStart=/usr/bin/docker run --rm --name {{ app_domain }}-laravel-job-{{ item.name }} -v /srv:/srv:z {{ item.image }} {{ app_deploy_target }}/scripts/laravel.sh {{ item.args }}
+ExecStop=/usr/bin/docker stop {{ app_domain }}-laravel-job-{{ item.name }}
+WorkingDirectory={{ app_deploy_target }}
+Restart=always
+
+[Install]
+WantedBy=multi-user.target

+ 14 - 0
deploy/roles/mint-v2.3/templates/laravel-scheduler.service.j2

@@ -0,0 +1,14 @@
+[Unit]
+Description=Laravel scheduler({{ app_domain }}).
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+User={{ ansible_user }}
+ExecStart=/usr/bin/docker run --rm --name {{ app_domain }}-laravel-scheduler-{{ item.version }} -v /srv:/srv:z {{ item.image }} {{ app_deploy_target }}/scripts/laravel.sh schedule:run
+ExecStop=/usr/bin/docker stop {{ app_domain }}-laravel-scheduler-{{ item.version }}
+WorkingDirectory={{ app_deploy_target }}
+
+[Install]
+WantedBy=multi-user.target

+ 9 - 0
deploy/roles/mint-v2.3/templates/laravel-scheduler.timer.j2

@@ -0,0 +1,9 @@
+[Unit]
+Description=Run laravel scheduler every minute({{ app_domain }}).
+
+[Timer]
+OnBootSec=15minutes
+OnUnitActiveSec=1minute
+
+[Install]
+WantedBy=timers.target

+ 63 - 0
deploy/roles/mint-v2.3/templates/laravel.sh.j2

@@ -0,0 +1,63 @@
+#!/bin/bash
+
+set -e
+
+# -----------------------------------------------------------------------------
+if [[ "$EUID" -ne 0 ]]; then
+    echo "please run this script as root."
+    exit 1
+fi
+
+. /etc/os-release
+if [[ "$ID" != "ubuntu" ]]; then
+    echo "unsupported distribution $ID"
+    exit 1
+fi
+
+. $HOME/.nvm/nvm.sh
+# -----------------------------------------------------------------------------
+
+export PHP_VERSION="$(php -r 'echo PHP_VERSION;')"
+
+if [[ "$PHP_VERSION" == "8.1.34" ]]; then
+    export WORKSPACE={{ app_deploy_target }}/api-v8
+elif [[ "$PHP_VERSION" == "8.4.16" ]]; then
+    export WORKSPACE={{ app_deploy_target }}/api-v12
+else
+    echo "unsupported php $PHP_VERSION"
+    exit 1
+fi
+
+# -----------------------------------------------------------------------------
+
+if [[ "$#" -eq 1 && "$1" == "diagnose" ]]; then
+    echo "workspace: $WORKSPACE"
+    php --version
+    echo "NodeJs: $(node -v)"
+    echo "npm: $(npm -v)"
+    composer diagnose
+elif [[ "$#" -eq 1 && "$1" == "setup" ]]; then
+    cd $WORKSPACE/
+    echo "caching configuration"
+    php artisan config:cache
+    echo "caching events"
+    php artisan event:cache
+    echo "caching routes"
+    php artisan route:cache
+    echo "caching views"
+    php artisan view:cache
+
+    echo "check file permissions"    
+    chown -R www-data:www-data bootstrap/cache storage
+elif [[ "$#" -eq 1 ]]; then
+    cd $WORKSPACE/
+    echo "run $1..."
+    php -d memory_limit={{ app_php_memory_limit }} artisan $1
+else
+    echo "unsupported args"
+    exit 1
+fi
+
+exit 0
+
+

+ 5 - 0
deploy/roles/mint-v2.3/templates/open-ai-server.json.j2

@@ -0,0 +1,5 @@
+{ 
+    "port": 8080,
+    "debug": false, 
+    "api-url": "{{ app_api_server }}/api" 
+}

+ 15 - 0
deploy/roles/mint-v2.3/templates/open-ai-server.service.j2

@@ -0,0 +1,15 @@
+[Unit]
+Description=open-ai server({{ app_domain }}).
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=simple
+User={{ ansible_user }}
+ExecStart=/usr/bin/docker run --rm --name {{ app_domain }}-open-ai-server -p 127.0.0.1:{{ app_openai_proxy_listen_port }}:8080/tcp -v /srv:/srv:z {{ app_nodejs_image }} {{ app_deploy_target }}/scripts/open-ai-server.sh
+ExecStop=/usr/bin/docker stop {{ app_domain }}-open-ai-server
+WorkingDirectory={{ app_deploy_target }}
+Restart=always
+
+[Install]
+WantedBy=multi-user.target

+ 9 - 0
deploy/roles/mint-v2.3/templates/open-ai-server.sh.j2

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+set -e
+
+. $HOME/.nvm/nvm.sh
+cd {{ app_deploy_target }}/open-ai-server/
+node main.*.js config.json
+
+exit 0

+ 15 - 0
deploy/roles/mint-v2.3/templates/php-fpm-8.1.service.j2

@@ -0,0 +1,15 @@
+[Unit]
+Description=php-fpm 8.1(mint).
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=simple
+User={{ ansible_user }}
+ExecStart=/usr/bin/docker run --rm --name php-fpm-8.1 -p 0.0.0.0:{{ app_php_fpm81_port }}:9000/tcp -v /srv:/srv:z {{ app_php81_image }} /srv/bin/php-fpm.sh 8.1
+ExecStop=/usr/bin/docker stop php-fpm-8.1
+WorkingDirectory=/srv
+Restart=always
+
+[Install]
+WantedBy=multi-user.target

+ 15 - 0
deploy/roles/mint-v2.3/templates/php-fpm-8.4.service.j2

@@ -0,0 +1,15 @@
+[Unit]
+Description=php-fpm 8.4(mint).
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=simple
+User={{ ansible_user }}
+ExecStart=/usr/bin/docker run --rm --name php-fpm-8.4 -p 0.0.0.0:{{ app_php_fpm84_port }}:9000/tcp -v /srv:/srv:z {{ app_php84_image }} /srv/bin/php-fpm.sh 8.4
+ExecStop=/usr/bin/docker stop php-fpm-8.4
+WorkingDirectory=/srv
+Restart=always
+
+[Install]
+WantedBy=multi-user.target

+ 18 - 0
deploy/roles/mint-v2.3/templates/v1/config.js.j2

@@ -0,0 +1,18 @@
+var ICP_CODE = "{{ app_icp_code }}"
+var GRPC_WEB_SERVER = "{{ app_grpc_web_server }}";
+var DOCUMENTS_SERVER = "{{ app_documents_server }}";
+var REACT_APP_QUESTIONNAIRE_LINK = "{{ app_questionnaire_link }}";
+var DASHBOARD_BASE_PATH = "{{ app_dashboard_v4_base_path }}";
+
+/*
+  |---------------
+  |网站资源文件,非用户的图片,音频,视频
+  |---------------
+  |对应/public/tmp/ 目录 开发线可以设置为 http://127.0.0.1:8000/tmp
+  |所有文件存储在 https://drive.google.com/drive/folders/1-4dn4juD-0-lsKndDui2W9nT9wcS_Y33?usp=sharing
+  |开发线可自行下载放到/public/tmp/
+  |或直接引用离您最近的assets server
+  |------------------------
+*/
+
+var ASSETS_SERVER = "{{ app_assets_server }}";

+ 67 - 0
deploy/roles/mint-v2.3/templates/v1/config.php.j2

@@ -0,0 +1,67 @@
+<?php
+
+#域名设置
+define("GRPC_WEB_SERVER","{{ app_grpc_web_server}}");
+define("ASSETS_SERVER","{{ app_assets_server }}");
+define("DOCUMENTS_SERVER","{{ app_documents_server }}");
+define('APP_KEY','{{ app_secret_key }}');
+define('APP_ENV','{{ app_env }}');
+define('CORS_ALLOWED_ORIGINS', '{{ app_cors_allowed_origins }}');
+define('DASHBOARD_BASE_PATH', "{{ app_dashboard_v4_base_path }}");
+
+/*
+电子邮件设置
+PHPMailer
+*/
+define("Email", [
+                                 "Host"=>"{{ app_smtp_host }}",//Set the SMTP server to send through
+                                 "SMTPAuth"=>true,//Enable SMTP authentication
+                                 "Username"=>'{{ app_smtp_user }}',//SMTP username
+                                 "Password"=>'{{ app_smtp_password }}',//SMTP password
+                                 "Port"=>{{ app_smtp_port }},//TCP port to connect to 465; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS`
+                                 "From"=>"{{ app_smtp_user }}",
+                                 "Sender"=>"webmaster"
+                                 ]);
+
+/*
+数据库设置
+*/
+define("Database",[
+        "type"=>"pgsql",
+        "server"=>"{{ app_postgresql_host }}",
+        "port"=>{{ app_postgresql_port }},
+        "name"=>"{{ app_postgresql_dbname }}",
+        "sslmode" => "disable",
+        "user" => "{{ app_postgresql_user }}",
+        "password" => "{{ app_postgresql_password }}"
+]);
+
+
+/*
+Redis 设置,
+*/
+define("Redis",[        
+        "host" => "{{ app_redis_host }}",
+        "port" => {{ app_redis_port }},
+        "password" => "",
+        "prefix"=>"{{ app_domain }}://v1/"
+]);
+
+
+# 雪花id
+define("SnowFlake",[
+        "DatacenterId"=>{{ app_snowflake_data_center_id }},
+        "WorkerId"=>{{ app_snowflake_worker_id }}
+]);
+
+#目录设置,不能更改
+require_once __DIR__."/config.dir.php";
+
+/*
+数据表
+*/
+#表设置,此行不能更改
+require_once __DIR__."/config.table.php";
+
+
+?>

+ 97 - 0
deploy/roles/mint-v2.3/templates/v2/env.j2

@@ -0,0 +1,97 @@
+BASE_DIR="{{ app_deploy_target }}/.env.global"
+CACHE_DIR="${BASE_DIR}/cache"
+TMP_DIR="${BASE_DIR}/tmp"
+
+APP_NAME="wikipali"
+APP_ENV={{ app_env }}
+APP_KEY={{ app_secret_key }}
+APP_DEBUG={{ app_debug }}
+APP_URL="https://{{ app_domain }}"
+
+LOG_CHANNEL=daily
+LOG_DEPRECATIONS_CHANNEL=null
+LOG_LEVEL=debug
+
+DB_CONNECTION=pgsql
+DB_HOST={{ app_postgresql_host }}
+DB_PORT={{ app_postgresql_port }}
+DB_DATABASE={{ app_postgresql_dbname }}
+DB_USERNAME={{ app_postgresql_user }}
+DB_PASSWORD="{{ app_postgresql_password }}"
+
+BROADCAST_DRIVER=log
+CACHE_DRIVER=redis
+FILESYSTEM_DRIVER=s3
+QUEUE_CONNECTION=sync
+SESSION_DRIVER=cookie
+SESSION_LIFETIME=120
+
+AWS_ACCESS_KEY_ID={{ app_aws_access_key }}
+AWS_SECRET_ACCESS_KEY={{ app_aws_secret_key }}
+AWS_DEFAULT_REGION={{ app_aws_region }}
+AWS_BUCKET=assets
+AWS_URL=
+AWS_ENDPOINT=https://{{ app_aws_endpoint_domain }}
+
+REDIS_HOST={{ app_redis_host }}
+REDIS_PORT={{ app_redis_port }}
+REDIS_PASSWORD=
+REDIS_DB=0
+REDIS_PREFIX="{{ app_domain }}://v20250323/"
+
+REDIS_CACHE_HOST={{ app_redis_cache_host }}
+REDIS_CACHE_PORT={{ app_redis_cache_port }}
+REDIS_CACHE_PASSWORD=
+REDIS_CACHE_DB={{ app_redis_cache_db }}
+REDIS_CACHE_PREFIX="{{ app_domain }}://v20250323/"
+
+
+MAIL_MAILER=smtp
+MAIL_HOST={{ app_smtp_host }}
+MAIL_PORT={{ app_smtp_port }}
+MAIL_USERNAME="{{ app_smtp_user }}"
+MAIL_PASSWORD="{{ app_smtp_password }}"
+MAIL_ENCRYPTION=ssl
+MAIL_FROM_ADDRESS="{{ app_smtp_user }}"
+MAIL_FROM_NAME="wikipali"
+
+PUSHER_APP_ID=
+PUSHER_APP_KEY=
+PUSHER_APP_SECRET=
+PUSHER_APP_CLUSTER=mt1
+
+MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
+MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
+
+DASHBOARD_BASE_PATH="{{ app_dashboard_v4_base_path }}"
+ASSETS_SERVER="{{ app_assets_server }}"
+GRPC_WEB_SERVER="{{ app_grpc_web_server }}"
+DOCUMENTS_SERVER="{{ app_documents_server }}"
+CORS_ALLOWED_ORIGINS="{{ app_cors_allowed_origins }}"
+
+MORUS_GRPC_HOST="{{ app_morus_grpc_host }}"
+MORUS_GRPC_PORT="{{ app_morus_listen_port }}"
+LILY_GRPC_HOST="{{ app_lily_grpc_host }}"
+LILY_GRPC_PORT="{{ app_lily_listen_port }}"
+TULIP_GRPC_HOST="{{ app_tulip_grpc_host }}"
+TULIP_GRPC_PORT="{{ app_tulip_listen_port }}"
+
+SNOWFLAKE_DATA_CENTER_ID={{ app_snowflake_data_center_id }}
+SNOWFLAKE_WORKER_ID={{ app_snowflake_worker_id }}
+
+RABBITMQ_HOST="{{ app_rabbitmq_host }}"
+RABBITMQ_PORT={{ app_rabbitmq_port }}
+RABBITMQ_VIRTUAL_HOST="{{ app_rabbitmq_virtual_host }}"
+RABBITMQ_USER="{{ app_rabbitmq_user }}"
+RABBITMQ_PASSWORD="{{ app_rabbitmq_password }}"
+
+CDN_URLS={{ app_cdn_urls }}
+ATTACHMENTS_TEMPORARY_BUCKET_NAME={{ app_attachments_bucket_name }}-t
+ATTACHMENTS_PERMANTENT_BUCKET_NAME={{ app_attachments_bucket_name }}-p
+
+FTP_HOST="{{ app_ftp_host }}"
+
+APP_ICP_CODE="{{ app_icp_code }}"
+APP_MPS_CODE="{{ app_mps_code }}"
+
+MQ_LOOP_LIMIT_AI_TRANSLATE="{{ app_consumer_loop_limit_ai_translate }}"

+ 2 - 2
docker/mint/Dockerfile

@@ -10,7 +10,7 @@ RUN apt -y install lsb-release apt-utils \
     debian-keyring debian-archive-keyring apt-transport-https software-properties-common curl wget gnupg
 RUN add-apt-repository -y ppa:ondrej/php
 RUN apt -y upgrade
-RUN apt -y install git vim locales locales-all tzdata build-essential \
+RUN apt -y install git vim locales locales-all inetutils-telnet iproute2 tzdata build-essential \
     php${PHP_VERSION}-cli php${PHP_VERSION}-fpm \
     php${PHP_VERSION}-xml php${PHP_VERSION}-imap php${PHP_VERSION}-intl php${PHP_VERSION}-mbstring php${PHP_VERSION}-bcmath \
     php${PHP_VERSION}-bz2 php${PHP_VERSION}-zip php${PHP_VERSION}-curl php${PHP_VERSION}-gd php${PHP_VERSION}-imagick \
@@ -24,7 +24,7 @@ RUN apt -y install git vim locales locales-all tzdata build-essential \
     fonts-ipaexfont fonts-ipafont fonts-konatu fonts-ipafont-nonfree-jisx0208 fonts-ipafont-nonfree-uigothic \
     fonts-mikachan \
     fonts-tibetan-machine fonts-ddc-uchen fonts-monlam fonts-sambhota-tsugring fonts-sambhota-yigchung \
-    imagemagick ffmpeg graphviz pandoc texlive-full \
+    imagemagick ffmpeg graphviz pandoc texlive-full p7zip-full \
     python3-full python3-dev \
     libssl-dev libpq-dev libmysqlclient-dev \
     && apt -y autoremove \

+ 21 - 1
scripts/3rd-pack.sh

@@ -10,7 +10,10 @@ export TAR="tar -cJf"
 
 git config --global --add safe.directory $PWD
 
-if [[ "$PHP_VERSION" == "8.1.34" ]]; then    
+# docker run --rm -it -v $(dirname $PWD):/workspace:z wikipali/mint:php-8.1-20251225
+# docker run --rm -it -v $(dirname $PWD):/workspace:z wikipali/mint:php-8.4-20260108
+
+if [[ "$PHP_VERSION" == "8.1.34" ]]; then
     cd $WORKSPACE/
     $TAR api-v8-$VERSION.tar.xz -C api-v8 node_modules package-lock.json vendor composer.lock public/node_modules public/package-lock.json public/vendor public/composer.lock
     $TAR dashboard-v4-$VERSION.tar.xz -C dashboard-v4/dashboard node_modules yarn.lock
@@ -18,12 +21,29 @@ elif [[ "$PHP_VERSION" == "8.4.16" ]]; then
     cd $WORKSPACE/api-v12/
     composer install --optimize-autoloader --no-dev
     npm install
+    
     cd $WORKSPACE/dashboard-v6/
     npm install
+    
+    cd $WORKSPACE/open-ai-server/
+    npm install
+
+    cd $WORKSPACE/ai-translate/
+    if [ ! -d /srv/python3 ]
+    then
+        python3 -m venv /srv/python3
+    fi
+    . /srv/python3/bin/activate
+    python3 -m pip install -e .
+
+    # npm install --omit=dev
 
     cd $WORKSPACE/
     $TAR api-v12-$VERSION.tar.xz -C api-v12 node_modules package-lock.json vendor composer.lock    
     $TAR dashboard-v6-$VERSION.tar.xz -C dashboard-v6 node_modules package-lock.json
+    $TAR open-ai-server-$VERSION.tar.xz -C open-ai-server node_modules package-lock.json
+    $TAR ai-translate-$VERSION.tar.xz -C ai-translate ai_translate.egg-info
+    $TAR python3-$VERSION.tar.xz -C /srv python3
 else
     echo "unsupported php version $PHP_VERSION"
     exit 1

+ 0 - 31
scripts/container/mint.sh

@@ -1,31 +0,0 @@
-#!/bin/bash
-
-set -e
-
-# -----------------------------------------------------------------------------
-if [[ "$EUID" -ne 0 ]]; then
-    echo "please run this script as root."
-    exit 1
-fi
-
-. /etc/os-release
-if [[ "$ID" != "ubuntu" ]]; then
-    echo "unsupported distribution $ID"
-    exit 1
-fi
-# -----------------------------------------------------------------------------
-
-if [[ "$#" -eq 1 && "$1" == "diagnose" ]]; then
-    php --version
-    echo "NodeJs: $(node -v)"
-    echo "npm: $(npm -v)"
-    composer diagnose
-elif [[ "$#" -eq 2 && "$1" == "fcgi" ]]; then
-    echo "start FastCGI server listening on 0.0.0.0:$2"
-    php-fpm -F -R
-else
-    echo "unsupported args, USAGE: $0 diagnose|fcgi PORT"
-    exit 1
-fi
-
-exit 0

+ 0 - 3
scripts/container/run.sh

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-set -e