Security/dependency hardening 2026 07 22 #56
@@ -254,30 +254,16 @@ jobs:
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
git pull
|
git pull
|
||||||
|
|
||||||
- name: Sync .env files from .env.example
|
- name: Inject .env files from Gitea variables
|
||||||
working-directory: /docker/FarmaFinder
|
working-directory: /docker/FarmaFinder
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ vars.GITEA_TOKEN }}
|
||||||
|
GITEA_OWNER: Ichitux
|
||||||
|
GITEA_REPO: FarmaFinder
|
||||||
|
WORK_DIR: /docker/FarmaFinder
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
while IFS= read -r env_example; do
|
bash scripts/deploy-env.sh
|
||||||
dir=$(dirname "$env_example")
|
|
||||||
env_file="$dir/.env"
|
|
||||||
|
|
||||||
if [ ! -f "$env_file" ]; then
|
|
||||||
cp "$env_example" "$env_file"
|
|
||||||
echo "[env-sync] Created $env_file from $env_example"
|
|
||||||
else
|
|
||||||
while IFS= read -r line || [ -n "$line" ]; do
|
|
||||||
case "$line" in
|
|
||||||
''|\#*) continue ;;
|
|
||||||
esac
|
|
||||||
key=$(echo "$line" | cut -d'=' -f1)
|
|
||||||
if ! grep -qF "$key=" "$env_file"; then
|
|
||||||
echo "$line" >> "$env_file"
|
|
||||||
echo "[env-sync] Added missing key '$key' to $env_file"
|
|
||||||
fi
|
|
||||||
done < "$env_example"
|
|
||||||
fi
|
|
||||||
done < <(find apps -name ".env.example" -type f)
|
|
||||||
|
|
||||||
- name: Deploy containers
|
- name: Deploy containers
|
||||||
working-directory: /docker/FarmaFinder
|
working-directory: /docker/FarmaFinder
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# deploy-env.sh — Fetch Gitea Actions variables and write .env files.
|
||||||
|
# Called by the CI/CD deploy step on the self-hosted runner.
|
||||||
|
#
|
||||||
|
# Required env vars (injected by Gitea Actions):
|
||||||
|
# GITEA_TOKEN — API token with repo read access
|
||||||
|
# GITEA_OWNER — repo owner (e.g. Ichitux)
|
||||||
|
# GITEA_REPO — repo name (e.g. FarmaFinder)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# GITEA_TOKEN=xxx GITEA_OWNER=Ichitux GITEA_REPO=FarmaFinder ./scripts/deploy-env.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
GITEA_URL="${GITEA_URL:-https://git.hacecalor.net}"
|
||||||
|
WORK_DIR="${WORK_DIR:-.}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Helper: fetch all repo variables from Gitea API (handles pagination)
|
||||||
|
# Returns JSON array on stdout
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
fetch_variables() {
|
||||||
|
local page=1 limit=50 all='[]'
|
||||||
|
while true; do
|
||||||
|
local resp
|
||||||
|
resp=$(curl -sf \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/actions/variables?limit=${limit}&page=${page}")
|
||||||
|
|
||||||
|
local count
|
||||||
|
count=$(echo "$resp" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))")
|
||||||
|
|
||||||
|
all=$(python3 -c "
|
||||||
|
import sys, json
|
||||||
|
a = json.loads('''${all}''')
|
||||||
|
b = json.loads(sys.stdin.read())
|
||||||
|
print(json.dumps(a + b))
|
||||||
|
" <<< "$resp")
|
||||||
|
|
||||||
|
if [ "$count" -lt "$limit" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
page=$((page + 1))
|
||||||
|
done
|
||||||
|
echo "$all"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Helper: write a .env file from a JSON object of key-value pairs.
|
||||||
|
# Keys are sorted alphabetically. Comments from .env.example are preserved.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
write_env() {
|
||||||
|
local env_path="$1"
|
||||||
|
local json_obj="$2"
|
||||||
|
local example_path="${env_path}.example"
|
||||||
|
|
||||||
|
# If no example file exists, write a plain KEY=VALUE file
|
||||||
|
if [ ! -f "$example_path" ]; then
|
||||||
|
python3 -c "
|
||||||
|
import json, sys
|
||||||
|
obj = json.loads('''${json_obj}''')
|
||||||
|
for k in sorted(obj):
|
||||||
|
print(f'{k}={obj[k]}')
|
||||||
|
" > "$env_path"
|
||||||
|
echo "[env] Wrote $env_path (${#json_obj} bytes, no .example template)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If .example exists, produce a file that preserves comments/order
|
||||||
|
# and fills in values from the JSON
|
||||||
|
python3 -c "
|
||||||
|
import json, sys, re
|
||||||
|
|
||||||
|
example_path = '''${example_path}'''
|
||||||
|
env_path = '''${env_path}'''
|
||||||
|
values = json.loads('''${json_obj}''')
|
||||||
|
|
||||||
|
with open(example_path) as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
output = []
|
||||||
|
written_keys = set()
|
||||||
|
for line in lines:
|
||||||
|
stripped = line.rstrip('\n')
|
||||||
|
|
||||||
|
# Pass through comments and blank lines
|
||||||
|
if stripped.startswith('#') or stripped.strip() == '':
|
||||||
|
output.append(stripped)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Parse KEY=VALUE lines
|
||||||
|
m = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)=(.*)', stripped)
|
||||||
|
if m:
|
||||||
|
key, _ = m.group(1), m.group(2)
|
||||||
|
if key in values:
|
||||||
|
output.append(f'{key}={values[key]}')
|
||||||
|
written_keys.add(key)
|
||||||
|
else:
|
||||||
|
output.append(stripped) # keep example default
|
||||||
|
else:
|
||||||
|
output.append(stripped)
|
||||||
|
|
||||||
|
# Append any keys from values that weren't in .example
|
||||||
|
for k in sorted(values):
|
||||||
|
if k not in written_keys:
|
||||||
|
output.append(f'{k}={values[k]}')
|
||||||
|
|
||||||
|
with open(env_path, 'w') as f:
|
||||||
|
f.write('\n'.join(output) + '\n')
|
||||||
|
|
||||||
|
print(f'[env] Wrote {env_path} ({len(values)} vars)')
|
||||||
|
" 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Main
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "=== deploy-env.sh ==="
|
||||||
|
echo "Fetching variables from Gitea: ${GITEA_OWNER}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
VARS_JSON=$(fetch_variables)
|
||||||
|
VAR_COUNT=$(echo "$VARS_JSON" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))")
|
||||||
|
echo "Found ${VAR_COUNT} variables"
|
||||||
|
|
||||||
|
# Group variables by prefix
|
||||||
|
# ROOT_* → root .env (strip prefix)
|
||||||
|
# BACKEND_* → apps/backend/.env (strip prefix)
|
||||||
|
# FRONTEND_* → apps/frontend/.env (strip prefix)
|
||||||
|
# PARAPHARMACY_* → apps/parapharmacy-api/.env (strip prefix)
|
||||||
|
# No prefix → root .env (backward compat with original vars)
|
||||||
|
|
||||||
|
ROOT_VARS=$(python3 -c "
|
||||||
|
import json, sys
|
||||||
|
vars = json.loads(sys.stdin.read())
|
||||||
|
# Keys that are CI-only and should NOT appear in application .env files
|
||||||
|
skip = {'GITEA_TOKEN'}
|
||||||
|
result = {}
|
||||||
|
for v in vars:
|
||||||
|
name = v['name']
|
||||||
|
value = v['data']
|
||||||
|
if name in skip:
|
||||||
|
continue
|
||||||
|
if name.startswith('ROOT_'):
|
||||||
|
result[name[5:]] = value # strip ROOT_ prefix
|
||||||
|
elif not any(name.startswith(p) for p in ['BACKEND_', 'FRONTEND_', 'PARAPHARMACY_']):
|
||||||
|
result[name] = value # unprefixed → root
|
||||||
|
print(json.dumps(result))
|
||||||
|
" <<< "$VARS_JSON")
|
||||||
|
|
||||||
|
BACKEND_VARS=$(python3 -c "
|
||||||
|
import json, sys
|
||||||
|
vars = json.loads(sys.stdin.read())
|
||||||
|
result = {}
|
||||||
|
for v in vars:
|
||||||
|
name = v['name']
|
||||||
|
if name.startswith('BACKEND_'):
|
||||||
|
result[name[8:]] = value = v['data']
|
||||||
|
print(json.dumps(result))
|
||||||
|
" <<< "$VARS_JSON")
|
||||||
|
|
||||||
|
FRONTEND_VARS=$(python3 -c "
|
||||||
|
import json, sys
|
||||||
|
vars = json.loads(sys.stdin.read())
|
||||||
|
result = {}
|
||||||
|
for v in vars:
|
||||||
|
name = v['name']
|
||||||
|
if name.startswith('FRONTEND_'):
|
||||||
|
result[name[9:]] = v['data']
|
||||||
|
print(json.dumps(result))
|
||||||
|
" <<< "$VARS_JSON")
|
||||||
|
|
||||||
|
PARAPHARMACY_VARS=$(python3 -c "
|
||||||
|
import json, sys
|
||||||
|
vars = json.loads(sys.stdin.read())
|
||||||
|
result = {}
|
||||||
|
for v in vars:
|
||||||
|
name = v['name']
|
||||||
|
if name.startswith('PARAPHARMACY_'):
|
||||||
|
result[name[13:]] = v['data']
|
||||||
|
print(json.dumps(result))
|
||||||
|
" <<< "$VARS_JSON")
|
||||||
|
|
||||||
|
# Write each .env file
|
||||||
|
echo ""
|
||||||
|
write_env "${WORK_DIR}/.env" "$ROOT_VARS"
|
||||||
|
write_env "${WORK_DIR}/apps/backend/.env" "$BACKEND_VARS"
|
||||||
|
write_env "${WORK_DIR}/apps/frontend/.env" "$FRONTEND_VARS"
|
||||||
|
write_env "${WORK_DIR}/apps/parapharmacy-api/.env" "$PARAPHARMACY_VARS"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== All .env files written ==="
|
||||||
Reference in New Issue
Block a user