Configuration
newmeta strictly separates configuration into versioned defaults (config.php) and local secrets (config.local.php). On top of that, the Nuxt theme reads a .env, and CORS, API keys, and 2FA are managed dynamically in the backend.
config.php vs. config.local.php
| File | Contents | Git |
|---|---|---|
config.php | systemVersion, directory constants, password salt, base URL derivation | versioned |
config.local.php | DB credentials, update-server credentials | git-ignored |
config.local.example.php | Template for new installs | versioned |
config.php loads config.local.php at the top:
// config.php (excerpt)
if (file_exists(__DIR__ . '/config.local.php')) {
require_once __DIR__ . '/config.local.php';
}
$GLOBALS['systemVersion'] = "3.0.0";
$GLOBALS['pw_salt'] = '...';
$GLOBALS['baseurl'] = 'https://' . $_SERVER['HTTP_HOST'];Never change pw_salt
$GLOBALS['pw_salt'] is part of every user password hash. If you change the salt after the fact, every login fails. On a new install, set a fresh salt once — then leave it alone.
baseurl is hardcoded https://
$GLOBALS['baseurl'] does not check $_SERVER['HTTPS'] — the scheme is fixed to https://. Local development over http:// still works, but features that use baseurl to build absolute links (emails, sitemap, Schema.org) will emit wrong URLs. For local work: either use an HTTPS vhost (e.g. mkcert) or override $GLOBALS['baseurl'] manually.
config.local.php
Minimal template (config.local.example.php):
<?php
$GLOBALS["db_host"] = "localhost";
$GLOBALS["db_user"] = "";
$GLOBALS["db_pw"] = "";
$GLOBALS["db_db"] = "";
$GLOBALS["update_user"] = "";
$GLOBALS["update_password"] = "";
$GLOBALS["update_base_url"] = "https://update.newmeta.de";Update-server credentials
The Update Manager under /admin/update pulls ZIP updates from the update server. Without credentials, Git-based workflows still work — only in-place updates are disabled.
$GLOBALS['update_user'] = 'username';
$GLOBALS['update_password'] = 'password';
$GLOBALS['update_base_url'] = 'https://update.newmeta.de';See the Update Manager section in the README; dedicated update docs are coming later.
Theme .env files
The Nuxt theme lives under _theme/vue-base/ and reads a .env on dev and build:
# _theme/vue-base/.env
NUXT_API_KEY=123456
NUXT_PUBLIC_API_BASE=http://newmeta.local/api
NUXT_PUBLIC_SITE_URL=http://newmeta.local
NUXT_PUBLIC_THEME=vue-baseIn production, set the API and site URLs to the public domain (e.g. https://www.example.com/api / https://www.example.com).
| Variable | Purpose |
|---|---|
NUXT_API_KEY | Internal key for server-to-server calls from Nuxt (e.g. SSR fetches) |
NUXT_PUBLIC_API_BASE | REST API base URL, exposed to the client |
NUXT_PUBLIC_SITE_URL | Canonical site URL (Open Graph, sitemap, Schema.org) |
NUXT_PUBLIC_THEME | Active theme name — controls alias resolution (see Themes › Nuxt aliases) |
PurgeCSS safelist
Project-specific classes can be added without forking nuxt.config.ts:
NUXT_PURGECSS_SAFELIST_STANDARD=ma-,foo- # exact prefix match
NUXT_PURGECSS_SAFELIST_GREEDY=bar- # contains match
NUXT_PURGECSS_SAFELIST_DEEP=baz- # deep selector matchEach prefix becomes a ^prefix regex and is merged with the defaults (^ns-, ^swiper, ^fa-, …).
CORS / allowed origins
CORS is dynamic — your own domain is always allowed, and additional origins are managed in the backend:
- Admin →
/admin/api-keys→ Allowed Origins tab - Add an origin (e.g.
http://localhost:3000for a Nuxt dev server against a remote backend) - Activate — no deployment step needed
Requests from unregistered origins are rejected globally — that includes public endpoints like /api/pages. The exact failure mode (CORS reject without body vs. HTTP status code) depends on where in the request the rejection happens.
Local without cross-origin
If you serve the Nuxt theme through the same Apache vhost as the API, you have no CORS problem. The dev server (localhost:3000) against a remote API host, on the other hand, always needs an origin entry.
API keys for external access
External tools authenticate via the X-Api-Key header:
curl -H "X-Api-Key: nscms_k1_your_key_here" https://your-domain.com/api/pages?view=startManaged under /admin/api-keys — each key has configurable endpoints, HTTP methods, expiry, and allowed origins. Keys are stored as SHA-256 hashes and shown only once on creation.
Webhook subscriptions, HMAC secrets, and delivery history live in the same panel — see Plugins › Webhook events.
2FA setup for admin logins
2FA (TOTP per RFC 6238) is mandatory for every backend login:
- First login → scan the QR code (Google Authenticator, 1Password, Bitwarden, Authy, Microsoft Authenticator, …)
- Enter the 6-digit code
- Save the eight recovery codes — shown only once
- Every subsequent login additionally requires the TOTP code
The code comparison is timing-safe via hash_equals() — see _core/system/auth/TotpAuth.php::verifyCode(). The pending-2FA session expires after 5 minutes (auth/model.php::verify2fa()).
Brute-force protection: 2FA verification shares the login-attempts counter (loginattempts table, IP-based, 15-attempt limit) with the password check. A successful login clears the counter.
Keep your recovery codes
If an admin loses the second factor and the recovery codes, only a manual DB edit in user_recovery_codes can recover access. There is deliberately no web-UI reset.
Rate limiting
Rate limits are enforced via APCu — token bucket per identifier (backend user ID, API key, or IP), no DB load. The limiter is the first check in the request lifecycle and shields the database under load.
- Global limits: frontend 100 req/min, backend 250 req/min, API key 60 req/min
- Endpoint-specific: stricter per-IP limits on sensitive endpoints (
auth10 attempts,checkout10/min,elearning/certificate5/min) - Configuration:
_core/system/api/Ratelimiter.php($endpointLimitsarray) - Standard headers:
X-RateLimit-Limit,X-RateLimit-Remaining,Retry-After(on 429) - Graceful degradation: without APCu, the limiter silently returns
allowed:true— requests are not blocked
Response cache
Anonymous GET responses are cached in APCu (X-Cache: HIT|MISS header). Configuration lives in the cache_settings backend plugin — enable/disable caching, set the TTL, and clear the cache from there.
Personalized requests (logged-in users via $_SESSION['userid'], backend users via $_SESSION['backend_loggedin'], API-key requests via HTTP_X_API_KEY) are never cached — that's hardcoded. In addition, endpoint-level exclusions live in _public/extensions/core/backend/cache_settings/config/cache_exclude_endpoints.json (prefix match). All backend/* endpoints are excluded from the cache globally.
See also
- Installation — first-time setup, Apache, database import
- Directory Structure — what lives where
- Themes › Environment variables — all Nuxt env variables in detail
- Plugins › API endpoints — API auth and CORS from a plugin's perspective