Skip to content
Platform > Dynamic Components

Managing Components at Runtime

Components are managed in 2 ways: through the ami object on the admin console, or by editing data/ami.components directly. Both change the running estate without a restart, and both leave the file as the persisted record.

Overview

The admin console applies a change immediately and writes it back to the file. Editing the file is declarative: the change takes effect on the next poll. Use the console for interactive, one-off changes with immediate feedback, and file edits for repeatable or version-controlled configuration.

Manage components from the admin console

The ami object exposes the component lifecycle. Each call returns a confirmation string.

Operation Console call Result
Add and start a component ami.addComponent(name, type, pwd) Component added: <name>
Stop a component, keeping it defined ami.shutdownComponent(name) Component shutdown: <name>
Start a stopped component ami.startupComponent(name) Component started: <name>
Restart a component ami.restartComponent(name) Component restarted: <name>
Reload a component's property files from disk ami.reloadComponent(name) Component reloaded: <name>
Remove a component ami.removeComponent(name) Component removed: <name>

Adding and removing a component are written to data/ami.components. Stopping, starting, restarting, and reloading act on a component that is already declared, and do not change the file.

Do not stop, start, or restart a GATEWAY component

shutdownComponent, startupComponent, and restartComponent behave as described above for every component type except GATEWAY.

A Gateway does stop when it is shut down, and releases its ports, but it continues to report RUNNING in ami.showComponents(). Starting it again is then refused with Component is already running: <name>, and a restart, which is a shutdown followed by a start, fails the same way and leaves the Gateway stopped.

Use ami.reloadComponent(name) on a Gateway, which restores its listeners. Removing and re-adding the component also works. Neither needs an AmiCore restart.

The status column is the trap here: a stopped Gateway reads as RUNNING, so an operator watching component status does not see that clients have lost it.

To inspect the estate:

  • ami.showComponents() lists every component with its type, working directory, start time, and status.
  • ami.showPorts() lists the ports each component holds.
  • ami.showFiles() lists the files each component owns.

Edit the components file directly

To add a component by editing the file, append a line to data/ami.components:

edge;WEBBALANCER;.

Save the file. On the next poll, AmiCore starts the component. To remove a component, delete its line and save; AmiCore stops it on the next poll. Comments and blank lines in the file are preserved.

Check that the file ends in a newline before appending

When the console rewrites data/ami.components after an add or a remove, it writes the last entry without a trailing newline. Appending to the file in that state joins the new text onto the existing last line rather than starting a new one.

The result is a single malformed entry that replaces a working one. Appending edge;WEBBALANCER;. to a file ending web;WEB;. produces web;WEB;.edge;WEBBALANCER;., which AmiCore reads as the web component with an invalid working directory. It stops the running Web server, and the replacement does not start. ami.showComponents() then fails with Internal Error: NullPointerException rather than reporting the failed component, so the estate looks unreachable rather than misconfigured.

Add a leading newline, or confirm the file ends in one, before appending. Restoring the correct line brings the component back on the next poll, with no AmiCore restart needed.

Run a second component of the same type

Each combination of component type and working directory must be unique. Adding a second component of a type already running in a directory is rejected:

Component of type WEB already exists at: <pwd>

A second component of the same type must run in its own working directory with its own ports. Build that directory by copying the deployment's own configuration and changing only the ports, so the component starts from a complete configuration.

  1. Create the working directory and copy config, keys, and data into it. Properties such as https.keystore.file, ami.global.dir, and ami.web.headless.file resolve relative to the component's own working directory, so each component needs its own copy of these.

    mkdir web2
    cp -r config keys data web2/
    
  2. Change the listening ports in the copy's config/local.properties. Leave the Center connection settings as they are, so the new component registers with the same Center as the original, which is what makes it a second node rather than a second deployment.

    1
    2
    3
    http.port=43342
    https.port=43343
    ami.rpc.ws.port=43344
    
  3. Add the component, pointing at the new directory.

    ami.addComponent("web2", "WEB", "./web2")
    

The console replies Component added: web2. ami.showComponents() lists it as RUNNING beside the original, whose start time is unchanged, and ami.showPorts() shows each component on its own ports. The addition is written to data/ami.components, so the second component returns on the next restart.

Creating the working directory with the console instead

The 4-argument form of addComponent creates the working directory and writes its config/root.properties from the properties argument, then starts the component:

ami.addComponent(name, type, pwd, properties)

The properties argument is written verbatim, one key=value per line separated by newlines, and becomes the component's whole configuration. It inherits nothing from the deployment, so it must carry every property the component type requires.

This suits a component with a short configuration. For a second Web or Center, prefer the copy above: properties are validated in stages, so an incomplete set surfaces one missing key per attempt, reporting required property missing: ami.center.host, then https.keystore.password, then ami.center.ssl.pub.file, each only once the previous one is supplied.

A failed add can leave a port bound

Once enough properties are present for the component to reach its listener setup, a later validation failure leaves the listener bound. The port stays held by the AmiCore process, and the component is not registered: it does not appear in ami.showComponents() or ami.showPorts(), and ami.removeComponent() reports Component not found.

Nothing releases the port except restarting AmiCore, which is the one thing runtime component management exists to avoid. A failure early in validation, before the listener is set up, does not hold a port.

Build the property set for a new component in a non-production process first, so the add succeeds on the first attempt against a live one.

Create a transient component

A transient component is a short-lived copy that is never written to data/ami.components. Clone a running component with:

ami.copyComponentAsTransient(sourceName, optionsOverride)

optionsOverride is a map of property overrides applied to the copy, written as a map literal:

ami.copyComponentAsTransient("relay", {"ami.client.keyfile":"<path>/keys/3forge.pub","ami.keyfile":"<path>/keys/3forge.jks"})

The call replies Transient component added: <source>_transient<N>, naming the copy it created. The copy runs in the transient directory on an unused port from the configured range, and is never written to data/ami.components.

A component that relies on client keys requires its key-file paths in optionsOverride. The copy's keys/ directory receives the keystore but not the public key, so a clone made with an empty map fails to start with Error loading public keys: <transient path>\keys\3forge.pub.

Remove the copy with ami.removeTransientComponent(name, permanentlyDeleteFiles), where true requests deletion of its files and false retains them for the next-restart archive.

Note

A removal replies Transient component removed: <name> (files deleted) whether or not the files were deleted. AmiCore holds the copy's property files open, so deletion commonly fails: it retries, gives up, and records Failed to clean up transient path <path> after 6 attempts in the log. The directory remains under the transient directory's delete_me level until the process restarts. Check the log rather than the reply to confirm a deletion, and see Configuration for the transient directory, retention, and port-range properties.

What this does not support

A second component of the same type cannot run in the same working directory. Use a separate working directory with its own ports, as described in Run a second component of the same type.