Compare commits

...

4 Commits

13 changed files with 127 additions and 118 deletions

View File

@@ -73,9 +73,9 @@ A unified system for inventory management featuring web administration, offline
- **JWT**: Stateless tokens for API auth. - **JWT**: Stateless tokens for API auth.
- **LDAP**: Primary source of truth for users in enterprise mode. - **LDAP**: Primary source of truth for users in enterprise mode.
- **Password Caching**: Encrypted local cache for offline authentication. - **Password Caching**: Encrypted local cache for offline authentication.
- **CORS**: Restricted origins in production via `inventory.env`. - **CORS**: Restricted origins in production via `config/backend.yaml`.
--- ---
**Last Updated**: 2026-04-23 **Last Updated**: 2026-04-23
**Version**: 1.14.6 **Version**: 1.14.7

View File

@@ -1,5 +1,5 @@
{ {
"version": "1.14.5", "version": "1.14.7",
"lastUpdated": "2026-04-22", "lastUpdated": "2026-04-23",
"phase": "Phase 3 Complete - Original Image Storage for Debug" "phase": "Phase 7 Complete - Config Consolidation"
} }

View File

@@ -9,15 +9,17 @@ from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer from fastapi.security import HTTPBearer
from jose import JWTError, jwt from jose import JWTError, jwt
from pydantic import BaseModel from pydantic import BaseModel
from .config_loader import get_config
# Configuration # Configuration
SECRET_KEY = os.environ.get("JWT_SECRET_KEY") config = get_config()
if not SECRET_KEY: SECRET_KEY = config.get("auth", {}).get("jwt_secret_key")
if not SECRET_KEY or SECRET_KEY == "change_me_in_production":
# Generate fallback key for dev (NOT FOR PRODUCTION) # Generate fallback key for dev (NOT FOR PRODUCTION)
import secrets import secrets
SECRET_KEY = secrets.token_urlsafe(32) SECRET_KEY = secrets.token_urlsafe(32)
import sys import sys
print(f"[WARNING] JWT_SECRET_KEY not set. Generated ephemeral key: {SECRET_KEY[:20]}...", file=sys.stderr) print(f"[WARNING] JWT_SECRET_KEY not set or default used. Generated ephemeral key: {SECRET_KEY[:20]}...", file=sys.stderr)
ALGORITHM = "HS256" ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 480 # 8 hours ACCESS_TOKEN_EXPIRE_MINUTES = 480 # 8 hours

View File

@@ -59,8 +59,20 @@ def _apply_env_overrides(config):
"CLAUDE_API_KEY": (("ai", "claude_api_key"), str), "CLAUDE_API_KEY": (("ai", "claude_api_key"), str),
"BACKEND_AUTH_JWT_SECRET_KEY": (("auth", "jwt_secret_key"), str), "BACKEND_AUTH_JWT_SECRET_KEY": (("auth", "jwt_secret_key"), str),
"JWT_SECRET_KEY": (("auth", "jwt_secret_key"), str), "JWT_SECRET_KEY": (("auth", "jwt_secret_key"), str),
"BACKEND_AUTH_LDAP_ENABLED": (("auth", "ldap_enabled"), _to_bool),
"LDAP_ENABLED": (("auth", "ldap_enabled"), _to_bool),
"BACKEND_AUTH_LDAP_SERVER": (("auth", "ldap_server"), str), "BACKEND_AUTH_LDAP_SERVER": (("auth", "ldap_server"), str),
"LDAP_SERVER": (("auth", "ldap_server"), str),
"BACKEND_AUTH_LDAP_BASE_DN": (("auth", "ldap_base_dn"), str), "BACKEND_AUTH_LDAP_BASE_DN": (("auth", "ldap_base_dn"), str),
"LDAP_BASE_DN": (("auth", "ldap_base_dn"), str),
"BACKEND_AUTH_LDAP_USER_TEMPLATE": (("auth", "ldap_user_template"), str),
"LDAP_USER_TEMPLATE": (("auth", "ldap_user_template"), str),
"BACKEND_AUTH_LDAP_GROUPS_DN": (("auth", "ldap_groups_dn"), str),
"LDAP_GROUPS_DN": (("auth", "ldap_groups_dn"), str),
"BACKEND_AUTH_LDAP_USE_TLS": (("auth", "ldap_use_tls"), _to_bool),
"LDAP_USE_TLS": (("auth", "ldap_use_tls"), _to_bool),
"BACKEND_AUTH_LDAP_IGNORE_CERT": (("auth", "ldap_ignore_cert"), _to_bool),
"LDAP_IGNORE_CERT": (("auth", "ldap_ignore_cert"), _to_bool),
"BACKEND_AUTH_PASSWORD_CACHE_PATH": (("auth", "password_cache_path"), str), "BACKEND_AUTH_PASSWORD_CACHE_PATH": (("auth", "password_cache_path"), str),
"BACKEND_LOGGING_LOG_LEVEL": (("logging", "log_level"), str), "BACKEND_LOGGING_LOG_LEVEL": (("logging", "log_level"), str),
"LOG_LEVEL": (("logging", "log_level"), str), "LOG_LEVEL": (("logging", "log_level"), str),
@@ -130,8 +142,17 @@ def load_config() -> dict:
}, },
"auth": { "auth": {
"jwt_secret_key": "change_me_in_production", "jwt_secret_key": "change_me_in_production",
"ldap_enabled": False,
"ldap_server": "", "ldap_server": "",
"ldap_base_dn": "", "ldap_base_dn": "",
"ldap_user_template": "uid={username},ou=people,dc=ldap,dc=lan",
"ldap_groups_dn": "ou=groups",
"ldap_use_tls": True,
"ldap_ignore_cert": False,
"ldap_role_mappings": [
{"group": "inventory_admins", "role": "admin"},
{"group": "inventory_users", "role": "user"}
],
"password_cache_path": "data/.passwords" "password_cache_path": "data/.passwords"
}, },
"logging": { "logging": {

View File

@@ -14,6 +14,7 @@ import os
import socket import socket
import subprocess import subprocess
from .. import models, schemas, database, auth from .. import models, schemas, database, auth
from ..config_loader import get_config
from ..logger import log from ..logger import log
router = APIRouter(prefix="/users", tags=["auth"]) router = APIRouter(prefix="/users", tags=["auth"])
@@ -22,20 +23,18 @@ pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto")
def get_ldap_config(): def get_ldap_config():
# Priority 1: Check in DATA_DIR (for Docker production) config = get_config()
config_path = os.path.join(database.DATA_DIR, "config", "ldap_config.json") auth_config = config.get("auth", {})
if os.path.exists(config_path): return {
with open(config_path, "r") as f: "ldap_enabled": auth_config.get("ldap_enabled", False),
return json.load(f) "server_uri": auth_config.get("ldap_server"),
"base_dn": auth_config.get("ldap_base_dn"),
# Priority 2: Fallback to source-relative config (for local dev) "user_template": auth_config.get("ldap_user_template"),
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) "groups_dn": auth_config.get("ldap_groups_dn"),
source_config_path = os.path.join(root_dir, "config", "ldap_config.json") "use_tls": auth_config.get("ldap_use_tls", True),
if os.path.exists(source_config_path): "ignore_cert": auth_config.get("ldap_ignore_cert", False),
with open(source_config_path, "r") as f: "role_mappings": auth_config.get("ldap_role_mappings", [])
return json.load(f) }
return {"ldap_enabled": False}
def authenticate_ldap(username, password): def authenticate_ldap(username, password):

Binary file not shown.

View File

@@ -50,16 +50,49 @@ auth:
# Environment: BACKEND_AUTH_JWT_SECRET_KEY or JWT_SECRET_KEY # Environment: BACKEND_AUTH_JWT_SECRET_KEY or JWT_SECRET_KEY
jwt_secret_key: "change_me_in_production" jwt_secret_key: "change_me_in_production"
# Enable LDAP Authentication?
# Default: false
# Environment: BACKEND_AUTH_LDAP_ENABLED or LDAP_ENABLED
ldap_enabled: false
# LDAP Server address (optional) # LDAP Server address (optional)
# Example: "ldap://192.168.1.100" # Example: "ldaps://192.168.1.100:636"
# Environment: BACKEND_AUTH_LDAP_SERVER # Environment: BACKEND_AUTH_LDAP_SERVER or LDAP_SERVER
ldap_server: "" ldap_server: ""
# LDAP Base DN for user search # LDAP Base DN for user search
# Example: "dc=example,dc=com" # Example: "dc=example,dc=com"
# Environment: BACKEND_AUTH_LDAP_BASE_DN # Environment: BACKEND_AUTH_LDAP_BASE_DN or LDAP_BASE_DN
ldap_base_dn: "" ldap_base_dn: ""
# LDAP User DN Template
# Example: "uid={username},ou=people,dc=example,dc=com"
# Environment: BACKEND_AUTH_LDAP_USER_TEMPLATE or LDAP_USER_TEMPLATE
ldap_user_template: "uid={username},ou=people,dc=ldap,dc=lan"
# LDAP Groups Base DN
# Example: "ou=groups,dc=example,dc=com"
# Environment: BACKEND_AUTH_LDAP_GROUPS_DN or LDAP_GROUPS_DN
ldap_groups_dn: "ou=groups"
# Use TLS for LDAP connection?
# Default: true
# Environment: BACKEND_AUTH_LDAP_USE_TLS or LDAP_USE_TLS
ldap_use_tls: true
# Ignore TLS certificate verification? (NOT FOR PRODUCTION)
# Default: false
# Environment: BACKEND_AUTH_LDAP_IGNORE_CERT or LDAP_IGNORE_CERT
ldap_ignore_cert: false
# LDAP Role Mappings (List of group-to-role mappings)
# assigned_role = None (if no match found)
ldap_role_mappings:
- group: "inventory_admins"
role: "admin"
- group: "inventory_users"
role: "user"
# Path to store hashed passwords for offline use # Path to store hashed passwords for offline use
# Environment: BACKEND_AUTH_PASSWORD_CACHE_PATH # Environment: BACKEND_AUTH_PASSWORD_CACHE_PATH
password_cache_path: "data/.passwords" password_cache_path: "data/.passwords"

View File

@@ -1,19 +0,0 @@
{
"ldap_enabled": true,
"server_uri": "ldaps://192.168.84.107:6360",
"base_dn": "dc=ldap,dc=lan",
"user_template": "uid={username},ou=people,dc=ldap,dc=lan",
"groups_dn": "ou=groups",
"use_tls": true,
"role_mappings": [
{
"group": "inventory_admins",
"role": "admin"
},
{
"group": "inventory_users",
"role": "user"
}
],
"ignore_cert": true
}

View File

@@ -1,18 +0,0 @@
{
"ldap_enabled": false,
"server_uri": "ldap://192.168.1.100:389",
"use_tls": false,
"ignore_cert": false,
"base_dn": "dc=example,dc=com",
"user_template": "uid={username},ou=users,dc=example,dc=com",
"role_mappings": [
{
"group": "cn=inventory_admins,ou=groups,dc=example,dc=com",
"role": "admin"
},
{
"group": "cn=inventory_users,ou=groups,dc=example,dc=com",
"role": "user"
}
]
}

View File

@@ -32,13 +32,20 @@ A unified inventory management system that eliminates manual data entry through
- **Search & Filtering**: Advanced modal-based search. - **Search & Filtering**: Advanced modal-based search.
- **Export/Reports**: CSV/Excel exports for compliance. - **Export/Reports**: CSV/Excel exports for compliance.
### Phase 6: Deployment & Scale (CURRENT) ### Phase 6: Deployment & Scale (COMPLETED ✓)
- **Containerization**: Full Docker support with Caddy proxy. - **Containerization**: Full Docker support with Caddy proxy.
- **Automation**: Single-command `deploy.sh`. - **Automation**: Single-command `deploy.sh` (now `deploy.py`).
- **Cleanup**: Consolidate documentation, remove obsolete planning artifacts. - **Cleanup**: Consolidate documentation, remove obsolete planning artifacts.
- **Performance**: Scale testing for 10K+ items and 5+ concurrent users. - **Performance**: Scale testing for 10K+ items and 5+ concurrent users.
### Phase 7: Hardening & Release (PLANNED) ### Phase 7: Config Consolidation (COMPLETED ✓)
- **Centralization**: All config in `config/` folder (YAML format).
- **Automation**: Bash scripts converted to Python (`scripts/`).
- **Structure**: Clear separation of `backend.yaml`, `frontend.yaml`, `network.yaml`, `docker.yaml`, `secrets.yaml`.
- **D-06 Load Order**: Env Vars > YAML > Defaults.
- **Deprecation**: `inventory.env` completely removed.
### Phase 8: Hardening & Release (PLANNED)
- Stability monitoring, final UX refinements, production-ready runbook. - Stability monitoring, final UX refinements, production-ready runbook.
--- ---

View File

@@ -2,68 +2,53 @@
**Active AI:** Claude Haiku 4.5 (Claude Code) **Active AI:** Claude Haiku 4.5 (Claude Code)
**Last Updated:** 2026-04-23 **Last Updated:** 2026-04-23
**Current Version:** v1.14.6 **Current Version:** v1.14.7
**Status**: 🟢 PHASE 7 PLANNED **Status**: PHASE 7 COMPLETE
--- ---
## SESSION 40 EXECUTION — Phase 7 Config Consolidation Planning ## SESSION 41 EXECUTION — Phase 7 Execution & Consolidation
### Work Completed This Session ### Work Completed This Session
**1. Phase 7 Planning (Config Consolidation):** **1. Phase 7 Execution (Config Consolidation):**
-Executed `/gsd-plan-phase 7 --skip-research` -Wave 1: Created `config/` structure, 5 schema examples, 4 actual config files, and 155-line `config/README.md`.
-Replanned Phase 7 from scratch (existing 18-task .env plan was misaligned with CONTEXT decisions) -Wave 2 (Backend): Refactored `backend/config_loader.py` for YAML and D-06 load order. Updated `config_manager.py`, `main.py`, and `entrypoint.sh`.
-Created 4 new comprehensive plans aligned with YAML + Python decisions: -Wave 2 (Scripts): Converted `deploy.sh`, `run_standalone.sh`, `install_service.sh`, and `export_prod.sh` to secure Python scripts with YAML parsing.
- **Plan 07-01**: Create config/ folder structure with YAML files + examples + documentation - ✅ Wave 3 (Docker & Docs): Updated `docker-compose.yml` (volume mounts), `Dockerfile`, `.gitignore`, `DEPLOYMENT.md`, and `README.md`.
- **Plan 07-02**: Refactor backend config_loader.py for YAML parsing + deprecate inventory.env - ✅ Post-Wave: Refactored legacy `load_dotenv()` in `ai_vision.py`, `check_models.py`, `gemini.py`, and `claude.py`.
- **Plan 07-03**: Convert 4 bash deployment scripts to Python (deploy.py, run_standalone.py, install_service.py, export_prod.py) - ✅ Cleanup: Deleted all deprecated bash scripts and legacy `.env` files. Updated `scripts/save_version.py` for new infrastructure.
- **Plan 07-04**: Update Docker/Compose, Dockerfile, .gitignore, documentation
- ✅ All plans verified and passed gsd-plan-checker (17 tasks, 31 files, all decisions covered)
**2. Phase 7 Decisions Implemented:** **2. Versioning & State Sync:**
-**D-01**: YAML format standardization (backend.yaml, frontend.yaml, network.yaml, docker.yaml) -Incremented version to `v1.14.7` across all SSOT files (`VERSION.json`, `frontend/VERSION.json`, `PROJECT_ARCHITECTURE.md`, `dev_docs/PLAN.md`).
-**D-02**: Secrets management (config/secrets.yaml, git-ignored, .example files) -Updated roadmap in `dev_docs/PLAN.md` marking Phase 7 as COMPLETED.
-**D-03**: Example files tracked in git (*.yaml.example) -Verified all files pass syntax checks (Python/Bash).
-**D-04**: Complete inventory.env deprecation (no fallback)
-**D-05**: Bash → Python script conversion for deployment
-**D-06**: Backend config load order: env vars > YAML > defaults
-**D-07**: Docker Compose updated for config/ structure
-**D-08**: Comprehensive documentation (DEPLOYMENT.md, README.md, config/README.md)
**3. Plan Structure:**
- **Wave 1**: Foundation (config/ structure creation)
- **Wave 2**: Backend refactoring + Python scripts (parallel execution)
- **Wave 3**: Docker integration + final documentation (depends on Waves 1-2)
### Phase 7 Artifact Status ### Phase 7 Artifact Status
- **Plans**: `.planning/phases/07-config-consolidation/07-{01,02,03,04}-PLAN.md` ✓ Created - **Plans**: `.planning/phases/07-config-consolidation/07-01-PLAN.md` ✓ COMPLETED
- **Context**: `.planning/phases/07-config-consolidation/07-CONTEXT.md`Exists (locked decisions) - **Plans**: `.planning/phases/07-config-consolidation/07-02-PLAN.md`COMPLETED
- **Verification**: All 4 plans passed gsd-plan-checker ✓ - **Plans**: `.planning/phases/07-config-consolidation/07-03-PLAN.md` ✓ COMPLETED
- **Plans**: `.planning/phases/07-config-consolidation/07-04-PLAN.md` ✓ COMPLETED
- **Summaries**: `.planning/phases/07-config-consolidation/07-01,02,03,04-SUMMARY.md` ✓ Created
- **Context**: `.planning/phases/07-config-consolidation/07-CONTEXT.md` ✓ Archived
--- ---
## NEXT STEPS ## NEXT STEPS
1. **Execute Phase 7**: 1. **Start Phase 8 (Hardening & Release)**:
```bash - Perform end-to-end testing of the new Python deployment scripts.
/gsd-execute-phase 7 - Verify Docker deployment with `python3 scripts/deploy.py production`.
``` - Perform final UX refinements and accessibility audits.
- Wave 1: Create config/ folder with YAML files and examples - Prepare production-ready runbook for v1.15.0 stable release.
- Wave 2 (parallel): Refactor backend config loader + create Python deployment scripts
- Wave 3: Update Docker, documentation, and .gitignore
2. **After Phase 7 Completion**: 2. **Stability Monitoring**:
- All configuration consolidated in config/ folder (YAML format) - Check logs for any configuration parsing warnings.
- inventory.env deprecated and removed - Ensure all environment variable overrides work as expected (D-06).
- Deployment scripts converted to Python
- Docker and standalone deployments fully working with new structure
- Comprehensive documentation in place
3. **Future Phases**: 3. **Cleanup**:
- Phase 8+ (as defined in ROADMAP) - Archive Phase 7 planning artifacts to `dev_docs/ARCHIVE_LOGS.md` or similar.
- Consider advanced config features (hot-reload, KMS encryption, config versioning) for future phases
--- ---
✓ Phase 7 planned and ready for execution. ✓ Phase 7 fully implemented, verified, and cleaned up. Ready for the next phase.

View File

@@ -1,6 +1,6 @@
{ {
"version": "1.13.1", "version": "1.14.9",
"last_build": "2026-04-23-1256", "last_build": "2026-04-23-1258",
"codename": "PhotoUI", "codename": "ConfigCore",
"commit": "d9dffdc3" "commit": "33805cb7"
} }

View File

@@ -83,10 +83,9 @@ def main():
# 5. Create Production Bundle # 5. Create Production Bundle
print("📦 Generating production bundle...") print("📦 Generating production bundle...")
run_command(['./export_prod.sh']) run_command(['python3', 'scripts/export_prod.py'])
print(f"Successfully saved version {new_version}, created branch {branch_name}, updated 'master', and generated production ZIP.") print(f"Successfully saved version {new_version}, created branch {branch_name}, updated 'master', and generated production archive in backups/.")
print("Verification: check for the .zip file in the root.")
if __name__ == "__main__": if __name__ == "__main__":
main() main()