Voile Locker & Luggage Plugin

A visitor locker management plugin for Voile, the open-source GLAM library management system.

Provides per-node locker assignment during visitor check-in, staff management UI, session history, and auto-expiry.

Features


Requirements

Requirement Version
Elixir ~> 1.18
OTP ~> 27
Phoenix LiveView ~> 1.1
Voile >= 0.1.2
PostgreSQL >= 14

Installation

Option A — From Hex (coming soon)

# mix.exs
{:voile_locker_luggage, "~> 0.1.0"}

Option B — From GitHub (recommended for now)

You can pin to a specific release tag:

# mix.exs
{:voile_locker_luggage,
  github: "curatorian/voile_locker_luggage",
  tag: "v0.1.0",
  sparse: "."
}

Or track the latest commit on main (not recommended for production):

{:voile_locker_luggage,
  github: "curatorian/voile_locker_luggage",
  branch: "main",
  sparse: "."
}

Option C — From a local path (development / self-hosted)

If you have cloned this repository alongside your Voile installation:

# mix.exs — inside the host Voile app
{:voile_locker_luggage, path: "plugins/voile_locker_luggage"}

This is the recommended approach if you are running Voile from source in a monorepo-style setup (all plugins live under plugins/ inside the Voile repo).


Setup Steps

After adding the dependency, follow these steps inside your Voile host app:

1. Fetch dependencies

mix deps.get

2. Compile

mix compile

The plugin is a separate OTP application and will be compiled alongside Voile.

3. Install the plugin from the admin UI

Start Voile and navigate to:

/manage/plugins

Click "Install" next to Locker & Luggage. This runs the plugin's database migrations and registers it in voile_plugins.

Alternatively, install via IEx:

Voile.PluginManager.install("Elixir.VoileLockerLuggage")

4. Activate the plugin

After installing, click "Activate" in the admin UI, or via IEx:

Voile.PluginManager.activate("Elixir.VoileLockerLuggage")

Activation registers the check-in hook so the plugin participates in visitor flows.

5. Configure per-node

Navigate to /manage/plugins/locker_luggage/nodes and enable the locker system for each node (library branch / service desk) that has physical lockers.

6. Configure plugin settings

Navigate to /manage/plugins/locker_luggage/settings to configure:

Setting Default Description
Allow Self-Release true Visitors can release their own locker at check-out
Auto-Expire After (hours) 24 Expire sessions automatically after N hours (0 = disabled)
Show Locker Number on Receipt true Print locker number on check-in receipt
Notify Staff on Expiry false Dashboard notification when a session expires

Database Migrations

Migrations are applied automatically when you Install the plugin from the admin UI. They run against the same PostgreSQL database as the Voile host app.

Tables created by this plugin are prefixed with plugin_locker_luggage_:

To roll back all migrations (performed when you Uninstall the plugin):

Voile.PluginManager.uninstall("Elixir.VoileLockerLuggage")

Warning: Uninstalling drops all plugin tables and their data permanently.


Production Installation

Using mix release

Elixir releases bake in all compiled code and dependencies at build time. Because the plugin is a dependency in mix.exs, it is automatically included in the release artifact.

Build steps:

# 1. Add the dep to mix.exs (see Installation above), then:
mix deps.get --only prod
MIX_ENV=prod mix compile

# 2. Build the release
MIX_ENV=prod mix release

# 3. Deploy the release artifact to the server
# (copy _build/prod/rel/voile/ to the target machine)

# 4. On the server, start the app
./bin/voile start

# 5. Install and activate the plugin (one-time, via remote IEx)
./bin/voile remote
iex> Voile.PluginManager.install("Elixir.VoileLockerLuggage")
iex> Voile.PluginManager.activate("Elixir.VoileLockerLuggage")

From the second deployment onward, the plugin is already registered in the database — just redeploying the release is enough. If there are new migrations, call on_update/2 or re-run install from the admin UI.

Using Podman / Docker (container-based)

The plugin must be included in the container image at build time — there is no dynamic plugin loading at container runtime. The image acts as the release artifact.

Example Containerfile / Dockerfile additions:

# If using Option C (local path dep):
# Make sure the plugin dir is copied before mix deps.get
COPY plugins/ plugins/

# Standard Phoenix build steps — no changes needed
RUN mix deps.get --only prod
RUN MIX_ENV=prod mix compile
RUN MIX_ENV=prod mix release

If using Option B (GitHub dep), no extra COPY step is needed — mix deps.get fetches the plugin automatically during the image build.

Plugin activation in a containerised environment:

Since you cannot do an interactive IEx session easily in production containers, you can activate the plugin via a release eval command in your startup script or Docker entrypoint:

# In entrypoint.sh or deploy script — runs once on first deploy
./bin/voile eval "Voile.PluginManager.install(\"Elixir.VoileLockerLuggage\")"
./bin/voile eval "Voile.PluginManager.activate(\"Elixir.VoileLockerLuggage\")"

Or add an idempotent task to your Voile deployment hooks so it is safe to run on every container restart:

# Safe to run every time — install/activate are no-ops if already done
./bin/voile eval "
  case Voile.Plugins.get_plugin_by_plugin_id(\"locker_luggage\") do
    nil -> Voile.PluginManager.install(\"Elixir.VoileLockerLuggage\")
    _ -> :ok
  end
"

Key rule: Adding or removing a plugin always requires rebuilding the container image and redeploying. This is by design — plugins are trusted compiled Elixir code, not dynamic bytecode.


Updating the Plugin

1. Update the version in mix.exs

# Change tag to the new release
{:voile_locker_luggage, github: "your-org/voile_locker_luggage", tag: "v1.1.0"}

2. Fetch and rebuild

mix deps.update voile_locker_luggage
mix deps.get
MIX_ENV=prod mix release   # or rebuild the container image

3. Run update migrations

From the admin UI at /manage/plugins, click "Update". This calls on_update/2 which runs any new migrations.

Or via release eval:

./bin/voile eval "Voile.PluginManager.update(\"Elixir.VoileLockerLuggage\")"

Version Checking

This plugin exposes its current version via VoileLockerLuggage.metadata/0:

VoileLockerLuggage.metadata().version  # "1.0.0"

Voile's plugin system is designed to support automatic version-checking against GitHub Releases. When implemented, the check works as follows:

  1. Voile periodically fetches https://api.github.com/repos/curatorian/voile_locker_luggage/releases/latest
  2. Compares the tag_name against the installed plugin's metadata().version
  3. Shows an "Update available" badge in /manage/plugins if a newer version exists

This is an upcoming feature of the core Voile plugin manager. Until then, check the GitHub Releases page manually for new versions.


Uninstalling

  1. Navigate to /manage/plugins
  2. Click "Deactivate" — removes all hooks from the running system
  3. Click "Uninstall" — rolls back all database migrations

Warning: Uninstalling permanently drops all locker and session data.

  1. Remove the dependency from mix.exs:
# Remove or comment out:
# {:voile_locker_luggage, ...}
  1. Rebuild and redeploy.

Development

Clone this repository alongside Voile for local development:

git clone https://github.com/curatorian/voile_locker_luggage \
  /path/to/voile/plugins/voile_locker_luggage

Ensure Voile's mix.exs references the plugin as a path dep (see Option C above).

Plugin code hot-reloads automatically in dev mode — changes to .ex and .heex files under plugins/ are picked up by Phoenix's live reload without restarting the server.

Running plugin migrations in dev

cd /path/to/voile
mix ecto.migrate   # runs both core and plugin migrations

Or trigger via the admin UI after installing the plugin in dev.


Architecture

This plugin follows the Voile Plugin System contract:


License

Apache 2.0 — see LICENSE.