Compare commits

...

13 Commits

Author SHA1 Message Date
cosmistack-bot
10397be914 chore(release): 1.30.3-rc.1 [skip ci] 2026-03-24 06:58:37 +00:00
Jake Turner
149967f48b docs: update release notes 2026-03-23 23:46:30 -07:00
Tom Boucher
b2c9252342 fix: surface actual error message when service installation fails
Backend returned { error: message } on 400 but frontend expected { message }.
catchInternal swallowed Axios errors and returned undefined, causing a
generic 'An internal error occurred' message instead of the real reason
(already installed, already in progress, not found).

- Fix 400 response shape to { success: false, message } in controller
- Replace catchInternal with direct error handling in installService,
  affectService, and forceReinstallService API methods
- Extract error.response.data.message from Axios errors so callers
  see the actual server message
2026-03-23 23:46:30 -07:00
Bortlesboat
e7ae6bba8e fix: benchmark scores clamped to 0% for below-average hardware
The log2 normalization formula `50 * (1 + log2(ratio))` produces negative
values (clamped to 0) whenever the measured value is less than half the
reference. For example, a CPU scoring 1993 events/sec against a 5000
reference gives ratio=0.4, log2(0.4)=-1.32, score=-16 -> 0%.

Fix by dividing log2 by 3 to widen the usable range. This preserves the
50% score at the reference value while allowing below-average hardware
to receive proportional non-zero scores (e.g., 28% for the CPU above).

Also adds debug logging for CPU sysbench output parsing to aid future
diagnosis of parsing issues.

Fixes #415
2026-03-23 23:46:30 -07:00
Chris Sherwood
9480b2ec9f fix(ai): surface model download errors and prevent silent retry loops
Model downloads that fail (e.g., when Ollama is too old for a model)
were silently retrying 40 times with no UI feedback. Now errors are
broadcast via SSE and shown in the Active Model Downloads section.
Version mismatch errors use UnrecoverableError to fail immediately
instead of retrying. Stale failed jobs are cleared on retry so users
aren't permanently blocked.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:46:30 -07:00
Jake Turner
1e66d3c2e4 fix: bump default ollama and cyberchef versions 2026-03-23 23:46:30 -07:00
LuisMIguelFurlanettoSousa
22126835e0 fix(zim): adicionar método deleteZimFile ausente no API client
O Content Manager chamava api.deleteZimFile() para deletar arquivos
ZIM, mas esse método nunca foi implementado na classe API, causando
"TypeError: deleteZimFile is not a function".

O backend (DELETE /api/zim/:filename → ZimController.delete) já
existia e funcionava corretamente — só faltava o método no client
frontend que faz a ponte.

Closes #372
2026-03-23 23:46:30 -07:00
Chris Sherwood
1ce4347ace docs: add installation guide link to README
Link to the full step-by-step install walkthrough on projectnomad.us/install,
placed below the Quick Install command for users who need Ubuntu setup help.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:46:30 -07:00
Daniel Leiva
96e3a0cecd fix(install): correct nvidia runtime string evaluation
Removed quotes around the nvidia runtime string to properly match the unquoted output from docker info.
2026-03-23 23:46:30 -07:00
builder555
7b482d6123 fix(Collections): update ZIM files to latest versions (#332)
* fix: update data sources to newer versions
* fix: bump spec version for wikipedia
2026-03-23 23:46:30 -07:00
Divyank Singh
f7b715ad56 build: increase mysql healthcheck retries to avoid race condition on lower-end hardware (#480) 2026-03-23 23:46:30 -07:00
Chris Sherwood
a68d6ab593 fix(install): prevent MySQL credential mismatch on reinstall
When the install script runs a second time (e.g., after a failed first
attempt), it generates new random database passwords and writes them to
compose.yml. However, MySQL only initializes credentials on first startup
when its data directory is empty. If /opt/project-nomad/mysql/ persists
from the previous attempt, MySQL skips initialization and keeps the old
passwords, causing "Access denied" errors for nomad_admin.

Fix: remove the MySQL data directory before generating new credentials
so MySQL reinitializes with the correct passwords.

Closes #404

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:46:30 -07:00
chriscrosstalk
09c12e7574 fix: correct Rogue Support URL on Support the Project page (#472)
roguesupport.com changed to rogue.support (the actual domain).
Updates href and display text in two places.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:46:30 -07:00
17 changed files with 220 additions and 105 deletions

View File

@ -28,6 +28,8 @@ sudo apt-get update && sudo apt-get install -y curl && curl -fsSL https://raw.gi
Project N.O.M.A.D. is now installed on your device! Open a browser and navigate to `http://localhost:8080` (or `http://DEVICE_IP:8080`) to start exploring!
For a complete step-by-step walkthrough (including Ubuntu installation), see the [Installation Guide](https://www.projectnomad.us/install).
### Advanced Installation
For more control over the installation process, copy and paste the [Docker Compose template](https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/refs/heads/main/install/management_compose.yaml) into a `docker-compose.yml` file and customize it to your liking (be sure to replace any placeholders with your actual values). Then, run `docker compose up -d` to start the Command Center and its dependencies. Note: this method is recommended for advanced users only, as it requires familiarity with Docker and manual configuration before starting.

View File

@ -35,7 +35,7 @@ export default class SystemController {
if (result.success) {
response.send({ success: true, message: result.message });
} else {
response.status(400).send({ error: result.message });
response.status(400).send({ success: false, message: result.message });
}
}

View File

@ -1,4 +1,4 @@
import { Job } from 'bullmq'
import { Job, UnrecoverableError } from 'bullmq'
import { QueueService } from '#services/queue_service'
import { createHash } from 'crypto'
import logger from '@adonisjs/core/services/logger'
@ -63,6 +63,10 @@ export class DownloadModelJob {
logger.error(
`[DownloadModelJob] Failed to initiate download for model ${modelName}: ${result.message}`
)
// Don't retry errors that will never succeed (e.g., Ollama version too old)
if (result.retryable === false) {
throw new UnrecoverableError(result.message)
}
throw new Error(`Failed to initiate download for model: ${result.message}`)
}
@ -85,6 +89,15 @@ export class DownloadModelJob {
const queue = queueService.getQueue(this.queue)
const jobId = this.getJobId(params.modelName)
// Clear any previous failed job so a fresh attempt can be dispatched
const existing = await queue.getJob(jobId)
if (existing) {
const state = await existing.getState()
if (state === 'failed') {
await existing.remove()
}
}
try {
const job = await queue.add(this.key, params, {
jobId,
@ -104,9 +117,9 @@ export class DownloadModelJob {
}
} catch (error) {
if (error.message.includes('job already exists')) {
const existing = await queue.getJob(jobId)
const active = await queue.getJob(jobId)
return {
job: existing,
job: active,
created: false,
message: `Job already exists for model ${params.modelName}`,
}

View File

@ -571,10 +571,10 @@ export class BenchmarkService {
*/
private _normalizeScore(value: number, reference: number): number {
if (value <= 0) return 0
// Log scale: score = 50 * (1 + log2(value/reference))
// This gives 50 at reference value, scales logarithmically
// Log scale with widened range: dividing log2 by 3 prevents scores from
// clamping to 0% for below-average hardware. Gives 50% at reference value.
const ratio = value / reference
const score = 50 * (1 + Math.log2(Math.max(0.01, ratio)))
const score = 50 * (1 + Math.log2(Math.max(0.01, ratio)) / 3)
return Math.min(100, Math.max(0, score)) / 100
}
@ -583,9 +583,9 @@ export class BenchmarkService {
*/
private _normalizeScoreInverse(value: number, reference: number): number {
if (value <= 0) return 1
// Inverse: lower values = higher scores
// Inverse: lower values = higher scores, with widened log range
const ratio = reference / value
const score = 50 * (1 + Math.log2(Math.max(0.01, ratio)))
const score = 50 * (1 + Math.log2(Math.max(0.01, ratio)) / 3)
return Math.min(100, Math.max(0, score)) / 100
}
@ -619,6 +619,7 @@ export class BenchmarkService {
const eventsMatch = output.match(/events per second:\s*([\d.]+)/i)
const totalTimeMatch = output.match(/total time:\s*([\d.]+)s/i)
const totalEventsMatch = output.match(/total number of events:\s*(\d+)/i)
logger.debug(`[BenchmarkService] CPU output parsing - events/s: ${eventsMatch?.[1]}, total_time: ${totalTimeMatch?.[1]}, total_events: ${totalEventsMatch?.[1]}`)
return {
events_per_second: eventsMatch ? parseFloat(eventsMatch[1]) : 0,

View File

@ -615,8 +615,8 @@ export class DockerService {
* We'll download the lightweight mini Wikipedia Top 100 zim file for this purpose.
**/
const WIKIPEDIA_ZIM_URL =
'https://github.com/Crosstalk-Solutions/project-nomad/raw/refs/heads/main/install/wikipedia_en_100_mini_2025-06.zim'
const filename = 'wikipedia_en_100_mini_2025-06.zim'
'https://github.com/Crosstalk-Solutions/project-nomad/raw/refs/heads/main/install/wikipedia_en_100_mini_2026-01.zim'
const filename = 'wikipedia_en_100_mini_2026-01.zim'
const filepath = join(process.cwd(), ZIM_STORAGE_PATH, filename)
logger.info(`[DockerService] Kiwix Serve pre-install: Downloading ZIM file to ${filepath}`)

View File

@ -51,7 +51,7 @@ export class OllamaService {
* @param model Model name to download
* @returns Success status and message
*/
async downloadModel(model: string, progressCallback?: (percent: number) => void): Promise<{ success: boolean; message: string }> {
async downloadModel(model: string, progressCallback?: (percent: number) => void): Promise<{ success: boolean; message: string; retryable?: boolean }> {
try {
await this._ensureDependencies()
if (!this.ollama) {
@ -86,11 +86,21 @@ export class OllamaService {
logger.info(`[OllamaService] Model "${model}" downloaded successfully.`)
return { success: true, message: 'Model downloaded successfully.' }
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
logger.error(
`[OllamaService] Failed to download model "${model}": ${error instanceof Error ? error.message : error
}`
`[OllamaService] Failed to download model "${model}": ${errorMessage}`
)
return { success: false, message: 'Failed to download model.' }
// Check for version mismatch (Ollama 412 response)
const isVersionMismatch = errorMessage.includes('newer version of Ollama')
const userMessage = isVersionMismatch
? 'This model requires a newer version of Ollama. Please update AI Assistant from the Apps page.'
: `Failed to download model: ${errorMessage}`
// Broadcast failure to connected clients so UI can show the error
this.broadcastDownloadError(model, userMessage)
return { success: false, message: userMessage, retryable: !isVersionMismatch }
}
}
@ -379,6 +389,15 @@ export class OllamaService {
return models
}
private broadcastDownloadError(model: string, error: string) {
transmit.broadcast(BROADCAST_CHANNELS.OLLAMA_MODEL_DOWNLOAD, {
model,
percent: -1,
error,
timestamp: new Date().toISOString(),
})
}
private broadcastDownloadProgress(model: string, percent: number) {
transmit.broadcast(BROADCAST_CHANNELS.OLLAMA_MODEL_DOWNLOAD, {
model,

View File

@ -70,7 +70,7 @@ export default class ServiceSeeder extends BaseSeeder {
display_order: 3,
description: 'Local AI chat that runs entirely on your hardware - no internet required',
icon: 'IconWand',
container_image: 'ollama/ollama:0.15.2',
container_image: 'ollama/ollama:0.18.1',
source_repo: 'https://github.com/ollama/ollama',
container_command: 'serve',
container_config: JSON.stringify({
@ -94,7 +94,7 @@ export default class ServiceSeeder extends BaseSeeder {
display_order: 11,
description: 'Swiss Army knife for data encoding, encryption, and analysis',
icon: 'IconChefHat',
container_image: 'ghcr.io/gchq/cyberchef:10.19.4',
container_image: 'ghcr.io/gchq/cyberchef:10.22.1',
source_repo: 'https://github.com/gchq/CyberChef',
container_command: null,
container_config: JSON.stringify({

View File

@ -1,5 +1,25 @@
# Release Notes
## Unreleased
### Features
### Bug Fixes
- **Benchmark**: Fixed an issue where CPU and Disk Write scores could be displayed as 0 if the measured values was less than half of the reference mark. Thanks @bortlesboat for the fix!
- **Content Manager**: Fixed a missing API client method that was causing ZIM file deletions to fail. Thanks @LuisMIguelFurlanettoSousa for the fix!
- **Install**: Fixed an issue where the install script could incorrectly report the Docker NVIDIA runtime as missing. Thanks @brenex for the fix!
- **Support the Project**: Fixed a broken link to Rogue Support. Thanks @chriscrosstalk for the fix!
### Improvements
- **AI Assistant**: Improved error reporting and handling for model downloads. Thanks @chriscrosstalk for the contribution!
- **AI Assistant**: Bumped the default version of Ollama installed to v0.18.1 to take advantage of the latest performance improvements and bug fixes.
- **Apps**: Improved error reporting and handling for service installation failures. Thanks @trek-e for the contribution!
- **Collections**: Updated various curated collection links to their latest versions. Thanks @builder555 for the contribution!
- **Cyberchef**: Bumped the default version of CyberChef installed to v10.22.1 to take advantage of the latest features and bug fixes.
- **Docs**: Added a link to the step-by-step installation guide and video tutorial. Thanks @chriscrosstalk for the contribution!
- **Install**: Increased the retries limit for the MySQL service in Docker Compose to improve stability during installation on systems with slower performance. Thanks @dx4956 for the contribution!
- **Install**: Fixed an issue where stale data could cause credentials mismatch in MySQL on reinstall. Thanks @chriscrosstalk for the fix!
## Version 1.30.0 - March 20, 2026
### Features

View File

@ -1,6 +1,7 @@
import useOllamaModelDownloads from '~/hooks/useOllamaModelDownloads'
import HorizontalBarChart from './HorizontalBarChart'
import StyledSectionHeader from './StyledSectionHeader'
import { IconAlertTriangle } from '@tabler/icons-react'
interface ActiveModelDownloadsProps {
withHeader?: boolean
@ -17,19 +18,31 @@ const ActiveModelDownloads = ({ withHeader = false }: ActiveModelDownloadsProps)
downloads.map((download) => (
<div
key={download.model}
className="bg-desert-white rounded-lg p-4 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow"
className={`bg-desert-white rounded-lg p-4 border shadow-sm hover:shadow-lg transition-shadow ${
download.error ? 'border-red-400' : 'border-desert-stone-light'
}`}
>
<HorizontalBarChart
items={[
{
label: download.model,
value: download.percent,
total: '100%',
used: `${download.percent.toFixed(1)}%`,
type: 'ollama-model',
},
]}
/>
{download.error ? (
<div className="flex items-start gap-3">
<IconAlertTriangle className="text-red-500 flex-shrink-0 mt-0.5" size={20} />
<div>
<p className="font-medium text-text-primary">{download.model}</p>
<p className="text-sm text-red-600 mt-1">{download.error}</p>
</div>
</div>
) : (
<HorizontalBarChart
items={[
{
label: download.model,
value: download.percent,
total: '100%',
used: `${download.percent.toFixed(1)}%`,
type: 'ollama-model',
},
]}
/>
)}
</div>
))
) : (

View File

@ -5,6 +5,7 @@ export type OllamaModelDownload = {
model: string
percent: number
timestamp: string
error?: string
}
export default function useOllamaModelDownloads() {
@ -17,7 +18,19 @@ export default function useOllamaModelDownloads() {
setDownloads((prev) => {
const updated = new Map(prev)
if (data.percent >= 100) {
if (data.percent === -1) {
// Download failed — show error state, auto-remove after 15 seconds
updated.set(data.model, data)
const errorTimeout = setTimeout(() => {
timeoutsRef.current.delete(errorTimeout)
setDownloads((current) => {
const next = new Map(current)
next.delete(data.model)
return next
})
}, 15000)
timeoutsRef.current.add(errorTimeout)
} else if (data.percent >= 100) {
// If download is complete, keep it for a short time before removing to allow UI to show 100% progress
updated.set(data.model, data)
const timeout = setTimeout(() => {

View File

@ -1,4 +1,4 @@
import axios, { AxiosInstance } from 'axios'
import axios, { AxiosError, AxiosInstance } from 'axios'
import { ListRemoteZimFilesResponse, ListZimFilesResponse } from '../../types/zim'
import { ServiceSlim } from '../../types/services'
import { FileEntry } from '../../types/files'
@ -25,13 +25,19 @@ class API {
}
async affectService(service_name: string, action: 'start' | 'stop' | 'restart') {
return catchInternal(async () => {
try {
const response = await this.client.post<{ success: boolean; message: string }>(
'/system/services/affect',
{ service_name, action }
)
return response.data
})()
} catch (error) {
if (error instanceof AxiosError && error.response?.data?.message) {
return { success: false, message: error.response.data.message }
}
console.error('Error affecting service:', error)
return undefined
}
}
async checkLatestVersion(force: boolean = false) {
@ -192,13 +198,19 @@ class API {
}
async forceReinstallService(service_name: string) {
return catchInternal(async () => {
try {
const response = await this.client.post<{ success: boolean; message: string }>(
`/system/services/force-reinstall`,
{ service_name }
)
return response.data
})()
} catch (error) {
if (error instanceof AxiosError && error.response?.data?.message) {
return { success: false, message: error.response.data.message }
}
console.error('Error force reinstalling service:', error)
return undefined
}
}
async getChatSuggestions(signal?: AbortSignal) {
@ -459,13 +471,19 @@ class API {
}
async installService(service_name: string) {
return catchInternal(async () => {
try {
const response = await this.client.post<{ success: boolean; message: string }>(
'/system/services/install',
{ service_name }
)
return response.data
})()
} catch (error) {
if (error instanceof AxiosError && error.response?.data?.message) {
return { success: false, message: error.response.data.message }
}
console.error('Error installing service:', error)
return undefined
}
}
async listCuratedMapCollections() {
@ -518,6 +536,13 @@ class API {
})()
}
async deleteZimFile(filename: string) {
return catchInternal(async () => {
const response = await this.client.delete<{ message: string }>(`/zim/${filename}`)
return response.data
})()
}
async listZimFiles() {
return catchInternal(async () => {
return await this.client.get<ListZimFilesResponse>('/zim/list')

View File

@ -36,7 +36,7 @@ export default function SupportPage() {
<section className="mb-12">
<h2 className="text-2xl font-semibold mb-3">Need Help With Your Home Network?</h2>
<a
href="https://roguesupport.com"
href="https://rogue.support"
target="_blank"
rel="noopener noreferrer"
className="block mb-4 rounded-lg overflow-hidden hover:opacity-90 transition-opacity"
@ -52,12 +52,12 @@ export default function SupportPage() {
Think of it as Uber for computer networking expert help when you need it.
</p>
<a
href="https://roguesupport.com"
href="https://rogue.support"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-blue-600 hover:underline font-medium"
>
Visit RogueSupport.com
Visit Rogue.Support
<IconExternalLink size={16} />
</a>
</section>

View File

@ -1,5 +1,5 @@
{
"spec_version": "2026-02-11",
"spec_version": "2026-03-15",
"categories": [
{
"name": "Medicine",
@ -113,10 +113,10 @@
"resources": [
{
"id": "canadian_prepper_winterprepping_en",
"version": "2025-11",
"version": "2026-02",
"title": "Canadian Prepper: Winter Prepping",
"description": "Video guides for winter survival and cold weather emergencies",
"url": "https://download.kiwix.org/zim/videos/canadian_prepper_winterprepping_en_2025-11.zim",
"url": "https://download.kiwix.org/zim/videos/canadian_prepper_winterprepping_en_2026-02.zim",
"size_mb": 1340
},
{
@ -137,18 +137,18 @@
"resources": [
{
"id": "canadian_prepper_bugoutconcepts_en",
"version": "2025-11",
"version": "2026-02",
"title": "Canadian Prepper: Bug Out Concepts",
"description": "Strategies and planning for emergency evacuation",
"url": "https://download.kiwix.org/zim/videos/canadian_prepper_bugoutconcepts_en_2025-11.zim",
"url": "https://download.kiwix.org/zim/videos/canadian_prepper_bugoutconcepts_en_2026-02.zim",
"size_mb": 2890
},
{
"id": "urban-prepper_en_all",
"version": "2025-11",
"version": "2026-02",
"title": "Urban Prepper",
"description": "Comprehensive urban emergency preparedness video series",
"url": "https://download.kiwix.org/zim/videos/urban-prepper_en_all_2025-11.zim",
"url": "https://download.kiwix.org/zim/videos/urban-prepper_en_all_2026-02.zim",
"size_mb": 2240
}
]
@ -194,10 +194,10 @@
"resources": [
{
"id": "wikibooks_en_all_nopic",
"version": "2025-10",
"version": "2026-01",
"title": "Wikibooks",
"description": "Open-content textbooks covering math, science, computing, and more",
"url": "https://download.kiwix.org/zim/wikibooks/wikibooks_en_all_nopic_2025-10.zim",
"url": "https://download.kiwix.org/zim/wikibooks/wikibooks_en_all_nopic_2026-01.zim",
"size_mb": 3100
}
]
@ -210,35 +210,35 @@
"resources": [
{
"id": "ted_mul_ted-ed",
"version": "2025-07",
"version": "2026-01",
"title": "TED-Ed",
"description": "Educational video lessons on science, history, literature, and more",
"url": "https://download.kiwix.org/zim/ted/ted_mul_ted-ed_2025-07.zim",
"url": "https://download.kiwix.org/zim/ted/ted_mul_ted-ed_2026-01.zim",
"size_mb": 5610
},
{
"id": "wikiversity_en_all_maxi",
"version": "2025-11",
"version": "2026-02",
"title": "Wikiversity",
"description": "Tutorials, courses, and learning materials for all levels",
"url": "https://download.kiwix.org/zim/wikiversity/wikiversity_en_all_maxi_2025-11.zim",
"url": "https://download.kiwix.org/zim/wikiversity/wikiversity_en_all_maxi_2026-02.zim",
"size_mb": 2370
},
{
"id": "libretexts.org_en_math",
"version": "2025-01",
"version": "2026-01",
"title": "LibreTexts Mathematics",
"description": "Open-source math textbooks from algebra to calculus",
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_math_2025-01.zim",
"size_mb": 831
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_math_2026-01.zim",
"size_mb": 792
},
{
"id": "libretexts.org_en_phys",
"version": "2025-01",
"version": "2026-01",
"title": "LibreTexts Physics",
"description": "Physics courses and textbooks",
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_phys_2025-01.zim",
"size_mb": 560
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_phys_2026-01.zim",
"size_mb": 534
},
{
"id": "libretexts.org_en_chem",
@ -266,18 +266,18 @@
"resources": [
{
"id": "wikibooks_en_all_maxi",
"version": "2025-10",
"version": "2026-01",
"title": "Wikibooks (With Images)",
"description": "Open textbooks with full illustrations and diagrams",
"url": "https://download.kiwix.org/zim/wikibooks/wikibooks_en_all_maxi_2025-10.zim",
"url": "https://download.kiwix.org/zim/wikibooks/wikibooks_en_all_maxi_2026-01.zim",
"size_mb": 5400
},
{
"id": "ted_mul_ted-conference",
"version": "2025-08",
"version": "2026-02",
"title": "TED Conference",
"description": "Main TED conference talks on ideas worth spreading",
"url": "https://download.kiwix.org/zim/ted/ted_mul_ted-conference_2025-08.zim",
"url": "https://download.kiwix.org/zim/ted/ted_mul_ted-conference_2026-02.zim",
"size_mb": 16500
},
{
@ -290,11 +290,11 @@
},
{
"id": "libretexts.org_en_geo",
"version": "2025-01",
"version": "2026-01",
"title": "LibreTexts Geosciences",
"description": "Earth science, geology, and environmental studies",
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_geo_2025-01.zim",
"size_mb": 1190
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_geo_2026-01.zim",
"size_mb": 1127
},
{
"id": "libretexts.org_en_eng",
@ -306,11 +306,11 @@
},
{
"id": "libretexts.org_en_biz",
"version": "2025-01",
"version": "2026-01",
"title": "LibreTexts Business",
"description": "Business, economics, and management textbooks",
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_biz_2025-01.zim",
"size_mb": 840
"url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_biz_2026-01.zim",
"size_mb": 801
}
]
}
@ -331,18 +331,18 @@
"resources": [
{
"id": "woodworking.stackexchange.com_en_all",
"version": "2025-12",
"version": "2026-02",
"title": "Woodworking Q&A",
"description": "Stack Exchange Q&A for carpentry, joinery, and woodcraft",
"url": "https://download.kiwix.org/zim/stack_exchange/woodworking.stackexchange.com_en_all_2025-12.zim",
"url": "https://download.kiwix.org/zim/stack_exchange/woodworking.stackexchange.com_en_all_2026-02.zim",
"size_mb": 99
},
{
"id": "mechanics.stackexchange.com_en_all",
"version": "2025-12",
"version": "2026-02",
"title": "Motor Vehicle Maintenance Q&A",
"description": "Stack Exchange Q&A for car and motorcycle repair",
"url": "https://download.kiwix.org/zim/stack_exchange/mechanics.stackexchange.com_en_all_2025-12.zim",
"url": "https://download.kiwix.org/zim/stack_exchange/mechanics.stackexchange.com_en_all_2026-02.zim",
"size_mb": 321
}
]
@ -355,10 +355,10 @@
"resources": [
{
"id": "diy.stackexchange.com_en_all",
"version": "2025-12",
"version": "2026-02",
"title": "DIY & Home Improvement Q&A",
"description": "Stack Exchange Q&A for home repairs, electrical, plumbing, and construction",
"url": "https://download.kiwix.org/zim/stack_exchange/diy.stackexchange.com_en_all_2025-12.zim",
"url": "https://download.kiwix.org/zim/stack_exchange/diy.stackexchange.com_en_all_2026-02.zim",
"size_mb": 1900
}
]
@ -375,7 +375,7 @@
"title": "iFixit Repair Guides",
"description": "Step-by-step repair guides for electronics, appliances, and vehicles",
"url": "https://download.kiwix.org/zim/ifixit/ifixit_en_all_2025-12.zim",
"size_mb": 3570
"size_mb": 3380
}
]
}
@ -396,18 +396,18 @@
"resources": [
{
"id": "foss.cooking_en_all",
"version": "2025-11",
"version": "2026-02",
"title": "FOSS Cooking",
"description": "Quick and easy cooking guides and recipes",
"url": "https://download.kiwix.org/zim/zimit/foss.cooking_en_all_2025-11.zim",
"url": "https://download.kiwix.org/zim/zimit/foss.cooking_en_all_2026-02.zim",
"size_mb": 24
},
{
"id": "based.cooking_en_all",
"version": "2025-11",
"version": "2026-02",
"title": "Based.Cooking",
"description": "Simple, practical recipes from the community",
"url": "https://download.kiwix.org/zim/zimit/based.cooking_en_all_2025-11.zim",
"url": "https://download.kiwix.org/zim/zimit/based.cooking_en_all_2026-02.zim",
"size_mb": 16
}
]
@ -420,18 +420,18 @@
"resources": [
{
"id": "gardening.stackexchange.com_en_all",
"version": "2025-12",
"version": "2026-02",
"title": "Gardening Q&A",
"description": "Stack Exchange Q&A for growing your own food, plant care, and landscaping",
"url": "https://download.kiwix.org/zim/stack_exchange/gardening.stackexchange.com_en_all_2025-12.zim",
"url": "https://download.kiwix.org/zim/stack_exchange/gardening.stackexchange.com_en_all_2026-02.zim",
"size_mb": 923
},
{
"id": "cooking.stackexchange.com_en_all",
"version": "2025-12",
"version": "2026-02",
"title": "Cooking Q&A",
"description": "Stack Exchange Q&A for cooking techniques, food safety, and recipes",
"url": "https://download.kiwix.org/zim/stack_exchange/cooking.stackexchange.com_en_all_2025-12.zim",
"url": "https://download.kiwix.org/zim/stack_exchange/cooking.stackexchange.com_en_all_2026-02.zim",
"size_mb": 236
},
{
@ -485,18 +485,18 @@
"resources": [
{
"id": "freecodecamp_en_all",
"version": "2025-11",
"version": "2026-02",
"title": "freeCodeCamp",
"description": "Interactive programming tutorials - JavaScript, algorithms, and data structures",
"url": "https://download.kiwix.org/zim/freecodecamp/freecodecamp_en_all_2025-11.zim",
"url": "https://download.kiwix.org/zim/freecodecamp/freecodecamp_en_all_2026-02.zim",
"size_mb": 8
},
{
"id": "devdocs_en_python",
"version": "2026-01",
"version": "2026-02",
"title": "Python Documentation",
"description": "Complete Python language reference and tutorials",
"url": "https://download.kiwix.org/zim/devdocs/devdocs_en_python_2026-01.zim",
"url": "https://download.kiwix.org/zim/devdocs/devdocs_en_python_2026-02.zim",
"size_mb": 4
},
{
@ -533,26 +533,26 @@
"resources": [
{
"id": "arduino.stackexchange.com_en_all",
"version": "2025-12",
"version": "2026-02",
"title": "Arduino Q&A",
"description": "Stack Exchange Q&A for Arduino microcontroller projects",
"url": "https://download.kiwix.org/zim/stack_exchange/arduino.stackexchange.com_en_all_2025-12.zim",
"url": "https://download.kiwix.org/zim/stack_exchange/arduino.stackexchange.com_en_all_2026-02.zim",
"size_mb": 247
},
{
"id": "raspberrypi.stackexchange.com_en_all",
"version": "2025-12",
"version": "2026-02",
"title": "Raspberry Pi Q&A",
"description": "Stack Exchange Q&A for Raspberry Pi projects and troubleshooting",
"url": "https://download.kiwix.org/zim/stack_exchange/raspberrypi.stackexchange.com_en_all_2025-12.zim",
"url": "https://download.kiwix.org/zim/stack_exchange/raspberrypi.stackexchange.com_en_all_2026-02.zim",
"size_mb": 285
},
{
"id": "devdocs_en_node",
"version": "2026-01",
"version": "2026-02",
"title": "Node.js Documentation",
"description": "Node.js API reference and guides",
"url": "https://download.kiwix.org/zim/devdocs/devdocs_en_node_2026-01.zim",
"url": "https://download.kiwix.org/zim/devdocs/devdocs_en_node_2026-02.zim",
"size_mb": 1
},
{
@ -581,18 +581,18 @@
"resources": [
{
"id": "electronics.stackexchange.com_en_all",
"version": "2025-12",
"version": "2026-02",
"title": "Electronics Q&A",
"description": "Stack Exchange Q&A for circuit design, components, and electrical engineering",
"url": "https://download.kiwix.org/zim/stack_exchange/electronics.stackexchange.com_en_all_2025-12.zim",
"url": "https://download.kiwix.org/zim/stack_exchange/electronics.stackexchange.com_en_all_2026-02.zim",
"size_mb": 3800
},
{
"id": "robotics.stackexchange.com_en_all",
"version": "2025-12",
"version": "2026-02",
"title": "Robotics Q&A",
"description": "Stack Exchange Q&A for robotics projects and automation",
"url": "https://download.kiwix.org/zim/stack_exchange/robotics.stackexchange.com_en_all_2025-12.zim",
"url": "https://download.kiwix.org/zim/stack_exchange/robotics.stackexchange.com_en_all_2026-02.zim",
"size_mb": 233
},
{

View File

@ -37,7 +37,7 @@
"id": "all-nopic",
"name": "Complete Wikipedia (No Images)",
"description": "All articles without images. Comprehensive offline reference.",
"size_mb": 25000,
"size_mb": 49000,
"url": "https://download.kiwix.org/zim/wikipedia/wikipedia_en_all_nopic_2025-12.zim",
"version": "2025-12"
},
@ -45,7 +45,7 @@
"id": "all-maxi",
"name": "Complete Wikipedia (Full)",
"description": "The complete experience with all images and media.",
"size_mb": 115000,
"size_mb": 118000,
"url": "https://download.kiwix.org/zim/wikipedia/wikipedia_en_all_maxi_2026-02.zim",
"version": "2026-02"
}

View File

@ -403,6 +403,15 @@ download_management_compose_file() {
local db_root_password=$(generateRandomPass)
local db_user_password=$(generateRandomPass)
# If MySQL data directory exists from a previous install attempt, remove it.
# MySQL only initializes credentials on first startup when the data dir is empty.
# If stale data exists, MySQL ignores the new passwords above and uses the old ones,
# causing "Access denied" errors when the admin container tries to connect.
if [[ -d "${NOMAD_DIR}/mysql" ]]; then
echo -e "${YELLOW}#${RESET} Removing existing MySQL data directory to ensure credentials match...\\n"
sudo rm -rf "${NOMAD_DIR}/mysql"
fi
# Inject dynamic env values into the compose file
echo -e "${YELLOW}#${RESET} Configuring docker-compose file env variables...\\n"
sed -i "s|URL=replaceme|URL=http://${local_ip_address}:8080|g" "$compose_file_path"
@ -510,7 +519,7 @@ verify_gpu_setup() {
fi
# Check if Docker has NVIDIA runtime
if docker info 2>/dev/null | grep -q \"nvidia\"; then
if docker info 2>/dev/null | grep -q "nvidia"; then
echo -e "${GREEN}${RESET} Docker NVIDIA runtime configured\\n"
else
echo -e "${YELLOW}${RESET} Docker NVIDIA runtime not detected\\n"
@ -526,7 +535,7 @@ verify_gpu_setup() {
echo -e "${YELLOW}===========================================${RESET}\\n"
# Summary
if command -v nvidia-smi &> /dev/null && docker info 2>/dev/null | grep -q \"nvidia\"; then
if command -v nvidia-smi &> /dev/null && docker info 2>/dev/null | grep -q "nvidia"; then
echo -e "${GREEN}#${RESET} GPU acceleration is properly configured! The AI Assistant will use your GPU.\\n"
else
echo -e "${YELLOW}#${RESET} GPU acceleration not detected. The AI Assistant will run in CPU-only mode.\\n"

View File

@ -82,7 +82,7 @@ services:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 30s
timeout: 10s
retries: 3
retries: 10
redis:
image: redis:7-alpine
container_name: nomad_redis
@ -117,4 +117,4 @@ services:
volumes:
nomad-update-shared:
driver: local
driver: local

View File

@ -1,6 +1,6 @@
{
"name": "project-nomad",
"version": "1.30.2",
"version": "1.30.3-rc.1",
"description": "\"",
"main": "index.js",
"scripts": {