Fresh Deployment & Legacy Article Import

This guide walks you through standing up a new Wikantik instance against PostgreSQL and, optionally, importing an existing corpus from a legacy JSPWiki deployment. It reflects the current code on main: database migrations live in bin/db/migrations/, the WAR deploys as the ROOT context, and the React SPA is served from the same origin.

Deployment Stack at a Glance

A complete deployment touches five things:

LayerWhat it holdsHow it is provisioned
PostgreSQLUsers, roles, groups, policy grants, chunk embeddings, API keysbin/db/install-fresh.sh + bin/db/migrate.sh (idempotent)
Tomcat 11Servlet container + JNDI DataSourceDownloaded automatically by bin/deploy-local.sh
Page corpusMarkdown files under docs/wikantik-pages/Version-controlled; no per-instance copy step
Search indexesLucene (in workDir) + content_chunk_embeddings (Postgres)Built on startup + async bootstrap
Embedding backendOllama HTTP endpoint (optional)External service; URL configured in properties

Prerequisites

ToolVersionCheck
Java (JDK)21+java -version
Maven3.9+mvn -version
Node.js + npm18+Required — the WAR build runs npm install + vite build automatically
PostgreSQL15+ (with pgvector)psql --version; pgvector is installed by V004
curl, tarUsed by deploy-local.sh to fetch Tomcat

PostgreSQL must be reachable on localhost:5432 (or override PGHOST/PGPORT).


Fresh Deployment (Clean Instance)

1. Bootstrap the database

install-fresh.sh creates the database, the application role, and runs every migration in order. It is idempotent — re-running against an already-bootstrapped database is a no-op.

sudo -u postgres \
    DB_NAME=wikantik \
    DB_APP_USER=jspwiki \
    DB_APP_PASSWORD='ChangeMe123!' \
    bin/db/install-fresh.sh

This creates:

To bootstrap a dedicated migration role (recommended for production so ALTER migrations don't run as postgres), also set DB_MIGRATE_PASSWORD and the script will call create-migrate-user.sh and transfer ownership.

2. Build the WAR

mvn clean install -Dmaven.test.skip -T 1C

This compiles all modules, builds the React frontend, and produces wikantik-war/target/Wikantik.war. Use a full mvn clean install (without -Dmaven.test.skip) before any production cutover to make sure the test suite is green.

3. Deploy to Tomcat

bin/deploy-local.sh

The script is self-assembling. On first run it will:

  1. Download Tomcat 11 into tomcat/tomcat-11/ (gitignored)
  2. Download the PostgreSQL JDBC driver to tomcat/tomcat-11/lib/
  3. Copy the context template to tomcat/tomcat-11/conf/Catalina/localhost/ROOT.xml with a YOUR_SECURE_PASSWORD_HERE placeholder
  4. Copy the properties template to tomcat/tomcat-11/lib/wikantik-custom.properties, substituting @@REPO_ROOT@@ for the repo path
  5. Copy the Log4j config to tomcat/tomcat-11/lib/log4j2.xml
  6. Deploy Wikantik.war as webapps/ROOT.war
  7. Run migrate.sh against the target database (no-op on fresh install, applies any new migrations on subsequent runs)
  8. Run bin/db/seed-users.sql — upserts the admin / admin123 and jakefear@gmail.com / passw0rd dev accounts
  9. Start Tomcat

4. Set the database password

Edit the context file to replace the placeholder:

$EDITOR tomcat/tomcat-11/conf/Catalina/localhost/ROOT.xml
# username="wikantik" → username="jspwiki"
# password="YOUR_SECURE_PASSWORD_HERE" → password="ChangeMe123!" (or whatever you set)

Restart Tomcat so the DataSource picks up the new password:

tomcat/tomcat-11/bin/shutdown.sh
tomcat/tomcat-11/bin/startup.sh

5. Verify the Deployment

For automated testing, test.properties (gitignored) holds a dedicated testbot admin account — see CLAUDE.md for how to recreate it after a database reset.


Importing Articles from a Legacy JSPWiki Deployment

Wikantik stores pages as *.md files under docs/wikantik-pages/ (version-controlled), with optional *.properties sidecars for per-page metadata. A legacy JSPWiki corpus stores pages as *.txt files using classic wiki syntax. The import is a one-time conversion.

1. Export the legacy corpus

Copy the page directory and attachments from the old deployment:

# On the legacy host
tar -czf legacy-pages.tgz \
    /var/lib/jspwiki/pages \
    /var/lib/jspwiki/attachments

scp legacy-pages.tgz new-host:/tmp/

A JSPWiki page directory typically contains:

2. Stage the files

Unpack into a staging directory — not directly into docs/wikantik-pages/ until after conversion:

mkdir -p /tmp/wikantik-import
tar -xzf /tmp/legacy-pages.tgz -C /tmp/wikantik-import --strip-components=3

Drop the OLD/ hierarchy if you do not want to preserve old revisions as separate pages — Wikantik treats git as the version of record.

3. Convert wiki syntax to Markdown

The scripts/wiki2markdown.py converter is a faithful port of WikiToMarkdownConverter.java. It scans a directory for .txt files, scores each against a wiki-syntax heuristic, converts if the score is above threshold, renames straight to .md if the file is already Markdown, and deletes the source .txt.

# Dry-run first — prints every planned action with per-file warnings
python3 scripts/wiki2markdown.py /tmp/wikantik-import --dry-run

# Commit the conversion
python3 scripts/wiki2markdown.py /tmp/wikantik-import

Conversions performed:

WikiMarkdown
! Heading, !! Heading, !!! Heading# Heading, ## Heading, ### Heading
__bold__**bold**
''italic''*italic*
{{inline code}}`inline code`
[PageName][PageName]() (Flexmark plugin syntax — Wikantik resolves the empty URL on render)
[text|url][text](http://example.com)
[{Plugin args}]()[{Plugin args}]()
{{{ code block }}}triple-backtick fenced block
|| header || cells || / || row |GFM tables

Any unconverted constructs (%%style, old-style footnotes, etc.) are flagged as warnings next to the filename. Resolve these by hand or leave them — Flexmark will render them as literal text.

4. Move into the page corpus

# Pages
cp /tmp/wikantik-import/*.md       docs/wikantik-pages/
# Keep the property sidecars — they carry categories, keywords, authors
cp /tmp/wikantik-import/*.properties docs/wikantik-pages/ 2>/dev/null || true

Attachments are not yet version-controlled in docs/wikantik-pages/. If the legacy corpus uses them:

# Attachments live under the wiki workDir, referenced by wikantik.basicAttachmentProvider.storageDir
mkdir -p tomcat/tomcat-11/data/attachments
cp -R /tmp/wikantik-import/attachments/* tomcat/tomcat-11/data/attachments/

5. Backfill frontmatter (optional)

Wikantik expects a YAML frontmatter block on each Markdown page. If a page lacks one, wikantik.frontmatter.autoDefaults = true (set by the deploy template) will generate a minimal block — title, type: article, tags: [uncategorized], empty summary — the first time the page is saved through the UI. For a bulk import you typically want real frontmatter. A minimal block looks like:

---
type: article
tags:
- imported
summary: One-line summary for search and link previews.
---

Run a small script over the staging directory to inject this block on every .md file that does not already have one — the import script at scripts/wiki2markdown.py does not do this for you.

6. Commit and redeploy

git add docs/wikantik-pages/
git commit -m "import: legacy JSPWiki page corpus"
bin/deploy-local.sh

deploy-local.sh does not rebuild indexes automatically — Wikantik will pick up the new Markdown on first request, but Lucene and the chunk embeddings need to be rebuilt explicitly.

7. Rebuild Lucene and Embedding Indexes

Lucene + chunks — via admin UI at /admin/index-status, or via REST:

source <(grep -v '^#' test.properties | sed 's/test.user.//' | sed 's/= /="/' | sed 's/$/"/')

curl -u "${login}:${password}" -X POST \
     http://localhost:8080/admin/content/rebuild-indexes

Embedding reindex (hybrid search — only if wikantik.search.hybrid.enabled=true and the Ollama endpoint is reachable):

curl -u "${login}:${password}" -X POST \
     http://localhost:8080/admin/content/reindex-embeddings

The embedding rebuild is async and streams progress into GET /admin/content/index-status. For a corpus of a few thousand pages against a CPU-only Ollama host, budget 30–90 minutes.

On a completely fresh instance the startup BootstrapEmbeddingIndexer will kick off the embedding run automatically when it detects an empty content_chunk_embeddings table — no admin action needed unless you want to force a rerun.


Subsequent Deployments

After the one-time setup, redeploying is a single command:

bin/deploy-local.sh

The script is safe to re-run: it preserves the context file (and therefore the DB password), reapplies any new migrations, re-seeds users, and restarts Tomcat. Rebuild the project first with mvn clean install -Dmaven.test.skip -T 1C if you have source changes.

For a faster iteration when only the WAR has changed:

mvn clean install -Dmaven.test.skip -T 1C -pl wikantik-war -am
tomcat/tomcat-11/bin/shutdown.sh
rm -rf tomcat/tomcat-11/webapps/ROOT tomcat/tomcat-11/webapps/ROOT.war \
       tomcat/tomcat-11/data/workdir tomcat/tomcat-11/logs/wikantik

cp wikantik-war/target/Wikantik.war tomcat/tomcat-11/webapps/ROOT.war
tomcat/tomcat-11/bin/startup.sh

Configuration Reference

Git-tracked templates

Located in wikantik-war/src/main/config/tomcat/:

FileInstalled toPurpose
Wikantik-context.xml.templateconf/Catalina/localhost/ROOT.xmlJNDI DataSource for PostgreSQL
wikantik-custom-postgresql.properties.templatelib/wikantik-custom.propertiesWiki settings (page dir, workDir, admin bootstrap, hybrid search, CORS)
log4j2-local.xml.templatelib/log4j2.xmlLogging config pointed at ${catalina.base}/logs/wikantik

Key properties

PropertyDefaultNotes
wikantik.pageProviderVersioningFileProviderReads from the on-disk page directory
wikantik.fileSystemProvider.pageDir<repo>/docs/wikantik-pagesTracked in git
wikantik.workDir<repo>/tomcat/tomcat-11/data/workdirLucene index lives here
wikantik.datasourcejdbc/WikiDatabaseMust match the Resource name in ROOT.xml
wikantik.userdatabaseJDBCUserDatabaseUsers/roles stored in Postgres
wikantik.admin.bootstrapunsetSet to a login name on first deploy to guarantee admin access; remove after
wikantik.frontmatter.autoDefaultstrueGenerates minimal frontmatter on save for pages without it
wikantik.search.hybrid.enabledtrueToggle dense retrieval; falls back to BM25 if off
wikantik.search.embedding.base-urlhttp://inference.jakefear.com:11434Ollama endpoint
wikantik.search.embedding.modelqwen3-embedding-0.6bAlso supports nomic-embed-v1.5, bge-m3
wikantik.cors.allowedOriginsunsetComma list; supports https://*.example.com wildcards

Troubleshooting

Database Errors

SymptomCauseResolution
Connection refused in catalina.outPostgreSQL not runningsudo systemctl start postgresql
Password authentication failed for user "jspwiki"Password mismatch between ROOT.xml and roleEdit ROOT.xml; restart Tomcat
relation "users" does not existMigrations never ranDB_NAME=wikantik bin/db/migrate.sh --status to check state; then run without --status to apply
must be owner of table X during migrateTables owned by postgres, running as migrateRe-run bin/db/create-migrate-user.sh to transfer ownership
extension "vector" is not availablepgvector not installed on serversudo apt install postgresql-15-pgvector (or the distro equivalent)

Deployment Errors

SymptomCauseResolution
WAR file not foundBuild not runmvn clean install -Dmaven.test.skip -T 1C
npm not foundNode.js missingInstall Node 18+; the WAR build depends on vite
JNDI name not foundContext file not loadedConfirm ROOT.xml exists in conf/Catalina/localhost/
Login fails with seeded passwordSchema mismatch after a resetRe-run psql -d wikantik -f bin/db/seed-users.sql
500 on admin API after DB restartExpected: fail-soft for reads, hard-fail for writesConnection pool reheats on next request; admin writes need the DB back

Index Errors

SymptomCauseResolution
Empty search results on a freshly-imported corpusLucene emptyPOST /admin/content/rebuild-indexes
Hybrid search returns BM25-onlyEmbedding backend unreachable or circuit openCheck wikantik.search.embedding.base-url; inspect catalina.out for embedder warnings
reindex-embeddings returns 409A rebuild is already runningWait; poll GET /admin/content/index-status
reindex-embeddings returns 503wikantik.search.hybrid.enabled=false or no embedder configuredEnable hybrid search and confirm the Ollama endpoint responds

Logs

# Tomcat container log
tail -f tomcat/tomcat-11/logs/catalina.out

# Application logs (Log4j)
tail -f tomcat/tomcat-11/logs/wikantik/wikantik.log

# Filter for DataSource / JNDI errors
grep -i "jdbc\|datasource\|jndi" tomcat/tomcat-11/logs/catalina.out

# Migration status
DB_NAME=wikantik bin/db/migrate.sh --status

Full reset (local only — destroys data)

# Tomcat
tomcat/tomcat-11/bin/shutdown.sh
rm -rf tomcat/tomcat-11/webapps/ROOT tomcat/tomcat-11/webapps/ROOT.war \
       tomcat/tomcat-11/data/workdir tomcat/tomcat-11/logs/wikantik

# Database
sudo -u postgres psql -c 'DROP DATABASE wikantik;'
sudo -u postgres DB_NAME=wikantik DB_APP_USER=jspwiki \
     DB_APP_PASSWORD='ChangeMe123!' bin/db/install-fresh.sh

# Redeploy
bin/deploy-local.sh