Automate Gitea init, NocoDB auto sign-in, and fix prod compose
- Add scripts/gitea-init.sh: runs migrations + creates admin user on first boot, replacing the manual installation wizard - Set GITEA__security__INSTALL_LOCK=true in both compose files - Add NocoDB auth bridge (nginx) + /api/services/nocodb-auth proxy endpoint so the admin iframe auto-authenticates - Update NocoDBPage.tsx to fetch token and use auth bridge flow - Fix docker-compose.prod.yml missing Gitea env vars for API container (GITEA_URL, GITEA_API_TOKEN, GITEA_ADMIN_PASSWORD, etc.) - Pass NC_ADMIN_EMAIL/PASSWORD to API for NocoDB auth proxy - Increase Gitea auto-setup retries from 3 to 6 with admin auth check - Update config.sh non-interactive mode to set GITEA_ADMIN_USER - Include gitea-init.sh in release tarball (build-release.sh) Bunker Admin
This commit is contained in:
parent
0a8e1fe46b
commit
36b709b911
@ -222,6 +222,8 @@ GITEA_URL=http://gitea-changemaker:3000
|
||||
GITEA_PORT=3030
|
||||
GITEA_WEB_PORT=3030
|
||||
GITEA_SSH_PORT=2222
|
||||
# Admin user (auto-created on first boot by gitea-init.sh)
|
||||
GITEA_ADMIN_USER=admin
|
||||
GITEA_DB_TYPE=mysql
|
||||
GITEA_DB_HOST=gitea-db:3306
|
||||
GITEA_DB_NAME=gitea
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import { useOutletContext } from 'react-router-dom';
|
||||
import { Button, Space, Badge, Spin, Grid, Result, Alert } from 'antd';
|
||||
import { ReloadOutlined, LinkOutlined, DatabaseOutlined } from '@ant-design/icons';
|
||||
@ -7,8 +7,6 @@ import type { AppOutletContext } from '@/components/AppLayout';
|
||||
import type { ServicesStatus, ServicesConfig } from '@/types/api';
|
||||
import { buildServiceUrl } from '@/lib/service-url';
|
||||
|
||||
const BANNER_DISMISSED_KEY = 'nocodb-auth-banner-dismissed';
|
||||
|
||||
export default function NocoDBPage() {
|
||||
const { setPageHeader } = useOutletContext<AppOutletContext>();
|
||||
const screens = Grid.useBreakpoint();
|
||||
@ -17,9 +15,9 @@ export default function NocoDBPage() {
|
||||
const [online, setOnline] = useState<boolean | null>(null);
|
||||
const [config, setConfig] = useState<ServicesConfig | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [bannerDismissed, setBannerDismissed] = useState(
|
||||
() => localStorage.getItem(BANNER_DISMISSED_KEY) === 'true'
|
||||
);
|
||||
const [iframeSrc, setIframeSrc] = useState<string | null>(null);
|
||||
const [authFailed, setAuthFailed] = useState(false);
|
||||
const authAttempted = useRef(false);
|
||||
|
||||
const fetchStatus = useCallback(async () => {
|
||||
try {
|
||||
@ -44,7 +42,31 @@ export default function NocoDBPage() {
|
||||
? buildServiceUrl(config.nocodbSubdomain, config.domain, config.nocodbPort)
|
||||
: null;
|
||||
|
||||
// Auto sign-in: fetch NocoDB auth token and navigate iframe to auth bridge
|
||||
useEffect(() => {
|
||||
if (!serviceUrl || !online || authAttempted.current) return;
|
||||
authAttempted.current = true;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const res = await api.get<{ token: string }>('/services/nocodb-auth');
|
||||
if (res.data.token) {
|
||||
// Navigate iframe to auth bridge (same origin as NocoDB) which sets the cookie
|
||||
setIframeSrc(`${serviceUrl}/auth-bridge#${res.data.token}`);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Auth endpoint unavailable — fall back to direct URL
|
||||
}
|
||||
setAuthFailed(true);
|
||||
setIframeSrc(serviceUrl);
|
||||
})();
|
||||
}, [serviceUrl, online]);
|
||||
|
||||
const handleRefresh = useCallback(() => {
|
||||
authAttempted.current = false;
|
||||
setIframeSrc(null);
|
||||
setAuthFailed(false);
|
||||
fetchStatus();
|
||||
}, [fetchStatus]);
|
||||
|
||||
@ -115,11 +137,11 @@ export default function NocoDBPage() {
|
||||
|
||||
return (
|
||||
<div style={{ height: 'calc(100vh - 64px)', display: 'flex', flexDirection: 'column' }}>
|
||||
{!bannerDismissed && (
|
||||
{authFailed && (
|
||||
<Alert
|
||||
message={
|
||||
<>
|
||||
If the database browser appears blank, you may need to{' '}
|
||||
Auto sign-in unavailable. You may need to{' '}
|
||||
<a href={serviceUrl} target="_blank" rel="noopener noreferrer">
|
||||
sign in to NocoDB in a new tab
|
||||
</a>{' '}
|
||||
@ -129,23 +151,26 @@ export default function NocoDBPage() {
|
||||
type="info"
|
||||
showIcon
|
||||
closable
|
||||
onClose={() => {
|
||||
setBannerDismissed(true);
|
||||
localStorage.setItem(BANNER_DISMISSED_KEY, 'true');
|
||||
}}
|
||||
onClose={() => setAuthFailed(false)}
|
||||
style={{ borderRadius: 0, flexShrink: 0 }}
|
||||
/>
|
||||
)}
|
||||
<iframe
|
||||
src={serviceUrl}
|
||||
style={{
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
border: 'none',
|
||||
display: 'block',
|
||||
}}
|
||||
title="NocoDB"
|
||||
/>
|
||||
{iframeSrc ? (
|
||||
<iframe
|
||||
src={iframeSrc}
|
||||
style={{
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
border: 'none',
|
||||
display: 'block',
|
||||
}}
|
||||
title="NocoDB"
|
||||
/>
|
||||
) : (
|
||||
<div style={{ textAlign: 'center', padding: 80 }}>
|
||||
<Spin size="large" tip="Signing in to NocoDB..." />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -96,6 +96,8 @@ const envSchema = z.object({
|
||||
|
||||
// Platform Services (NocoDB, n8n, Gitea)
|
||||
NOCODB_URL: z.string().default('http://changemaker-v2-nocodb:8080'),
|
||||
NC_ADMIN_EMAIL: z.string().default(''),
|
||||
NC_ADMIN_PASSWORD: z.string().default(''),
|
||||
NOCODB_PORT: z.coerce.number().default(8091),
|
||||
NOCODB_EMBED_PORT: z.coerce.number().default(8881),
|
||||
N8N_URL: z.string().default('http://n8n-changemaker:5678'),
|
||||
|
||||
@ -393,17 +393,26 @@ async function autoSetupIfNeeded(): Promise<{ alreadyComplete: boolean; success:
|
||||
// DB might not be ready yet
|
||||
}
|
||||
|
||||
// Wait for Gitea to be available (up to 3 retries, 15s apart)
|
||||
// Wait for Gitea to be available and admin user to exist (up to 6 retries, 10s apart).
|
||||
// The gitea-init.sh script creates the admin user after migrations,
|
||||
// so we need to wait for both Gitea web AND admin auth to be ready.
|
||||
let giteaReady = false;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
for (let i = 0; i < 6; i++) {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||
try {
|
||||
// Check if Gitea is online
|
||||
const res = await fetch(`${env.GITEA_URL}/api/v1/version`, { signal: controller.signal });
|
||||
if (res.ok) {
|
||||
giteaReady = true;
|
||||
break;
|
||||
// Also verify admin auth works (user may not exist yet if init script is still running)
|
||||
try {
|
||||
await giteaBasicRequest<{ login: string }>('GET', '/user', 'admin', password);
|
||||
giteaReady = true;
|
||||
break;
|
||||
} catch {
|
||||
// Admin user not ready yet — gitea-init.sh may still be running
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
@ -411,14 +420,14 @@ async function autoSetupIfNeeded(): Promise<{ alreadyComplete: boolean; success:
|
||||
} catch {
|
||||
// Not ready yet
|
||||
}
|
||||
if (i < 2) {
|
||||
logger.info(`Gitea auto-setup: waiting for Gitea to be ready (attempt ${i + 1}/3)...`);
|
||||
await new Promise(r => setTimeout(r, 15000));
|
||||
if (i < 5) {
|
||||
logger.info(`Gitea auto-setup: waiting for Gitea + admin user (attempt ${i + 1}/6)...`);
|
||||
await new Promise(r => setTimeout(r, 10000));
|
||||
}
|
||||
}
|
||||
|
||||
if (!giteaReady) {
|
||||
return { alreadyComplete: false, success: false, error: 'Gitea not reachable after 3 attempts' };
|
||||
return { alreadyComplete: false, success: false, error: 'Gitea not reachable or admin user not ready after 6 attempts' };
|
||||
}
|
||||
|
||||
// Run setup
|
||||
|
||||
@ -64,6 +64,48 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// GET /api/services/nocodb-auth — proxy NocoDB signin to get an auth token for iframe auto-login
|
||||
router.get(
|
||||
'/nocodb-auth',
|
||||
async (_req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
if (!env.NC_ADMIN_EMAIL || !env.NC_ADMIN_PASSWORD) {
|
||||
res.status(503).json({ error: 'NocoDB admin credentials not configured' });
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||
try {
|
||||
const response = await fetch(`${env.NOCODB_URL}/api/v1/auth/user/signin`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: env.NC_ADMIN_EMAIL, password: env.NC_ADMIN_PASSWORD }),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
res.status(502).json({ error: 'NocoDB authentication failed' });
|
||||
return;
|
||||
}
|
||||
|
||||
const data = (await response.json()) as { token?: string };
|
||||
if (!data.token) {
|
||||
res.status(502).json({ error: 'No token in NocoDB response' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({ token: data.token });
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('NocoDB auth proxy failed', err);
|
||||
next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// GET /api/services/config — return public-facing port numbers + subdomain info for iframe URLs
|
||||
router.get(
|
||||
'/config',
|
||||
|
||||
16
config.sh
16
config.sh
@ -852,19 +852,29 @@ configure_features() {
|
||||
|
||||
if [[ "$NON_INTERACTIVE" == "false" ]]; then
|
||||
echo ""
|
||||
info "Gitea auto-setup will create the API token, repos, and OAuth app automatically."
|
||||
info "You need to provide the Gitea admin password (set during Gitea's first-run install)."
|
||||
info "Gitea will be auto-initialized on first boot (no manual install wizard)."
|
||||
info "The admin user and docs comment system will be configured automatically."
|
||||
info "Provide a password for the Gitea admin account:"
|
||||
echo ""
|
||||
|
||||
read -srp " Gitea admin password [leave blank to set up later via admin GUI]: " gitea_admin_pw
|
||||
echo ""
|
||||
if [[ -n "$gitea_admin_pw" ]]; then
|
||||
update_env_var "GITEA_ADMIN_USER" "admin"
|
||||
update_env_var "GITEA_ADMIN_PASSWORD" "$gitea_admin_pw"
|
||||
update_env_var "GITEA_COMMENTS_REPO_OWNER" "admin"
|
||||
success "Gitea admin password saved — auto-setup will run on next start"
|
||||
success "Gitea admin user + docs comment auto-setup configured"
|
||||
else
|
||||
info "No password provided. Run Gitea Setup from the admin GUI after first start."
|
||||
fi
|
||||
else
|
||||
# Non-interactive: reuse admin password for Gitea
|
||||
local gitea_pw="${NI_ADMIN_PASSWORD:-}"
|
||||
if [[ -n "$gitea_pw" ]]; then
|
||||
update_env_var "GITEA_ADMIN_USER" "admin"
|
||||
update_env_var "GITEA_ADMIN_PASSWORD" "$gitea_pw"
|
||||
update_env_var "GITEA_COMMENTS_REPO_OWNER" "admin"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
DOCS_COMMENTS_ENABLED="no"
|
||||
|
||||
@ -128,6 +128,16 @@ services:
|
||||
- GITEA_REGISTRY=${GITEA_REGISTRY:-gitea.bnkops.com/admin}
|
||||
- GITEA_REGISTRY_USER=${GITEA_REGISTRY_USER:-}
|
||||
- GITEA_REGISTRY_PASS=${GITEA_REGISTRY_PASS:-}
|
||||
# Gitea (docs comments, version history, auto-setup)
|
||||
- GITEA_URL=${GITEA_URL:-http://gitea-changemaker:3000}
|
||||
- GITEA_API_TOKEN=${GITEA_API_TOKEN:-}
|
||||
- GITEA_ADMIN_PASSWORD=${GITEA_ADMIN_PASSWORD:-}
|
||||
- GITEA_DOCS_REPO=${GITEA_DOCS_REPO:-admin/changemaker.lite}
|
||||
- GITEA_DOCS_PREFIX=${GITEA_DOCS_PREFIX:-mkdocs/docs}
|
||||
- GITEA_DOCS_BRANCH=${GITEA_DOCS_BRANCH:-v2}
|
||||
# NocoDB credentials (for auto sign-in proxy)
|
||||
- NC_ADMIN_EMAIL=${NC_ADMIN_EMAIL:-admin@cmlite.org}
|
||||
- NC_ADMIN_PASSWORD=${NC_ADMIN_PASSWORD:-}
|
||||
# GeoIP (MaxMind GeoLite2)
|
||||
- MAXMIND_ACCOUNT_ID=${MAXMIND_ACCOUNT_ID:-}
|
||||
- MAXMIND_LICENSE_KEY=${MAXMIND_LICENSE_KEY:-}
|
||||
@ -688,11 +698,19 @@ services:
|
||||
- GITEA__service__ENABLE_REVERSE_PROXY_EMAIL=false
|
||||
- GITEA__service__REVERSE_PROXY_AUTHENTICATION_HEADER=X-WEBAUTH-USER
|
||||
- GITEA__service__REQUIRE_SIGNIN_VIEW=true
|
||||
# Skip installation wizard — admin user created by gitea-init.sh
|
||||
- GITEA__security__INSTALL_LOCK=true
|
||||
# Admin user creation (used by gitea-init.sh on first boot)
|
||||
- GITEA_ADMIN_USER=${GITEA_ADMIN_USER:-admin}
|
||||
- GITEA_ADMIN_PASSWORD=${GITEA_ADMIN_PASSWORD:-}
|
||||
- GITEA_ADMIN_EMAIL=${INITIAL_ADMIN_EMAIL:-admin@cmlite.org}
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- gitea-data:/data
|
||||
- ./scripts/gitea-init.sh:/custom/gitea-init.sh:ro
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
command: ["/bin/sh", "/custom/gitea-init.sh"]
|
||||
ports:
|
||||
- "127.0.0.1:${GITEA_WEB_PORT:-3030}:3000"
|
||||
- "127.0.0.1:${GITEA_SSH_PORT:-2222}:22"
|
||||
|
||||
@ -136,6 +136,9 @@ services:
|
||||
- GITEA_DOCS_REPO=${GITEA_DOCS_REPO:-admin/changemaker.lite}
|
||||
- GITEA_DOCS_PREFIX=${GITEA_DOCS_PREFIX:-mkdocs/docs}
|
||||
- GITEA_DOCS_BRANCH=${GITEA_DOCS_BRANCH:-v2}
|
||||
# NocoDB credentials (for auto sign-in proxy)
|
||||
- NC_ADMIN_EMAIL=${NC_ADMIN_EMAIL:-admin@cmlite.org}
|
||||
- NC_ADMIN_PASSWORD=${NC_ADMIN_PASSWORD:-}
|
||||
# GeoIP (MaxMind GeoLite2)
|
||||
- MAXMIND_ACCOUNT_ID=${MAXMIND_ACCOUNT_ID:-}
|
||||
- MAXMIND_LICENSE_KEY=${MAXMIND_LICENSE_KEY:-}
|
||||
@ -714,11 +717,19 @@ services:
|
||||
- GITEA__service__ENABLE_REVERSE_PROXY_EMAIL=false
|
||||
- GITEA__service__REVERSE_PROXY_AUTHENTICATION_HEADER=X-WEBAUTH-USER
|
||||
- GITEA__service__REQUIRE_SIGNIN_VIEW=true
|
||||
# Skip installation wizard — admin user created by gitea-init.sh
|
||||
- GITEA__security__INSTALL_LOCK=true
|
||||
# Admin user creation (used by gitea-init.sh on first boot)
|
||||
- GITEA_ADMIN_USER=${GITEA_ADMIN_USER:-admin}
|
||||
- GITEA_ADMIN_PASSWORD=${GITEA_ADMIN_PASSWORD:-}
|
||||
- GITEA_ADMIN_EMAIL=${INITIAL_ADMIN_EMAIL:-admin@cmlite.org}
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- gitea-data:/data
|
||||
- ./scripts/gitea-init.sh:/custom/gitea-init.sh:ro
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
command: ["/bin/sh", "/custom/gitea-init.sh"]
|
||||
ports:
|
||||
- "127.0.0.1:${GITEA_WEB_PORT:-3030}:3000"
|
||||
- "127.0.0.1:${GITEA_SSH_PORT:-2222}:22"
|
||||
|
||||
@ -78,6 +78,14 @@ server {
|
||||
server_name db.${DOMAIN};
|
||||
add_header Content-Security-Policy "frame-ancestors 'self' app.${DOMAIN}" always;
|
||||
|
||||
# Auth bridge for iframe auto-sign-in (token passed via URL hash, never sent to server)
|
||||
location = /auth-bridge {
|
||||
default_type text/html;
|
||||
add_header Cache-Control "no-store" always;
|
||||
add_header Content-Security-Policy "frame-ancestors 'self' app.${DOMAIN}" always;
|
||||
return 200 '<!DOCTYPE html><html><head><meta charset="utf-8"><script>(function(){var t=location.hash.substring(1);if(!t){document.body.innerText="No token";return;}document.cookie="nocodb-token="+encodeURIComponent(t)+";path=/;SameSite=Lax;max-age=86400";try{localStorage.setItem("nocodb-token",JSON.stringify(t));}catch(e){}window.location.replace("/dashboard/");})()</script></head><body>Signing in...</body></html>';
|
||||
}
|
||||
|
||||
location / {
|
||||
set $upstream_nocodb http://changemaker-v2-nocodb:8080;
|
||||
proxy_pass $upstream_nocodb;
|
||||
@ -287,6 +295,14 @@ server {
|
||||
|
||||
server {
|
||||
listen ${NOCODB_EMBED_PORT};
|
||||
|
||||
# Auth bridge for iframe auto-sign-in (localhost/dev variant)
|
||||
location = /auth-bridge {
|
||||
default_type text/html;
|
||||
add_header Cache-Control "no-store" always;
|
||||
return 200 '<!DOCTYPE html><html><head><meta charset="utf-8"><script>(function(){var t=location.hash.substring(1);if(!t){document.body.innerText="No token";return;}document.cookie="nocodb-token="+encodeURIComponent(t)+";path=/;SameSite=Lax;max-age=86400";try{localStorage.setItem("nocodb-token",JSON.stringify(t));}catch(e){}window.location.replace("/dashboard/");})()</script></head><body>Signing in...</body></html>';
|
||||
}
|
||||
|
||||
location / {
|
||||
set $upstream_nocodb http://changemaker-v2-nocodb:8080;
|
||||
proxy_pass $upstream_nocodb;
|
||||
|
||||
@ -103,7 +103,7 @@ cp "$PROJECT_DIR/api/prisma/init-nocodb-db.sh" "$STAGE_DIR/scripts/"
|
||||
cp "$PROJECT_DIR/api/prisma/init-gancio-db.sh" "$STAGE_DIR/scripts/"
|
||||
|
||||
# Runtime scripts
|
||||
for script in nocodb-init.sh mkdocs-entrypoint.sh backup.sh \
|
||||
for script in nocodb-init.sh gitea-init.sh mkdocs-entrypoint.sh backup.sh \
|
||||
upgrade.sh upgrade-check.sh upgrade-watcher.sh \
|
||||
uninstall.sh test-deployment.sh; do
|
||||
if [[ -f "$PROJECT_DIR/scripts/$script" ]]; then
|
||||
|
||||
54
scripts/gitea-init.sh
Executable file
54
scripts/gitea-init.sh
Executable file
@ -0,0 +1,54 @@
|
||||
#!/bin/sh
|
||||
# =============================================================================
|
||||
# Gitea Initialization Script
|
||||
# =============================================================================
|
||||
# Replaces the default CMD in the Gitea Docker container.
|
||||
# Runs database migrations, creates the admin user (if credentials are provided
|
||||
# and the user doesn't already exist), then starts the Gitea web server.
|
||||
#
|
||||
# This script is exec'd by /usr/bin/entrypoint, which has already:
|
||||
# - Set up UID/GID
|
||||
# - Created directories with correct permissions
|
||||
# - Converted GITEA__* env vars into /data/gitea/conf/app.ini
|
||||
# =============================================================================
|
||||
set -e
|
||||
|
||||
PREFIX="[gitea-init]"
|
||||
log() { echo "$PREFIX $1"; }
|
||||
|
||||
# --- Step 1: Run database migrations ---
|
||||
log "Running database migrations..."
|
||||
MIGRATE_OK=false
|
||||
for i in $(seq 1 10); do
|
||||
if gitea migrate 2>&1; then
|
||||
MIGRATE_OK=true
|
||||
log "Migrations complete"
|
||||
break
|
||||
fi
|
||||
log "Waiting for database... (attempt $i/10)"
|
||||
sleep 3
|
||||
done
|
||||
|
||||
if [ "$MIGRATE_OK" = false ]; then
|
||||
log "WARNING: Migrations may not have completed — starting anyway"
|
||||
fi
|
||||
|
||||
# --- Step 2: Create admin user if credentials provided ---
|
||||
if [ -n "$GITEA_ADMIN_USER" ] && [ -n "$GITEA_ADMIN_PASSWORD" ] && [ -n "$GITEA_ADMIN_EMAIL" ]; then
|
||||
log "Creating admin user '${GITEA_ADMIN_USER}'..."
|
||||
if gitea admin user create --admin \
|
||||
--username "$GITEA_ADMIN_USER" \
|
||||
--password "$GITEA_ADMIN_PASSWORD" \
|
||||
--email "$GITEA_ADMIN_EMAIL" \
|
||||
--must-change-password false 2>&1; then
|
||||
log "Admin user created successfully"
|
||||
else
|
||||
log "Admin user already exists (or creation skipped)"
|
||||
fi
|
||||
else
|
||||
log "No GITEA_ADMIN_USER/PASSWORD/EMAIL set — skipping admin creation"
|
||||
fi
|
||||
|
||||
# --- Step 3: Start Gitea web server ---
|
||||
log "Starting Gitea web server..."
|
||||
exec gitea web
|
||||
Loading…
x
Reference in New Issue
Block a user