Skip to content

Live Update Manager

mp-live-update-manager is a full-stack Nuxt 4 application for managing over-the-air (OTA) live update bundles for the Medipal mobile app. It handles build orchestration, bundle storage, environment promotion, and audit logging.

For the client-side Capacitor plugin that applies updates on-device, see Live Update.


Purpose

  • Build live update bundles from any mp-mobile-app branch or tag
  • Store immutable bundles on S3 with metadata
  • Promote bundles across environments (development → staging → production)
  • Invalidate CloudFront cache on deployment for instant propagation
  • Maintain a complete audit trail of all actions

Tech Stack

LayerTechnologies
FrontendNuxt 4, Vue 3, TypeScript, Tailwind CSS, Nuxt UI v4
BackendNitro, AWS SDK v3 (S3 + CloudFront), Jose (JWT)
Code QualityESLint 9, Prettier, Husky, lint-staged
DeploymentDocker (Node.js 22 Alpine), GitHub Actions

Project Structure

mp-live-update-manager/
├── app/                              # Frontend (Nuxt 4)
│   ├── components/                   # Vue components
│   │   ├── ActivateDialog.vue        # Bundle deployment modal
│   │   ├── AppHeader.vue             # Navigation bar
│   │   ├── AuditTable.vue            # Audit entries table
│   │   ├── BuildLogViewer.vue        # Live log streaming modal
│   │   ├── BuildPanel.vue            # Build creation + status
│   │   ├── BundleTable.vue           # Bundle list
│   │   ├── DeleteConfirmDialog.vue   # Delete confirmation
│   │   └── EnvironmentCard.vue       # Environment status card
│   ├── composables/
│   │   └── useAuth.ts                # Auth state + API helper
│   ├── middleware/
│   │   └── auth.global.ts            # Client route guard
│   ├── pages/
│   │   ├── index.vue                 # Dashboard
│   │   ├── builds.vue                # Build management
│   │   ├── audit.vue                 # Audit log viewer
│   │   └── login.vue                 # Login form
│   └── types/
│       └── index.ts                  # Shared TypeScript interfaces
├── server/                           # Backend (Nitro)
│   ├── api/                          # 12 API endpoints
│   │   ├── auth/                     # login, session, logout
│   │   ├── builds/                   # create, list, get, logs
│   │   ├── bundles/                  # list, delete
│   │   ├── environments/             # list, activate
│   │   ├── git/                      # branches
│   │   └── audit/                    # log
│   ├── middleware/                   # API key + JWT validation
│   └── utils/                        # S3, CloudFront, audit, build worker
├── nuxt.config.ts
├── Dockerfile
└── package.json

Setup

Prerequisites

  • Node.js 22+
  • AWS credentials with S3 and CloudFront access
  • GitHub token with read access to mp-mobile-app

Installation

bash
git clone https://github.com/medipal/mp-live-update-manager.git
cd mp-live-update-manager
cp .env.example .env    # fill in all required values
npm install
npm run dev             # starts on http://localhost:3000

Authentication

All API requests require two layers of authentication:

  1. API KeyX-API-Key header on every request (anti-DDoS)
  2. JWT TokenAuthorization: Bearer <token> on all routes except login

Auth Flow

Client                              Server
  │                                   │
  ├─ POST /api/auth/login ───────────►│  (username + password + API key)
  │                                   │
  │◄── JWT token (HS256, 24h) ───────┤
  │                                   │
  ├─ GET /api/builds ────────────────►│  (Authorization: Bearer <token>)
  │  + X-API-Key header               │
  │                                   │

Credentials are configured via AUTH_USERNAME and AUTH_PASSWORD environment variables. Single-user authentication — no RBAC.


API Endpoints

MethodEndpointDescription
POST/api/auth/loginGenerate JWT token
GET/api/auth/sessionValidate current session
POST/api/auth/logoutClear authentication
GET/api/environmentsList environments with manifests
POST/api/environments/[env]/activateDeploy bundle to environment
POST/api/buildsQueue new build from git ref
GET/api/buildsList all builds
GET/api/builds/[id]Get build details
GET/api/builds/[id]/logsGet build logs
GET/api/bundlesList all bundles
DELETE/api/bundles/[id]Delete bundle (fails if active)
GET/api/git/branchesList remote branches and tags
GET/api/auditFetch audit log

Build Orchestration

Builds are queued in-memory and executed serially (max 1 concurrent build).

Build Flow

Queue → Clone → Install → Generate → Package → Upload → Done
  │                                                        │
  └─ Status tracked in real-time with log streaming ───────┘

Stages:

  1. Cloninggit clone --depth 1 --branch <ref> from mp-mobile-app
  2. Installingnpm ci with NPM_TOKEN and GIGET_TOKEN
  3. Buildingnuxt generate (static bundle)
  4. Packaginggenerate_live_update_package.jslive-update.zip
  5. Uploading — S3 PUT (ZIP + metadata.json)
  6. Logging — audit entry creation
  7. Cleanup — remove build directory

Build logs are streamed to the UI in real-time with 2-second polling. Failed stage output is highlighted in red.


Environment Promotion

Three environments: development, staging, production.

Activation Flow

Select bundle → Activate on environment → Update manifest.json → CloudFront invalidation
  1. User selects a bundle and target environment
  2. Server updates manifest.json on S3 (no-cache headers for instant propagation)
  3. CloudFront cache invalidation is triggered automatically
  4. Mobile app detects the new manifest on next launch and downloads the updated bundle

Safety checks prevent deleting bundles that are currently active on any environment.


S3 Storage Layout

s3://{S3_BUCKET}/{S3_PREFIX}/
├── bundles/{bundleId}/
│   ├── live-update.zip           # Immutable, Cache-Control: max-age=31536000
│   └── metadata.json             # Immutable, Cache-Control: max-age=31536000
├── environments/{env}/
│   └── manifest.json             # No-cache, no-store (instant propagation)
└── audit/
    └── log.jsonl                 # Append-only audit trail

Bundle metadata:

json
{
  "version": "1.2.3",
  "hash": "a1b2c3d4-...",
  "timestamp": "2026-03-05T12:00:00.000Z",
  "gitSha": "abc123def456",
  "gitBranch": "main",
  "size": 5242880,
  "builtBy": "live-update-manager"
}

Environment manifest:

json
{
  "hash": "a1b2c3d4-...",
  "version": "1.2.3",
  "timestamp": "2026-03-05T12:00:00.000Z",
  "contentUrl": "https://cdn.medipal.dev/live-update/bundles/..."
}

Audit Trail

All actions are logged in JSONL format on S3 (audit/log.jsonl). The log is append-only.

Entry Schema

json
{
  "timestamp": "2026-03-05T12:00:00.000Z",
  "action": "activate",
  "environment": "production",
  "bundleId": "1.2.3_abc123",
  "version": "1.2.3",
  "fromVersion": "1.2.2",
  "user": "admin",
  "details": "Promoted to production"
}

Actions: activate, delete, upload.


Docker Deployment

dockerfile
# Multi-stage build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
RUN apk add --no-cache git
WORKDIR /app
COPY --from=builder /app/.output .output
ENV NITRO_HOST=0.0.0.0
ENV NITRO_PORT=3000
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
bash
docker build -t mp-live-update-manager .
docker run -p 3000:3000 --env-file .env mp-live-update-manager

Environment Variables

VariableRequiredDefaultDescription
API_KEYYesAnti-DDoS key for all API requests
AUTH_USERNAMENoadminLogin username
AUTH_PASSWORDYesLogin password
JWT_SECRETYesHS256 signing key
JWT_EXPIRES_INNo24hToken expiration
AWS_ACCESS_KEY_IDYesAWS credentials
AWS_SECRET_ACCESS_KEYYesAWS credentials
AWS_REGIONNoeu-north-1AWS region
S3_BUCKETNomp-cdn-filesS3 bucket name
S3_PREFIXNolive-updateS3 key prefix
CLOUDFRONT_DISTRIBUTION_IDYesCloudFront distribution for invalidation
CDN_BASE_URLNohttps://cdn.medipal.devCDN base URL for bundle URLs
GIT_REPO_URLYesRepository to clone for builds
GIT_TOKENNoGitHub token for authenticated clones
NPM_TOKENNonpm registry token
GIGET_TOKENNoGiget token
BUILD_API_URLNohttp://localhost:8000API URL injected into builds
BUILD_CRYPTO_KEYNoCrypto key injected into builds

See Also