# Configuration Guide
> How ArchiTools is configured at build time, runtime, and per-module.
---
## Environment Variables
All environment variables are defined in `.env.local` for development and injected via Docker for production. The `.env.example` file in the repository root documents every variable:
```bash
# =============================================================================
# ArchiTools Environment Configuration
# =============================================================================
# Copy this file to .env.local for development.
# For Docker, pass variables via docker-compose.yml or Portainer.
# =============================================================================
# -----------------------------------------------------------------------------
# Application
# -----------------------------------------------------------------------------
NEXT_PUBLIC_APP_NAME=ArchiTools
NEXT_PUBLIC_APP_URL=http://localhost:3000
# Version displayed in footer/about. Set by CI or manually.
NEXT_PUBLIC_APP_VERSION=0.1.0
# -----------------------------------------------------------------------------
# Storage
# -----------------------------------------------------------------------------
# Active storage adapter: 'localStorage' | 'api'
NEXT_PUBLIC_STORAGE_ADAPTER=localStorage
# REST API storage backend (required when STORAGE_ADAPTER=api)
# STORAGE_API_URL=http://api.internal/storage
# MinIO object storage (server-side only, for file/binary storage)
# MINIO_ENDPOINT=minio.internal:9000
# MINIO_ACCESS_KEY=
# MINIO_SECRET_KEY=
# MINIO_BUCKET=architools
# MINIO_USE_SSL=false
# -----------------------------------------------------------------------------
# Authentication (future — Authentik SSO)
# -----------------------------------------------------------------------------
# AUTHENTIK_URL=https://auth.internal
# AUTHENTIK_CLIENT_ID=
# AUTHENTIK_CLIENT_SECRET=
# NEXTAUTH_URL=http://localhost:3000
# NEXTAUTH_SECRET=
# -----------------------------------------------------------------------------
# Feature Flags
# -----------------------------------------------------------------------------
# Comma-separated list of flag overrides. Format: flag_name=true/false
# Example: NEXT_PUBLIC_FLAGS_OVERRIDE=module_ai_chat=true,module_password_vault=false
NEXT_PUBLIC_FLAGS_OVERRIDE=
# -----------------------------------------------------------------------------
# External Services
# -----------------------------------------------------------------------------
# N8N webhook endpoint for automation triggers
# N8N_WEBHOOK_URL=https://n8n.internal/webhook
# Gitea API (for potential repo integration)
# GITEA_URL=https://git.internal
# GITEA_TOKEN=
# -----------------------------------------------------------------------------
# External Tool Links (displayed on dashboard)
# -----------------------------------------------------------------------------
# NEXT_PUBLIC_PORTAINER_URL=https://portainer.internal
# NEXT_PUBLIC_DOZZLE_URL=https://dozzle.internal
# NEXT_PUBLIC_NETDATA_URL=https://netdata.internal
# NEXT_PUBLIC_UPTIME_KUMA_URL=https://uptime.internal
# NEXT_PUBLIC_IT_TOOLS_URL=https://it-tools.internal
# NEXT_PUBLIC_STIRLING_PDF_URL=https://pdf.internal
# NEXT_PUBLIC_FILEBROWSER_URL=https://files.internal
# NEXT_PUBLIC_N8N_URL=https://n8n.internal
# NEXT_PUBLIC_GITEA_URL=https://git.internal
# NEXT_PUBLIC_MINIO_CONSOLE_URL=https://minio-console.internal
```
### Naming Rules
| Prefix | Scope | Access |
|---|---|---|
| `NEXT_PUBLIC_` | Client + Server | Bundled into client JS. Never put secrets here. |
| No prefix | Server only | Available in API routes, server components, middleware. |
**Never put credentials, API keys, or secrets in `NEXT_PUBLIC_` variables.** They are embedded in the JavaScript bundle and visible to anyone with browser DevTools.
---
## Module Configuration
**File:** `src/config/modules.ts`
Defines the module registry. Every module in the system is declared here. Modules not in this registry do not exist to the platform.
```typescript
// src/config/modules.ts
import type { LucideIcon } from 'lucide-react';
interface ModuleDefinition {
/** Unique module identifier. Matches the directory name under src/modules/. */
id: string;
/** Romanian display name shown in navigation and headers. */
label: string;
/** Short Romanian description for tooltips and dashboard cards. */
description: string;
/** Lucide icon component reference. */
icon: LucideIcon;
/** URL path segment. Module is accessible at /modules/{path}. */
path: string;
/** Storage namespace. Must match STORAGE-LAYER.md namespace table. */
namespace: string;
/** Feature flag that controls activation. References flags.ts. */
featureFlag: string;
/** Default enabled state when no flag override exists. */
defaultEnabled: boolean;
/** Minimum role required to see this module. */
minRole: Role;
/** Sort order in navigation sidebar. Lower = higher. */
order: number;
/** If true, module is loaded only when navigated to. */
lazy: boolean;
/** Which companies this module is relevant to. Empty = all. */
companies: CompanyId[];
}
export const MODULE_REGISTRY: ModuleDefinition[] = [
{
id: 'dashboard',
label: 'Panou Principal',
description: 'Tablou de bord cu widget-uri și acces rapid',
icon: LayoutDashboard,
path: 'dashboard',
namespace: 'dashboard',
featureFlag: 'module_dashboard',
defaultEnabled: true,
minRole: 'viewer',
order: 0,
lazy: false,
companies: [],
},
{
id: 'registratura',
label: 'Registratură',
description: 'Registru de intrări și ieșiri documente',
icon: BookOpen,
path: 'registratura',
namespace: 'registratura',
featureFlag: 'module_registratura',
defaultEnabled: true,
minRole: 'user',
order: 10,
lazy: true,
companies: [],
},
// ... remaining modules follow same pattern
];
```
### Adding a New Module
1. Create the module directory: `src/modules/{module-id}/`.
2. Add an entry to `MODULE_REGISTRY` in `src/config/modules.ts`.
3. Add a feature flag in `src/config/flags.ts`.
4. Add the namespace to the namespace table in the storage layer docs.
5. Create the App Router page: `src/app/modules/{path}/page.tsx`.
6. The navigation system and feature flag engine pick it up automatically.
---
## Feature Flag Configuration
**File:** `src/config/flags.ts`
```typescript
// src/config/flags.ts
interface FeatureFlag {
/** Flag identifier. Convention: module_{id} for module toggles, feature_{name} for features. */
id: string;
/** Romanian description. */
description: string;
/** Default value when no override exists. */
defaultValue: boolean;
/** If true, flag is only visible in admin settings. */
adminOnly: boolean;
/** If true, flag is experimental and shown with a warning badge. */
experimental: boolean;
}
export const FEATURE_FLAGS: FeatureFlag[] = [
// Module flags
{ id: 'module_dashboard', description: 'Panou principal', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'module_registratura', description: 'Registratură', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'module_email_signature', description: 'Generator semnătură email', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'module_word_xml', description: 'Generatoare Word XML', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'module_digital_signatures', description: 'Semnături și ștampile digitale', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'module_password_vault', description: 'Seif parole partajat', defaultValue: true, adminOnly: true, experimental: false },
{ id: 'module_it_inventory', description: 'Inventar IT', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'module_address_book', description: 'Agendă de contacte', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'module_prompt_generator', description: 'Generator de prompturi AI', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'module_word_templates', description: 'Șabloane Word', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'module_tag_manager', description: 'Manager etichete', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'module_mini_utilities', description: 'Mini utilitare', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'module_ai_chat', description: 'Chat AI', defaultValue: false, adminOnly: false, experimental: true },
// Feature flags (non-module)
{ id: 'feature_dark_mode', description: 'Mod întunecat', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'feature_export_import', description: 'Export/import date', defaultValue: true, adminOnly: true, experimental: false },
{ id: 'feature_cross_tab_sync', description: 'Sincronizare între tab-uri', defaultValue: true, adminOnly: false, experimental: false },
{ id: 'feature_infra_links', description: 'Linkuri infrastructură pe panou',defaultValue: true, adminOnly: false, experimental: false },
];
```
### Flag Resolution Order
1. **Environment override** (`NEXT_PUBLIC_FLAGS_OVERRIDE`): Highest priority. Parsed as comma-separated `key=value` pairs.
2. **Runtime override** (stored in `system` namespace under key `flag-overrides`): Set through admin UI. Persists across sessions.
3. **Default value** (`defaultValue` in `flags.ts`): Lowest priority. Used when no override exists.
```typescript
// src/lib/flags/resolve.ts
function resolveFlag(flagId: string): boolean {
// 1. Environment override
const envOverrides = parseEnvOverrides(process.env.NEXT_PUBLIC_FLAGS_OVERRIDE);
if (flagId in envOverrides) return envOverrides[flagId];
// 2. Runtime override (from storage)
const runtimeOverrides = getRuntimeOverrides(); // from system namespace
if (flagId in runtimeOverrides) return runtimeOverrides[flagId];
// 3. Default
const flag = FEATURE_FLAGS.find(f => f.id === flagId);
return flag?.defaultValue ?? false;
}
```
### Usage in Components
```typescript
import { useFeatureFlag } from '@/lib/flags/hooks';
function SomeComponent() {
const aiChatEnabled = useFeatureFlag('module_ai_chat');
if (!aiChatEnabled) return null;
return ;
}
```
---
## Navigation Configuration
**File:** `src/config/navigation.ts`
Navigation is derived from the module registry. No separate navigation config is maintained. The navigation system reads `MODULE_REGISTRY`, filters by feature flags and role, sorts by `order`, and renders the sidebar.
```typescript
// src/config/navigation.ts
import { MODULE_REGISTRY } from './modules';
interface NavGroup {
label: string;
modules: string[]; // Module IDs belonging to this group
}
/**
* Groups organize modules in the sidebar.
* Modules not listed in any group appear under "Altele" (Others).
*/
export const NAV_GROUPS: NavGroup[] = [
{
label: 'Principal',
modules: ['dashboard'],
},
{
label: 'Documente',
modules: ['registratura', 'word-templates', 'word-xml', 'email-signature'],
},
{
label: 'Resurse',
modules: ['address-book', 'digital-signatures', 'password-vault', 'it-inventory'],
},
{
label: 'Instrumente',
modules: ['prompt-generator', 'ai-chat', 'mini-utilities'],
},
{
label: 'Administrare',
modules: ['tag-manager'],
},
];
```
### External Links
Infrastructure tool links are configured via environment variables and displayed in a separate sidebar section or dashboard widget:
```typescript
// src/config/navigation.ts
interface ExternalLink {
label: string;
envVar: string; // Environment variable holding the URL
icon: LucideIcon;
category: 'infra' | 'dev' | 'tools';
}
export const EXTERNAL_LINKS: ExternalLink[] = [
{ label: 'Portainer', envVar: 'NEXT_PUBLIC_PORTAINER_URL', icon: Container, category: 'infra' },
{ label: 'Dozzle', envVar: 'NEXT_PUBLIC_DOZZLE_URL', icon: ScrollText, category: 'infra' },
{ label: 'Netdata', envVar: 'NEXT_PUBLIC_NETDATA_URL', icon: Activity, category: 'infra' },
{ label: 'Uptime Kuma', envVar: 'NEXT_PUBLIC_UPTIME_KUMA_URL', icon: HeartPulse, category: 'infra' },
{ label: 'Gitea', envVar: 'NEXT_PUBLIC_GITEA_URL', icon: GitBranch, category: 'dev' },
{ label: 'MinIO', envVar: 'NEXT_PUBLIC_MINIO_CONSOLE_URL', icon: Database, category: 'dev' },
{ label: 'IT-Tools', envVar: 'NEXT_PUBLIC_IT_TOOLS_URL', icon: Wrench, category: 'tools' },
{ label: 'Stirling PDF', envVar: 'NEXT_PUBLIC_STIRLING_PDF_URL', icon: FileText, category: 'tools' },
{ label: 'N8N', envVar: 'NEXT_PUBLIC_N8N_URL', icon: Workflow, category: 'tools' },
{ label: 'Filebrowser', envVar: 'NEXT_PUBLIC_FILEBROWSER_URL', icon: FolderOpen, category: 'tools' },
];
```
Links with no URL configured (empty env var) are hidden automatically.
---
## Company Configuration
**File:** `src/config/companies.ts`
Static company data. Updated rarely and only by developers.
```typescript
// src/config/companies.ts
import type { CompanyId, Company } from '@/types/company';
export const COMPANIES: Record = {
beletage: {
id: 'beletage',
name: 'Beletage SRL',
shortName: 'Beletage',
cui: 'RO12345678', // replace with real CUI
address: '...',
email: 'office@beletage.ro',
phone: '...',
},
'urban-switch': {
id: 'urban-switch',
name: 'Urban Switch SRL',
shortName: 'Urban Switch',
cui: 'RO23456789',
address: '...',
email: 'office@urbanswitch.ro',
phone: '...',
},
'studii-de-teren': {
id: 'studii-de-teren',
name: 'Studii de Teren SRL',
shortName: 'Studii de Teren',
cui: 'RO34567890',
address: '...',
email: 'office@studiideteren.ro',
phone: '...',
},
group: {
id: 'group',
name: 'Grup Beletage',
shortName: 'Grup',
cui: '',
},
};
export const COMPANY_IDS: CompanyId[] = ['beletage', 'urban-switch', 'studii-de-teren'];
export const ALL_COMPANY_IDS: CompanyId[] = [...COMPANY_IDS, 'group'];
```
`COMPANY_IDS` excludes `'group'` for UI dropdowns where a real company selection is required. `ALL_COMPANY_IDS` includes it for contexts where "all companies" is valid.
---
## Theme Configuration
**File:** `src/config/theme.ts`
Theme tokens extend the shadcn/ui and Tailwind defaults.
```typescript
// src/config/theme.ts
export const THEME_CONFIG = {
/** Default theme on first visit. User preference is stored in system namespace. */
defaultTheme: 'light' as 'light' | 'dark' | 'system',
/** Company brand colors for badges, indicators, and charts. */
companyColors: {
beletage: { primary: '#1E3A5F', accent: '#4A90D9' },
'urban-switch': { primary: '#2D5F3E', accent: '#6BBF8A' },
'studii-de-teren': { primary: '#5F4B1E', accent: '#D9A44A' },
group: { primary: '#374151', accent: '#6B7280' },
},
/** Tag category default colors. */
tagCategoryColors: {
project: '#3B82F6',
client: '#8B5CF6',
phase: '#F59E0B',
type: '#10B981',
priority: '#EF4444',
domain: '#6366F1',
custom: '#6B7280',
},
} as const;
```
Theme switching is handled by `next-themes` (shadcn/ui standard). The user's preference is stored in the `system` storage namespace under the key `theme-preference`.
### Tailwind Integration
Company colors and tag colors are registered in `tailwind.config.ts` as extended colors:
```typescript
// tailwind.config.ts (relevant excerpt)
theme: {
extend: {
colors: {
beletage: { DEFAULT: '#1E3A5F', accent: '#4A90D9' },
urbanswitch: { DEFAULT: '#2D5F3E', accent: '#6BBF8A' },
studiideteren: { DEFAULT: '#5F4B1E', accent: '#D9A44A' },
},
},
},
```
---
## Build-Time vs Runtime Configuration
| Configuration Type | When Resolved | How Set | Changeable Without Rebuild |
|---|---|---|---|
| `NEXT_PUBLIC_*` env vars | Build time (bundled into JS) | `.env.local`, Docker build args | No (requires rebuild) |
| Server-only env vars | Runtime (read on each request) | Docker env vars, `.env.local` | Yes |
| Feature flag defaults | Build time (in `flags.ts`) | Source code | No |
| Feature flag overrides (env) | Build time (via `NEXT_PUBLIC_FLAGS_OVERRIDE`) | Env var | No |
| Feature flag overrides (runtime) | Runtime (from storage) | Admin UI | Yes |
| Module registry | Build time (in `modules.ts`) | Source code | No |
| Company data | Build time (in `companies.ts`) | Source code | No |
| Theme preference | Runtime (from storage) | User toggle | Yes |
| External tool URLs | Build time (via `NEXT_PUBLIC_*`) | Env vars | No |
### Making `NEXT_PUBLIC_` Variables Runtime-Configurable in Docker
Next.js inlines `NEXT_PUBLIC_` variables at build time, which is problematic for Docker images that should be configurable at deploy time. Solution:
**1. Build with placeholder values:**
```dockerfile
# Dockerfile
ARG NEXT_PUBLIC_APP_URL=__NEXT_PUBLIC_APP_URL__
ARG NEXT_PUBLIC_STORAGE_ADAPTER=__NEXT_PUBLIC_STORAGE_ADAPTER__
```
**2. Replace at container start:**
```bash
#!/bin/sh
# docker/entrypoint.sh
# Replace build-time placeholders with runtime environment values
find /app/.next -type f -name '*.js' | while read file; do
sed -i "s|__NEXT_PUBLIC_APP_URL__|${NEXT_PUBLIC_APP_URL:-http://localhost:3000}|g" "$file"
sed -i "s|__NEXT_PUBLIC_STORAGE_ADAPTER__|${NEXT_PUBLIC_STORAGE_ADAPTER:-localStorage}|g" "$file"
# ... repeat for each NEXT_PUBLIC_ variable
done
exec node server.js
```
**3. Use in docker-compose:**
```yaml
# docker-compose.yml
services:
architools:
image: architools:latest
environment:
- NEXT_PUBLIC_APP_URL=https://tools.internal
- NEXT_PUBLIC_STORAGE_ADAPTER=api
- STORAGE_API_URL=http://api:8080/storage
- MINIO_ENDPOINT=minio:9000
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
ports:
- "3000:3000"
```
---
## Docker Environment Injection
### Development
```bash
# .env.local (gitignored)
NEXT_PUBLIC_APP_NAME=ArchiTools
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_STORAGE_ADAPTER=localStorage
```
### Production (Docker Compose)
```yaml
# docker-compose.yml
version: '3.8'
services:
architools:
build:
context: .
dockerfile: Dockerfile
container_name: architools
restart: unless-stopped
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- NEXT_PUBLIC_APP_NAME=ArchiTools
- NEXT_PUBLIC_APP_URL=https://tools.internal
- NEXT_PUBLIC_STORAGE_ADAPTER=api
- STORAGE_API_URL=http://api:8080/storage
- MINIO_ENDPOINT=minio:9000
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
- MINIO_BUCKET=architools
# External links
- NEXT_PUBLIC_PORTAINER_URL=https://portainer.internal
- NEXT_PUBLIC_GITEA_URL=https://git.internal
- NEXT_PUBLIC_N8N_URL=https://n8n.internal
networks:
- internal
networks:
internal:
external: true
```
### Production (Portainer)
When deploying via Portainer, set environment variables in the container's Environment section. Sensitive values (MinIO keys, auth secrets) should use Portainer's secrets management rather than plain environment variables.
---
## Configuration Precedence
Resolution order from lowest to highest priority:
```
1. Source code defaults (flags.ts, modules.ts, companies.ts, theme.ts)
↑ overridden by
2. Environment variables (.env.local / Docker env)
↑ overridden by
3. Runtime overrides (admin UI → stored in system namespace)
```
If a conflict exists, the higher-priority source wins. Runtime overrides are only available for feature flags and user preferences (theme, sidebar state). Structural configuration (module registry, company data, navigation groups) is not runtime-overridable; it requires a code change and rebuild.
---
## Configuration File Index
| File | Purpose | Changeable at Runtime |
|---|---|---|
| `src/config/modules.ts` | Module registry and metadata | No |
| `src/config/flags.ts` | Feature flag definitions and defaults | Overridable via env/storage |
| `src/config/navigation.ts` | Sidebar groups and external links | No |
| `src/config/companies.ts` | Company master data | No |
| `src/config/theme.ts` | Theme tokens and brand colors | Theme preference only |
| `.env.local` | Development environment variables | N/A (dev only) |
| `.env.example` | Documented variable template (committed) | N/A (reference) |
| `docker-compose.yml` | Production environment variables | At deploy time |
| `docker/entrypoint.sh` | Runtime placeholder replacement | At container start |
---
## Validation
On application startup, the config system validates:
1. All required `NEXT_PUBLIC_` variables are set (not empty or placeholder).
2. `NEXT_PUBLIC_STORAGE_ADAPTER` is a known adapter type.
3. If adapter is `api`, `STORAGE_API_URL` is set.
4. If MinIO is configured, all three of `MINIO_ENDPOINT`, `MINIO_ACCESS_KEY`, and `MINIO_SECRET_KEY` are present.
5. Feature flag overrides parse correctly (no malformed entries).
6. Module IDs in `NAV_GROUPS` reference existing modules in `MODULE_REGISTRY`.
Validation errors are logged to the console with `[ArchiTools Config]` prefix and do not crash the application. Missing optional config results in graceful degradation (e.g., external links not shown, MinIO features unavailable).