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:
parent
c56905d34f
commit
26c4e0934d
26 changed files with 229 additions and 1555 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -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
21
LICENSE
Normal 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.
|
||||||
|
|
@ -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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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: []
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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í.
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -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
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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?"
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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..."
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 ""
|
||||||
|
|
|
||||||
|
|
@ -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/*
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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"},
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue