mirror of
https://github.com/saltbo/zpan.git
synced 2026-08-31 02:04:40 +08:00
af43701daf
* fix(version): resolve app version at runtime in node entry E2E runs the Node server via tsx, which bypasses the tsup build-time define, leaving __ZPAN_APP_VERSION__ unset so getAppVersion throws and /api/licensing/pair returns 500. Resolve the version at runtime via resolveAppVersion (git describe) when the global is unset; in the built output the define inlines the constant, so the branch is never reached and git is not invoked in production. Keeps the git-describe version as the single source of truth across all paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(version): inject app version into docker build The Docker build excludes .git from its context (.dockerignore), so the build-time git describe in build:node would throw and break the image build. Let resolveAppVersion read ZPAN_APP_VERSION, set it from an APP_VERSION build arg in the builder stage, and pass the release tag from the release workflow. git describe stays the default everywhere else, so the version is unified across CF Workers, tsx, and Docker. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
116 lines
4.5 KiB
TypeScript
116 lines
4.5 KiB
TypeScript
import { release as osRelease } from 'node:os'
|
|
import { serve } from '@hono/node-server'
|
|
import { serveStatic } from '@hono/node-server/serve-static'
|
|
import { Hono } from 'hono'
|
|
import { resolveAppVersion } from '../scripts/app-version.mjs'
|
|
import { ZPAN_CLOUD_URL_DEFAULT } from '../shared/constants'
|
|
import { createBootstrap } from './bootstrap'
|
|
import { buildCloudInstanceInfo } from './licensing/instance-info'
|
|
import { createLibsqlPlatform } from './platform/libsql'
|
|
import { createNodePlatform } from './platform/node'
|
|
import { syncPendingCloudTrafficReports } from './services/cloud-traffic-metering'
|
|
import { INSTANCE_TELEMETRY_CRON, reportInstanceTelemetry } from './services/instance-telemetry'
|
|
import { runLicensingRefresh } from './services/licensing-refresh-runner'
|
|
import { syncPendingRemoteDownloadUsageReports } from './services/remote-download-usage'
|
|
import { getSitePublicOrigin } from './services/site-public-origin'
|
|
|
|
const REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000 // 6 hours
|
|
const TRAFFIC_SYNC_INTERVAL_MS = 10 * 60 * 1000 // 10 minutes
|
|
const INSTANCE_TELEMETRY_INTERVAL_MS = 12 * 60 * 60 * 1000 // 12 hours
|
|
const appVersionGlobalKey = '__ZPAN_APP_VERSION__'
|
|
|
|
// tsx runs this entry directly (dev + E2E) without the tsup build-time define,
|
|
// so resolve the version at runtime. In the built output the define inlines the
|
|
// constant, turning this condition into `if (false)`, so resolveAppVersion is
|
|
// never reached and git is never invoked in production. The assignment uses
|
|
// bracket access so the define does not rewrite it into an invalid literal LHS.
|
|
if (!globalThis.__ZPAN_APP_VERSION__) {
|
|
globalThis[appVersionGlobalKey] = resolveAppVersion()
|
|
}
|
|
|
|
const platform = process.env.TURSO_DATABASE_URL
|
|
? await createLibsqlPlatform({
|
|
TURSO_DATABASE_URL: process.env.TURSO_DATABASE_URL,
|
|
TURSO_AUTH_TOKEN: process.env.TURSO_AUTH_TOKEN,
|
|
})
|
|
: createNodePlatform()
|
|
|
|
const app = await createBootstrap(platform)
|
|
|
|
const server = new Hono()
|
|
server.route('/', app)
|
|
server.use('/*', serveStatic({ root: './dist' }))
|
|
server.get('/*', serveStatic({ root: './dist', path: 'index.html' }))
|
|
|
|
const port = Number(process.env.PORT) || 8222
|
|
console.log(`ZPan server running on http://localhost:${port}`)
|
|
serve({ fetch: server.fetch, port })
|
|
|
|
// Start licensing refresh background scheduler
|
|
const cloudBaseUrl = process.env.ZPAN_CLOUD_URL ?? ZPAN_CLOUD_URL_DEFAULT
|
|
|
|
function envAllowsIp(value: string | undefined): boolean {
|
|
return !['0', 'false', 'no', 'off'].includes(value?.trim().toLowerCase() ?? '')
|
|
}
|
|
|
|
function isGitHubActionsE2E(): boolean {
|
|
return process.env.GITHUB_ACTIONS === 'true' && process.env.BETTER_AUTH_URL === 'http://localhost:5185'
|
|
}
|
|
|
|
console.log('licensing.refresh.scheduler.started interval=6h')
|
|
setInterval(() => {
|
|
// runLicensingRefresh handles all errors internally and never rejects.
|
|
void (async () => {
|
|
const instanceUrl = await getSitePublicOrigin(platform.db)
|
|
const instance = instanceUrl
|
|
? await buildCloudInstanceInfo(platform.db, {
|
|
url: instanceUrl,
|
|
runtime: {
|
|
runtime: { provider: 'node', target: 'node/docker' },
|
|
server: { os: { platform: process.platform, arch: process.arch, release: osRelease() } },
|
|
node: { version: process.version },
|
|
},
|
|
})
|
|
: undefined
|
|
await runLicensingRefresh(platform.db, cloudBaseUrl, instance)
|
|
})()
|
|
}, REFRESH_INTERVAL_MS)
|
|
|
|
console.log('traffic.sync.scheduler.started interval=10m')
|
|
setInterval(() => {
|
|
void syncPendingCloudTrafficReports({ db: platform.db, cloudBaseUrl })
|
|
void syncPendingRemoteDownloadUsageReports({ db: platform.db, cloudBaseUrl })
|
|
}, TRAFFIC_SYNC_INTERVAL_MS)
|
|
|
|
function reportNodeInstanceTelemetry(): void {
|
|
if (isGitHubActionsE2E()) return
|
|
|
|
void (async () => {
|
|
try {
|
|
await reportInstanceTelemetry({
|
|
db: platform.db,
|
|
config: {
|
|
allowIp: envAllowsIp(process.env.ZPAN_TELEMETRY_ALLOW_IP),
|
|
},
|
|
cron: INSTANCE_TELEMETRY_CRON,
|
|
trigger: 'runtime',
|
|
runtime: {
|
|
target: 'node/docker',
|
|
provider: 'node',
|
|
osPlatform: process.platform,
|
|
osArch: process.arch,
|
|
osRelease: osRelease(),
|
|
nodeVersion: process.version,
|
|
},
|
|
})
|
|
} catch (err) {
|
|
const code = err instanceof Error ? err.message : String(err)
|
|
console.error(`instance.telemetry.error code=${code}`)
|
|
}
|
|
})()
|
|
}
|
|
|
|
console.log('instance.telemetry.scheduler.started interval=12h')
|
|
reportNodeInstanceTelemetry()
|
|
setInterval(reportNodeInstanceTelemetry, INSTANCE_TELEMETRY_INTERVAL_MS)
|