CLAUDE.md - app-portal
This file provides guidance to Claude Code when working with the app-portal Backstage application.
🆕 Updated for Backstage v1.42.0 with New Frontend System and New Backend System
Prerequisites​
# Install required tools
brew install node@20 direnv sops age
Quick Start​
# Enter directory (auto-loads environment and decrypts secrets)
cd app-portal
direnv allow
# Install and start
yarn install
yarn start
Frontend: http://localhost:3000
Backend: http://localhost:7007
Secret Management​
This project uses SOPS for secret encryption with SSH keys:
- Encrypted files:
.env.enc- GitHub App credentialsgithub-app-key.pem.enc- GitHub App private key
- Auto-decryption: Via direnv when entering directory
- SSH-based: Uses your existing SSH key from GitHub
Adding Team Members​
# Get their SSH public key
ssh-add -L # They run this
# Add to .sops.yaml, then:
sops updatekeys .env.enc
sops updatekeys github-app-key.pem.enc
Development Commands​
# Start development server (auto-detects cluster from kubectl context)
yarn start # Loads app-config.{cluster}.local.yaml automatically
yarn start:log # Same as above, with timestamped logging
yarn start --log # Alternative syntax for logging
# Installation
yarn install
yarn install:log # With timestamped logging (Unix/Linux/macOS only)
# Build for production
yarn build:backend
yarn build:all
# Testing
yarn test
yarn test:all
yarn test:e2e
# Linting and formatting
yarn lint
yarn lint:all
yarn prettier:check
yarn fix
# Clean build artifacts
yarn clean
# Create new plugin
yarn new
Dynamic Start Script​
The yarn start command uses a Node.js script (start.js in root) that:
- Detects current kubectl context via
kubectl config current-context - Extracts cluster name from context (multiple contexts can share same cluster)
- Looks for cluster-specific config:
app-config.{cluster}.local.yaml - Automatically loads both base and cluster configs
- Shows which configuration is being used
- Falls back to context-based config for backward compatibility
- Falls back gracefully if no context or config found
Example:
- Context:
osp-openportal→ Cluster:openportal→ Loads:app-config.openportal.local.yaml - Context:
osp-openportal-oidc→ Cluster:openportal→ Loads:app-config.openportal.local.yaml - Context:
rancher-desktop→ Cluster:rancher-desktop→ Loads:app-config.rancher-desktop.local.yaml
Key Benefit: Multiple contexts with different authentication methods can share the same configuration when connecting to the same cluster.
Logging Support​
The start script supports logging via --log flag:
- Creates timestamped log files in
./logs/directory - Custom location:
BACKSTAGE_LOG_DIR=/path yarn start:log - Captures both stdout and stderr for debugging
🆕 New Frontend System Architecture (v1.42.0)​
This app uses Backstage's New Frontend System with significant architecture changes:
Key Differences from Legacy System​
| Aspect | Legacy System | New Frontend System |
|---|---|---|
| Main Import | @backstage/app-defaults | @backstage/frontend-defaults |
| Plugin Loading | plugins: [plugin] | features: [pluginAlpha] |
| Plugin Exports | Default exports | Alpha subpath exports (/alpha) |
| App Creation | createApp({...}).createRoot(<jsx>) | createApp({features}).createRoot() |
| Extensions | Limited customization | Full extension system |
| Route Binding | Manual JSX routes | Extension-based routing |
New Frontend System Features​
- Automatic Plugin Discovery: via
app.packages: all - Extension System: PageBlueprint, SignInPageBlueprint, etc.
- Alpha Plugin Exports: All plugins use
/alphasubpath exports - Modern Architecture: Clean separation with extension tree
Frontend Structure (New System)​
// packages/app/src/App.tsx
import { createApp } from '@backstage/frontend-defaults';
const app = createApp({
features: [
// All plugins use /alpha exports
catalogPlugin,
scaffolderPlugin,
searchPlugin,
// ... other plugins
],
});
export default app.createRoot();
🆕 New Backend System (v1.42.0)​
The backend uses the New Backend System with:
Backend Architecture Changes​
| Component | Legacy | New System |
|---|---|---|
| Plugin Registration | Manual router setup | backend.add(import('plugin')) |
| Modules | Custom setup | createBackendModule() |
| Plugin Loading | Individual imports | Automatic discovery |
| Config Structure | clusterLocatorMethods only | serviceLocatorMethod + clusterLocatorMethods |
Backend Structure (New System)​
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
// Core plugins
backend.add(import('@backstage/plugin-app-backend'));
backend.add(import('@backstage/plugin-scaffolder-backend'));
// Custom modules
backend.add(import('./scaffolder')); // Custom scaffolder actions
backend.start();
Custom Backend Modules (New System)​
Custom scaffolder actions are now modules:
// packages/backend/src/scaffolder/index.ts
const scaffolderModuleCustomActions = createBackendModule({
pluginId: 'scaffolder',
moduleId: 'custom-actions',
register(reg) {
reg.registerInit({
deps: { scaffolder: scaffolderActionsExtensionPoint },
async init({ scaffolder }) {
scaffolder.addActions(createGenerateIdAction());
},
});
},
});
Project Structure​
app-portal/
├── packages/
│ ├── app/ # Frontend React application (New Frontend System)
│ │ ├── src/
│ │ │ ├── App.tsx # Main app component (New System)
│ │ │ └── components/ # UI components
│ │ └── package.json
│ └── backend/ # Backend Node.js services (New Backend System)
│ ├── src/
│ │ ├── index.ts # Backend entry point (New System)
│ │ └── scaffolder/ # Custom scaffolder actions (New Module System)
│ │ ├── index.ts # Module registration
│ │ └── generateId.ts # Custom action implementation
│ └── package.json
├── plugins/ # Custom Backstage plugins
│ ├── crossplane-ingestor/ # Advanced Crossplane XRD discovery (16k+ lines)
│ │ ├── src/ # Source code
│ │ ├── tests/ # Comprehensive test suite
│ │ └── docs/ # Detailed documentation
│ └── ... # Other custom plugins
├── docs/ # Application documentation
│ ├── modular-config.md # Modular configuration guide
│ └── crossplane-ingestor.md # Crossplane ingestor guide
├── app-config/ # Modular configuration directory (NEW!)
│ ├── auth.yaml # Authentication providers
│ ├── backend.yaml # Backend settings
│ ├── catalog.yaml # Catalog configuration
│ ├── ingestor.yaml # Ingestor plugins config
│ ├── integrations.yaml # SCM integrations
│ ├── kubernetes.yaml # K8s clusters
│ ├── scaffolder.yaml # Scaffolder settings
│ └── techdocs.yaml # Documentation platform
├── examples/ # Example data for development
│ ├── entities.yaml # Example catalog entities
│ ├── org.yaml # Example users/groups
│ └── template/ # Example template
├── app-config.yaml # Base configuration (legacy/reference)
├── app-config.production.yaml # Production overrides
├── app-config.local.yaml # Local overrides (gitignored) with serviceLocatorMethod
├── .envrc # Direnv config (auto-loads secrets)
├── .sops.yaml # SOPS encryption config
└── backstage.json # Backstage version (v1.42.0+)
Configuration​
🆕 Modular Configuration Architecture​
Configuration is now split into focused modules in the app-config/ directory:
# The start.js script loads all modules automatically:
yarn start # Loads: app-config.yaml + app-config/*.yaml + app-config.{cluster}.local.yaml
Configuration Modules:
auth.yaml- Authentication providers (GitHub, GitLab, OAuth)backend.yaml- Backend settings (ports, CORS, database)catalog.yaml- Catalog providers and locationsingestor.yaml- Kubernetes and Crossplane ingestorsintegrations.yaml- SCM integrationskubernetes.yaml- Cluster connectionsscaffolder.yaml- Template settingstechdocs.yaml- Documentation platform
See Modular Configuration Guide for details.
New Backend System Requirements​
The kubernetes section in app-config.yaml requires serviceLocatorMethod for the New Backend System:
kubernetes:
serviceLocatorMethod:
type: 'multiTenant'
clusterLocatorMethods:
- type: 'config'
clusters:
- url: ${KUBERNETES_API_URL}
name: ${KUBERNETES_CLUSTER_NAME}
authProvider: 'serviceAccount'
skipTLSVerify: true
serviceAccountToken: ${KUBERNETES_SERVICE_ACCOUNT_TOKEN}
GitHub App Integration​
The GitHub App provides:
- Repository discovery
- Template scaffolding
- Authentication
Credentials are stored encrypted in .env.enc.
Catalog Providers​
-
GitHub Organization Scanner
- Org:
open-service-portal - Frequency: 30 minutes
- Org:
-
Template Discovery
- Pattern:
service-*-templaterepositories - Auto-imports templates from GitHub
- Pattern:
Authentication​
- Development: GitHub OAuth + Guest auth
- Production: GitHub OAuth only
Custom Features​
🆕 Crossplane Ingestor Plugin​
A comprehensive Crossplane integration plugin with 16,000+ lines of production code:
Features:
- Discovers XRDs from multiple Kubernetes clusters
- Generates Backstage template entities automatically
- Creates API documentation entities
- Tracks Composition relationships
- Includes CLI tools for debugging and testing
- Configuration-driven templates from
app-config/ingestor.yaml - GitOps workflow support with PR-based resource creation
- Auto-detection of cluster name from kubectl context for targeting
CLI Tools:
cd plugins/crossplane-ingestor
yarn cli discover --cluster local # Discover XRDs
yarn cli transform --xrd ./xrd.yaml # Transform XRD to template
yarn cli export --cluster local # Export all entities
yarn cli validate --xrd ./xrd.yaml # Validate XRD compatibility
Template System:
- Unified templates: Single
default.hbshandles both namespaced and cluster-scoped resources usingxrd.spec.scopedetection - GitOps workflow:
gitops.hbsstep template for PR-based deployment - Configuration validation: Fails fast with helpful messages if required config is missing
- HTML escaping fix: Use
{{{...}}}(triple braces) for Backstage variables to prevent HTML entity encoding
Configuration: See app-config/ingestor.yaml for settings including GitOps repository configuration.
Documentation: See Crossplane Ingestor Guide and plugins/crossplane-ingestor/docs/ for detailed documentation.
Scaffolder Actions (New Module System)​
Custom action for unique ID generation:
- Location:
packages/backend/src/scaffolder/generateId.ts - Module:
packages/backend/src/scaffolder/index.ts - Registration: Via New Backend System in
packages/backend/src/index.ts - Action ID:
portal:utils:generateId - Functionality: Generates unique resource identifiers (hex or alphanumeric)
Test Custom Actions:
- Visit: http://localhost:3000/create/actions
- Look for:
portal:utils:generateId - Verify action appears with correct input/output schema
Integrations​
- GitHub (primary) - Repository discovery and authentication
- Kubernetes (configured in app-config.local.yaml with New Backend System)
- TechDocs (local builder)
- TeraSky Crossplane Plugin (@terasky/backstage-plugin-crossplane-resources-frontend)
- TeraSky Scaffolder Utils (@terasky/backstage-plugin-scaffolder-backend-module-terasky-utils)
Ingestor Plugins​
The backend includes two internal ingestor plugins:
kubernetes-ingestor- Legacy internal versioncrossplane-ingestor- Refactored Crossplane-focused version
These can be selected via the ingestorSelector config in app-config/ingestor.yaml.
Adding External Ingestors: Additional ingestor plugins can be installed via NPM:
# Example: Install a published ingestor plugin
yarn add @organization/backstage-plugin-some-ingestor
Then add to packages/backend/src/index.ts:
backend.add(import('@organization/backstage-plugin-some-ingestor'));
Troubleshooting​
Secrets not loading​
# Check if SSH key is loaded
ssh-add -L
# If "The agent has no identities", add your SSH key:
ssh-add ~/.ssh/id_ed25519 # or ~/.ssh/id_rsa
# Manually test decryption
sops -d --input-type dotenv --output-type dotenv .env.enc
# Re-allow direnv
direnv allow
SSH key with passphrase​
If using a passphrase-protected SSH key, you'll be prompted when entering the directory:
- Enter the passphrase once per terminal session
- Or add key to ssh-agent:
ssh-add ~/.ssh/id_ed25519 - Alternative: Use a dedicated key without passphrase for development
New Frontend System Issues​
Plugin not found:
- Check if plugin has
/alphaexport - Verify plugin is included in
featuresarray inApp.tsx - Check
app-config.yamlhaspackages: all
Extension errors:
- Verify extension imports use correct paths
- Check PageBlueprint and SignInPageBlueprint usage
- Ensure proper extension tree structure
New Backend System Issues​
Module registration errors:
- Check
createBackendModulesyntax in custom modules - Verify extension points are imported correctly
- Ensure modules are added via
backend.add(import('./module'))
Config errors:
- Verify new backend config structure (kubernetes
serviceLocatorMethod) - Check module IDs don't conflict
- Ensure proper dependency injection
Custom scaffolder action not appearing:
- Check module registration in
packages/backend/src/index.ts - Verify action ID is unique:
portal:utils:generateId - Check action schema and handler implementation
- Visit http://localhost:3000/create/actions to verify registration
Build issues​
# Clean and rebuild (New System compatible)
yarn clean
yarn install
yarn build:backend
Port conflicts​
Default ports:
- Frontend: 3000
- Backend: 7007
Change in app-config.yaml if needed.
Testing​
# Unit tests
yarn test
# With coverage
yarn test:all
# E2E tests (requires running app)
yarn test:e2e
Testing Custom Actions​
-
Start the app:
yarn start -
Verify action registration:
- Visit: http://localhost:3000/create/actions
- Search for:
portal:utils:generateId - Check input schema:
length(number, default: 8),type(enum: hex/alphanumeric) - Check output schema:
id(string)
-
Test in template:
steps:
- id: generate-id
name: Generate ID
action: portal:utils:generateId
input:
length: 12
type: hex
Migration Notes​
From Legacy to New Frontend System​
This app was migrated from Legacy System to New Frontend System v1.42.0:
Completed:
- ✅ Frontend migration to New Frontend System
- ✅ Backend migration to New Backend System
- ✅ Custom scaffolder actions ported to module system
- ✅ All plugins using
/alphaexports - ✅ Extension system implemented (PageBlueprint, SignInPageBlueprint)
- ✅ Plugin discovery enabled via
packages: all - ✅ TeraSky plugins integrated
Key Migration Changes:
App.tsxcompletely rewritten for New Frontend System- All plugin imports changed to
/alphasubpaths - Backend modules converted to New Backend System with
createBackendModule() - Configuration updated for new backend requirements (
serviceLocatorMethod) - Custom scaffolder actions restructured as backend modules
Breaking Changes Handled:
- Plugin loading mechanism completely changed
- Extension system replaces manual JSX routing
- Backend plugin registration simplified
- Config structure updated for kubernetes integration
Deployment​
See deploy-backstage repository for Kubernetes deployment.
Contributing​
- Create feature branch from
main - Make changes following New Frontend/Backend System patterns
- Run tests:
yarn test - Run linter:
yarn lint - Test custom actions: Visit http://localhost:3000/create/actions
- Create PR with semantic commit message
Related Repositories​
- portal-workspace - Parent workspace with documentation
- service-nodejs-template - Node.js service template
- deploy-backstage - Kubernetes deployment (coming soon)