OSS cleanup: remove internal references, harden deployment, add config env interpolation

Phase 1 - Internal reference cleanup:
- Delete dev_docs/meetings/ (internal meeting notes/transcripts)
- Replace hardcoded usernames (padak/matejkys/dasa) with deploy/generic
- Replace "Internal AI Data Analyst" with "AI Data Analyst"
- Replace keboola/internal_ai_data_analyst URLs with your-org/ai-data-analyst
- Replace /tmp/keboola_load/ with /tmp/data_analyst_staging/ in dev_docs

Phase 2 - Deployment hardening:
- Tighten sudoers wildcards to explicit paths (visudo, sudoers cp)
- setup.sh creates all groups (data-ops, dataread, data-private) and deploy user
- webapp-setup.sh copies sudoers-webapp from repo instead of inline definition
- deploy.sh conditional copy for data_description.md (not in git for OSS)
- deploy.sh ownership changed to deploy:data-ops for /data/{scripts,docs,examples}

Phase 3 - Config and misc:
- Add ${ENV_VAR} interpolation to config/loader.py
- Expand config/instance.yaml.example with all sections (admins, deployment, auth, etc.)
- Create config/.env.template for secret values
- Add MIT LICENSE
- Fix .gitignore: add .venv/, docs/data_description.md
- Fix README.md: CSV status Planned, remove metrics/, update license text
- Translate Czech comments in requirements.txt to English
- Fix test_account_service.py: mock username mapping instead of relying on instance config

All 118 tests pass.
This commit is contained in:
Petr 2026-03-09 07:59:57 +01:00
parent c56905d34f
commit 26c4e0934d
26 changed files with 229 additions and 1555 deletions

4
.gitignore vendored
View file

@ -68,6 +68,7 @@ htmlcov/
dmypy.json dmypy.json
.pyre/ .pyre/
venv/ venv/
.venv/
env/ env/
ENV/ ENV/
env.bak/ env.bak/
@ -109,6 +110,9 @@ target/
config/instance.yaml config/instance.yaml
config/data_description.md config/data_description.md
# Instance-specific data description (generated per-instance)
docs/data_description.md
# Actual deploy workflow (created from .example, may contain secrets in comments) # Actual deploy workflow (created from .example, may contain secrets in comments)
.github/workflows/deploy.yml .github/workflows/deploy.yml

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 AI Data Analyst Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -103,8 +103,7 @@ ai-data-analyst/
├── docs/ # User-facing documentation ├── docs/ # User-facing documentation
│ ├── QUICKSTART.md # Setup guide │ ├── QUICKSTART.md # Setup guide
│ ├── data_description.md # Table schemas (single source of truth) │ └── data_description.md # Table schemas (single source of truth)
│ └── metrics/ # Business metric definitions
├── dev_docs/ # Developer and operator documentation ├── dev_docs/ # Developer and operator documentation
│ ├── server.md # Server administration │ ├── server.md # Server administration
@ -121,7 +120,7 @@ ai-data-analyst/
| Adapter | Status | Description | | Adapter | Status | Description |
|---------|--------|-------------| |---------|--------|-------------|
| Keboola Storage | Available | Pulls tables via the Keboola Storage API | | Keboola Storage | Available | Pulls tables via the Keboola Storage API |
| CSV | Available | Imports local or mounted CSV files | | CSV | Planned | Imports local or mounted CSV files |
| BigQuery | Planned | Google BigQuery adapter | | BigQuery | Planned | Google BigQuery adapter |
| Snowflake | Planned | Snowflake adapter | | Snowflake | Planned | Snowflake adapter |
@ -152,7 +151,7 @@ Claude Code will connect to the local DuckDB database, write and execute SQL, an
## License ## License
This project is not yet released under a specific open-source license. A license will be added before public release. Until then, all rights are reserved. This project is licensed under the [MIT License](LICENSE).
--- ---

View file

@ -1,32 +1,22 @@
# Keboola Storage API Token # AI Data Analyst - Environment Variables
# Jak získat token: # ========================================
# 1. Přihlaš se do Keboola: https://connection.us-east4.gcp.keboola.com # Secret values referenced by ${VAR} in config/instance.yaml.
# 2. Naviguj do Projects -> [Tvůj projekt] -> Settings -> API Tokens # Copy to .env: cp config/.env.template .env
# 3. Vytvoř nový token s názvem "Data Analyst Local" a scopem "Read-only" # .env is gitignored - NEVER commit it.
# 4. Zkopíruj token sem (místo "your_token_here")
KEBOOLA_STORAGE_TOKEN=your_token_here
# Keboola Stack URL # Required for webapp
# URL Keboola instance - pro US East 4 GCP WEBAPP_SECRET_KEY= # python -c "import secrets; print(secrets.token_hex(32))"
KEBOOLA_STACK_URL=https://connection.us-east4.gcp.keboola.com GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Keboola Project ID # Keboola adapter (skip if using CSV)
# ID projektu, ze kterého stahujeme data # KEBOOLA_STORAGE_TOKEN=
KEBOOLA_PROJECT_ID=5118
# Data Directory # Optional
# Folder for data storage (Parquet, metadata, DuckDB) # SENDGRID_API_KEY=
# Local: relative path from repo root # TELEGRAM_BOT_TOKEN=
# Server: absolute path to data directory # DESKTOP_JWT_SECRET=
# # JIRA_API_TOKEN=
# Local: DATA_DIR=./data # JIRA_WEBHOOK_SECRET=
# Server: DATA_DIR=/data/src_data # JIRA_SLA_API_TOKEN=
DATA_DIR=./data # ANTHROPIC_API_KEY=
# Data Source Type
# Zdroj dat: "local" = přímý download z Keboola, "gcs" = sync z Google Cloud Storage (budoucnost)
DATA_SOURCE=local
# Logging Level
# DEBUG, INFO, WARNING, ERROR, CRITICAL
LOG_LEVEL=INFO

View file

@ -1,68 +1,93 @@
# AI Data Analyst - Instance Configuration # AI Data Analyst - Instance Configuration
# Copy this file to instance.yaml and fill in your values. # ==========================================
# instance.yaml is gitignored - never commit it. # This is the main configuration file for your instance.
# Copy to instance.yaml and fill in your values.
#
# SECRET VALUES use ${ENV_VAR} syntax - actual values go in .env file.
# Non-secret values are set directly here.
# Instance branding # --- Instance branding ---
instance: instance:
name: "AI Data Analyst" # Display name (used in emails, UI title) name: "AI Data Analyst"
subtitle: "Your Organization" # Shown in header (e.g., "Acme Corp Internal") subtitle: "Your Organization"
copyright: "Your Organization" # Footer copyright text copyright: "Your Organization"
# Authentication # --- Server ---
server:
hostname: "" # DNS name (e.g., "data.acme.com")
host: "" # IP address
app_dir: "/opt/data-analyst" # Installation directory
# --- Admin users ---
# Manage the server, own data files, get unlimited resource limits.
# SSH keys are used by server/setup.sh during provisioning.
admins:
- username: "admin"
ssh_public_key: "ssh-ed25519 AAAA..."
# --- Deployment ---
deployment:
method: "manual" # manual | github_actions
repo_url: "" # e.g., "git@github.com:acme/ai-data-analyst.git"
branch: "main"
# --- Authentication ---
auth: auth:
# Google OAuth domain restriction (e.g., "acme.com") allowed_domain: "" # Google OAuth domain (e.g., "acme.com")
allowed_domain: "" google_client_id: "${GOOGLE_CLIENT_ID}"
google_client_secret: "${GOOGLE_CLIENT_SECRET}"
webapp_secret_key: "${WEBAPP_SECRET_KEY}"
# Email settings (for password auth) # --- Data source ---
data_source:
type: "keboola" # keboola | csv (bigquery planned)
keboola:
storage_token: "${KEBOOLA_STORAGE_TOKEN}"
stack_url: "" # e.g., "https://connection.keboola.com"
project_id: ""
# --- Email (optional, for password auth) ---
email: email:
from_address: "noreply@example.com" from_address: "noreply@example.com"
from_name: "AI Data Analyst" from_name: "AI Data Analyst"
sendgrid_api_key: "${SENDGRID_API_KEY}"
# Server connection # --- Desktop app (optional) ---
server:
host: "" # Server IP address
hostname: "" # Server DNS name (e.g., "data.acme.com")
# Desktop app
desktop: desktop:
jwt_issuer: "data-analyst" jwt_issuer: "data-analyst"
jwt_secret: "${DESKTOP_JWT_SECRET}"
url_scheme: "data-analyst" url_scheme: "data-analyst"
# Data source adapter # --- Telegram notifications (optional) ---
data_source:
type: "keboola" # Options: keboola, csv, bigquery (future)
# User display names (for Corporate Memory avatars)
# Maps server username to display info
users:
# example_user:
# name: "John Doe"
# initials: "JD"
# Username mapping (webapp email-derived username -> server home dir name)
# Only needed when they differ
username_mapping:
# john.doe: john
# Optional datasets (for sync settings UI)
# Define available optional datasets that users can enable/disable
datasets:
# dataset_key:
# label: "Human-readable name"
# description: "What this dataset contains"
# size_hint: "~50 MB"
# requires: null # or another dataset key
# Data catalog categories
# Maps folder names to display categories
catalog:
categories:
# folder_name:
# label: "Display Name"
# icon: "icon_type"
order: [] # Display order of folder names
# Telegram bot
telegram: telegram:
bot_username: "" # Bot @username on Telegram (e.g., "MyDataBot") bot_token: "${TELEGRAM_BOT_TOKEN}"
domain_suffix: "" # Domain for email derivation (e.g., "acme.com") bot_username: ""
domain_suffix: ""
# --- Jira integration (optional) ---
jira:
domain: ""
email: ""
api_token: "${JIRA_API_TOKEN}"
webhook_secret: "${JIRA_WEBHOOK_SECRET}"
sla_email: ""
sla_api_token: "${JIRA_SLA_API_TOKEN}"
cloud_id: ""
# --- Corporate Memory AI (optional) ---
ai:
anthropic_api_key: "${ANTHROPIC_API_KEY}"
# --- User display (for Corporate Memory avatars) ---
users: {}
# --- Username mapping (webapp email -> server username, only if different) ---
username_mapping: {}
# --- Optional datasets (sync settings UI) ---
datasets: {}
# --- Data catalog ---
catalog:
categories: {}
order: []

View file

@ -3,10 +3,15 @@ Instance configuration loader.
Loads instance.yaml from CONFIG_DIR (env var) or ./config/ fallback. Loads instance.yaml from CONFIG_DIR (env var) or ./config/ fallback.
Used by both webapp and src modules for instance-specific settings. Used by both webapp and src modules for instance-specific settings.
Supports ${ENV_VAR} syntax in YAML values for secret interpolation.
Actual secret values are stored in .env (gitignored), while the YAML
structure stays in instance.yaml.
""" """
import logging import logging
import os import os
import re
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -16,6 +21,29 @@ logger = logging.getLogger(__name__)
CONFIG_DIR = Path(os.environ.get("CONFIG_DIR", "./config")) CONFIG_DIR = Path(os.environ.get("CONFIG_DIR", "./config"))
_ENV_PATTERN = re.compile(r"\$\{([^}]+)\}")
def _resolve_env_refs(value: Any) -> Any:
"""Resolve ${ENV_VAR} references in config values.
Walks the config tree recursively. String values containing ${VAR}
are replaced with the corresponding environment variable value
(empty string if not set). Non-string values pass through unchanged.
"""
if isinstance(value, str):
def replacer(match: re.Match) -> str:
env_key = match.group(1)
return os.environ.get(env_key, "")
return _ENV_PATTERN.sub(replacer, value)
if isinstance(value, dict):
return {k: _resolve_env_refs(v) for k, v in value.items()}
if isinstance(value, list):
return [_resolve_env_refs(item) for item in value]
return value
def load_instance_config() -> dict[str, Any]: def load_instance_config() -> dict[str, Any]:
"""Load instance configuration from instance.yaml. """Load instance configuration from instance.yaml.
@ -47,6 +75,7 @@ def load_instance_config() -> dict[str, Any]:
if not config: if not config:
raise ValueError("instance.yaml is empty") raise ValueError("instance.yaml is empty")
config = _resolve_env_refs(config)
_validate_config(config) _validate_config(config)
logger.info("Instance config loaded from %s", path) logger.info("Instance config loaded from %s", path)
return config return config

View file

@ -105,7 +105,7 @@ Disk Layout:
```bash ```bash
mkdir -p /opt/data-analyst mkdir -p /opt/data-analyst
chown deploy:data-ops /opt/data-analyst chown deploy:data-ops /opt/data-analyst
sudo -u deploy git clone git@github.com:keboola/internal_ai_data_analyst.git /opt/data-analyst/repo sudo -u deploy git clone git@github.com:your-org/ai-data-analyst.git /opt/data-analyst/repo
git config --global --add safe.directory /opt/data-analyst/repo git config --global --add safe.directory /opt/data-analyst/repo
/opt/data-analyst/repo/server/setup.sh /opt/data-analyst/repo/server/setup.sh
``` ```

View file

@ -19,7 +19,7 @@ This is NOT a usage log. It is a **strategic command center** that:
- **Current state**: Demo mockup with fictional data (DEMO badge in header) - **Current state**: Demo mockup with fictional data (DEMO badge in header)
- **URL**: https://your-instance.example.com/activity-center (requires login) - **URL**: https://your-instance.example.com/activity-center (requires login)
- **PR**: https://github.com/keboola/internal_ai_data_analyst/pull/122 - **PR**: https://github.com/your-org/ai-data-analyst/pull/122
- **Branch**: `feature/activity-center` - **Branch**: `feature/activity-center`
- **Dashboard link**: Not added yet (UX placement decided, implementation pending) - **Dashboard link**: Not added yet (UX placement decided, implementation pending)

View file

@ -500,11 +500,11 @@ The SLA polling job runs every 15 minutes via systemd timer (`jira-sla-poll.time
3. Updates raw JSON atomically (`tempfile.mkstemp()` + `os.fchmod(fd, 0o660)` + `os.replace()`) 3. Updates raw JSON atomically (`tempfile.mkstemp()` + `os.fchmod(fd, 0o660)` + `os.replace()`)
4. Triggers incremental Parquet transform (inside advisory file lock) 4. Triggers incremental Parquet transform (inside advisory file lock)
**Self-healing:** The poll fetches `status`, `resolution`, `resolutiondate`, and `updated` alongside the SLA fields. If a ticket is resolved in Jira but still appears "open" in Parquet (e.g. due to a missed webhook), the poll automatically corrects the status in JSON and re-transforms to Parquet. Log output: `Self-healing: SUPPORT-XXXX is resolved in Jira`. This was added in response to [#203](https://github.com/keboola/internal_ai_data_analyst/issues/203) where 12 tickets were permanently stale after a permission bug prevented webhooks from updating JSON files. **Self-healing:** The poll fetches `status`, `resolution`, `resolutiondate`, and `updated` alongside the SLA fields. If a ticket is resolved in Jira but still appears "open" in Parquet (e.g. due to a missed webhook), the poll automatically corrects the status in JSON and re-transforms to Parquet. Log output: `Self-healing: SUPPORT-XXXX is resolved in Jira`. This was added in response to [#203](https://github.com/your-org/ai-data-analyst/issues/203) where 12 tickets were permanently stale after a permission bug prevented webhooks from updating JSON files.
**File locking:** The entire read-modify-write + Parquet transform is wrapped in a per-issue advisory file lock (`src/jira_file_lock.py`) to prevent races with the webhook handler. The webhook handler (`webapp/jira_service.py`) uses the same lock. Different issue keys don't block each other. **File locking:** The entire read-modify-write + Parquet transform is wrapped in a per-issue advisory file lock (`src/jira_file_lock.py`) to prevent races with the webhook handler. The webhook handler (`webapp/jira_service.py`) uses the same lock. Different issue keys don't block each other.
**Important — `mkstemp` and ACL:** The `issues/` directory uses POSIX ACLs with `default:mask::rwx`. `tempfile.mkstemp()` creates files with mode `0600`, which overrides the ACL mask to `---` and breaks group access for www-data (webhook handler) and deploy (batch transform). The `os.fchmod(fd, 0o660)` call immediately after `mkstemp()` restores the mask to `rw-`, preserving ACL-based access. See [#203](https://github.com/keboola/internal_ai_data_analyst/issues/203) for the full incident report. **Important — `mkstemp` and ACL:** The `issues/` directory uses POSIX ACLs with `default:mask::rwx`. `tempfile.mkstemp()` creates files with mode `0600`, which overrides the ACL mask to `---` and breaks group access for www-data (webhook handler) and deploy (batch transform). The `os.fchmod(fd, 0o660)` call immediately after `mkstemp()` restores the mask to `rw-`, preserving ACL-based access. See [#203](https://github.com/your-org/ai-data-analyst/issues/203) for the full incident report.
```bash ```bash
# Manual run # Manual run

View file

@ -1,201 +0,0 @@
### Akční položky
- @Padák - Specifikovat a implementovat mechanismus, který automaticky směruje artefakty do „User/Artifacts“ - [Termín neuveden].
- @Speaker 2 - Zpomalit scrollování a otevřít si podklady - [Termín neuveden].
- @Speaker 2 - Definovat/uložit správný cílový folder pro instalaci requirements v „initě“ - [Termín neuveden].
- @Padák - Doplnit dokumentaci (Docs Notifications MD) o cooldown hodnoty a případně o informace k venv - [Termín neuveden].
- @Padák & @Speaker 2 - Otestovat režim read-only pro serverové složky v souladu s „directory structure“ (never modify) - [Termín neuveden].
- @Padák - Opravit venv v souvislosti s úpravami Telegram notifikačního skriptu a nastavení permissions v /tmp - [Termín neuveden].
- @Speaker 2 - Zrevidovat, co se reálně děje při updatu dat z Kebuly (GIDA píšuje přiřazena Speaker 2) - [Termín neuveden].
- @Speaker 2 - Doplnit synchronizaci „Cloud Local MD“ z lokálu na server v rámci „sync data“, umístit v home uživatele - [Termín neuveden].
- @Padák - Předělat umístění user homů na samostatný disk (např. SDC) a nastavit snapshotování; připravit změny dle research tasku - [Termín neuveden].
- @Speaker 2 - Doplnit úpravy oprávnění do deploy skriptu podle dříve provedených ručních změn - [Termín neuveden].
- @Speaker 2 - Ověřit, jak se systém chová při neexistenci souboru metadata (zda má zastavit nebo zvolit alternativní běh) - [Termín neuveden].
- @Padák - Přehodnotit použití „check freshness/check-freeze“ vzhledem k chybějícím metadatům a dlouhému běhu - [Termín neuveden].
- @Team - Zvážit změnu pořadí kroků: spustit check freshness jako první a jasně definovat, kdy se provádí AirSync pro stažení metadat - [Termín neuveden].
- @Team - Prošetřit, proč AirSync synchronizuje „všechno“ (resp. příliš mnoho), a identifikovat příčinu nadměrných přenosů - [Termín neuveden].
- @Padák - Prověřit, zda API skutečně nepodporuje filtraci na timestamp změny a zda existuje alternativa - [Termín neuveden].
- @Speaker 2 - Ověřit, kde jsou var filtry z Data Description skutečně aplikovány v API callu - [Termín neuveden].
- @Speaker 2 - Přesměrovat implementaci na použití Export Async s „change since“ (timestamp) místo var filtrů - [Termín neuveden].
- @Padák - Implementovat parametr „--delete“ do AirSync - [Termín neuveden].
- @Speaker 2 - Obnovit timestamp a opravit parametr endpointu - [Termín neuveden].
- @Speaker 2 - Pracovat na telemetrických datech (dokončení, kontrola správnosti, příprava k synchronizaci) - [Dnes].
- @Speaker 2 - Pracovat ve vlastní branchi a vytvořit pull request k review - [Termín neuveden].
- @Padák - Provést code review pull requestu od Speaker 2 - [Po vytvoření PR].
- @Daša Dama - Zařadit navržené Cloud Settings do původního initu prostředí - [Termín neuveden].
- @Speaker 3 - Provést review dotazů (queries) k Infrastructure Cost Data (Lucka) - [Dnes].
- @Matěj - Připravit certifikované queries pro „top odpovídačku“ - [Příští týden].
- @Speaker 2 - Prověřit a řešit tok Sync + inkrementální stahování; podívat se na dotaz („kverinu“) týkající se „kostů“ - [Termín neuveden].
- @Speaker 2 - Zkontrolovat a případně převzít aktualizační skript týkající se „banner available data“ - [Termín neuveden].
- @Speaker 2 - V případě nejasností se doptat na konkrétní změny v aktualizačním skriptu - [Termín neuveden].
- @Speaker 3 (tým) - Posílat urgentní žádosti (odpovědi, review PR) na WhatsApp kvůli rychlejší reakci - [Termín neuveden].
### Klíčová rozhodnutí
- Změnit rozsah „sync data“ tak, aby zahrnoval nejen data, ale i skripty a dokumentaci — Odůvodnění: jinak se změny k uživatelům nedostanou.
- Přesměrovat „sync“ na plnou synchronizaci obsahu (skripty, dokumentace, instrukce) — Odůvodnění: manuální postup (rsync) není škálovatelný.
- Nešířit uživatelské skripty a artefakty plošně všem — Odůvodnění: zachovat izolaci uživatelského obsahu a zabránit nechtěné distribuci.
- Doplnit informace do CloudMD ohledně potřebných instrukcí — Odůvodnění: záměr „stačí to napsat…“, i když chybí část o serveru.
- Použít Cloud settings permissions k zákazu editací serverových složek (read-only přístup) — Odůvodnění: lepší než spoléhat na text v CloudMD.
- Zavést rozlišení mezi CloudMD (auto-updated from server) a Cloud Local MD (personal, never overwritten).
- Přesunout uživatelské homy na samostatný disk (SDB/SDC) a využít Google snapshoty pro zálohy — Odůvodnění: zálohovatelnost a kapacita.
- Potvrzení příčiny chyby metadat: chybějící write permission zabránilo zápisu metadat — Odůvodnění: logy z cron běhu ukázaly nenastavené permission.
- Timestampový sloupec je povinný pro podporu inkrementálních operací extraktorů/writerů — Odůvodnění: pro filtrování změn.
- Brát popis skriptu jako skutečné chování: timestamp existuje na úrovni API i když není v exportovaných datech.
- Preferovat jednoduchou strategii export všeho při prvním běhu a poté jen změny podle timestampu uloženého ve state.
- Implementovat inkrementální exporty založené na timestampu a metadata state.
- Nepoužívat WHERE filtry přes SDK; využít správný endpoint pro inkrementální export (Export Async).
- Využít „change since“/timestamp mechaniku endpointu Export Async pro inkrementální exporty namísto var filtrů.
- Přidat parametr „--delete“ do AirSync — Odůvodnění: klientská složka musí odrážet stav serveru včetně mazání.
- Věci kolem GitHubu a okolí mají nízkou prioritu a mohou počkat — Odůvodnění: soustředit se na notifikace a desktopovou aplikaci.
- Parquet soubory budou udržovány ve vysoké kvalitě na zdroji; neprovádět castování datových typů až na klientovi — Odůvodnění: konzistence, méně práce u klientů, přímý přístup.
### Detailní zápis
[00:01-01:05] Uživatelský přístup k CloudMD a instrukcím je blokován, protože synchronizace přenáší pouze data, nikoli skripty a dokumentaci.
- Instalace je hotová pro více uživatelů (LabCone, Pavel, Jirka), Matěj Kis správně přidal CloudMD, ale konkrétní uživatel se k němu nedostane; „sync data“ synchronizuje pouze parquet soubory.
- Změny se nedostávají k uživatelům; regenerace „instructions“ (project.json → clod.md) se nepropaguje.
- Key Decision: „Sync data“ musí zahrnout i skripty a dokumentaci.
[01:05-01:29] Dočasně je možné řešit distribuci skriptů ručním spuštěním rsync, ale cílové řešení je, aby synchronizace přenášela vše ze serveru, nejen data.
- Ruční rsync nyní funguje; návrh dát postup do manuálu „clodových dat“.
- Key Decision: Plná synchronizace obsahu; manuální postup není škálovatelný.
[01:30-02:01] Synchronizace clod.md vytváří konflikt mezi aktualizací instrukcí a uživatelskými úpravami; přepisování souboru při každém syncu je problém.
- clod.md se regeneruje při každém stažení dat; uživatelské úpravy se přepisují.
- Identifikován druhý problém vedle nedostupnosti skriptů/instrukcí.
[02:01-03:14] Změny v notifikacích a potřebě obousměrné synchronizace odhalily riziko nechtěné distribuce uživatelských skriptů všem uživatelům.
- Lokální Python pro notifikace by se při nahrání mohl rozšířit všem; nežádoucí.
- Key Decision: Nešířit uživatelské skripty a artefakty plošně.
[03:15-03:44] Uživatel (Claude) iniciativně modifikoval serverový folder Scripts; pokud nezasáhne Sync Script, je to opravitelné, ale přesto nežádoucí.
- Změny by se při updatu přepsaly; jedná se o dvě nedomyšlené části (synchronizace a práva/izolace).
[03:45-04:23] Přechod k demo/ukázce: požadavek na sdílení obrazovky, potvrzení dostupnosti, a popis lokálního vs. serverového „home“.
- Diskuse o sdílení desktopu; ukázka rozdílu mezi lokálním a serverovým „home“.
[04:47-05:19] Struktura adresářů: rozlišení mezi „naše věci“ a „User“ obsahem; DuckDB je generován na klientovi a není synchronizován ze serveru.
- Server: Docs, Example, Metadata, Parked, Scripts; User: Artifacts, DuckDB, Notifications, Parked, Scripts.
- DuckDB vzniká na klientovi; není součástí serverového syncu.
[05:20-05:50] Definice „User/Artifacts“ a dotaz na mechanismus ukládání; zatím se ukládání řeší pouze informováním uživatele. (Sloučeno z 320904-349296 a 349395-350095)
- „Artifacts“ jsou uživatelské výstupy; otevření artefaktu zobrazí dashboard.
- Dotaz na mechanismus ukládání; zatím jen pokyn „kam ukládat“.
- Action Item: @Padák - Specifikovat a implementovat automatické směrování artefaktů do „User/Artifacts“.
[05:50-06:21] Přehled reorganizace složek a komponent: přesun „bordelu“ do Artifacts, vymezení Cloud MD (správa Matěj Kis), DugDB při inicializaci, Notifikace a Parkety.
- Cloud MD spravuje Matěj Kis; Notifikace mají vlastní složku; Parkety prázdné pro případ předpočítání.
[06:21-06:52] Popis aplikace pro pricing kalkulačky a uživatelských transformací dat mimo centrální update.
- Pavel postavil aplikaci; update dat neřeší; pipeline by mohla generovat další data.
[06:52-07:22] Mapování parity složek na serveru a u uživatelů; umístění složek users v home.
- Paritní serverová struktura; users ve vlastním home, data nikam neodcházejí.
[07:22-08:01] Kopie adresáře users v Padákově home, obsah Notifications a změny v ECOV; vysvětlení SyncData.
- SyncData: synchronizuje User Folder na server; Server Folder ze serveru.
[08:04-08:25] Omezení synchronizace: zatím neprobíhá sync ze serveru do lokálních User Folderů; sdílení browseru.
- Zatím jen pro to, aby bylo na serveru; lokální User Foldery se neaktualizují.
[08:38-08:41] Návrh: připojit se na KOL a domluvit si diskuzi.
- Krátký organizační návrh.
[08:50-09:21] Redesign dashboardu a jeho dynamická generace při update z Kebuly; jasná místa pro úpravy.
- Dashboard bez scrollu; dynamický generátor při update.
[09:21-09:39] Dashboard ukazuje last sync, compressed/uncompressed a Telegram notifications; přechod ke sdílení obrazovky.
- Telegram notifikace „unlinked“; potřeba sdílet screen.
[09:51-10:36] Autorizace notifikací: Slack vs Telegram; preference Telegramu.
- Slack autorizovaný Kebula účtem; preference Telegramu.
[10:38-11:07] Nastavení kebula data bot v Telegramu a navázání konverzace.
- Přidání bota, zahájení konverzace.
[11:08-12:16] Mechanismus linkování Telegramu pomocí kódu: generování, pending kody, identifikace uživatele.
- Generace kódu, pending kódy, verifikace; „vítej“ chybí.
[12:17-13:20] Stav po verifikaci: evidování uživatele v telegram users; test report a jeho výstupy.
- Uživatel evidován; „test“ posílá text+obrázek.
[13:27-14:02] Vysvětlení „test“: jednotný test spojení; „status“ vypisuje dostupné user notifications s tlačítkem.
- „Status“ spustí report; výstup definován ve skriptu.
[14:33-15:38] Automatické doručení reportu přes crontab, venv správa Klodem lokálně i na serveru; požadavek na identická prostředí.
- Venv musí být identický lokálně i na serveru; NotifyRunner běží z venvu.
[15:38-16:09] NotifyRunner běží pod uživatelem Petr a spouští skripty v jeho home; identifikace důvodu neproběhnutí notifikace.
- Důvod: design notifikačního systému.
[16:09-17:46] Design cooldown mechaniky v notifikacích: .notifications/state; omezení spamování; příklad CRM konektoru.
- Cooldown perioda; po smazání state dorazily data.
[18:13-19:08] Diskuze o komplikacích cooldownu v pilotu a Padákovo stanovisko k flexibilitě konfigurace.
- Cooldown volitelný; možnost migrací a fallback v sync scriptu.
[20:13-20:16] Shrnutí: aktuální setup, který Padák zavedl.
- Stručné shrnutí.
[20:17-24:28] Technické dotazy ke zrcadlení venv mezi lokálem a serverem; hledání init skriptů a vytvoření venv. (Sloučeno z 1217011-1328285, 1329185-1468035 a 1462655-1468035)
- Diskuse o tom, kdy a jak se na serveru vytvoří venv; hledání „python -m venv“ v initu; potvrzení, že je stažené; potřeba jasného postupu.
- Action Item: @Speaker 2 - Definovat cílový folder pro requirements v „initě“.
[24:29-25:18] Účastníci se vyjasňují nad inicializačním skriptem (init), rolí „Bula Internal Data Analyst, Crypt“, formátováním a orientací ve skriptech.
- „Init“ a formátování; Padák nezkoumá detailně skripty ostatních.
- Action Item: @Speaker 2 - Zpomalit scroll a otevřít podklady.
[25:34-26:42] Je nutné vyřešit automatickou instalaci závislostí při nasazení notifikací, včetně správného umístění a práce s venv na serveru.
- Potřeba jasného procesu instalace; příklad chybějících balíčků.
[26:51-27:33] Padák navrhuje doplnit informace do CloudMD; současně konstatuje, že o serveru tam nic není.
- Key Decision: Doplnit instrukce do CloudMD (část o serveru chybí).
[27:58-28:45] Dokumentace k notifikacím obsahuje strukturu a postupy; zvažováno doplnění informací o venv.
- „Server Docs Notification MD“ a „Docs Notifications MD“; doplnit cooldown a venv.
- Action Item: @Padák - Doplnit dokumentaci.
[29:15-30:58] Diskuse o omezení práv (permissions) pro Clouda: read-only přístup do některých složek; zabránit editacím serverových skriptů.
- Dokumentace „directory structure“; nastavení v Cloud settings.
- Key Decision: Použít Cloud settings k zákazu editací; Action Item: test read-only režimu.
[33:14-34:20] Zavedení rozlišení mezi CloudMD (auto-updated) a Cloud Local MD (personal, never overwritten); plán na opravu venv a poznámky k Telegramu a permissions.
- Key Decision: Rozlišení MD; Action Item: @Padák - Opravit venv a permissions v /tmp.
[35:02-36:47] Optimalizace synchronizace: přidán „--checksum“ do SyncData; zátěž vs. přesnost; návrh syncovat i Cloud Local MD.
- Action Item: @Speaker 2 - Zrevidovat update flow; doplnit sync Cloud Local MD na server.
[37:49-40:35] Plán zálohování a disaster recovery: přesun homů na samostatný disk; snapshoty; kapacitní limity. (Sloučeno z 2269116-2433634 a 2434514-2435554)
- Key Decision: Přesun homů na SDB/SDC; Google snapshoty.
- Action Item: @Padák - Připravit přesun a snapshoty.
[40:36-41:47] Řešení nesrovnalostí v oprávněních a roli deploy skriptu při generování metadat; identifikace write-permission problému.
- Action Item: @Speaker 2 - Doplnit oprávnění do deploy skriptu.
- Key Decision: Příčina chyby: chybějící write permission.
[42:15-44:14] Účel metadat na straně uživatele; check freshness a absence .metadata JSON; exit code != 0; AirSync pořadí.
- Action Item: @Speaker 2 - Ověřit chování při neexistenci metadata.
- Action Item: @Padák - Přehodnotit check freshness; @Team - Spustit check freshness jako první.
[44:38-45:43] Analýza skriptu: chybí explicitní stažení; AirSync porovnává velikost a mtime; flexibilní definice „stáří“ dat.
- Diskuse o denních vs. hodinových kritériích; bez finálního rozhodnutí.
[46:25-47:11] Návrh vyřadit „check freshness“ a spoléhat na AirSync; otázka proč se synchronizovalo „všechno“.
- Action Item: @Team - Prošetřit nadměrné přenosy AirSync.
[47:24-50:58] Zjišťování rozsahu synchronizace; inkrementy na velkých tabulkách; omezení API; „To je divný.“ (Sloučeno z 2844196-3018360 a 3057430-3058450)
- API nepodporuje snadné timestamp filtry v datech; Padák má ověřit alternativy.
- Action Item: @Padák - Prověřit podporu timestamp filtrů v API.
[51:00-51:39] Uznání možné chyby na začátku; lokalizace skriptu v repozitáři; potřeba zjistit název a volání.
- Skript je v repozitáři; nutné dohledat přesné volání.
[51:56-52:59] Identifikace zdroje a komponenty: zdroj „www.hradeckralove.org“ a skript „DataSync“ ve složce „src“.
- Potvrzení názvu a umístění.
[53:01-54:22] Požadavek na detailní popis „DataSync“; důležitost timestampového sloupce; incremental vs. partition sync.
- Key Decision: Timestamp povinný; rozlišit incremental vs. partition; ověřit aplikovaný režim.
[54:41-57:06] Problém exportu celé tabulky bez timestampu z API vs. záměr timestamp filtrování; objasnění, že timestamp je na úrovni API.
- Key Decision: Popis skriptu jako zdroj pravdy; partition sync chybí timestamp v output; WHERE nad date sloupci.
[57:06-59:31] Var filtry vs. timestamp; plán znovu zkusit; preferovat jednoduchou strategii timestamp state.
- Key Decision: První běh full, pak změny podle timestamp.
[01:00:02-01:02:01] Workflow: full export → Parquet → state s timestampem → následně inkrementální exporty „větší než timestamp“.
- Key Decision: Implementovat inkrementální exporty podle timestampu.
[01:00:44-01:04:05] Identifikace endpointu; omezení SDK; použít Export Async a „change since“ (deprecated) pro timestamp. (Sloučeno z 3644882-3721116, 3771414-3845886 a 3841326-3845886)
- SDK nepodporuje WHERE filtry; „change since“ sahá na timestamp.
- Key Decision: Využít Export Async s „change since“; Action Item: @Speaker 2 - Přesměrovat implementaci.
[01:04:06-01:04:43] Návrh 15minutových refreshů dat s využitím ChangeSince; prázdné joby bez změn.
- Efektivní běh bez zbytečných přenosů.
[01:05:16-01:06:13] Organizace souborů po tabulkách a intervalech (měsíce vs. hodiny); dopad na objem; AirSync to zvládne; pozdější konsolidace.
- Začít jednoduše; konsolidovat později.
[01:06:18-01:07:49] Řešení mazání a plných reloadů při změnách dat; chování AirSync.
- Full delete + full export při mazání; AirSync srovná obsah.
[01:07:50-01:08:35] Oprava špatného parametru endpointu; nabídka testu AirSync na test serveru.
- Action Item: @Speaker 2 - Obnovit timestamp a opravit parametr; @Padák - Mock test AirSync.
[01:08:37-01:11:37] Rozsah synchronizace AirSync (data vs. dokumentace); aktualizace server MD; praktická demonstrace test rsync.
- AirSync synchronizuje vše v server folderu; test ukázal potřebu „--delete“.
[01:11:40-01:13:02] Plán dopracovat mazání v AirSync pomocí „--delete“; odklad detailů.
- Key Decision: Přidat „--delete“.
[01:13:05-01:14:34] Další postup práce na datech; PR workflow; telemetrie; potvrzení pokračování. (Sloučeno z 4385398-4473430 a 4473790-4474870)
- Action Items: PR a review; práce na telemetrii; komunikace.
[01:14:36-01:15:19] Konfigurace Cloud Settings s explicitními oprávněními; příprava testu.
- Nastavení deny/read; povolení write/view credentials/secret; test s novým „Clodem“.
[01:15:15-01:15:54] Rozdíl mezi „write“ a „edit“ a jeho dopady v praxi.
- Obě znamenají modifikaci; „edit“ může mít omezení.
[01:15:57-01:16:45] Test notifikací a úprav dokumentace (Telegram vs. Slack); zapsání „Telegram“ kapitálkami do dokumentu.
- Očekávání výsledku; „first line kontrola“.
[01:17:09-01:17:42] Nastavení, aby server skripty běžely bez potvrzení.
- Návrh v settings; bez záznamu o provedení.
[01:17:47-01:20:44] Návrh settings.json pro init prostředí; allow/deny seznamy; zásady pro přístup; přesun do GitHub Issue; rozšíření notifikací. (Sloučeno z 4667718-4766404 a 4792916-4844396)
- Allow: fetch/status/...; Deny: env/credentials/secrets/...; některé server akce „Ask“.
- Action Item: @Daša Dama - Zařadit Cloud Settings do initu.
[01:20:45-01:22:10] Experiment: macOS status bar aplikace pro zobrazování notifikací; instalační skript; cílení na CSU.
- Desktop notifikace; 15min updaty jako atraktivní.
[01:22:28-01:23:24] Cloud Learnings; úkol pro Mañana/Anneli; review Infrastructure Cost Data queries.
- Action Item: @Speaker 3 - Review dnes.
[01:23:25-01:29:57] Architektonické doporučení: minimalizace Snowflake nákladů; využití parquet + DuckDB; offload na klienty.
- DuckDB rychlé; snížit závislost na Snowflake; stream costů možný, zatím bez use-case.
[01:27:59-01:29:57] Diskuse o podpoře parquet v Kebule; exportní možnosti; omezení UI/debug mode.
- Parquety přes debug mode; časové členění neovlivnitelné; ponechat stav; výpočty na klientovi.
[01:30:41-01:31:22] Stabilizace a certifikace queries; jejich role pro „Cloda“.
- Action Item: @Matěj - Certifikované queries příští týden.
[01:31:22-01:32:23] Castování typů na klientovi vs. kvalita parquet; závěr segmentu.
- Key Decision: Kvalita parquet na zdroji; necastovat na klientovi.
[01:32:24-01:32:55] Ukončení předchozí části; priority: notifikace; nízká priorita GitHub okolí; odstranit „check sam“ ze Sync scriptu; potvrzení.
- Key Decision: Nízká priorita pro GitHub okolí.
[01:32:55-01:33:53] Plán: řešení Syncu s inkrementálním stahováním; „kosty“ query; aktualizační skript „banner available data“; komunikační preference; ukončení hovoru.
- Action Items: @Speaker 2 - Sync + inkrementální; kontrola banner skriptu; dotazování na změny; @Speaker 3 - WhatsApp pro urgentní; rozloučení.

View file

@ -1,77 +0,0 @@
## Klíčové body
---
- Byl identifikován problém, že po instalaci se nové úpravy (např. dokumentace) nedostanou ke stávajícím uživatelům, protože skript `sync_data` synchronizuje pouze data, nikoli zbytek struktury.
- Při pokusu o rozšíření `sync_data` vznikl konflikt: přepisování souborů jako `cloud.md` by mazalo uživatelské poznámky. Současně `home` adresář na serveru nerozlišoval mezi uživateli.
- Byla implementována nová adresářová struktura oddělující serverové soubory (složka `server`) a uživatelské soubory (složka `user`), aby se zabránilo konfliktům a nechtěným přepisům. Uživatelské soubory se nyní synchronizují na server do `home` adresáře daného uživatele.
- Padák prezentoval redesignovaný dynamický dashboard a novou funkci notifikací přes Telegram. Propojení účtu vyžaduje zadání unikátního kódu vygenerovaného botem.
- Byla identifikována příčina selhání automatického spouštění reportů: funkce "cooldown period", která brání opakovanému odeslání stejné notifikace v krátkém čase.
- Řešilo se fungování virtuálních prostředí (`venv`) a byla nalezena chyba ve skriptu `init` (chybějící tečka před `venv`).
- Pro ochranu serverových souborů bylo navrženo použít oprávnění `read-only`. Byl představen soubor `cloud.local.md` pro uživatelské poznámky, který se nepřepisuje.
- Synchronizační skript byl upraven přidáním parametru `--checksum`, aby se přenášely pouze soubory se změněným obsahem, nikoli jen s novějším datem modifikace.
- Byl představen plán na přesun `/home` adresářů na samostatný, zálohovaný diskový oddíl kvůli nedostatku místa na systémovém disku.
- Diskutovalo se o nefunkčním skriptu `check_freshness`, který měl kontrolovat aktuálnost dat. Padák navrhl jeho odstranění, protože `rsync` je pro tento účel dostatečně efektivní.
- Bylo zjištěno, že API pro stahování dat z Kebooly nepodporuje efektivní filtrování podle časové značky změny, což komplikuje inkrementální stahování pouze změněných řádků.
- Bylo zjištěno, že pro inkrementální synchronizaci dat z Kebooly je správným přístupem použít parametr `changed_since` v API volání namísto zastaralého a komplikovaného `where` filtru.
- Padák navrhl provádět aktualizace dat z Kebooly každých 15 minut a pro zajištění smazání starých souborů na straně klienta použít parametr `--delete` při synchronizaci.
- Byl navržen přesun výpočtů a zpracování dat (např. Infrastructure Cost Data) z drahých cloudových služeb (Snowflake) na stranu klienta pomocí DuckDB, aby se snížily náklady a zvýšila rychlost.
- Pro nová prostředí bude v `settings.json` nastaveno, aby systém vyžadoval potvrzení pro rizikové operace (např. `Push Force`).
- Padák experimentuje s vývojem desktopové aplikace pro macOS pro zobrazování notifikací.
## Přijatá rozhodnutí
---
- Skript `sync_data` bude synchronizovat veškerý potřebný obsah ze serveru (skripty, dokumentaci atd.), nejen data.
- Byla zavedena nová adresářová struktura striktně oddělující serverové (`server`) a uživatelské (`user`) soubory.
- Pro notifikace bude v pilotní fázi použit Telegram s ověřováním přes unikátní kód.
- Byl implementován volitelný mechanismus "cooldown period" pro zamezení zahlcení uživatelů notifikacemi.
- Ochrana serverových souborů bude řešena instrukcemi `read-only` v `Cloud.md` a využitím souboru `cloud.local.md` pro uživatelské poznámky.
- Pro synchronizaci dat se bude používat parametr `--checksum`, aby se předešlo zbytečným přenosům dat.
- Skript pro kontrolu aktuálnosti dat (`check_freshness`) bude odstraněn a pro synchronizaci se bude spoléhat výhradně na `rsync`.
- Pro inkrementální export dat z Kebuly se bude používat parametr `changed_since` místo `where` filtrů.
- Aktualizace dat z Kebuly se budou provádět v 15minutových intervalech.
- Pro synchronizaci souborů se bude používat parametr `--delete` k zajištění smazání souborů, které již neexistují na zdroji.
- Zpracování dat se bude přesouvat z cloudových služeb (Snowflake) na stranu klienta s využitím DuckDB.
- Přetypování dat (kastování) se nebude provádět na straně klienta, aby byla zachována kvalita zdrojových parquet souborů.
## Akční body
---
### Úkoly
| Task | Responsible Party | Deadline | Notes |
| :--- | :--- | :--- | :--- |
| Doplnit do `cloud.md` instrukce pro uživatele ohledně ukládání do složky `artifacts`. | Matěj Kis (předpoklad) / Tým | Není specifikováno | Potřeba aktualizace byla zmíněna, ale obsah nebyl aktivně řešen. |
| Připojit se ke Claude a komunikovat s ním. | Padák | Není specifikováno | Zmíněno jako další krok na konci schůzky. |
| Zdebugovat, proč se automatická notifikace v 7:30 nespustila dle plánu v `crontab`. | Padák | Není specifikováno | Příčinou je pravděpodobně funkce "cooldown period". |
| Doplnit informace o `venv` do dokumentace. | Padák | Není specifikováno | Doplnit popis do `Docs/Notifications.md`. |
| Opravit chybu ve skriptu `init` (přidat tečku před `venv`). | Speaker 2 | Není specifikováno | Zajistit správné vytváření virtuálního prostředí. |
| Zkontrolovat, co se reálně děje při aktualizaci dat z Kebooly. | Speaker 2 | Není specifikováno | V návaznosti na změnu synchronizace pomocí `--checksum`. |
| Upravit synchronizační skript, aby synchronizoval i soubor `cloud.local.md` ze stroje uživatele na server. | Speaker 2 | Není specifikováno | Zajistí zálohu uživatelských poznámek na serveru. |
| Předělat strukturu serveru, přesunout uživatelské home adresáře na samostatný zálohovaný disk. | Padák | Není specifikováno | V rámci research úkolu na "Backup and disaster recovery". |
| Opravit problém s oprávněními souborového systému u Telegram bota. | Padák | Není specifikováno | Zapsáno v GIDA píšu jako poznámka k úpravě tlačítek v Telegramu. |
| Zkontrolovat a případně doplnit skripty pro nasazení (deploy scripts) o správné nastavení oprávnění složek. | Speaker 2 | Není specifikováno | Zajistit, aby skripty automatizovaly ruční úpravy oprávnění. |
| Zkontrolovat, proč se nestahují metadata (soubor .metadata). | Speaker 2 | Není specifikováno | Původní problém byl v chybějících oprávněních k zápisu. |
| Zjistit, kde se aplikují `where` filtry definované v `Data Description`. | Speaker 2 | Není specifikováno | Zvláštní pozornost věnovat použití sloupce s časovým razítkem (timestamp). |
| Upravit exportní skript tak, aby pro inkrementální synchronizaci používal parametr `changed_since`. | Speaker 2 | Není specifikováno | Cílem je zjednodušit logiku a zajistit efektivní načítání pouze změněných dat. |
| Připravit a promyslet způsob zacházení s daty v tabulkách (např. při přemazání). | Speaker 2 | Není specifikováno | Bude řešeno v rámci celé flow inkrementálního zpracování. |
| Vyzkoušet funkci `AirSync` a zaměřit se na její chování při synchronizaci. | Speaker 2 | Není specifikováno | Zapsáno k řešení v momentě implementace. |
| Zkontrolovat telemetrická data a připravit je k synchronizaci. | Speaker 2 | Dnes | Ověřit správnost dat před jejich nasynchronizováním. |
| Práce na implementaci (v branchi) s možností review přes pull request. | Speaker 2 | Není specifikováno | Padák nabídl provedení revize kódu. |
| Vložit obsah `settings.json` do nového GitHub issue a přiřadit ho Daše Dama. | Padák | Není specifikováno | Cílem je, aby Daša Dama toto nastavení zapracovala do init skriptu. |
| Dokončit úpravy notifikací, přidat podporu pro Slack a opravit chyby v ručním spouštění reportů. | Padák | Není specifikováno | |
| Vyvinout a otestovat prototyp desktopové aplikace pro macOS pro zobrazování notifikací. | Padák | Není specifikováno | Probíhá v rámci větve `MacOS app branch`. |
| Provést review query pro data o nákladech. | Matěj | Není specifikováno | Bude řešeno na začátku příštího týdne. |
| Zkontrolovat, zda se skripty pro synchronizaci (tablety) spouštějí správně. | Speaker 3 | Není specifikováno | Pokud ne, odstranit `check sam` ze `Sync scriptu`. Nízká priorita. |
| Podívat se na query týkající se nákladů (costů). | Speaker 2 | Není specifikováno | |
| Řešit "certifikované query". | Matěj | Příští týden | Cílem je vytvořit "top odpovídačku". |
### Termíny
- **za 10 dní**: Vytvoření nových PSUGO projektů (dle příkladu notifikačního skriptu, nejedná se o skutečný termín).
- **7:30 (evropského času)**: Měla proběhnout automatická notifikace pro uživatele Petr, což se nestalo.
- **Dnes**: Speaker 2 se bude věnovat kontrole telemetrických dat a jejich přípravě k synchronizaci.
- **Příští týden**: Matěj bude řešit certifikované query.
### Následné kroky
- Pokračovat v diskuzi a spolupráci s AI (Claude).
- Padák prověří a opraví problém s automatickým spouštěním reportů naplánovaných přes `crontab`.
- Padák připraví a provede přesun uživatelských `home` adresářů na nový diskový oddíl.
- Speaker 2 se podrobněji podívá na proces inicializace virtuálních prostředí a synchronizace dat, včetně implementace zálohování `cloud.local.md`.
- Zvážit odstranění skriptu `check_freshness` a spoléhat se výhradně na `rsync`.
- Prozkoumat možnosti API pro stahování dat, zda by bylo možné efektivněji filtrovat pouze změněné záznamy.
- Speaker 2 provede revizi a úpravu skriptu pro export dat z Kebuly s využitím parametru `changed_since`.
- Speaker 2 se bude zabývat daty, jejich strukturou a celkovým procesem synchronizace, Padák bude k dispozici pro revizi kódu.
- Daša Dama implementuje nová nastavení z `settings.json` do init skriptu prostředí.
- Padák bude pokračovat ve vývoji desktopové aplikace pro notifikace.

View file

@ -1,78 +0,0 @@
## Informace o schůzce
> Datum: 2026-02-01 20:35:35
> Místo: [Vložit místo]
> Účastníci: [Padák] [Speaker 2] [Speaker 3] [Speaker 4]
## Poznámky ze schůzky
- Synchronizace a distribuce skriptů, dokumentace a dat (CloudMD, Sync/AirSync, rsync)
- Aktuální sync řeší pouze data (parquet), nikoli skripty ani dokumentaci; CloudMD (dříve project.json) se u uživatele neaktualizuje.
- Dočasně se používá ruční rsync; návrh rozšířit „sync data“ na synchronizaci všeho ze serveru (data, skripty, dokumentace) a zavést obousměrnost pro User foldery.
- Identifikován problém s přepisováním uživatelských úprav CloudMD při každém stažení; potřeba strategie pro merge/ochranu změn (lokální overrides, verzování).
- Hrozba nechtěné distribuce uživatelských skriptů všem; chybí identifikace vlastníka (User/scripts bez namespace).
- AI zasahovala do serverové složky Scripts; nutná ochrana/locky a jasná práva, aby nedocházelo k přepisům.
- Struktura adresářů: lokálně CloudMD + serverové složky (Docs, Example, Metadata, Parked, Scripts) a User (Artifacts, DuckDB, Notifications, Parked, Scripts). Na serveru parita server folderu a Users v home.
- SyncData nyní synchronizuje User folder na server a Server folder ze serveru; zpětná synchronizace do lokálního User zatím není.
- Přidán rsync parametr --checksum kvůli častým změnám mtime u parquetů; snižuje zbytečné přenosy, ale zvyšuje IO/CPU.
- AirSync porovnává velikost/timestamp a rychle končí při nezměněných souborech; doporučeno nahradit „check freshness“ AirSyncem a správně logovat metadatové soubory (.metadata/JSON).
- Potřeba parametru „--delete“ (force) v AirSync pro odstranění přebytečných lokálních souborů a zrcadlení stavu serveru.
- Inkrementální export z Kebuly, granularita a API omezení
- Rozdíl full export vs. inkrementální export dle timestampu; potvrzeno použití Storage Tables „Export Async“ s parametrem „changeSince“ (i když je označen jako deprecated), který vrací pouze změněné řádky od zadaného času.
- Where filtry nejsou v SDK pro Export Async podporované; dokumentace a skripty musí sladit chování a omezení SDK.
- Udržovat „state“ s timestampem posledního běhu na serveru; další běhy volají API s „changeSince“ a partitioning (po hodinách) se provádí následně na serveru.
- Inkrement je nastaven jen u vybraných tabulek (např. company snapshot); jinde dochází k nadměrnému stahování kvůli omezením API (filtrace podle „datum“ sloupce není ekvivalent timestampu).
- Návrh struktury: aktuální data po hodinách (jemná granularita, ~8700 souborů/rok per tabulka), historická data po měsících; AirSync by měl zvládnout velký počet souborů.
- Automatizace, notifikace a prostředí (Telegram, Slack, venv, crontab, cooldown)
- Telegram bot: postup „start“ → kód → „verify“; ukládání uživatele („telegram users“), příkazy „help“, „who am I“, „test“, „status“ (spuštění reportu tlačítkem).
- Slack notifikace nevyžadují linkování (autorizace přes Kebula účet); plán doplnit Slack vedle Telegramu.
- NotifyRunner běží pod uživatelem, loguje a spouští skripty z jeho home; v crontabu naplánované odeslání (např. 7:30) neproběhlo kvůli aktivnímu cooldownu; po smazání state zpráva dorazila.
- Cooldown brání spamování; konfigurovatelný (např. denní), v pilotu lze dočasně vypnout. Složka .notifications obsahuje logs a state; příkaz „notification state pgDailyReport“ ukazuje poslední odeslání.
- Důraz na identické venv lokálně i na serveru (stejné balíčky); doplnit dokumentaci (Docs Notifications MD, CloudMD) k procesu tvorby/aktivace venv a umístění balíčků v uživatelském home.
- V rámci FS oprávnění zmiňován sticky bit v /tmp; řešit omezení vůči Telegram bot skriptu a dočasným souborům.
- Bezpečnost, oprávnění a Cloud Settings
- Cíl: read-only přístup pro cloud agenta do serverových složek; v CloudMD definováno „server read-only“ a „never modify“ pro vybrané adresáře; ověřit v praxi.
- Rozlišení: Cloud MD (serverem spravované instrukce, auto-update ze serveru) vs. Cloud Local MD (uživatelské, nikdy nepřepisovat); implementovat zálohu/sync Cloud Local MD, aby se nevytratily jedinečné instrukce.
- Nastavení Cloud Settings: zpřísněné čtení, explicitní „write and view credentials“ pro secrety, „write a edit“ na server; auditovat změny oprávnění.
- Vytvořit settings.json v init skriptu: politika Allow (běh vybraných serverových skriptů, git fetch/status/tag apod.), Deny (ENV, credentials, secrets, PEM, keystore, password, token, API keys), Ask (reset, clean, push/force-push, RMM). Připravit GitHub issue, přiřadit Daša Dama, začlenit do initu prostředí.
- Zálohy, disky a provozní infrastruktura
- Stav disků: root ~10 GB (jen ~3 GB volné), data ~30 GB (/data). Plán přesunout uživatelské homy na samostatný disk (např. sdc) nebo na /data; řešit velikost venv (stovky MB na uživatele).
- Google snapshoty pro obnovu do 10 dnů; deploy script z GitHubu obnoví konfiguraci (sudoers atd.). Potřeba definovat retenci/frekvenci snapshotů, logování a DR postupy.
- Data pipeline: náklady, Parquet/DuckDB a typování
- Směřování k parquet + lokální DuckDB, minimalizace Snowflake tabulek; minutové/dvouminutové refreshy ve Snowflake/Kebula mohou být nákladné (řádově tisíce USD/měsíc), lokální přístup je levnější/rychlejší.
- Offload výpočtu na klienta: rozdílné refresh intervaly dle uživatelů (např. sekundové vs. 20 minut), inspirace praxí (Carvago: Snowpark + DuckDB).
- Podpora Parquet v Kebule: možnost získat Parquet přes debug mode (file=parquet) v input mappingu; chybí jemná kontrola granularit exportu a jsou otázky kolem datových typů.
- Doporučení zajistit top-notch Parquet soubory s konzistentním castingem typů, neodsouvat typové opravy pouze na klienta.
- Workflow, code review a operativa
- Zavést práci v branchích a PR review, aby se předešlo chybám (např. nesprávné kontroly dat, parametry Change Since).
- Ověřit a zdokumentovat cesty/názvy (test rsync vs. test sync, absolutní cesty).
- Komunikace: preferovat rychlé reakce přes WhatsApp; Slack spíše okrajově.
- Telemetrie: přichází pomalu; ověřit kvalitu a následně synchronizovat, definovat validační pravidla a monitoring.
## Další kroky
- [ ] Upravit „sync data“ pro synchronizaci skriptů a dokumentace ze serveru; zvážit obousměrnost pro User foldery.
- [ ] Navrhnout mechanismus ochrany uživatelských úprav v CloudMD (merge, overrides, verzování).
- [ ] Přidat uživatelskou identifikaci/namespace do User/scripts; definovat pravidla publikace (opt-in, whitelisting).
- [ ] Zavést ochranu/lock pro serverové složky (Scripts, Sync Script), nastavit cloud permissions (server read-only/never modify), otestovat v agentovi.
- [ ] Nahradit „check freshness“ AirSyncem; opravit oprávnění zápisu pro generátor metadat; standardizovat .metadata/JSON formát a umístění.
- [ ] Přidat do AirSync parametr „--delete“; připravit test plány pro velké adresáře (rename vs. delete).
- [ ] Ověřit SDK Kebula Storage pro podporu „changeSince“; upravit skript na inkrementální export přes „Export Async“; zavést a ověřit „state“ s timestampem na serveru.
- [ ] Nastavit 15minutové refresh intervaly s „changeSince“; definovat granularitu: aktuální měsíc hodinově, historie měsíčně; zdokumentovat.
- [ ] Doplnit dokumentaci k venv (init/activate, umístění balíčků v home) do Docs Notifications MD a CloudMD; sjednotit venv mezi lokálem a serverem.
- [ ] Zdebagovat crontab (časové zóny, cooldown stav); zvážit dočasné vypnutí/úpravu cooldownu v pilotu; nastavit alerty při selhání NotifyRunneru.
- [ ] Ověřit Telegram verifikaci a „test/status“ příkazy pro všechny uživatele; doplnit podporu Slacku v notifikacích.
- [ ] Vytvořit GitHub issue se settings.json, přiřadit Daša Dama; začlenit politiku Allow/Deny/Ask do initu prostředí; zavést audit změn oprávnění.
- [ ] Implementovat zálohu/sync Cloud Local MD; přesunout uživatelské homy na nový disk (/data nebo sdc); nastavit snapshot politiku (retence, frekvence, obnova).
- [ ] Zlepšit kvalitu Parquet typování; definovat odpovědnost za konzistentní casting datových typů.
- [ ] Zavést pravidelné code review, pracovat v branchích; zdokumentovat správné cesty/názvy; validovat telemetrická data před synchronizací.
- [ ] Otestovat inkrementální export na velkých tabulkách (timestamp filtrace); definovat backtracking window a testovací strategii, aby se předešlo nechtěnému full loadu.
## AI doporučení
> 1. Strategie aktualizace CloudMD bez přepisování uživatelských změn (verzování, diffs, templating) potřebuje konkrétní návrh a implementaci.
> 2. Jednoznačná identifikace/izolace uživatelských skriptů (uživatelské ID, namespaces) v adresářové struktuře je nutná, aby se předešlo nechtěné distribuci.
> 3. Nastavení směru a pravidel synchronizace User folderů (jednosměrně vs. obousměrně, konfliktní řešení) vyžaduje standard a testy.
> 4. Ochrana kritických serverových skriptů (locky, práva, CI/CD přepis) a audit oprávnění musí být jasně definována a ověřena.
> 5. Parametr „changeSince“ je deprecated; ověřit aktuální doporučený ekvivalent a plán kompatibility do budoucna.
> 6. Standardizovat formát/umístění metadat (.metadata/JSON), doplnit monitoring/alerting pro selhání zápisu.
> 7. Definovat granularitu exportů napříč tabulkami (aktuální vs. historická data) a technickou realizaci mimo Snowflake.
> 8. Minimalizovat IO zátěž při --checksum (plánování běhů, rate limiting); vyhodnotit dopady na velké datasety.
> 9. Nastavit testovací strategii pro inkrementální export (validace timestampu, pozdní data/backfill, prevence full exportu omylem).
> 10. Sjednotit dokumentaci s reálným chováním SDK/API (endpointy, omezení where filtrů).
> 11. Definovat politiku cooldownu v pilotu a produkci; nastavit alerty pro NotifyRunner/migrace (fallback scénáře).
> 12. Vyjasnit integraci Slack vs. Telegram v jednotné konfiguraci notifikací.
> 13. Stanovit odpovědnost za typování Parquet a kvalitu dat; plán integrace DuckDB backendu do Kebuly (milníky, feasibility).

File diff suppressed because it is too large Load diff

View file

@ -81,7 +81,7 @@ gcloud compute ssh data-broker-for-claude --project=kids-ai-data-analysis --zone
/run/notify-bot/ # Systemd RuntimeDirectory (mode 0755) /run/notify-bot/ # Systemd RuntimeDirectory (mode 0755)
└── bot.sock # Unix socket for send API (mode 0666) └── bot.sock # Unix socket for send API (mode 0666)
/tmp/keboola_load/ # Keboola staging directory (root:data-ops, 2770 setgid) /tmp/data_analyst_staging/ # Keboola staging directory (root:data-ops, 2770 setgid)
└── *.parquet # Temporary Parquet files during Keboola data load └── *.parquet # Temporary Parquet files during Keboola data load
``` ```
@ -145,7 +145,7 @@ except Exception:
raise raise
``` ```
Use `0o660` for files accessed by services via data-ops group ACL, `0o644` for world-readable files (e.g., profiler output). See [#203](https://github.com/keboola/internal_ai_data_analyst/issues/203) for a production incident caused by missing `fchmod`. Use `0o660` for files accessed by services via data-ops group ACL, `0o644` for world-readable files (e.g., profiler output). See [#203](https://github.com/your-org/ai-data-analyst/issues/203) for a production incident caused by missing `fchmod`.
**Per-issue file locking for concurrent writers:** **Per-issue file locking for concurrent writers:**
@ -599,7 +599,7 @@ Application is automatically deployed via GitHub Actions when changes are pushed
- `/data/corporate-memory/` (knowledge base) - `/data/corporate-memory/` (knowledge base)
- `/data/user_sessions/` (session logs) - `/data/user_sessions/` (session logs)
- `/data/examples/` (example scripts) - `/data/examples/` (example scripts)
- `/tmp/keboola_load/` (Keboola staging) - `/tmp/data_analyst_staging/` (Keboola staging)
- Deploys systemd units: - Deploys systemd units:
- `notify-bot.service` (Telegram bot) - `notify-bot.service` (Telegram bot)
- `ws-gateway.service` (WebSocket gateway) - `ws-gateway.service` (WebSocket gateway)
@ -640,7 +640,7 @@ The `deploy` user has limited sudo access defined in `/etc/sudoers.d/deploy`:
- Can manage `/data/auth/` (password auth state) - Can manage `/data/auth/` (password auth state)
- Can manage `/data/corporate-memory/` (knowledge base) - Can manage `/data/corporate-memory/` (knowledge base)
- Can manage `/data/user_sessions/` (session collector data) - Can manage `/data/user_sessions/` (session collector data)
- Can manage `/tmp/keboola_load/` (Keboola staging directory) - Can manage `/tmp/data_analyst_staging/` (Keboola staging directory)
**Special Permissions:** **Special Permissions:**
- Can run `notify-scripts` as any user (list/run notification scripts) - Can run `notify-scripts` as any user (list/run notification scripts)
@ -678,7 +678,7 @@ sudo cat /home/deploy/.ssh/id_ed25519.pub
``` ```
**3. Add Deploy Key to GitHub:** **3. Add Deploy Key to GitHub:**
- Go to: https://github.com/keboola/internal_ai_data_analyst/settings/keys - Go to: https://github.com/your-org/ai-data-analyst/settings/keys
- Click "Add deploy key" - Click "Add deploy key"
- Title: `data-broker-server` - Title: `data-broker-server`
- Key: (paste public key from previous step) - Key: (paste public key from previous step)
@ -688,7 +688,7 @@ sudo cat /home/deploy/.ssh/id_ed25519.pub
```bash ```bash
sudo mkdir -p /opt/data-analyst sudo mkdir -p /opt/data-analyst
sudo chown deploy:data-ops /opt/data-analyst sudo chown deploy:data-ops /opt/data-analyst
sudo -u deploy git clone git@github.com:keboola/internal_ai_data_analyst.git /opt/data-analyst/repo sudo -u deploy git clone git@github.com:your-org/ai-data-analyst.git /opt/data-analyst/repo
sudo git config --global --add safe.directory /opt/data-analyst/repo sudo git config --global --add safe.directory /opt/data-analyst/repo
sudo -u deploy git config --global --add safe.directory /opt/data-analyst/repo sudo -u deploy git config --global --add safe.directory /opt/data-analyst/repo
sudo /opt/data-analyst/repo/server/setup.sh sudo /opt/data-analyst/repo/server/setup.sh
@ -1144,7 +1144,7 @@ cat ~/.notifications/logs/runner.log
### Known Issues ### Known Issues
**On-demand script execution security hardening (partially resolved):** **On-demand script execution security hardening (partially resolved):**
The `notify-scripts` helper replaced direct `sudo -H -u ... /usr/bin/env ...` calls with a single auditable entry point. Services no longer need filesystem access to user home directories (750 permissions are preserved). The bot still requires `NoNewPrivileges=false` and `/tmp` in `ReadWritePaths` for sudo execution. A queue-based approach ([#51](https://github.com/keboola/internal_ai_data_analyst/issues/51)) could further improve this by having `notify-runner` pick up run requests from a queue instead of the bot calling sudo directly. The `notify-scripts` helper replaced direct `sudo -H -u ... /usr/bin/env ...` calls with a single auditable entry point. Services no longer need filesystem access to user home directories (750 permissions are preserved). The bot still requires `NoNewPrivileges=false` and `/tmp` in `ReadWritePaths` for sudo execution. A queue-based approach ([#51](https://github.com/your-org/ai-data-analyst/issues/51)) could further improve this by having `notify-runner` pick up run requests from a queue instead of the bot calling sudo directly.
## Data Sync Settings (Web Portal) ## Data Sync Settings (Web Portal)
@ -1510,7 +1510,7 @@ for row in result:
- API token has read-only access to Jira (no write permissions needed) - API token has read-only access to Jira (no write permissions needed)
- Webhook events are logged for audit purposes - Webhook events are logged for audit purposes
- Multiple services write to `/data/src_data/raw/jira/`: webapp (www-data), SLA poll (root), consistency check (root), backfill scripts (admin users) - Multiple services write to `/data/src_data/raw/jira/`: webapp (www-data), SLA poll (root), consistency check (root), backfill scripts (admin users)
- Concurrent writes to the same issue JSON are serialized via per-issue advisory file locking (`src/jira_file_lock.py`, `fcntl.flock`). Lock files in `issues/.locks/`. See [#203](https://github.com/keboola/internal_ai_data_analyst/issues/203). - Concurrent writes to the same issue JSON are serialized via per-issue advisory file locking (`src/jira_file_lock.py`, `fcntl.flock`). Lock files in `issues/.locks/`. See [#203](https://github.com/your-org/ai-data-analyst/issues/203).
## Data Profiler ## Data Profiler
@ -1967,7 +1967,7 @@ The Corporate Memory page at `/corporate-memory` provides:
- **No credentials stored**: Knowledge items are filtered before storage - **No credentials stored**: Knowledge items are filtered before storage
- **Source attribution**: Items track which users contributed (displayed as avatar initials) - **Source attribution**: Items track which users contributed (displayed as avatar initials)
- **Read-only for analysts**: `/data/corporate-memory/` is only writable by data-ops group - **Read-only for analysts**: `/data/corporate-memory/` is only writable by data-ops group
- **Atomic writes**: All JSON file updates use `tempfile.mkstemp()` + `os.replace()` to prevent corruption. **Critical:** always call `os.fchmod(fd, 0o660)` (or appropriate mode) immediately after `mkstemp()` — otherwise the default `0600` mode overrides the POSIX ACL mask to `---`, breaking group-based access for other services. See [#203](https://github.com/keboola/internal_ai_data_analyst/issues/203). - **Atomic writes**: All JSON file updates use `tempfile.mkstemp()` + `os.replace()` to prevent corruption. **Critical:** always call `os.fchmod(fd, 0o660)` (or appropriate mode) immediately after `mkstemp()` — otherwise the default `0600` mode overrides the POSIX ACL mask to `---`, breaking group-based access for other services. See [#203](https://github.com/your-org/ai-data-analyst/issues/203).
## Session Collector ## Session Collector

View file

@ -1,10 +1,10 @@
# Getting Started with Internal AI Data Analyst # Getting Started with AI Data Analyst
Quick start guide for analysts who want to explore company data using AI. Quick start guide for analysts who want to explore company data using AI.
## What is This? ## What is This?
**Internal AI Data Analyst** gives you local access to your organization's data (sales, HR, finance, telemetry) so you can analyze it using Claude Code with natural language questions. **AI Data Analyst** gives you local access to your organization's data (sales, HR, finance, telemetry) so you can analyze it using Claude Code with natural language questions.
Instead of writing SQL queries manually, you can ask Claude questions like: Instead of writing SQL queries manually, you can ask Claude questions like:
- "Which companies have the highest revenue?" - "Which companies have the highest revenue?"

View file

@ -1,5 +1,5 @@
version: "1.0" version: "1.0"
project_name: "internal_ai_data_analyst" project_name: "ai_data_analyst"
project_dir: "." project_dir: "."
server: server:

View file

@ -2,31 +2,31 @@
kbcstorage>=0.9.0 # For Keboola adapter kbcstorage>=0.9.0 # For Keboola adapter
# Data processing # Data processing
# pandas - hlavní knihovna pro práci s tabulkovými daty # pandas - core tabular data processing library
# pyarrow - podpora pro Parquet formát a rychlé operace # pyarrow - Parquet format support and fast operations
# pytz - timezone support required by DuckDB for reading timezone-aware Parquet columns # pytz - timezone support required by DuckDB for reading timezone-aware Parquet columns
pandas>=2.0.0 pandas>=2.0.0
pyarrow>=12.0.0 pyarrow>=12.0.0
pytz>=2024.1 pytz>=2024.1
# Analytická databáze # Analytical database
# DuckDB - in-process SQL OLAP databáze pro analytické dotazy # DuckDB - in-process SQL OLAP database for analytical queries
duckdb>=0.9.0 duckdb>=0.9.0
# Konfigurace # Configuration
# python-dotenv - načítání environment variables z .env souborů # python-dotenv - loading environment variables from .env files
# pyyaml - parsování YAML konfigurace z data_description.md # pyyaml - parsing YAML configuration from data_description.md
python-dotenv>=1.0.0 python-dotenv>=1.0.0
pyyaml>=6.0 pyyaml>=6.0
# Progress tracking a logging # Progress tracking and logging
# tqdm - progress bary pro dlouhotrvající operace (download, sync) # tqdm - progress bars for long-running operations (download, sync)
tqdm>=4.65.0 tqdm>=4.65.0
# Web application (Google SSO portal) # Web application (Google SSO portal)
# flask - web framework pro self-service portal # flask - web framework for self-service portal
# authlib - OAuth 2.0 / OpenID Connect knihovna pro Google SSO # authlib - OAuth 2.0 / OpenID Connect library for Google SSO
# gunicorn - WSGI server pro production deployment # gunicorn - WSGI server for production deployment
flask>=3.0.0 flask>=3.0.0
authlib>=1.3.0 authlib>=1.3.0
gunicorn>=21.0.0 gunicorn>=21.0.0

View file

@ -1,6 +1,6 @@
# Scripts # Scripts
Helper scripts for working with Internal AI Data Analyst project. Helper scripts for working with AI Data Analyst project.
These scripts are synced from the server into `server/scripts/` on the analyst's machine. These scripts are synced from the server into `server/scripts/` on the analyst's machine.

View file

@ -83,13 +83,15 @@ sudo /usr/bin/cp "${REPO_DIR}"/scripts/sync_jira.sh /data/scripts/
sudo /usr/bin/cp "${REPO_DIR}"/scripts/generate_user_sync_configs.py /data/scripts/ sudo /usr/bin/cp "${REPO_DIR}"/scripts/generate_user_sync_configs.py /data/scripts/
sudo /usr/bin/cp "${REPO_DIR}"/scripts/collect_session.py /data/scripts/ sudo /usr/bin/cp "${REPO_DIR}"/scripts/collect_session.py /data/scripts/
sudo /usr/bin/chmod -R 755 /data/scripts sudo /usr/bin/chmod -R 755 /data/scripts
sudo /usr/bin/chown -R padak:data-ops /data/scripts sudo /usr/bin/chown -R deploy:data-ops /data/scripts
log " Scripts updated in /data/scripts/" log " Scripts updated in /data/scripts/"
# Update documentation in /data/docs # Update documentation in /data/docs
log "Updating documentation..." log "Updating documentation..."
sudo /usr/bin/mkdir -p /data/docs/setup sudo /usr/bin/mkdir -p /data/docs/setup
if [[ -f "${REPO_DIR}/docs/data_description.md" ]]; then
sudo /usr/bin/cp "${REPO_DIR}"/docs/data_description.md /data/docs/ sudo /usr/bin/cp "${REPO_DIR}"/docs/data_description.md /data/docs/
fi
sudo /usr/bin/cp "${REPO_DIR}"/docs/GETTING_STARTED.md /data/docs/ sudo /usr/bin/cp "${REPO_DIR}"/docs/GETTING_STARTED.md /data/docs/
if [[ -f "${REPO_DIR}/docs/notifications.md" ]]; then if [[ -f "${REPO_DIR}/docs/notifications.md" ]]; then
sudo /usr/bin/cp "${REPO_DIR}"/docs/notifications.md /data/docs/ sudo /usr/bin/cp "${REPO_DIR}"/docs/notifications.md /data/docs/
@ -114,7 +116,7 @@ if [[ -d "${REPO_DIR}/docs/datasets" ]]; then
log " Dataset docs (*.md) copied to /data/docs/datasets/" log " Dataset docs (*.md) copied to /data/docs/datasets/"
fi fi
sudo /usr/bin/chmod -R 775 /data/docs sudo /usr/bin/chmod -R 775 /data/docs
sudo /usr/bin/chown -R padak:data-ops /data/docs sudo /usr/bin/chown -R deploy:data-ops /data/docs
log " Documentation updated in /data/docs/" log " Documentation updated in /data/docs/"
# Deploy notify-runner to /usr/local/bin # Deploy notify-runner to /usr/local/bin
@ -254,7 +256,7 @@ for example in "${REPO_DIR}"/examples/notifications/*.py; do
fi fi
done done
sudo /usr/bin/chmod -R 755 /data/examples sudo /usr/bin/chmod -R 755 /data/examples
sudo /usr/bin/chown -R padak:data-ops /data/examples sudo /usr/bin/chown -R deploy:data-ops /data/examples
# Update resource limits configuration # Update resource limits configuration
log "Updating resource limits..." log "Updating resource limits..."

View file

@ -5,24 +5,6 @@
# Wildcard rules at the bottom apply to all non-admin users # Wildcard rules at the bottom apply to all non-admin users
# === ADMIN ENTRIES (managed by add-admin - do not edit manually) === # === ADMIN ENTRIES (managed by add-admin - do not edit manually) ===
padak soft nproc unlimited
padak hard nproc unlimited
padak - as unlimited
padak - fsize unlimited
padak - nofile 65535
matejkys soft nproc unlimited
matejkys hard nproc unlimited
matejkys - as unlimited
matejkys - fsize unlimited
matejkys - nofile 65535
dasa soft nproc unlimited
dasa hard nproc unlimited
dasa - as unlimited
dasa - fsize unlimited
dasa - nofile 65535
deploy soft nproc unlimited deploy soft nproc unlimited
deploy hard nproc unlimited deploy hard nproc unlimited
deploy - as unlimited deploy - as unlimited

View file

@ -23,10 +23,18 @@ apt-get update -qq
apt-get install -y rsync apt-get install -y rsync
echo " rsync installed" echo " rsync installed"
# Create data-ops group if it doesn't exist # Create groups
if ! getent group data-ops > /dev/null 2>&1; then for group in data-ops dataread data-private; do
echo "Creating data-ops group..." if ! getent group "$group" > /dev/null 2>&1; then
groupadd data-ops groupadd "$group"
echo "Created group: $group"
fi
done
# Create deploy user (for CI/CD automated deployment)
if ! id deploy > /dev/null 2>&1; then
useradd -r -m -s /bin/bash -G data-ops deploy
echo "Created deploy user"
fi fi
# Create directory structure # Create directory structure
@ -87,9 +95,8 @@ echo " remove-analyst - Remove user"
echo " list-analysts - List all analysts" echo " list-analysts - List all analysts"
echo "" echo ""
echo "Next steps:" echo "Next steps:"
echo " 1. Add existing admins to data-ops group:" echo " 1. Add admin users to data-ops group:"
echo " usermod -aG data-ops padak" echo " usermod -aG data-ops <admin_username>"
echo " usermod -aG data-ops matejkys"
echo "" echo ""
echo " 2. Set up GitHub Actions deploy key (see .github/workflows/deploy.yml)" echo " 2. Set up GitHub Actions deploy key (see .github/workflows/deploy.yml)"
echo "" echo ""

View file

@ -8,10 +8,13 @@
deploy ALL=(ALL) NOPASSWD: /usr/bin/cp /opt/data-analyst/repo/server/bin/* /usr/local/bin/* deploy ALL=(ALL) NOPASSWD: /usr/bin/cp /opt/data-analyst/repo/server/bin/* /usr/local/bin/*
deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod 755 /usr/local/bin/* deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod 755 /usr/local/bin/*
# Allow deploy user to manage sudoers files # Allow deploy user to manage sudoers files (explicit paths, no wildcards)
deploy ALL=(ALL) NOPASSWD: /usr/sbin/visudo -cf * deploy ALL=(ALL) NOPASSWD: /usr/sbin/visudo -cf /opt/data-analyst/repo/server/sudoers-deploy
deploy ALL=(ALL) NOPASSWD: /usr/bin/cp /opt/data-analyst/repo/server/sudoers-* /etc/sudoers.d/* deploy ALL=(ALL) NOPASSWD: /usr/sbin/visudo -cf /opt/data-analyst/repo/server/sudoers-webapp
deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod 440 /etc/sudoers.d/* deploy ALL=(ALL) NOPASSWD: /usr/bin/cp /opt/data-analyst/repo/server/sudoers-deploy /etc/sudoers.d/deploy
deploy ALL=(ALL) NOPASSWD: /usr/bin/cp /opt/data-analyst/repo/server/sudoers-webapp /etc/sudoers.d/webapp
deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod 440 /etc/sudoers.d/deploy
deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod 440 /etc/sudoers.d/webapp
# Allow deploy user to manage application directory permissions # Allow deploy user to manage application directory permissions
deploy ALL=(ALL) NOPASSWD: /usr/bin/chown -R root\:data-ops /opt/data-analyst deploy ALL=(ALL) NOPASSWD: /usr/bin/chown -R root\:data-ops /opt/data-analyst
@ -42,7 +45,7 @@ deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod 640 /opt/data-analyst/.env
deploy ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /data/scripts deploy ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /data/scripts
deploy ALL=(ALL) NOPASSWD: /usr/bin/cp /opt/data-analyst/repo/scripts/* /data/scripts/* deploy ALL=(ALL) NOPASSWD: /usr/bin/cp /opt/data-analyst/repo/scripts/* /data/scripts/*
deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod -R 755 /data/scripts deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod -R 755 /data/scripts
deploy ALL=(ALL) NOPASSWD: /usr/bin/chown -R padak\:data-ops /data/scripts deploy ALL=(ALL) NOPASSWD: /usr/bin/chown -R deploy\:data-ops /data/scripts
# Allow deploy user to manage documentation in /data/docs # Allow deploy user to manage documentation in /data/docs
deploy ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /data/docs deploy ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /data/docs
@ -50,7 +53,7 @@ deploy ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /data/docs/*
deploy ALL=(ALL) NOPASSWD: /usr/bin/cp /opt/data-analyst/repo/docs/* /data/docs/* deploy ALL=(ALL) NOPASSWD: /usr/bin/cp /opt/data-analyst/repo/docs/* /data/docs/*
deploy ALL=(ALL) NOPASSWD: /usr/bin/cp -r /opt/data-analyst/repo/docs/* /data/docs/ deploy ALL=(ALL) NOPASSWD: /usr/bin/cp -r /opt/data-analyst/repo/docs/* /data/docs/
deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod -R 775 /data/docs deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod -R 775 /data/docs
deploy ALL=(ALL) NOPASSWD: /usr/bin/chown -R padak\:data-ops /data/docs deploy ALL=(ALL) NOPASSWD: /usr/bin/chown -R deploy\:data-ops /data/docs
# Allow deploy user to manage notifications directory # Allow deploy user to manage notifications directory
deploy ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /data/notifications deploy ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /data/notifications
@ -86,7 +89,7 @@ deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod 644 /etc/security/limits.d/99-users.co
deploy ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /data/examples/notifications deploy ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /data/examples/notifications
deploy ALL=(ALL) NOPASSWD: /usr/bin/cp /opt/data-analyst/repo/examples/notifications/* /data/examples/notifications/* deploy ALL=(ALL) NOPASSWD: /usr/bin/cp /opt/data-analyst/repo/examples/notifications/* /data/examples/notifications/*
deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod -R 755 /data/examples deploy ALL=(ALL) NOPASSWD: /usr/bin/chmod -R 755 /data/examples
deploy ALL=(ALL) NOPASSWD: /usr/bin/chown -R padak\:data-ops /data/examples deploy ALL=(ALL) NOPASSWD: /usr/bin/chown -R deploy\:data-ops /data/examples
# Allow deploy user to manage Jira data directory # Allow deploy user to manage Jira data directory
deploy ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /data/src_data/raw/jira/* deploy ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /data/src_data/raw/jira/*

View file

@ -83,13 +83,10 @@ fi
echo "Adding www-data to data-ops group..." echo "Adding www-data to data-ops group..."
usermod -aG data-ops www-data usermod -aG data-ops www-data
# Configure sudoers for www-data to run add-analyst # Install sudoers rules for www-data (from repo, includes all required rules)
echo "Configuring sudoers..." echo "Configuring sudoers..."
SUDOERS_FILE="/etc/sudoers.d/webapp" SUDOERS_FILE="/etc/sudoers.d/webapp"
cat > "$SUDOERS_FILE" << 'EOF' cp "${REPO_DIR}/server/sudoers-webapp" "$SUDOERS_FILE"
# Allow www-data (webapp) to run add-analyst without password
www-data ALL=(ALL) NOPASSWD: /usr/local/bin/add-analyst
EOF
chmod 440 "$SUDOERS_FILE" chmod 440 "$SUDOERS_FILE"
# Validate sudoers syntax # Validate sudoers syntax

View file

@ -84,11 +84,12 @@ class TestParseCronSchedule:
class TestGetServerUsername: class TestGetServerUsername:
"""Test webapp-to-server username mapping.""" """Test webapp-to-server username mapping."""
@patch("webapp.account_service.WEBAPP_TO_SERVER_USERNAME", {"john.doe": "john"})
def test_mapped_user(self): def test_mapped_user(self):
assert _get_server_username("petr.simecek") == "petr" assert _get_server_username("john.doe") == "john"
def test_unmapped_user(self): def test_unmapped_user(self):
assert _get_server_username("dasa.damaskova") == "dasa.damaskova" assert _get_server_username("jane.smith") == "jane.smith"
class TestGetNotificationScripts: class TestGetNotificationScripts:
@ -198,6 +199,7 @@ class TestGetAccountDetails:
assert result["last_sync_display"] is None assert result["last_sync_display"] is None
assert result["sync_datasets_enabled"] == [] assert result["sync_datasets_enabled"] == []
@patch("webapp.account_service.WEBAPP_TO_SERVER_USERNAME", {"john.doe": "john"})
@patch("webapp.account_service._get_enabled_datasets") @patch("webapp.account_service._get_enabled_datasets")
@patch("webapp.account_service._get_last_sync") @patch("webapp.account_service._get_last_sync")
@patch("webapp.account_service._get_cron_schedule") @patch("webapp.account_service._get_cron_schedule")
@ -208,8 +210,8 @@ class TestGetAccountDetails:
mock_sync.return_value = None mock_sync.return_value = None
mock_datasets.return_value = [] mock_datasets.return_value = []
get_account_details("petr.simecek") get_account_details("john.doe")
# Verify server username mapping: petr.simecek -> petr # Verify server username mapping: john.doe -> john
mock_scripts.assert_called_once_with("petr") mock_scripts.assert_called_once_with("john")
mock_cron.assert_called_once_with("petr") mock_cron.assert_called_once_with("john")
mock_sync.assert_called_once_with("petr") mock_sync.assert_called_once_with("john")

View file

@ -83,7 +83,7 @@ def _parse_sudoers_commands(sudoers_path: Path) -> list[dict]:
if m: if m:
user = m.group(1) user = m.group(1)
command = m.group(2).strip() command = m.group(2).strip()
# Unescape sudoers backslash-colon (e.g., padak\:data-ops -> padak:data-ops) # Unescape sudoers backslash-colon (e.g., deploy\:data-ops -> deploy:data-ops)
command = command.replace("\\:", ":") command = command.replace("\\:", ":")
# Check for deploy-guard: ignore in preceding comment # Check for deploy-guard: ignore in preceding comment
ignored = False ignored = False
@ -536,9 +536,9 @@ class TestFileOwnership:
# Explicit list of critical directories and their expected ownership. # Explicit list of critical directories and their expected ownership.
# Maintained manually - extend when new critical directories are added. # Maintained manually - extend when new critical directories are added.
CRITICAL_DIRS = { CRITICAL_DIRS = {
"/data/scripts": {"owner": "padak", "group": "data-ops"}, "/data/scripts": {"owner": "deploy", "group": "data-ops"},
"/data/docs": {"owner": "padak", "group": "data-ops"}, "/data/docs": {"owner": "deploy", "group": "data-ops"},
"/data/examples": {"owner": "padak", "group": "data-ops"}, "/data/examples": {"owner": "deploy", "group": "data-ops"},
"/data/notifications": {"owner": "deploy", "group": "data-ops"}, "/data/notifications": {"owner": "deploy", "group": "data-ops"},
"/data/auth": {"owner": "www-data", "group": "data-ops"}, "/data/auth": {"owner": "www-data", "group": "data-ops"},
"/data/corporate-memory": {"owner": "deploy", "group": "data-ops"}, "/data/corporate-memory": {"owner": "deploy", "group": "data-ops"},

View file

@ -2195,7 +2195,7 @@
function copyBootstrapInstructions() { function copyBootstrapInstructions() {
const bootstrapYaml = {{ bootstrap_yaml | tojson }}; const bootstrapYaml = {{ bootstrap_yaml | tojson }};
const instructions = `Set up Internal AI Data Analyst project according to the following YAML instruction: const instructions = `Set up AI Data Analyst project according to the following YAML instruction:
--- ---
${bootstrapYaml} ${bootstrapYaml}