The standalone prototype should be the root-level project shape for fengqun while preserving the existing planning documents already at the root. This keeps README, examples, tests, and the Python package directly discoverable without deleting the prior docs. Constraint: User clarified that swarm-minimal is the repository root, but other existing root files must remain. Rejected: Deleting existing root docs | They are part of the fengqun repository context and were explicitly protected. Confidence: high Scope-risk: narrow Directive: Keep secrets in ignored .env only; do not commit live credentials. Tested: python3 -B -m unittest discover -s tests; git diff --check; secret-pattern scan showed only placeholders/test values/task-id false positives. Not-tested: Remote web UI rendering after push.
80 lines
2.3 KiB
Python
80 lines
2.3 KiB
Python
from pathlib import Path
|
|
from getpass import getpass
|
|
import os
|
|
import stat
|
|
import sys
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from run_full_live_test import main as run_full_live_test
|
|
from swarm_minimal.local_env import load_env_file
|
|
|
|
|
|
FIELDS = [
|
|
("PGHOST", "PostgreSQL host", False, ""),
|
|
("PGUSER", "PostgreSQL user", False, ""),
|
|
("PGPORT", "PostgreSQL port", False, "5432"),
|
|
("PGDATABASE", "PostgreSQL database", False, ""),
|
|
("PGPASSWORD", "PostgreSQL password", True, ""),
|
|
("SWARM_REDIS_CONNECTION_STRING", "Redis connection string", True, ""),
|
|
("AZURE_STORAGE_CONNECTION_STRING", "Azure Storage connection string", True, ""),
|
|
("SWARM_BLOB_CONTAINER", "Blob container", False, "swarm-artifacts"),
|
|
("NEWAPI_BASE_URL", "NewAPI base URL", False, ""),
|
|
("NEWAPI_API_KEY", "NewAPI API key", True, ""),
|
|
("NEWAPI_MODEL", "Optional fallback NewAPI model", False, ""),
|
|
]
|
|
|
|
|
|
def main() -> None:
|
|
env_path = ROOT / ".env"
|
|
load_env_file(env_path)
|
|
|
|
print("Enter missing live-test values. Secret fields are hidden.")
|
|
for key, label, secret, default in FIELDS:
|
|
current = os.environ.get(key)
|
|
if current:
|
|
continue
|
|
|
|
prompt = f"{label}"
|
|
if default:
|
|
prompt += f" [{default}]"
|
|
prompt += ": "
|
|
|
|
value = getpass(prompt) if secret else input(prompt)
|
|
if not value and default:
|
|
value = default
|
|
if not value:
|
|
raise SystemExit(f"{key} is required")
|
|
|
|
os.environ[key] = value
|
|
|
|
save = input("Save these values to ignored .env for this machine? [y/N]: ").strip().lower()
|
|
if save == "y":
|
|
write_env_file(env_path)
|
|
print(f"Saved local credentials to {env_path} with 0600 permissions.")
|
|
|
|
run_full_live_test()
|
|
|
|
|
|
def write_env_file(path: Path) -> None:
|
|
lines = []
|
|
for key, *_ in FIELDS:
|
|
value = os.environ.get(key)
|
|
if value is None:
|
|
continue
|
|
lines.append(f"{key}={quote_env(value)}")
|
|
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
path.chmod(stat.S_IRUSR | stat.S_IWUSR)
|
|
|
|
|
|
def quote_env(value: str) -> str:
|
|
if not value or any(char.isspace() or char in "'\"#" for char in value):
|
|
return "'" + value.replace("'", "'\"'\"'") + "'"
|
|
return value
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|