Compare commits

...

2 Commits

11 changed files with 91 additions and 115 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):

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.8",
"last_build": "2026-04-23-1256", "last_build": "2026-04-23-1258",
"codename": "PhotoUI", "codename": "ConfigCore",
"commit": "d9dffdc3" "commit": "88e5d66f"
} }

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()