Pular para o conteúdo principal

Installation

Lacuna Bulk Signer is a single service that can run as four supported targets:

TargetProcess modelLifecycle managed by
Linux systemdBackground servicesystemctl
Windows ServiceBackground serviceservices.msc / sc.exe
Docker / ComposeContainerdocker compose
Console (one-off / test)ForegroundOperator (Ctrl+C)

The same binary supports all four. The startup banner prints a host mode = … line that tells you which lifetime is actually active.

Lacuna Software provides a deployment package containing the published application bundle (the publish/ directory), the per-target install scripts (the deploy/ directory), and an annotated sample configuration file (appsettings.Production.json.sample). The instructions below assume you have that package on (or copied to) the target machine.

Choose your target

Where will the service run?Use
Linux serversystemd — deploy/linux/install.sh
Windows serverWindows Service — deploy/windows/Install-Service.ps1
Any host with DockerContainer — deploy/docker/docker-compose.yml
Just testing locallyConsole — run the published executable in the foreground

Prerequisites — common to every target

  1. Lacuna PKI SDK license string (base64), supplied by Lacuna Software. Required at startup; without it, the service refuses to boot. See Obtaining the PKI SDK license.

  2. A signing certificate source. Pick one of:

    • PFX — a .pfx / .p12 file plus the password that unlocks it.
    • PKCS#11 — a vendor driver (.so on Linux, .dll on Windows) plus the SHA-1 thumbprint of the signing certificate on the token, plus the PIN supplied through an environment variable.
    • Windows certificate store — Windows targets only, plus the SHA-1 thumbprint.

    See Certificates for details.

  3. Encryption decision. Leave disabled (default) or enable BSENC v1. If you enable encryption, decide where the password and salt will live before first boot. See Encryption.

  4. TLS termination. The service listens on plain HTTP by default. The recommended deployment terminates TLS at a reverse proxy (nginx, IIS, Traefik). The Hosting:RequireHttps flag (default false) gates the in-process HTTPS redirect — set it to true only if you have configured a Kestrel certificate.

  5. Watched input folders. Decide whether you need one input folder (default) or several. With a single folder, omit Storage:Inputs[] entirely — the service creates one named default at {Root}/input. For multiple folders, populate Storage:Inputs[] with one entry per folder; see Configuration.

Every install seeds an editable production config from the provided appsettings.Production.json.sample. The sample is annotated with REQUIRED and SECRET markers; review it before first start.

Obtaining the PKI SDK license

The license is a base64 string supplied by Lacuna Software. Two ways to load it:

WhereHow
Environment variable (preferred)Set Signing__License=<base64-license>
Config fileSet Signing:License in appsettings.Production.json

The environment variable takes precedence at boot. The install scripts read the environment variable from the per-target file (/etc/bulksigner/bulksigner.env on Linux, machine-scope environment variables on Windows, .env on Docker) so the license never lands in a committed file. See Security for the full secrets-handling story.

Linux — systemd

# 1. Copy the publish/ bundle and deploy/ scripts to the target machine, then:
sudo bash deploy/linux/install.sh --from publish

# 2. Edit the production config and the secrets env file.
sudo nano /etc/bulksigner/appsettings.Production.json
sudo nano /etc/bulksigner/bulksigner.env

# 3. Restart so config changes take effect.
sudo systemctl restart bulksigner

# 4. Verify the service is up.
curl http://localhost:8080/api/health
curl http://localhost:8080/api/ready
systemctl --no-pager status bulksigner
journalctl -u bulksigner -f

Install paths (FHS conventions):

PathPurposeModeOwner
/opt/bulksignerBinary (read-only after install)0755root:root
/var/lib/bulksignerData: input / processing / output / db0750bulksigner:bulksigner
/var/log/bulksignerDurable log files0750bulksigner:bulksigner
/etc/bulksignerappsettings.Production.json + bulksigner.env0750bulksigner:bulksigner

The systemd unit uses Type=notify so systemctl status reports active (running) only after the full bootstrap (license load + database migration + pipeline recovery) succeeds. Hardening flags (NoNewPrivileges, ProtectSystem=strict, PrivateTmp) are on by default.

Uninstall:

sudo bash deploy/linux/uninstall.sh # stop + remove the unit, preserve data
sudo bash deploy/linux/uninstall.sh --purge # also wipe data, logs, config, and the system user

Windows — Windows Service

# 1. Copy the publish/ bundle and deploy/ scripts to the target machine, then in an
# ELEVATED PowerShell prompt:
.\deploy\windows\Install-Service.ps1 -From publish

# 2. Edit the production config:
notepad C:\ProgramData\Lacuna\BulkSigner\config\appsettings.Production.json

# 3. Set secrets as machine-scope environment variables:
[Environment]::SetEnvironmentVariable("Signing__License", "<base64-license>", "Machine")
[Environment]::SetEnvironmentVariable("Auth__ApiKey", "<api-key>", "Machine")
[Environment]::SetEnvironmentVariable("BULK_SIGNER_PKCS11_PIN", "<hsm-pin>", "Machine")
[Environment]::SetEnvironmentVariable("BULK_SIGNER_ENCRYPTION_PASSWORD", "<password>", "Machine")
Restart-Service LacunaBulkSigner

# 4. Verify.
Invoke-WebRequest http://localhost:8080/api/health
Invoke-WebRequest http://localhost:8080/api/ready
Get-Service LacunaBulkSigner
Get-Content C:\ProgramData\Lacuna\BulkSigner\logs\bulksigner-*.log -Tail 50 -Wait

Install paths (Windows conventions):

PathPurpose
C:\Program Files\Lacuna\BulkSignerBinary (read-only after install)
C:\ProgramData\Lacuna\BulkSigner\configappsettings.Production.json
C:\ProgramData\Lacuna\BulkSigner\dataOperational data (input / processing / output / db)
C:\ProgramData\Lacuna\BulkSigner\logsLog files

The service runs under a virtual account (NT SERVICE\LacunaBulkSigner) — no operator password to manage, no domain account to permission. The install script grants this account access to the ProgramData tree and configures crash recovery (restart after 5 s on the first and second failure, 30 s on the third).

observação

Application-level logs go through the file sink only. The Windows Application event log carries service lifecycle entries (start / stop / failure) for this service — not the per-job log lines. Look in the log file for those.

Uninstall:

.\deploy\windows\Uninstall-Service.ps1 # stop + remove the service, preserve data
.\deploy\windows\Uninstall-Service.ps1 -Purge # also wipe ProgramData and the machine env vars

Docker / Compose

cd deploy/docker

# 1. Prepare working directories on the host.
cp .env.sample .env
mkdir -p data logs config
cp ../appsettings.Production.json.sample config/appsettings.Production.json

# 2. Edit the config and the env file.
nano config/appsettings.Production.json
nano .env

# 3. The container runs as UID 1654. On Linux hosts:
sudo chown -R 1654:1654 data logs

# 4. Start.
docker compose up -d

# 5. Verify.
curl http://localhost:8080/api/health
docker compose ps # should show "healthy" after ~30 s
docker compose logs -f bulksigner

The image is Debian-slim based — not Alpine. HSM .so libraries are generally not musl-compatible, so Alpine is off the table. The image ships generic PKCS#11 tooling (libpcsclite1 + opensc); vendor HSM drivers (SafeNet, Thales, Entrust, Yubico) are operator-mounted at runtime via volumes: in the compose file. See the commented examples in deploy/docker/docker-compose.yml.

A HEALTHCHECK polls /api/health every 30 seconds, so docker ps and orchestrators see accurate (healthy) / (unhealthy) status.

Bind mounts and host paths:

Container pathHost pathPurpose
/app/appsettings.Production.json./config/appsettings.Production.json (read-only)Operator-edited config
/var/lib/bulksigner./dataOperational data tree (input / processing / output / db)
/var/log/bulksigner./logsDurable log files

Foreground console (one-off / test)

Run the published executable directly to start the service in the foreground — useful for a quick local test or to see bootstrap errors immediately:

# Linux
./publish/Lacuna.BulkSigner

# Windows
.\publish\Lacuna.BulkSigner.exe
  • The data/ tree is created relative to the working directory.
  • Use Ctrl+C to stop. The bootstrap banner prints host mode = console.
  • On an interactive terminal, a live status panel replaces the streaming log. See Console dashboard.

Upgrades

The database schema migrates automatically at startup. To upgrade in place:

TargetSteps
Linuxsudo bash deploy/linux/install.sh --from <new-publish-dir> — stops the unit, redeploys the binary, restarts.
Windows.\deploy\windows\Install-Service.ps1 -From <new-publish-dir> — stops the service, mirrors the binary tree, restarts.
Dockerdocker compose pull && docker compose up -d.
Always back up the operational database before upgrading.
TargetBackup command
Linuxsudo cp /var/lib/bulksigner/db/bulksigner.db /var/lib/bulksigner/db/bulksigner.db.bak
WindowsCopy-Item C:\ProgramData\Lacuna\BulkSigner\data\db\bulksigner.db -Dest .\bulksigner.db.bak
Dockercp deploy/docker/data/db/bulksigner.db deploy/docker/data/db/bulksigner.db.bak

The startup recovery sweep moves any job left in flight by the previous version aside automatically — no manual cleanup needed. See Operations.

Quick health checks

After installing on any target:

URLWhat it tells you
http://localhost:8080/api/healthLiveness — anonymous, returns 200 OK if the host process is up.
http://localhost:8080/api/readyReadiness — anonymous, returns a body listing each probe (DB, input folder, license). 503 if any probe failed.
http://localhost:8080/The operator dashboard. Sign in with the API key from Auth:ApiKey.
http://localhost:8080/scalar/v1The live OpenAPI reference UI for the REST surface.

/api/health is always anonymous so external health checkers do not need credentials. /api/ready is anonymous too and returns a structured body. /api/metrics is API-key-gated by default — see Security.


Next: Configuration — what every appsettings.json key does.