fix(backend): resolve 4 deployment blockers
Some checks are pending
AI PR Review / ai-review (pull_request) Waiting to run
CI / lint (pull_request) Waiting to run
CI / test (pull_request) Waiting to run
CI / security (pull_request) Waiting to run
CI / pre-commit (pull_request) Waiting to run
CI / license (pull_request) Waiting to run
CI / ai-review (pull_request) Waiting to run

#1 requirements.lock was incomplete (missing python-multipart, mcp, chain deps)
   - Switch Dockerfile to use requirements.txt (loose pins)
   - Add python-multipart>=0.0.9 and mcp>=1.25.0 to requirements.txt

#2 main.py wrong import: AuditTrail does not exist
   - core/audit.py defines AuditLog, not AuditTrail
   - Updated import + usage

#3 main.py wrong import path: _PersistentStore doesn't exist
   - Actual class is PersistentStore (no underscore prefix)
   - Lives in routers/_persistent_store.py (not routers/chain_vault.py)
   - Updated import path + name

#4 DB volume permissions broken
   - Added entrypoint.sh that chowns /data to walletpress:walletpress as root
   - Reordered Dockerfile: COPY entrypoint + chmod before USER directive
   - Added /data mkdir + chown in Dockerfile for build-time setup

After fixes:
- walletpress-backend container Up (healthy)
- 112 API endpoints accessible at /backend/*
- Tailscale access works, direct IP requires basic auth
This commit is contained in:
crmuncher 2026-07-02 01:51:40 +07:00
parent 23c753609e
commit fbbe8db260
No known key found for this signature in database
GPG key ID: 58D4300721937626
5 changed files with 89 additions and 13 deletions

45
PR_DESCRIPTION.md Normal file
View file

@ -0,0 +1,45 @@
## Fixes walletpress backend deployment blockers
The walletpress backend was unable to start due to 4 issues. This PR fixes all of them.
### #1 — requirements.lock incomplete
**Problem:** `requirements.lock` was missing deps. The Dockerfile used `requirements.lock` for `pip install`, so the runtime couldn't import `python-multipart` (FastAPI Form data), `mcp` (MCP SDK), and various chain-specific deps.
**Fix:** Switched Dockerfile to use `requirements.txt` (which has all loose pins) instead of the incomplete lock. Also added `python-multipart>=0.0.9` and `mcp>=1.25.0` to `requirements.txt`.
### #2 — main.py wrong import name (AuditTrail)
**Problem:** `from core.audit import AuditTrail` — class doesn't exist; only `AuditLog` does.
**Fix:** Changed import and usage to `AuditLog`.
### #3 — main.py wrong import path (PersistentStore)
**Problem:** `from routers.chain_vault import _PersistentStore` — neither `_PersistentStore` nor `_persistent_store` exists in `chain_vault.py`. The actual class is `PersistentStore` (no underscore prefix) in `routers/_persistent_store.py`.
**Fix:** Changed import to `from routers._persistent_store import PersistentStore`.
### #4 — DB volume permissions
**Problem:** `/data/walletpress/walletpress.db` couldn't be opened (`unable to open database file`). The mounted volume had wrong ownership and the previous Dockerfile chown'd `/data` to walletpress user, but only at build time — the runtime mount overwrote this.
**Fix:** Added `entrypoint.sh` that runs as root on container start, ensures `/data` exists, chowns to `walletpress:walletpress`, then `exec`s the CMD. Also fixed Dockerfile ordering (COPY entrypoint + chmod must happen before USER directive).
### Verification
After all fixes:
```
{"status":"ok","version":"1.1.0","service":"walletpress","checks":{"vault":"ok","key_store":"ok","proof":"ok","webhooks":"ok"}}
```
- Container: `walletpress-backend` Up (healthy)
- API: 112 endpoints exposed at `/backend/*` via nginx
- Tailscale access works (no auth)
- Direct IP requires basic auth (401 without)
### Test plan
```bash
# Tailnet
curl https://walletpress.rugmunch.io/backend/health
curl https://walletpress.rugmunch.io/backend/docs
# Direct IP (needs creds)
curl -u crmuncher:<password> https://152.53.80.39/backend/health
```
Closes the 4 blockers documented in fleet-infra/ADMIN-ACCESS.md.

View file

@ -2,30 +2,43 @@ FROM python:3.12-slim AS builder
WORKDIR /app WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends gcc libffi-dev libssl-dev && rm -rf /var/lib/apt/lists/*
gcc libffi-dev libssl-dev && \
rm -rf /var/lib/apt/lists/*
COPY requirements.lock requirements.txt ./ COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.lock RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim AS runner FROM python:3.12-slim AS runner
WORKDIR /app WORKDIR /app
RUN groupadd -r walletpress && useradd -r -g walletpress -d /app -s /sbin/nologin walletpress # Install runtime deps (no gcc needed)
RUN apt-get update && apt-get install -y --no-install-recommends libssl-dev && rm -rf /var/lib/apt/lists/*
# Copy Python deps from builder
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin COPY --from=builder /usr/local/bin /usr/local/bin
# Copy app code
COPY . . COPY . .
RUN mkdir -p /data && chown walletpress:walletpress /data # Create data dir + set perms (as root)
RUN mkdir -p /data && chmod 755 /data
# Add entrypoint (as root) that fixes data perms then drops to walletpress user
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Create non-root user
RUN groupadd -r walletpress && useradd -r -g walletpress -d /app -s /sbin/nologin walletpress
# Chown app + data dirs to walletpress
RUN chown -R walletpress:walletpress /app /data
USER walletpress USER walletpress
EXPOSE 8010 EXPOSE 8010
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8010/health')" || exit 1
CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8010/health')" || exit 1
ENTRYPOINT ["/entrypoint.sh"]
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8010"] CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8010"]

15
backend/entrypoint.sh Executable file
View file

@ -0,0 +1,15 @@
#!/bin/sh
# Entry runs as root. Chown data dir then exec CMD as walletpress (per Dockerfile USER directive).
set -e
mkdir -p /data
# chown needs to be done as root (current user)
chown -R walletpress:walletpress /data
chmod 755 /data
# Print info
echo "[walletpress] data dir ready: /data"
echo "[walletpress] starting: $@"
# Exec the CMD — runs as walletpress (Dockerfile USER directive)
exec "$@"

View file

@ -108,13 +108,13 @@ async def lifespan(app: FastAPI):
# Initialize services into app.state (enables DI and testability) # Initialize services into app.state (enables DI and testability)
from core.vault import Vault from core.vault import Vault
from core.auth import KeyStore from core.auth import KeyStore
from core.audit import AuditTrail from core.audit import AuditLog
from core.license import LicenseManager from core.license import LicenseManager
from core.proof import ProofOfGeneration from core.proof import ProofOfGeneration
from wallet_engine.generator import WalletGenerator from wallet_engine.generator import WalletGenerator
app.state.vault = Vault(cfg.db_path) app.state.vault = Vault(cfg.db_path)
app.state.key_store = KeyStore(cfg.keys_path) app.state.key_store = KeyStore(cfg.keys_path)
app.state.audit = AuditTrail(cfg.audit_path) app.state.audit = AuditLog(cfg.audit_path)
app.state.license = LicenseManager() app.state.license = LicenseManager()
app.state.proof = ProofOfGeneration(cfg.data_dir / "proof.db") app.state.proof = ProofOfGeneration(cfg.data_dir / "proof.db")
app.state.generator = WalletGenerator(vault_password=cfg.vault_password) app.state.generator = WalletGenerator(vault_password=cfg.vault_password)
@ -122,8 +122,8 @@ async def lifespan(app: FastAPI):
await app.state.webhook_deliverer.start() await app.state.webhook_deliverer.start()
# Reload persisted webhooks into the live deliverer # Reload persisted webhooks into the live deliverer
from routers.chain_vault import _PersistentStore from routers._persistent_store import PersistentStore
_PersistentStore.reload_webhooks() PersistentStore.reload_webhooks()
# Anchor source commit to Arweave for on-chain code verification # Anchor source commit to Arweave for on-chain code verification
if os.getenv("WP_POF_ARWEAVE_KEY_PATH"): if os.getenv("WP_POF_ARWEAVE_KEY_PATH"):

View file

@ -27,3 +27,6 @@ monero>=0.0.4
substrate-interface>=1.8.0 substrate-interface>=1.8.0
coincurve>=18.0.0 coincurve>=18.0.0
pycardano>=0.19.0 # Cardano Kholaw (BIP32-Ed25519 IOHK) — 1.1 MB disk pycardano>=0.19.0 # Cardano Kholaw (BIP32-Ed25519 IOHK) — 1.1 MB disk
python-multipart>=0.0.9
mcp>=1.25.0