Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 578dccd0cf | |||
| 0ecad475ef | |||
| d5789b79a6 | |||
| 45ce90f3cc | |||
| 3a817625c5 | |||
| d5abea48b3 | |||
| 1da69ac272 | |||
| bd2092a3ea | |||
| afb6fb8ac7 | |||
| 414e9f4c33 | |||
| 10035221fb | |||
| 52a6f4368c | |||
| e774dbc301 | |||
| 5bf582f3ad | |||
| 7398bb0a16 | |||
| 79d36df83c | |||
| 2146c06b02 | |||
| 7d955cd89f | |||
| 68082fd893 |
@@ -0,0 +1,85 @@
|
|||||||
|
name: PR
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: https://git.keligrubb.com/actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: https://git.keligrubb.com/actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
cache: "npm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run lint
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: https://git.keligrubb.com/actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: https://git.keligrubb.com/actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
cache: "npm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm run test
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: mcr.microsoft.com/playwright:v1.58.2-noble
|
||||||
|
steps:
|
||||||
|
- uses: https://git.keligrubb.com/actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: https://git.keligrubb.com/actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
cache: "npm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Generate dev cert
|
||||||
|
run: ./scripts/gen-dev-cert.sh
|
||||||
|
|
||||||
|
- name: Run e2e tests
|
||||||
|
run: npm run test:e2e
|
||||||
|
env:
|
||||||
|
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: https://git.keligrubb.com/actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Set Docker image tag
|
||||||
|
id: image
|
||||||
|
run: |
|
||||||
|
REGISTRY="${GITHUB_SERVER_URL#https://}"
|
||||||
|
REGISTRY="${REGISTRY#http://}"
|
||||||
|
echo "tag=${REGISTRY}/${{ github.repository }}:latest" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: https://git.keligrubb.com/actions/docker-setup-buildx-action@v4
|
||||||
|
|
||||||
|
- name: Build (dry run)
|
||||||
|
uses: https://git.keligrubb.com/actions/docker-build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: false
|
||||||
|
tags: ${{ steps.image.outputs.tag }}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
name: Push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-docker-helm:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: https://git.keligrubb.com/actions/checkout@v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.KESTRELOS_REPO_TOKEN }}
|
||||||
|
|
||||||
|
- name: Release (bump, tag, push, create release)
|
||||||
|
env:
|
||||||
|
CI_REPO_OWNER: ${{ github.actor }}
|
||||||
|
CI_REPO_NAME: ${{ github.event.repository.name }}
|
||||||
|
CI_FORGE_URL: ${{ github.server_url }}
|
||||||
|
CI_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||||
|
GITEA_REPO_TOKEN: ${{ secrets.KESTRELOS_REPO_TOKEN }}
|
||||||
|
run: |
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y -qq git wget
|
||||||
|
./scripts/release.sh
|
||||||
|
|
||||||
|
- name: Log in to container registry
|
||||||
|
uses: https://git.keligrubb.com/actions/docker-login-action@v4
|
||||||
|
with:
|
||||||
|
registry: git.keligrubb.com
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.KESTRELOS_REPO_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: https://git.keligrubb.com/actions/docker-setup-buildx-action@v4
|
||||||
|
|
||||||
|
- name: Build Docker image
|
||||||
|
uses: https://git.keligrubb.com/actions/docker-build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
load: true
|
||||||
|
tags: kestrelos:built
|
||||||
|
|
||||||
|
- name: Push Docker image (all tags from .tags)
|
||||||
|
run: |
|
||||||
|
REGISTRY="git.keligrubb.com"
|
||||||
|
IMAGE="$REGISTRY/${{ github.repository }}"
|
||||||
|
while read -r tag; do
|
||||||
|
docker tag kestrelos:built "$IMAGE:$tag"
|
||||||
|
docker push "$IMAGE:$tag"
|
||||||
|
done < .tags
|
||||||
|
|
||||||
|
- name: Set up Helm
|
||||||
|
uses: https://git.keligrubb.com/actions/setup-helm@v4
|
||||||
|
|
||||||
|
- name: Package and push Helm chart
|
||||||
|
env:
|
||||||
|
GITEA_REPO_TOKEN: ${{ secrets.KESTRELOS_REPO_TOKEN }}
|
||||||
|
run: |
|
||||||
|
helm package helm/kestrelos
|
||||||
|
for f in kestrelos-*.tgz; do
|
||||||
|
curl -sf -u "${{ github.actor }}:$GITEA_REPO_TOKEN" -X POST --upload-file "$f" \
|
||||||
|
"${{ github.server_url }}/api/packages/${{ github.actor }}/helm/api/charts"
|
||||||
|
done
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: lint
|
|
||||||
image: node:24-slim
|
|
||||||
depends_on: []
|
|
||||||
commands:
|
|
||||||
- npm ci
|
|
||||||
- npm run lint
|
|
||||||
|
|
||||||
- name: test
|
|
||||||
image: node:24-slim
|
|
||||||
depends_on: []
|
|
||||||
commands:
|
|
||||||
- npm ci
|
|
||||||
- npm run test
|
|
||||||
|
|
||||||
- name: e2e
|
|
||||||
image: mcr.microsoft.com/playwright:v1.58.2-noble
|
|
||||||
depends_on: []
|
|
||||||
commands:
|
|
||||||
- npm ci
|
|
||||||
- ./scripts/gen-dev-cert.sh
|
|
||||||
- npm run test:e2e
|
|
||||||
environment:
|
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
|
||||||
|
|
||||||
- name: docker-build
|
|
||||||
image: woodpeckerci/plugin-kaniko
|
|
||||||
depends_on: []
|
|
||||||
settings:
|
|
||||||
repo: ${CI_REPO_OWNER}/${CI_REPO_NAME}
|
|
||||||
registry: git.keligrubb.com
|
|
||||||
tags: latest
|
|
||||||
dry-run: true
|
|
||||||
single-snapshot: true
|
|
||||||
cleanup: true
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
when:
|
|
||||||
- event: push
|
|
||||||
branch: main
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: release
|
|
||||||
image: alpine
|
|
||||||
commands:
|
|
||||||
- apk add --no-cache git
|
|
||||||
- ./scripts/release.sh
|
|
||||||
environment:
|
|
||||||
GITEA_REPO_TOKEN:
|
|
||||||
from_secret: gitea_repo_token
|
|
||||||
|
|
||||||
- name: docker
|
|
||||||
image: woodpeckerci/plugin-kaniko
|
|
||||||
depends_on: [release]
|
|
||||||
settings:
|
|
||||||
repo: ${CI_REPO_OWNER}/${CI_REPO_NAME}
|
|
||||||
registry: git.keligrubb.com
|
|
||||||
username: ${CI_REPO_OWNER}
|
|
||||||
password:
|
|
||||||
from_secret: gitea_registry_token
|
|
||||||
single-snapshot: true
|
|
||||||
cleanup: true
|
|
||||||
|
|
||||||
- name: helm
|
|
||||||
image: alpine/helm
|
|
||||||
depends_on: [release]
|
|
||||||
environment:
|
|
||||||
GITEA_REGISTRY_TOKEN:
|
|
||||||
from_secret: gitea_registry_token
|
|
||||||
commands:
|
|
||||||
- apk add --no-cache curl
|
|
||||||
- helm package helm/kestrelos
|
|
||||||
- curl -sf -u $CI_REPO_OWNER:$GITEA_REGISTRY_TOKEN -X POST --upload-file kestrelos-*.tgz https://git.keligrubb.com/api/packages/$CI_REPO_OWNER/helm/api/charts
|
|
||||||
@@ -1,3 +1,35 @@
|
|||||||
|
## [1.0.8] - 2026-03-12
|
||||||
|
### Changed
|
||||||
|
- fix release file (#22)
|
||||||
|
|
||||||
|
## [1.0.7] - 2026-03-06
|
||||||
|
### Changed
|
||||||
|
- chore(deps): update docker/build-push-action action to v7 (#19)
|
||||||
|
|
||||||
|
## [1.0.6] - 2026-03-05
|
||||||
|
### Changed
|
||||||
|
- fix docker login during push stage (#18)
|
||||||
|
|
||||||
|
## [1.0.5] - 2026-03-05
|
||||||
|
### Changed
|
||||||
|
- fix deploy pipeline stages for token registry uploads (#17)
|
||||||
|
|
||||||
|
## [1.0.4] - 2026-03-04
|
||||||
|
### Changed
|
||||||
|
- fix deploy pipeline (#15)
|
||||||
|
|
||||||
|
## [1.0.3] - 2026-02-23
|
||||||
|
### Changed
|
||||||
|
- fix(deps): update dependency vue-router to v5 (#12)
|
||||||
|
|
||||||
|
## [1.0.2] - 2026-02-22
|
||||||
|
### Changed
|
||||||
|
- chore(deps): update dependency eslint to v10 (#10)
|
||||||
|
|
||||||
|
## [1.0.1] - 2026-02-22
|
||||||
|
### Changed
|
||||||
|
- chore: Configure Renovate (#7)
|
||||||
|
|
||||||
## [1.0.0] - 2026-02-17
|
## [1.0.0] - 2026-02-17
|
||||||
### Changed
|
### Changed
|
||||||
- kestrel is now a tak server (#6)
|
- kestrel is now a tak server (#6)
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ apiVersion: v2
|
|||||||
name: kestrelos
|
name: kestrelos
|
||||||
description: KestrelOS TOC for OSINT feeds - map, camera feeds, offline tiles
|
description: KestrelOS TOC for OSINT feeds - map, camera feeds, offline tiles
|
||||||
type: application
|
type: application
|
||||||
version: 1.0.0
|
version: 1.0.8
|
||||||
appVersion: "1.0.0"
|
appVersion: "1.0.8"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ replicaCount: 1
|
|||||||
|
|
||||||
image:
|
image:
|
||||||
repository: git.keligrubb.com/keligrubb/kestrelos
|
repository: git.keligrubb.com/keligrubb/kestrelos
|
||||||
tag: 1.0.0
|
tag: 1.0.8
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
service:
|
service:
|
||||||
|
|||||||
Generated
+2624
-3876
File diff suppressed because it is too large
Load Diff
+3
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kestrelos",
|
"name": "kestrelos",
|
||||||
"version": "1.0.0",
|
"version": "1.0.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -31,9 +31,8 @@
|
|||||||
"nuxt": "^4.0.0",
|
"nuxt": "^4.0.0",
|
||||||
"openid-client": "^6.8.2",
|
"openid-client": "^6.8.2",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"sqlite3": "^5.1.7",
|
|
||||||
"vue": "^3.4.0",
|
"vue": "^3.4.0",
|
||||||
"vue-router": "^4.4.0",
|
"vue-router": "^5.0.0",
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -43,7 +42,7 @@
|
|||||||
"@playwright/test": "^1.58.2",
|
"@playwright/test": "^1.58.2",
|
||||||
"@vitest/coverage-v8": "^4.0.0",
|
"@vitest/coverage-v8": "^4.0.0",
|
||||||
"@vue/test-utils": "^2.4.0",
|
"@vue/test-utils": "^2.4.0",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^10.0.0",
|
||||||
"happy-dom": "^20.6.1",
|
"happy-dom": "^20.6.1",
|
||||||
"vitest": "^4.0.0"
|
"vitest": "^4.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||||
|
}
|
||||||
+33
-7
@@ -3,8 +3,21 @@ set -e
|
|||||||
|
|
||||||
# version
|
# version
|
||||||
msg="${CI_COMMIT_MESSAGE:-}"
|
msg="${CI_COMMIT_MESSAGE:-}"
|
||||||
|
# optional PR body (written by workflow from Gitea API when this commit is a merged PR)
|
||||||
|
if [ -f .ci_pr_body ]; then
|
||||||
|
CI_PR_DESCRIPTION=$(cat .ci_pr_body); rm -f .ci_pr_body
|
||||||
|
else
|
||||||
|
CI_PR_DESCRIPTION=""
|
||||||
|
fi
|
||||||
|
export CI_PR_DESCRIPTION
|
||||||
bump=patch
|
bump=patch
|
||||||
echo "$msg" | grep -qi minor: && bump=minor
|
# Conventional commits: chore/fix => patch, feat => minor
|
||||||
|
echo "$msg" | grep -Eqi '(^|[[:space:]])(fix|chore)(\([^)]*\))?:' && bump=patch
|
||||||
|
echo "$msg" | grep -Eqi '(^|[[:space:]])feat(\([^)]*\))?:' && bump=minor
|
||||||
|
# Conventional commits breaking change: type!:
|
||||||
|
echo "$msg" | grep -Eqi '(^|[[:space:]])[a-zA-Z]+(\([^)]*\))?!:' && bump=major
|
||||||
|
# Explicit bump prefixes still supported (but never downgrade a major bump)
|
||||||
|
echo "$msg" | grep -qi minor: && [ "$bump" != "major" ] && bump=minor
|
||||||
echo "$msg" | grep -qi major: && bump=major
|
echo "$msg" | grep -qi major: && bump=major
|
||||||
cur=$(awk '/"version"/ { match($0, /[0-9]+\.[0-9]+\.[0-9]+/); print substr($0, RSTART, RLENGTH); exit }' package.json)
|
cur=$(awk '/"version"/ { match($0, /[0-9]+\.[0-9]+\.[0-9]+/); print substr($0, RSTART, RLENGTH); exit }' package.json)
|
||||||
major=$(echo "$cur" | cut -d. -f1); minor=$(echo "$cur" | cut -d. -f2); patch=$(echo "$cur" | cut -d. -f3)
|
major=$(echo "$cur" | cut -d. -f1); minor=$(echo "$cur" | cut -d. -f2); patch=$(echo "$cur" | cut -d. -f3)
|
||||||
@@ -12,9 +25,21 @@ case "$bump" in major) major=$((major+1)); minor=0; patch=0 ;; minor) minor=$((m
|
|||||||
newVersion="$major.$minor.$patch"
|
newVersion="$major.$minor.$patch"
|
||||||
[ -z "$cur" ] && { echo "error: could not read version from package.json"; exit 1; }
|
[ -z "$cur" ] && { echo "error: could not read version from package.json"; exit 1; }
|
||||||
|
|
||||||
# changelog entry (strip prefix from first line)
|
# changelog entry (strip explicit bump prefixes & any conventional-commit type(scope):); optional PR description enriches it
|
||||||
changelogEntry=$(echo "$msg" | head -1 | awk '{sub(/^[mM]ajor:[ \t]*/,""); sub(/^[mM]inor:[ \t]*/,""); sub(/^[pP]atch:[ \t]*/,""); print}')
|
changelogEntry=$(
|
||||||
|
echo "$msg" \
|
||||||
|
| head -1 \
|
||||||
|
| sed -E 's/^[[:space:]]*[mM]ajor:[[:space:]]*//; s/^[[:space:]]*[mM]inor:[[:space:]]*//; s/^[[:space:]]*[pP]atch:[[:space:]]*//' \
|
||||||
|
| sed -E 's/^[[:space:]]*[a-zA-Z]+(\([^)]*\))?:[[:space:]]*//'
|
||||||
|
)
|
||||||
[ -z "$changelogEntry" ] && changelogEntry="Release v$newVersion"
|
[ -z "$changelogEntry" ] && changelogEntry="Release v$newVersion"
|
||||||
|
if [ -n "$CI_PR_DESCRIPTION" ]; then
|
||||||
|
changelogFull="- $changelogEntry
|
||||||
|
|
||||||
|
$CI_PR_DESCRIPTION"
|
||||||
|
else
|
||||||
|
changelogFull="- $changelogEntry"
|
||||||
|
fi
|
||||||
|
|
||||||
# bump files
|
# bump files
|
||||||
awk -v v="$newVersion" '/"version"/ { sub(/[0-9]+\.[0-9]+\.[0-9]+/, v) } { print }' package.json > package.json.tmp && mv package.json.tmp package.json
|
awk -v v="$newVersion" '/"version"/ { sub(/[0-9]+\.[0-9]+\.[0-9]+/, v) } { print }' package.json > package.json.tmp && mv package.json.tmp package.json
|
||||||
@@ -24,10 +49,11 @@ awk -v v="$newVersion" '/^ tag:/ { $0 = " tag: " v }; { print }' helm/kestrelo
|
|||||||
# changelog
|
# changelog
|
||||||
new="## [$newVersion] - $(date +%Y-%m-%d)
|
new="## [$newVersion] - $(date +%Y-%m-%d)
|
||||||
### Changed
|
### Changed
|
||||||
- $changelogEntry
|
$changelogFull
|
||||||
|
|
||||||
"
|
"
|
||||||
{ [ ! -f CHANGELOG.md ] && printf '# Changelog\n\n'; printf '%s' "$new"; [ -f CHANGELOG.md ] && cat CHANGELOG.md; } > CHANGELOG.md.tmp && mv CHANGELOG.md.tmp CHANGELOG.md
|
# Create CHANGELOG.md if missing (first release); otherwise prepend new entry to existing content.
|
||||||
|
{ [ ! -f CHANGELOG.md ] && printf '# Changelog\n\n'; printf '%s' "$new"; [ -f CHANGELOG.md ] && cat CHANGELOG.md || true; } > CHANGELOG.md.tmp && mv CHANGELOG.md.tmp CHANGELOG.md
|
||||||
|
|
||||||
# git
|
# git
|
||||||
git config user.email "ci@kestrelos" && git config user.name "CI"
|
git config user.email "ci@kestrelos" && git config user.name "CI"
|
||||||
@@ -35,7 +61,7 @@ git add package.json helm/kestrelos/Chart.yaml helm/kestrelos/values.yaml CHANGE
|
|||||||
git commit -m "release v$newVersion [skip ci]"
|
git commit -m "release v$newVersion [skip ci]"
|
||||||
url="https://${CI_REPO_OWNER}:${GITEA_REPO_TOKEN}@${CI_FORGE_URL#https://}/${CI_REPO_OWNER}/${CI_REPO_NAME}.git"
|
url="https://${CI_REPO_OWNER}:${GITEA_REPO_TOKEN}@${CI_FORGE_URL#https://}/${CI_REPO_OWNER}/${CI_REPO_NAME}.git"
|
||||||
git tag "v$newVersion"
|
git tag "v$newVersion"
|
||||||
# artifact for kaniko (tag list)
|
# artifact for docker (tag list)
|
||||||
printf '%s\n%s\n' "$newVersion" "latest" > .tags
|
printf '%s\n%s\n' "$newVersion" "latest" > .tags
|
||||||
retry() { n=0; while ! "$@"; do n=$((n+1)); [ $n -ge 3 ] && return 1; sleep 2; done; }
|
retry() { n=0; while ! "$@"; do n=$((n+1)); [ $n -ge 3 ] && return 1; sleep 2; done; }
|
||||||
retry git push "$url" HEAD:main "v$newVersion"
|
retry git push "$url" HEAD:main "v$newVersion"
|
||||||
@@ -43,7 +69,7 @@ retry git push "$url" HEAD:main "v$newVersion"
|
|||||||
# gitea release
|
# gitea release
|
||||||
body="## Changelog
|
body="## Changelog
|
||||||
### Changed
|
### Changed
|
||||||
- $changelogEntry
|
$changelogFull
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
- [Docker image](${CI_FORGE_URL}/${CI_REPO_OWNER}/-/packages/container/${CI_REPO_NAME})
|
- [Docker image](${CI_FORGE_URL}/${CI_REPO_OWNER}/-/packages/container/${CI_REPO_NAME})
|
||||||
|
|||||||
+86
-24
@@ -1,14 +1,12 @@
|
|||||||
import { join, dirname } from 'node:path'
|
import { join, dirname } from 'node:path'
|
||||||
import { mkdirSync, existsSync } from 'node:fs'
|
import { mkdirSync, existsSync } from 'node:fs'
|
||||||
|
import { randomBytes, randomUUID } from 'node:crypto'
|
||||||
import { createRequire } from 'node:module'
|
import { createRequire } from 'node:module'
|
||||||
import { promisify } from 'node:util'
|
|
||||||
import { randomBytes } from 'node:crypto'
|
|
||||||
import { hashPassword } from './password.js'
|
import { hashPassword } from './password.js'
|
||||||
import { registerCleanup } from './shutdown.js'
|
import { registerCleanup } from './shutdown.js'
|
||||||
|
|
||||||
// Resolve from project root so bundled server (e.g. .output) finds node_modules/sqlite3
|
|
||||||
const requireFromRoot = createRequire(join(process.cwd(), 'package.json'))
|
const requireFromRoot = createRequire(join(process.cwd(), 'package.json'))
|
||||||
const sqlite3 = requireFromRoot('sqlite3')
|
const { DatabaseSync } = requireFromRoot('node:sqlite')
|
||||||
|
|
||||||
const SCHEMA_VERSION = 4
|
const SCHEMA_VERSION = 4
|
||||||
const DB_BUSY_TIMEOUT_MS = 5000
|
const DB_BUSY_TIMEOUT_MS = 5000
|
||||||
@@ -144,7 +142,6 @@ const initDb = async (db, run, all, get) => {
|
|||||||
catch {
|
catch {
|
||||||
// WAL not supported (e.g., network filesystem)
|
// WAL not supported (e.g., network filesystem)
|
||||||
}
|
}
|
||||||
db.configure('busyTimeout', DB_BUSY_TIMEOUT_MS)
|
|
||||||
|
|
||||||
await run(SCHEMA.schema_version)
|
await run(SCHEMA.schema_version)
|
||||||
await run(SCHEMA.users)
|
await run(SCHEMA.users)
|
||||||
@@ -168,7 +165,7 @@ const initDb = async (db, run, all, get) => {
|
|||||||
|
|
||||||
await run(
|
await run(
|
||||||
'INSERT INTO users (id, identifier, password_hash, role, created_at, auth_provider, oidc_issuer, oidc_sub) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
'INSERT INTO users (id, identifier, password_hash, role, created_at, auth_provider, oidc_issuer, oidc_sub) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
||||||
[crypto.randomUUID(), identifier, hashPassword(plainPassword), 'admin', new Date().toISOString(), 'local', null, null],
|
[randomUUID(), identifier, hashPassword(plainPassword), 'admin', new Date().toISOString(), 'local', null, null],
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
@@ -181,37 +178,104 @@ const initDb = async (db, run, all, get) => {
|
|||||||
export async function getDb() {
|
export async function getDb() {
|
||||||
if (dbInstance) return dbInstance
|
if (dbInstance) return dbInstance
|
||||||
|
|
||||||
const db = new sqlite3.Database(getDbPath(), (err) => {
|
const sqliteDb = (() => {
|
||||||
if (err) {
|
try {
|
||||||
|
return new DatabaseSync(getDbPath(), { timeout: DB_BUSY_TIMEOUT_MS })
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
console.error('[db] Failed to open database:', err.message)
|
console.error('[db] Failed to open database:', err.message)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
})
|
})()
|
||||||
|
|
||||||
const run = promisify(db.run.bind(db))
|
const run = (sql, params = []) =>
|
||||||
const all = promisify(db.all.bind(db))
|
new Promise((resolve, reject) => {
|
||||||
const get = promisify(db.get.bind(db))
|
const stmt = sqliteDb.prepare(sql)
|
||||||
|
try {
|
||||||
|
stmt.run(...params)
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
stmt.finalize()
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// ignore finalize errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const all = (sql, params = []) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const stmt = sqliteDb.prepare(sql)
|
||||||
|
try {
|
||||||
|
const rows = stmt.all(...params)
|
||||||
|
resolve(rows)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
stmt.finalize()
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// ignore finalize errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const get = (sql, params = []) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const stmt = sqliteDb.prepare(sql)
|
||||||
|
try {
|
||||||
|
const row = stmt.get(...params)
|
||||||
|
resolve(row)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
stmt.finalize()
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// ignore finalize errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const wrappedDb = {
|
||||||
|
close(callback) {
|
||||||
|
try {
|
||||||
|
sqliteDb.close()
|
||||||
|
if (typeof callback === 'function') callback(null)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (typeof callback === 'function') callback(error)
|
||||||
|
else throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await initDb(db, run, all, get)
|
await initDb(wrappedDb, run, all, get)
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
db.close()
|
wrappedDb.close()
|
||||||
console.error('[db] Database initialization failed:', error.message)
|
console.error('[db] Database initialization failed:', error.message)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
dbInstance = { db, run, all, get }
|
dbInstance = { db: wrappedDb, run, all, get }
|
||||||
|
|
||||||
registerCleanup(async () => {
|
registerCleanup(async () => {
|
||||||
if (dbInstance) {
|
if (dbInstance) {
|
||||||
try {
|
try {
|
||||||
await new Promise((resolve, reject) => {
|
dbInstance.db.close()
|
||||||
dbInstance.db.close((err) => {
|
|
||||||
if (err) reject(err)
|
|
||||||
else resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('[db] Error closing database during shutdown:', error?.message)
|
console.error('[db] Error closing database during shutdown:', error?.message)
|
||||||
@@ -290,9 +354,7 @@ export async function withTransaction(db, callback) {
|
|||||||
export function closeDb() {
|
export function closeDb() {
|
||||||
if (!dbInstance) return
|
if (!dbInstance) return
|
||||||
try {
|
try {
|
||||||
dbInstance.db.close((err) => {
|
dbInstance.db.close()
|
||||||
if (err) console.error('[db] Error closing database:', err.message)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('[db] Error closing database:', error.message)
|
console.error('[db] Error closing database:', error.message)
|
||||||
|
|||||||
Reference in New Issue
Block a user