commit 74cd0050d84e37ca70e3e64314cd308fd5c7a4fb Author: Nicolas Cantu Date: Sat Mar 21 17:43:45 2026 +0100 readme diff --git a/Modelfile-qwen3-code-webdev b/Modelfile-qwen3-code-webdev new file mode 100644 index 0000000..5bc08d6 --- /dev/null +++ b/Modelfile-qwen3-code-webdev @@ -0,0 +1,3 @@ +# Alias for AnythingLLM: name requested by operator. +# Base: Qwen3 Coder (cloud). For full local weights use: FROM qwen3-coder:latest +FROM qwen3-coder:480b-cloud diff --git a/README.md b/README.md new file mode 100644 index 0000000..ca0f9b4 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# smart_ide — IDE orienté intention et IA locale + +Projet d’environnement de développement où l’**inférence** repose sur **Ollama**, la **mémoire documentaire et RAG** sur **AnythingLLM**, et la bureautique métier sur **ONLYOFFICE**. Les **agents métier** existants (`ia_dev` et sous-agents) restent le noyau opératoire ; l’éditeur et l’orchestrateur les exposent via une **grammaire de commandes** plutôt que via une navigation fichiers classique. + +## Première cible de déploiement + +Le **premier déploiement** visé est un **poste Linux client** qui se connecte en **SSH** à un **serveur distant** hébergeant : + +- le **socle technique IA** (Ollama, AnythingLLM, services associés) ; +- les **dépôts** (sources, agents, procédures). + +L’UX (ex. Lapce) et les flux utilisateur peuvent tourner sur le client ; l’exécution lourde, la mémoire projet et Git vivent **sur le serveur**. Détail : [docs/deployment-target.md](./docs/deployment-target.md). + +## Positionnement + +- **Pas d’explorer comme surface principale** : la navigation primaire passe par intentions, recherche, contexte, timeline, objets logiques et artefacts ; un accès brut (fichiers / arborescence) reste disponible en **mode expert / secours**, pas comme flux nominal. +- **Machine de travail orientée opérations** plutôt qu’éditeur de fichiers : l’utilisateur exprime *ce qu’il veut faire*, *sur quel objet logique*, *avec quels droits*, *dans quel contexte projet*, *avec quelle procédure*, *avec quel agent*, *avec quel résultat attendu*. +- **Socle éditeur envisagé : [Lapce](https://lapce.dev/)** — open source, Rust, rendu natif / GPU, positionné comme éditeur rapide et léger : base cohérente pour un noyau d’édition + agents, sans empiler l’historique complet d’un IDE classique. Choix d’architecture, pas une obligation figée. + +## AnythingLLM et projets + +Pour chaque **projet**, un **workspace AnythingLLM** dédié est créé (ou rattaché) : corpus, embeddings et conversations restent **isolés par projet**. Une **moulinette de synchronisation** aligne un sous-ensemble de fichiers du dépôt avec le workspace concerné afin de garder la mémoire RAG alignée avec le code et la doc utiles. + +Voir [docs/anythingllm-workspaces.md](./docs/anythingllm-workspaces.md). + +## Documentation + +| Document | Contenu | +|----------|---------| +| [docs/README.md](./docs/README.md) | Index de la documentation technique | +| [docs/infrastructure.md](./docs/infrastructure.md) | LAN, SSH, scripts d’accès hôte | +| [docs/services.md](./docs/services.md) | Ollama, AnythingLLM Docker, intégration | +| [docs/anythingllm-workspaces.md](./docs/anythingllm-workspaces.md) | Workspaces par projet, synchronisation | +| [docs/ux-navigation-model.md](./docs/ux-navigation-model.md) | Remplacer l’explorer : intentions, risques, vues, graphe, mode expert | +| [docs/system-architecture.md](./docs/system-architecture.md) | Couches, modules, agents, gateway, OpenShell, événements | +| [docs/deployment-target.md](./docs/deployment-target.md) | Client Linux + SSH : serveur = socle IA + repos | + +## Dépôt actuel (outillage) + +Scripts d’installation et d’exploitation sur Ubuntu : SSH, sudo ciblé, AnythingLLM Docker, Ollama exposé pour Docker, modèle Ollama alias `qwen3-code-webdev`, installer Desktop AnythingLLM. Ces scripts ciblent en priorité l’**hôte serveur** qui porte le socle IA et les repos ; le **client Linux** repose surtout sur SSH et l’IDE. L’IDE complet (Lapce + orchestrateur + gateway) est **cible de conception** ; ce dépôt documente et supporte la **stack sur serveur** (Ollama + AnythingLLM) et l’accès distant. + +**Auteur :** Équipe 4NK diff --git a/add-ssh-key.sh b/add-ssh-key.sh new file mode 100755 index 0000000..06d7161 --- /dev/null +++ b/add-ssh-key.sh @@ -0,0 +1,142 @@ +#!/bin/bash + +# Add SSH public key to ~/.ssh/authorized_keys on infrastructure hosts. +# +# Modes (pick one): +# ADD_KEY_LOCAL=1 — you are already SSH'd on the target host (e.g. 192.168.1.164): only +# update the current user's ~/.ssh/authorized_keys on this machine. +# LAN_DIRECT=1 — same LAN as hosts: ssh BACKEND_USER@192.168.1.x directly (no ProxyJump, +# no 4nk.myftp.biz). Host list includes proxy .100, backends, and .164. +# (default) — bastion JUMP then ProxyJump to each backend (Internet / standard doc). +# +# The key embedded below (desk@desk) is what gets appended remotely; client auth uses your +# existing keys (SSH_IDENTITY_FILE / agent). +# +# Run as the SSH user, not root: sudo uses /root/.ssh and causes Permission denied (publickey). +# +# Optional env: +# BACKEND_USER=ncantu +# JUMP=ncantu@4nk.myftp.biz # default jump host when LAN_DIRECT is unset +# SSH_IDENTITY_FILE=~/.ssh/id_ed25519 +# SSH_VERBOSE=1 +# EXTRA_LAN_IPS="192.168.1.42 ..." # space-separated, appended when LAN_DIRECT=1 +# Usage: +# ADD_KEY_LOCAL=1 ./add-ssh-key.sh +# LAN_DIRECT=1 ./add-ssh-key.sh + +set -euo pipefail + +SSH_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDyLeCZh0tJ7rEp1sktpMlA2EaBBKBU5jNRMgboYAOsk desk@desk" +KEY_FINGERPRINT="AAAAC3NzaC1lZDI1NTE5AAAAIDyLeCZh0tJ7rEp1sktpMlA2EaBBKBU5jNRMgboYAOsk" + +JUMP="${JUMP:-ncantu@4nk.myftp.biz}" +BACKEND_USER="${BACKEND_USER:-ncantu}" + +BACKEND_IPS=( + "192.168.1.101" # test + "192.168.1.102" # pprod + "192.168.1.103" # prod + "192.168.1.104" # services + "192.168.1.105" # bitcoin + "192.168.1.173" # ia +) + +LAN_IPS=( + "192.168.1.100" # proxy + "${BACKEND_IPS[@]}" + "192.168.1.164" # workstation / host 164 on LAN +) + +SSH_OPTS=( + -o StrictHostKeyChecking=accept-new +) +if [ -n "${SSH_IDENTITY_FILE:-}" ]; then + idf="${SSH_IDENTITY_FILE/#\~/$HOME}" + SSH_OPTS+=(-i "$idf" -o IdentitiesOnly=yes) +fi +if [ -n "${SSH_VERBOSE:-}" ]; then + SSH_OPTS+=(-v) +fi + +if [ "$(id -u)" -eq 0 ]; then + echo "Do not run this script with sudo/root: SSH will use /root/.ssh and fail with Permission denied (publickey)." >&2 + exit 1 +fi + +add_key_to_current_user() { + local auth="${HOME}/.ssh/authorized_keys" + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + touch "${auth}" + chmod 600 "${auth}" + if ! grep -qF "${KEY_FINGERPRINT}" "${auth}" 2>/dev/null; then + printf '%s\n' "${SSH_KEY}" >> "${auth}" + echo "Key added (local user $(whoami)@$(hostname -f 2>/dev/null || hostname))" + else + echo "Key already present (local user $(whoami)@$(hostname -f 2>/dev/null || hostname))" + fi +} + +run_add_key_remote() { + local -a ssh_cmd=("$@") + "${ssh_cmd[@]}" bash -s </dev/null; then + printf '%s\n' "\${KEY_LINE}" >> "\${AUTH}" + echo "Key added" +else + echo "Key already present" +fi +EOF +} + +if [ "${ADD_KEY_LOCAL:-0}" = "1" ]; then + echo "ADD_KEY_LOCAL=1: updating authorized_keys for current user only." + echo "Key: $SSH_KEY" + echo "Host: $(hostname) (${USER})" + add_key_to_current_user + exit 0 +fi + +if [ "${LAN_DIRECT:-0}" = "1" ]; then + echo "LAN_DIRECT=1: direct SSH on LAN (no ProxyJump / no bastion hostname)." + echo "Key: $SSH_KEY" + echo "User: ${BACKEND_USER}" + if [ -n "${EXTRA_LAN_IPS:-}" ]; then + # shellcheck disable=SC2206 + extra_ips=( ${EXTRA_LAN_IPS} ) + LAN_IPS+=( "${extra_ips[@]}" ) + fi + for ip in "${LAN_IPS[@]}"; do + echo "" + echo "Processing: ${BACKEND_USER}@${ip}" + run_add_key_remote ssh "${SSH_OPTS[@]}" "${BACKEND_USER}@${ip}" + done + echo "" + echo "SSH key addition completed (LAN direct)." + exit 0 +fi + +echo "Adding SSH key to all servers (bastion + ProxyJump)..." +echo "Key: $SSH_KEY" +echo "Jump: $JUMP" +echo "" + +echo "Processing bastion (proxy): ${JUMP}" +run_add_key_remote ssh "${SSH_OPTS[@]}" "$JUMP" +echo "" + +for ip in "${BACKEND_IPS[@]}"; do + echo "Processing backend: ${BACKEND_USER}@${ip} (via ${JUMP})" + run_add_key_remote ssh "${SSH_OPTS[@]}" -J "$JUMP" "${BACKEND_USER}@${ip}" + echo "" +done + +echo "SSH key addition completed for bastion and all listed backends." diff --git a/add-sudo-nopasswd-ncantu.sh b/add-sudo-nopasswd-ncantu.sh new file mode 100755 index 0000000..a6c86fb --- /dev/null +++ b/add-sudo-nopasswd-ncantu.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Add passwordless sudo for user ncantu. +# Must be run as root (e.g. sudo ./add-sudo-nopasswd-ncantu.sh). +# Creates /etc/sudoers.d/99-ncantu-nopasswd and validates with visudo -c. + +set -euo pipefail + +SUDOERS_FILE="/etc/sudoers.d/99-ncantu-nopasswd" +USER_NAME="ncantu" + +if [ "$(id -u)" -ne 0 ]; then + echo "Run as root: sudo $0" >&2 + exit 1 +fi + +echo "${USER_NAME} ALL=(ALL) NOPASSWD: ALL" > "${SUDOERS_FILE}" +chmod 440 "${SUDOERS_FILE}" +visudo -c -f "${SUDOERS_FILE}" +echo "Done. User ${USER_NAME} can run sudo without password." diff --git a/configure-ollama-for-docker.sh b/configure-ollama-for-docker.sh new file mode 100755 index 0000000..d0f52fa --- /dev/null +++ b/configure-ollama-for-docker.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Expose Ollama on all interfaces so Docker containers (AnythingLLM) can reach it via +# host.docker.internal (--add-host=host.docker.internal:host-gateway). +# Requires sudo. Idempotent. + +set -euo pipefail + +if [ "$(id -u)" -ne 0 ]; then + echo "Run: sudo $0" >&2 + exit 1 +fi + +mkdir -p /etc/systemd/system/ollama.service.d +cat <<'EOF' > /etc/systemd/system/ollama.service.d/override.conf +[Service] +Environment="OLLAMA_HOST=0.0.0.0:11434" +EOF + +systemctl daemon-reload +systemctl restart ollama +echo "Ollama listens on 0.0.0.0:11434 (check: ss -tlnp | grep 11434)" diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..2a9b173 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,21 @@ +# smart_ide — documentation + +Operational, architectural, and UX-design notes for the local-AI IDE initiative and the host tooling in this repository. + +| Document | Content | +|----------|---------| +| [../README.md](../README.md) | Project overview (French): vision, Lapce, AnythingLLM per project | +| [deployment-target.md](./deployment-target.md) | First target: Linux client + SSH remote server (AI stack + repos) | +| [infrastructure.md](./infrastructure.md) | Host inventory (LAN), SSH key workflow, host scripts | +| [services.md](./services.md) | Ollama, AnythingLLM (Docker), Desktop installer, Ollama ↔ Docker | +| [anythingllm-workspaces.md](./anythingllm-workspaces.md) | One AnythingLLM workspace per project; sync pipeline | +| [ux-navigation-model.md](./ux-navigation-model.md) | Beyond file explorer: intentions, graph, palette, risks, expert mode | +| [system-architecture.md](./system-architecture.md) | Layers, modules, agent gateway, OpenShell, events, Lapce | + +**Author:** 4NK + +**Related external docs** + +- AnythingLLM Docker: +- Ollama: +- Lapce: diff --git a/docs/anythingllm-workspaces.md b/docs/anythingllm-workspaces.md new file mode 100644 index 0000000..2fff43e --- /dev/null +++ b/docs/anythingllm-workspaces.md @@ -0,0 +1,16 @@ +# AnythingLLM — workspaces par projet + +## Principe + +- Un **workspace AnythingLLM** est créé (ou associé) **par projet** : documents indexés, embeddings, threads et paramètres RAG sont **scopés au projet**, pas mélangés entre dépôts. +- Cela permet à la mémoire interrogée par `ask` / les agents de rester **pertinente** et **traçable** par contexte métier. + +## Synchronisation avec le dépôt + +- Une **moulinette** (pipeline de synchro) met à jour le workspace à partir de fichiers sélectionnés du dépôt : sources, doc, configs exposées volontairement, etc. +- Les règles de ce qu’on synchronise (inclusions / exclusions, secrets interdits) doivent être **explicites** et alignées avec la politique de sécurité du projet. + +## Exploitation + +- Instance Docker décrite dans [services.md](./services.md) : stockage hôte typiquement sous `$HOME/anythingllm` sur l’**hôte qui exécute le conteneur** — en **première cible de déploiement**, cet hôte est le **serveur distant** (SSH), pas obligatoirement le poste Linux client ; la création de **plusieurs workspaces** se fait dans l’UI AnythingLLM (ou via API) en conservant la convention « un workspace = un projet ». +- L’orchestrateur IDE décide **quand** interroger AnythingLLM (voir [system-architecture.md](./system-architecture.md)). L’URL vue depuis le client peut exiger un **tunnel SSH** ou un rebond réseau : [deployment-target.md](./deployment-target.md). diff --git a/docs/deployment-target.md b/docs/deployment-target.md new file mode 100644 index 0000000..56a60fd --- /dev/null +++ b/docs/deployment-target.md @@ -0,0 +1,25 @@ +# Première cible de déploiement — client Linux + serveur distant (SSH) + +## Modèle + +La **première cible de déploiement** n’est pas un poste tout-en-un sur la même machine que le socle IA. + +| Rôle | Où ça tourne | Contenu typique | +|------|----------------|-----------------| +| **Client** | Machine **Linux** de l’utilisateur (poste local) | Shell d’édition / UX (ex. Lapce), orchestrateur côté client si applicable, connexion **SSH** persistante ou à la demande | +| **Serveur distant** | Hôte joignable en **SSH** (LAN, bastion, ou jump host selon l’infra) | **Socle technique IA** (Ollama, AnythingLLM Docker, services associés), **clones des dépôts**, exécution des **agents** / scripts / OpenShell sur le périmètre autorisé | + +L’utilisateur travaille depuis un **Linux client** ; le **calcul**, les **modèles**, la **mémoire RAG** et les **sources de vérité Git** résident sur le **serveur** (ou une ferme de serveurs derrière la même session SSH). + +## Conséquences + +- Les URLs « locales » du serveur (`localhost:11434`, `localhost:3001`, …) sont **locales au serveur**. Depuis le client, l’accès passe par **tunnel SSH** (`-L`), **ProxyJump**, ou configuration explicite (hostname interne, VPN) selon la politique réseau. +- L’**agent gateway** et le **policy-runtime** (OpenShell) s’exécutent idéalement **là où tournent les agents et les repos** — le serveur — sauf décision contraire documentée. +- Le **workspace AnythingLLM par projet** vit **côté serveur** (stockage du conteneur ou chemin monté sur l’hôte distant). La moulinette de synchro lit les **dépôts sur le serveur**. +- Le client doit disposer d’une **identité SSH** autorisée sur le serveur (voir `add-ssh-key.sh` et [infrastructure.md](./infrastructure.md)). + +## Documentation liée + +- Topologie LAN / bastion : [infrastructure.md](./infrastructure.md) +- Services Ollama / AnythingLLM sur l’hôte qui **héberge** le socle : [services.md](./services.md) +- Répartition logique des modules : [system-architecture.md](./system-architecture.md) (à lire avec ce découpage physique) diff --git a/docs/infrastructure.md b/docs/infrastructure.md new file mode 100644 index 0000000..aa3cb34 --- /dev/null +++ b/docs/infrastructure.md @@ -0,0 +1,58 @@ +# Infrastructure + +## Scope + +This repository ships shell scripts used on Ubuntu workstations and related LAN hosts. It does **not** define cloud Terraform or CI; it documents how those scripts map to the **private LAN** layout used with the 4NK bastion model. + +## First deployment shape (client / server) + +The **primary deployment target** is a **Linux client** that connects over **SSH** to a **remote server** where the **AI stack** (Ollama, AnythingLLM, etc.) and **Git repositories** live. Install scripts in this repo apply mainly to that **server** (or to a LAN workstation that plays the same role). The client uses SSH (and optionally port forwarding) to reach services that bind to the server’s loopback or internal interfaces. See [deployment-target.md](./deployment-target.md). + +## LAN host roles (reference) + +Private segment **192.168.1.0/24** (DHCP with MAC reservations). The table matches the host lists in `add-ssh-key.sh`. + +| IP | Role | +|----|------| +| 192.168.1.100 | Proxy / bastion (public entry via DynDNS `4nk.myftp.biz`) | +| 192.168.1.101 | test | +| 192.168.1.102 | pre-production | +| 192.168.1.103 | production | +| 192.168.1.104 | services (Git, Mempool, Rocket.Chat, …) | +| 192.168.1.105 | bitcoin | +| 192.168.1.173 | ia | +| 192.168.1.164 | Example workstation on LAN (included in `LAN_DIRECT` list) | + +Internet access to backends uses **SSH ProxyJump** via `ncantu@4nk.myftp.biz` (see `JUMP` in `add-ssh-key.sh`). On the same LAN, direct `ssh ncantu@192.168.1.x` is valid. + +## Scripts (infrastructure / access) + +### `add-ssh-key.sh` + +Appends a fixed **Ed25519 public key** (comment `desk@desk`) to `~/.ssh/authorized_keys` on target hosts. + +| Mode | When to use | +|------|-------------| +| Default | From a machine that can reach `JUMP` (`ncantu@4nk.myftp.biz`), then ProxyJump to each backend IP. | +| `LAN_DIRECT=1` | Same LAN: direct SSH to each IP in `LAN_IPS` (proxy, backends, `.164`). No bastion hostname. | +| `ADD_KEY_LOCAL=1` | Already logged in on the target host: update **current user** only (e.g. workstation `.164`). | + +**Do not run with `sudo`:** the SSH client would use `/root/.ssh` and fail with `Permission denied (publickey)`. + +**Environment (optional):** `JUMP`, `BACKEND_USER`, `SSH_IDENTITY_FILE`, `SSH_VERBOSE=1`, `EXTRA_LAN_IPS` (with `LAN_DIRECT=1`). + +### `add-sudo-nopasswd-ncantu.sh` + +One-time **root** execution: creates `/etc/sudoers.d/99-ncantu-nopasswd` with `ncantu ALL=(ALL) NOPASSWD: ALL`, `chmod 440`, `visudo -c`. Use only where this policy is explicitly required. + +## Data paths (host) + +| Path | Purpose | +|------|---------| +| `$HOME/anythingllm` | AnythingLLM Docker bind mount (storage + `.env`), default from `install-anythingllm-docker.sh` | +| `$HOME/.ssh/authorized_keys` | SSH access; updated by `add-ssh-key.sh` modes | + +## Security notes + +- SSH is key-based; the embedded key in `add-ssh-key.sh` is for a designated client (`desk@desk`). Rotate or replace in script if the key is compromised. +- Passwordless sudo reduces interactive friction and **increases** local privilege impact; scope to trusted machines only. diff --git a/docs/services.md b/docs/services.md new file mode 100644 index 0000000..7edb381 --- /dev/null +++ b/docs/services.md @@ -0,0 +1,88 @@ +# Services + +## Where these services run (first deployment) + +For the **first deployment target**, Ollama and AnythingLLM run on the **remote SSH server** that hosts the AI stack and repositories, not necessarily on the user’s Linux laptop. Access from the client may use **SSH local forwarding** or internal hostnames. See [deployment-target.md](./deployment-target.md). + +## Overview + +| Service | Delivery | Default URL / port | Config / persistence | +|---------|----------|--------------------|------------------------| +| Ollama | systemd (`ollama.service`) | `http://127.0.0.1:11434` (API) | Models under Ollama data dir; listen address via systemd override | +| AnythingLLM | Docker (`mintplexlabs/anythingllm`) | `http://localhost:3001` | `$HOME/anythingllm` + `.env` bind-mounted ; **one workspace per project** (see [anythingllm-workspaces.md](./anythingllm-workspaces.md)) | +| AnythingLLM Desktop | AppImage (optional) | local Electron app | User profile under `~/.config/anythingllm-desktop` (installer) | + +## Ollama + +- **Install:** official script `https://ollama.com/install.sh` (used on target Ubuntu hosts). +- **Service:** `systemctl enable --now ollama` (handled by installer). +- **Default bind:** loopback only (`127.0.0.1:11434`), which **blocks** Docker containers on the same host from calling Ollama. + +### Expose Ollama to Docker on the same host + +Run **`configure-ollama-for-docker.sh`** as root (or equivalent): + +- Drop-in: `/etc/systemd/system/ollama.service.d/override.conf` +- `Environment="OLLAMA_HOST=0.0.0.0:11434"` +- `systemctl daemon-reload && systemctl restart ollama` + +Verify: `ss -tlnp | grep 11434` shows `*:11434`. + +### Models (reference) + +- Embeddings for AnythingLLM + Ollama: `ollama pull nomic-embed-text` +- Custom name **`qwen3-code-webdev`:** not in the public Ollama library as-is; this repo includes `Modelfile-qwen3-code-webdev` defining an alias (default base: `qwen3-coder:480b-cloud`). Rebuild with `ollama create qwen3-code-webdev -f Modelfile-qwen3-code-webdev` after editing `FROM`. + +## AnythingLLM (Docker) + +### Workspaces and projects + +AnythingLLM is used with **dedicated workspaces per project** so RAG memory, documents, and threads stay isolated. A **sync job** (“moulinette”) keeps selected repository files aligned with each workspace. Operational rules: [anythingllm-workspaces.md](./anythingllm-workspaces.md). + +**Script:** `install-anythingllm-docker.sh` + +- **Image:** `mintplexlabs/anythingllm` (override with `ANYTHINGLLM_IMAGE`). +- **Container name:** `anythingllm` (override with `ANYTHINGLLM_CONTAINER_NAME`). +- **Ports:** `HOST_PORT:3001` (default `3001:3001`). +- **Capabilities:** `--cap-add SYS_ADMIN` (Chromium / document features in container). +- **Networking:** `--add-host=host.docker.internal:host-gateway` so the app can reach Ollama on the host at `http://host.docker.internal:11434` once `OLLAMA_HOST` is set as above. +- **Volumes:** + - `${STORAGE_LOCATION}:/app/server/storage` + - `${STORAGE_LOCATION}/.env:/app/server/.env` + +Re-running the script **removes** the existing container by name and starts a new one; data remains in `STORAGE_LOCATION` if the bind path is unchanged. + +### Configure LLM provider (Ollama) + +In `$STORAGE_LOCATION/.env` (mounted into the container), set at minimum: + +- `LLM_PROVIDER='ollama'` +- `OLLAMA_BASE_PATH='http://host.docker.internal:11434'` +- `OLLAMA_MODEL_PREF=''` (e.g. `qwen3-code-webdev`) +- `EMBEDDING_ENGINE='ollama'` +- `EMBEDDING_BASE_PATH='http://host.docker.internal:11434'` +- `EMBEDDING_MODEL_PREF='nomic-embed-text:latest'` +- `VECTOR_DB='lancedb'` (default stack) + +See upstream `.env.example`: + + +After editing `.env`, restart the container: `docker restart anythingllm`. + +## AnythingLLM Desktop (AppImage) + +**Script:** `installer.sh` — downloads the official AppImage, optional AppArmor profile, `.desktop` entry. Interactive prompts; not a headless service. + +- Documentation: +- Use **either** Docker **or** Desktop on the same machine if you want to avoid conflicting ports and duplicate workspaces. + +## Operational checks + +```bash +systemctl is-active ollama +curl -sS http://127.0.0.1:11434/api/tags | head +docker ps --filter name=anythingllm +docker exec anythingllm sh -c 'curl -sS http://host.docker.internal:11434/api/tags | head' +``` + +The last command must succeed after `OLLAMA_HOST=0.0.0.0:11434` and `host.docker.internal` are configured. diff --git a/docs/system-architecture.md b/docs/system-architecture.md new file mode 100644 index 0000000..f7a4383 --- /dev/null +++ b/docs/system-architecture.md @@ -0,0 +1,111 @@ +# Architecture système — IDE, agents, runtime, mémoire + +## Répartition physique (première cible) + +Pour le **premier déploiement**, un **poste Linux** (client) établit des sessions **SSH** vers un **serveur** qui concentre : + +- Ollama, AnythingLLM (et extensions du socle IA) ; +- les **dépôts** et l’exécution des **agents** / OpenShell sur chemins autorisés. + +L’**éditeur** et une partie de l’UX peuvent rester sur le client ; le **gateway**, le **policy-runtime** et les **knowledge-services** (fichiers RAG, workspaces AnythingLLM) sont cohérents **côté serveur**, sauf architecture hybride explicitement documentée. Voir [deployment-target.md](./deployment-target.md). + +## Couches fonctionnelles (vue cible) + +| Couche | Rôle | +|--------|------| +| **Agents `ia_dev`** | Noyau **métier et opératoire** (existant) ; sous-agents, recettes, tools | +| **OpenShell** | **Sécurité et exécution** : sandboxes, politiques, résolution contrôlée | +| **Éditeur (ex. Lapce)** | **Interaction** : texte léger, terminal, palette, timeline, UX intentionnelle | +| **Orchestrateur maison** | **Routage** : pas « d’intelligence » au sens LLM, mais décision de flux | +| **Ollama** | Backend d’**inférence** locale | +| **AnythingLLM** | **Mémoire documentaire** et RAG ; **un workspace par projet** ([anythingllm-workspaces.md](./anythingllm-workspaces.md)) | +| **ONLYOFFICE** | Backend **documentaire métier** (documents, feuilles, présentations) | + +Flux type : demande utilisateur → orchestrateur → préparation (scripts / tools génériques) → agents → besoin LLM → Ollama ; besoin doc / RAG → AnythingLLM ; besoin bureautique → ONLYOFFICE. + +## Orchestrateur — décisions de routage + +Décider notamment : + +- quelle **commande** utilisateur appelle quel **agent principal** ; +- quel agent appelle quel **sous-agent** ; +- quand privilégier un **script** vs un **tool générique** vs `ia_dev` ; +- quand `ia_dev` peut **escalader** vers Ollama ; +- quand interroger **AnythingLLM** ; +- quand passer par **ONLYOFFICE** ; +- quand **refuser**. + +## Descripteur stable par agent + +Chaque agent devrait exposer au minimum : **nom**, **rôle**, **entrées**, **sorties**, **droits**, **dépendances**, **scripts** appelés, **modèles** éventuels, **préconditions**, **postconditions**, **timeouts**, **coût**, **niveau de risque**. + +Un **protocole unique** couvre les implémentations hétérogènes (script, workflow, wrapper d’outil, appel IA). + +Hiérarchie agent → sous-agent : à **formaliser** explicitement pour éviter un graphe implicite ingouvernable. + +## Événements normalisés (exemples) + +`started`, `tool_selected`, `script_started`, `model_called`, `waiting_validation`, `completed`, `failed`, `rolled_back`, `artifact_created`. + +Sortie **événementielle uniforme** vers l’éditeur ; **journalisation** ; possibilité de **rejouer**, diagnostiquer, **convertir en recette** stable. + +## Sécurité — OpenShell central + +- Chaque nouvelle résolution devrait pouvoir devenir **recipe**, **tool** ou **sous-agent** stable (travail des commandes UX de haut niveau). +- Les agents ne devraient **pas** exécuter directement sur l’hôte sans contrôle : **sandboxes** avec droits dérivés du **type d’agent** et du **projet**. + +Exemples de **profils de policy** : lecture seule ; lecture + scripts locaux ; écriture bornée ; déploiement pprod / prod ; génération documentaire ; accès tickets ; accès base ; accès ONLYOFFICE. + +**Pas de fallback implicite** non spécifié : refus ou erreur explicite selon les règles du projet. + +## Couche de normalisation (registre agents) + +Registre unifié des agents `ia_dev`, chacun exposé comme **objet standardisé** : + +- identité, catégorie, **commandes déclenchantes**, permissions, environnements compatibles, mode d’exécution, outils requis, **politiques OpenShell**, formats d’entrée/sortie, stratégies de rejet (pas de fallback silencieux), journalisation, possibilité d’appeler d’autres agents. + +Sans cette couche, l’IDE reste dépendant de **conventions implicites** du dépôt. + +## Agent gateway (adaptateur) + +Ne pas brancher le dépôt `ia_dev` directement dans l’éditeur : passer par une **agent gateway** qui : + +1. Charge le **registre** +2. **Valide** les permissions +3. **Résout** les dépendances +4. Ouvre la **sandbox OpenShell** adaptée +5. **Injecte** le contexte autorisé +6. **Lance** l’agent +7. **Capture** les événements +8. **Uniformise** les sorties +9. **Publie** le flux vers l’éditeur +10. **Journalise** +11. Gère **rollbacks** et statuts + +## Modules logiciels (découpage) + +| Module | Responsabilité | +|--------|----------------| +| **editor-shell** | Éditeur texte léger, terminal, explorer **mode expert**, onglets, palette de commandes, timeline, UX | +| **agent-gateway** | Adaptateur uniforme UX ↔ `ia_dev` | +| **policy-runtime** | OpenShell, profils de policy, providers, sandboxes, journaux | +| **knowledge-services** | AnythingLLM, mémoire projet, index documentaire, routage RAG | +| **doc-services** | ONLYOFFICE, flux `present`, `write`, `sheet` | + +Les **agents** restent le noyau opératoire ; les modules encadrent et exposent. + +## UX — masquage des agents + +L’utilisateur ne « choisit pas un agent » dans le flux nominal : il exprime une **intention** (`ask`, `fix`, …). Le **routeur** sélectionne l’agent ou la chaîne d’agents. + +## Socle éditeur : Lapce + +**Lapce** (open source, Rust, rendu natif / GPU) est le candidat retenu pour un **éditeur rapide et léger** avec agents, au lieu d’un IDE historique très chargé. Positionnement aligné avec le rôle « coquille + orchestration + transparence contextuelle » décrit dans [ux-navigation-model.md](./ux-navigation-model.md). + +## Taxonomie des droits + +Les droits doivent être **nommés**, **vérifiables** et **traçables** (lien avec OpenShell et le registre d’agents). Pas de contournement par défaut. + +## Mémoire d’exécution + +Conserver une mémoire d’exécution **exploitable** : rejouer, auditer, promouvoir une exécution réussie en **recette** versionnée. diff --git a/docs/ux-navigation-model.md b/docs/ux-navigation-model.md new file mode 100644 index 0000000..b3bb018 --- /dev/null +++ b/docs/ux-navigation-model.md @@ -0,0 +1,116 @@ +# Modèle UX — au-delà de l’explorateur fichiers + +## Renoncer à l’explorer classique + +Renoncer à une vue fichiers / dossiers / explorateur **ne signifie pas** l’absence de structure : c’est une interface **orientée intention**, **contexte**, **flux d’actions** et **artefacts**. + +Si Git est peu visible en surface, si les **procédures** sont déterministes, si les **agents** savent installer, importer et développer des **outils génériques** avant d’appeler l’IA, et si l’utilisateur travaille surtout via des commandes de haut niveau (`ask`, `fix`, `improve`, `deploy`, `ticket`, `present`, `write`, `sheet`, `extract`, `think adversarial`, etc.), l’explorer n’est pas remplacé par « rien », mais par des **vues plus adaptées** au modèle. + +## Ce que l’explorer faisait — et comment le redistribuer + +| Besoin historique (explorer) | Remplacement ciblé | +|------------------------------|-------------------| +| Localiser physiquement les fichiers | Recherche, commandes, **mémoire projet**, points d’entrée contextuels | +| Comprendre la structure du projet | Vues **sémantiques** : modules, services, tickets, environnements, procédures, agents, artefacts, documents | +| Agir sur des objets concrets | **Palette d’intentions** + vues d’**objets métier** | + +Le bon niveau d’abstraction devient : + +1. Ce que je veux faire +2. Sur quel **objet logique** +3. Avec quels **droits** +4. Dans quel **contexte projet** +5. Avec quelle **procédure** +6. Avec quel **agent** +7. Avec quel **résultat attendu** + +→ Plus proche d’une **machine de travail orientée opérations** que d’un éditeur de fichiers. + +## Risques si l’explorer disparaît sans compensations + +1. **Perte de repère spatial** +2. **Difficulté d’inspection libre** +3. **Fragilité agentique** si le graphe sémantique (agents, index, résumés, outils, procédures) est **incomplet** — l’absence d’explorer est alors vécue comme une **privation**, pas comme une abstraction supérieure +4. **Effet boîte noire** + +## Compensations obligatoires + +### Palette d’accès universelle (« go to anything ») + +Trouver et ouvrir immédiatement, par nom, alias, rôle, description, historique, proximité contextuelle, **permissions** : + +- fichier, module, service, fonction, procédure, agent, ticket, environnement, document, support de présentation, feuille, job, log, artefact généré, commande. + +### Vue des objets logiques + +À la place d’une arborescence de fichiers : **services**, **modules**, **environnements**, **tickets**, **bases**, **agents**, **recipes**, **tools**, **documents**, **supports**, **jobs**, **déploiements**, **artefacts**. + +L’utilisateur voit en permanence **sur quoi il travaille** : projet courant, tâche courante, commande active, agents impliqués, artefacts produits, documents de référence, état stable disponible, environnement concerné. + +### Recherche structurée (au-delà du grep fichiers) + +Capacité de chercher dans : code, **symboles**, recettes, outils, **historiques de session**, tickets, documents, logs, artefacts ONLYOFFICE, sorties d’agents, **mémoire AnythingLLM**. + +### Vue artefacts + +Artefacts **récents** et **liés au contexte courant** : scripts, documents, feuilles, présentations, tickets, logs, plans, sorties diverses. + +### Vue timeline / stream + +Ce n’est pas la liste des fichiers, mais les **actions** : lancé, lu, généré, échoué, disponible, repris. + +## Types d’objets ouvrables + +- **Texte** : source, config, script, note, document texte +- **Logique** : service, module, fonction, procédure, agent, ticket +- **Exécution** : job, déploiement, test, lint, run agentique, terminal de tâche +- **Artefact** : présentation, document, feuille, rapport, résumé, sortie de recette + +Navigation **sans penser « répertoire »**, en pensant **« objet »**. + +## Arborescence vs graphe + +L’explorer repose sur un **arbre**. Le système cible un **graphe** : ticket → agents → recettes → scripts → artefacts → projet → environnements → procédures → documents → support → ticket, etc. + +Rendre visible : structure logique, objets importants, actions disponibles, **relations**, état courant, artefacts récents modifiés, sorties d’agents, points d’entrée, chemins de reprise. + +## Conditions de réussite + +1. **Indexation solide** : fichiers, symboles, modules, services, tickets, scripts, recettes, documents, artefacts, historiques +2. **Palette universelle** exceptionnelle +3. **Recherche** plein texte + sémantique +4. **Vues contexte + timeline** fiables +5. **Mémoire projet** fiable (AnythingLLM par projet + synchro) +6. **Commandes principales** réellement suffisantes au quotidien +7. **Mode expert / secours** pour accéder au niveau bas (y compris vue fichiers brute) si l’abstraction haute échoue + +**Ne pas supprimer totalement l’explorer du produit** : le retirer de l’interface **normale**, mais garder un accès discret / désactivable par défaut si indexation, relations ou agents déraillent. + +## Trois niveaux proposés + +| Niveau | Rôle | +|--------|------| +| **Principal** | Pas d’explorer classique ; commandes, recherche, contexte, timeline, objets logiques, artefacts | +| **Secondaire** | Vue « structure » non arborescente : services, modules, objets, procédures | +| **Expert** | Accès secours à la structure brute (fichiers / répertoires), discret, éventuellement désactivé par défaut | + +## Grammaire des commandes + +Plus l’explorer disparaît de la surface, plus les commandes doivent être **rigoureuses** : + +- `ask` — interrogation du graphe de connaissances et d’objets +- `fix` — réparation guidée (tests), sans obligation de « trouver le fichier » manuellement +- `improve` — optimisation sur objet logique ou contexte courant +- `deploy` — action sur **environnement** et **procédure**, pas sur un répertoire +- `ticket` — entrée de navigation métier +- `present`, `write`, `sheet` — générateurs d’artefacts liés au contexte logique du projet +- `extract` — extraction structurée depuis sources / documents / contexte +- `think adversarial` — revue ou stress test (sécurité, hypothèses, angles de défaillance) sur l’objet ou le livrable courant + +Synthèse : *pas d’explorer comme surface principale ; navigation par intentions, recherche, objets logiques, timeline et artefacts ; accès brut réservé au mode expert.* + +## Chaîne UX cible (rappel) + +Éditeur → **orchestrateur UX** → **runtime sécurisé** → agents `ia_dev` → sous-agents → (préparation / scripts / tools) → IA avec éléments demandés → réponse intégrée à l’IDE. + +L’explorer est remplacé par **mieux** : palette universelle, navigation par objets, vue contexte, timeline, recherche puissante, vue artefacts, niveau expert de secours. diff --git a/install-anythingllm-docker.sh b/install-anythingllm-docker.sh new file mode 100755 index 0000000..fcfb714 --- /dev/null +++ b/install-anythingllm-docker.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Install / start AnythingLLM on Ubuntu via Docker (official image mintplexlabs/anythingllm). +# Docs: https://docs.anythingllm.com/installation-docker/local-docker +# UI: http://localhost:3001 +# +# Optional env: +# STORAGE_LOCATION default: $HOME/anythingllm +# HOST_PORT default: 3001 +# Usage: ./install-anythingllm-docker.sh + +set -euo pipefail + +STORAGE_LOCATION="${STORAGE_LOCATION:-${HOME}/anythingllm}" +HOST_PORT="${HOST_PORT:-3001}" +IMAGE="${ANYTHINGLLM_IMAGE:-mintplexlabs/anythingllm}" +CONTAINER_NAME="${ANYTHINGLLM_CONTAINER_NAME:-anythingllm}" + +mkdir -p "${STORAGE_LOCATION}" +touch "${STORAGE_LOCATION}/.env" + +docker pull "${IMAGE}" + +if docker ps -a --format '{{.Names}}' | grep -qx "${CONTAINER_NAME}"; then + docker rm -f "${CONTAINER_NAME}" +fi + +docker run -d \ + -p "${HOST_PORT}:3001" \ + --name "${CONTAINER_NAME}" \ + --cap-add SYS_ADMIN \ + --add-host=host.docker.internal:host-gateway \ + -v "${STORAGE_LOCATION}:/app/server/storage" \ + -v "${STORAGE_LOCATION}/.env:/app/server/.env" \ + -e STORAGE_DIR="/app/server/storage" \ + "${IMAGE}" + +echo "AnythingLLM is starting. Open http://localhost:${HOST_PORT}" +echo "Storage: ${STORAGE_LOCATION}" diff --git a/installer.sh b/installer.sh new file mode 100755 index 0000000..da02056 --- /dev/null +++ b/installer.sh @@ -0,0 +1,198 @@ +#!/bin/sh +# This script installs AnythingLLMDesktop on Linux. +# On systems with AppArmor enabled, the AppImage needs to also create a userspace +# apparmor profile so that the AppImage can be run without SUID requirements due to root chromium ownership. +# +# Todo: Detect the current location of the AppImage so that we can update the application +# in-place without the user needed to manually move the application to the new location. +# This is also useful so that the apparmor location is always correct if the user edits it. +set -eu + +status() { echo "$*" >&2; } +error() { echo "ERROR $*"; exit 1; } +warning() { echo "WARNING: $*"; } + +# Detect AppArmor major version so we generate compatible profile syntax. +# AppArmor 4.x (Ubuntu 24.04+) supports abi declarations and userns rules; +# older versions (Ubuntu 20.04/22.04) need classic syntax only. +get_apparmor_major_version() { + if command -v apparmor_parser >/dev/null 2>&1; then + apparmor_parser --version 2>/dev/null | grep -oE '[0-9]+' | head -1 + else + echo "0" + fi +} + +# Create an AppArmor profile for AnythingLLMDesktop for systems with AppArmor enabled +# https://askubuntu.com/questions/1512287/obsidian-appimage-the-suid-sandbox-helper-binary-was-found-but-is-not-configu/1528215#1528215 +create_apparmor_profile() { + status "Checking for sudo privileges..." + sudo -v + if [ $? -ne 0 ]; then + error "Failed to get sudo privileges! Aborting..." + exit 1 + fi + + AA_MAJOR=$(get_apparmor_major_version) + status "Creating AppArmor profile for AnythingLLM (AppArmor version ${AA_MAJOR}.x detected)..." + + if [ "${AA_MAJOR}" -ge 4 ] 2>/dev/null; then + APP_ARMOR_CONTENT=$(cat <<'EOF' +# AnythingLLMDesktop AppArmor profile (4.x syntax) +abi , +include + +profile anythingllmdesktop /**/AnythingLLMDesktop.AppImage flags=(unconfined) { + userns, +} +EOF +) + else + APP_ARMOR_CONTENT=$(cat <<'EOF' +# AnythingLLMDesktop AppArmor profile (classic syntax for AppArmor 2.x/3.x) +#include + +profile anythingllmdesktop /**/AnythingLLMDesktop.AppImage flags=(unconfined) { +} +EOF +) + fi + + echo "$APP_ARMOR_CONTENT" | sudo tee /etc/apparmor.d/anythingllmdesktop > /dev/null + status "Reloading AppArmor service..." + if sudo apparmor_parser -r /etc/apparmor.d/anythingllmdesktop 2>/dev/null; then + status "AppArmor profile created - you can now run AnythingLLMDesktop without SUID requirements." + elif sudo systemctl reload apparmor.service 2>/dev/null; then + status "AppArmor profile created - you can now run AnythingLLMDesktop without SUID requirements." + else + warning "AppArmor reload failed. The profile was written to /etc/apparmor.d/anythingllmdesktop" + warning "but could not be loaded. You may need to reboot or run:" + warning " sudo apparmor_parser -r /etc/apparmor.d/anythingllmdesktop" + warning "If issues persist, you can disable the profile for this app only:" + warning " sudo ln -sf /etc/apparmor.d/anythingllmdesktop /etc/apparmor.d/disable/" + warning " sudo systemctl reload apparmor" + fi +} + +check_to_create_apparmor_profile() { + # Check if the system has AppArmor enabled + if [ -f /sys/kernel/security/apparmor/profiles ]; then + if ! [ -f /etc/apparmor.d/anythingllmdesktop ]; then + status "AppArmor is enabled on this system." + status "\e[31m[Warning]\e[0m You will get an error about SUID permission issues when running the AppImage without creating an AppArmor profile." + status "This requires sudo privileges. If you are unsure, you can create an AppArmor profile manually." + read -p "Do you want to auto-create an AppArmor profile for AnythingLLM now? (y/n): " create_apparmor + case "$create_apparmor" in [yY]) create_apparmor="y" ;; esac + if [ "$create_apparmor" = "y" ]; then + create_apparmor_profile + else + status "AppArmor is enabled on this system." + status "AppArmor profile creation skipped. You may not be able to run AnythingLLMDesktop without it." + fi + else + status "AppArmor profile already exists." + read -p "Do you want to overwrite it with the latest version? (y/n): " overwrite_apparmor + case "$overwrite_apparmor" in [yY]) overwrite_apparmor="y" ;; esac + if [ "$overwrite_apparmor" = "y" ]; then + create_apparmor_profile + fi + fi + else + status "AppArmor could not be automatically detected or does not exist. If you get an SUID error on startup, you may need to create an AppArmor profile for AnythingLLMDesktop.AppImage manually." + fi +} + +check_or_create_desktop_profile() { + if ! [ -f $HOME/.local/share/applications/anythingllmdesktop.desktop ]; then + status "Desktop profile not found. Creating..." + + # Default Exec command + EXEC_CMD="$INSTALL_DIR/AnythingLLMDesktop.AppImage" + + # Check for Wayland + KDE specifically + # We check XDG_SESSION_TYPE for "wayland" + # We check XDG_CURRENT_DESKTOP for "KDE" (handles "KDE", "KDE-Plasma", etc) + # We use ':-' to safely handle unbound variables in 'set -u' mode + if [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then + case "${XDG_CURRENT_DESKTOP:-}" in + *"KDE"*) + status "Detected KDE Plasma on Wayland. Adding specific flags for IME support." + EXEC_CMD="$INSTALL_DIR/AnythingLLMDesktop.AppImage --enable-features=UseOzonePlatform --ozone-platform=wayland --enable-wayland-ime" + ;; + esac + fi + + DESKTOP_CONTENT=$(cat < /dev/null + status "Desktop profile created!" + fi +} + +arch=$(uname -m) +[ "$(uname -s)" = "Linux" ] || error 'This script is intended to run on Linux only.' +if [ "$(id -u)" -eq 0 ]; then + status "This script should not be run as root. Please run it as a regular user." + exit 1 +fi + +# Allow custom installation directory via ANYTHING_LLM_INSTALL_DIR environment variable +# Defaults to $HOME if not set +INSTALL_DIR="${ANYTHING_LLM_INSTALL_DIR:-$HOME}" + +status "#########################################################" +status " Welcome to the AnythingLLM Desktop Installer" +status " by Mintplex Labs Inc (team@mintplexlabs.com)" +status " Architecture: $arch" +status " Install Directory: $INSTALL_DIR" +status "#########################################################" + +if [ "$arch" = "arm64" ] || [ "$arch" = "aarch64" ]; then + APPIMAGE_URL="https://cdn.anythingllm.com/latest/AnythingLLMDesktop-Arm64.AppImage" +else + APPIMAGE_URL="https://cdn.anythingllm.com/latest/AnythingLLMDesktop.AppImage" +fi +APPIMAGE_FILE="AnythingLLMDesktop.AppImage" + +mkdir -p "$INSTALL_DIR" +SHOULD_DOWNLOAD="true" +if [ -f "$INSTALL_DIR/$APPIMAGE_FILE" ]; then + status "Existing installation found at $INSTALL_DIR/$APPIMAGE_FILE" + read -p "Do you want to re-download and overwrite it? (y/n): " overwrite + case "$overwrite" in [yY]) ;; *) SHOULD_DOWNLOAD="false" ;; esac +fi + +if [ "$SHOULD_DOWNLOAD" = "true" ]; then + status "Downloading AnythingLLM Desktop..." + curl --fail --show-error --location --progress-bar -o "$INSTALL_DIR/$APPIMAGE_FILE" "$APPIMAGE_URL" + chmod +x "$INSTALL_DIR/$APPIMAGE_FILE" +fi + +status "AnythingLLM Desktop is ready to run!" +status "$INSTALL_DIR/$APPIMAGE_FILE to start AnythingLLMDesktop" +status "\e[36mHeads up!\e[0m You can rerun this installer anytime to get the latest version of AnythingLLM without effecting your existing data." +status "Documentation: https://docs.anythingllm.com" +status "Issues: https://github.com/Mintplex-Labs/anything-llm" +status "\e[36mThanks for using AnythingLLM!\e[0m\n\n" + +status "Next, we will create a desktop profile and AppArmor profile for AnythingLLMDesktop." +status "This is required for the AppImage to be able to run without SUID requirements." +status "You can manually create these profiles if you prefer." + +check_or_create_desktop_profile +check_to_create_apparmor_profile + +read -p "Do you want to start AnythingLLMDesktop now? (y/n): " start +case "$start" in [yY]) start="y" ;; esac +if [ "$start" = "y" ]; then + "$INSTALL_DIR/$APPIMAGE_FILE" +fi \ No newline at end of file