# FeatherQuilld plugins

> Extend FeatherQuilld with .NET plugins: plugin.yml, capabilities, routes, settings and the 162 events you can hook, generated from the Quilld SDK.

Source: https://featherpanel.com/docs/quilld/plugins/

FeatherQuilld can load plugins written in .NET. A plugin can add HTTP routes to the daemon, run code before or after its own requests, and hook events such as a WebSpace starting or a backup being created. These are daemon plugins and are separate from [FeatherPanel plugins](https://featherpanel.com/docs/plugins/).

## Before you enable plugins

Plugins run inside the daemon, which runs as root. Turning them on is the same as giving every plugin author root on the node. That is why they are off by default. Only install plugins you have read or built yourself.

## Enabling and installing

Set `plugins.enabled: true`, either in `/etc/featherquilld/config.yml` or in the web node's Advanced Config Overrides (`{"plugins":{"enabled":true}}`). The panel's Plugins Directory field sets`plugins.directory`. Restart the service after adding or changing plugins.

```
plugins:
  enabled: true
  directory: /var/lib/featherquilld/plugins
  strict: false
  disabled: []
  settings:
    hello:
      greeting: Hi there
```

-   Each plugin goes in its own folder under the plugins directory, with its DLL and an optional `plugin.yml`.
-   `disabled` lists plugin IDs to skip.
-   `settings.<id>` overrides the settings in the plugin's manifest.
-   With `strict: true` a plugin that fails to load, verify or configure stops the daemon. Otherwise it is skipped with a warning.

A plugin is rejected when it has no ID, name or version, when its ID is already loaded, or when`MinHostVersion` is newer than the running daemon. `GET /api/system/plugins` lists the loaded plugins and `DELETE /api/system/plugins/<id>` unloads one until the next restart.

## Plugin structure

A plugin is a class library that references `FeatherQuilld.Plugins` and has one class implementing`IPlugin`. `Configure(PluginContext)` is called at startup. Implement`IPluginLifecycle` as well to get `StartAsync` and `StopAsync`. The context gives you`Routes`, `Events`, `Logger`, `Settings`, `Services` for dependency injection and `Host` for daemon information. This is the sample plugin from the repository, shortened:

```
public sealed class HelloPlugin : IPlugin, IPluginLifecycle
{
    public PluginMetadata Metadata { get; } = new()
    {
        Id = "hello", Name = "Hello Plugin", Version = "0.3.0",
        MinHostVersion = "0.1.0",
        Capabilities = [PluginCapabilities.RoutesPublic, PluginCapabilities.WebSpacesRead],
    };

    public void Configure(PluginContext context)
    {
        context.Routes
            .MapGet("/api/hello", (HttpContext _) => Results.Json(new
            {
                webspaces = context.Host?.WebSpaces.List().Count,
            }))
            .WithName("hello-greeting");

        context.Events.On<WebSpacePowerBeforeEvent>(evt =>
        {
            context.Logger.LogInformation("Power {Action} for {Uuid}", evt.Action, evt.WebSpaceUuid);
            return HookResult.Continue();
        });
    }

    public Task StartAsync(CancellationToken ct = default) => Task.CompletedTask;
    public Task StopAsync(CancellationToken ct = default) => Task.CompletedTask;
}
```

Its `plugin.yml`. Values here override the metadata in code; `main` names the DLL when the folder has more than one.

```
id: hello
name: Hello Plugin
version: 0.3.0
description: Sample plugin demonstrating full SDK surfaces.
author: FeatherQuilld
main: FeatherQuilld.Plugins.Hello.dll
enabled: true
capabilities:
  - routes.public
  - routes.system
  - host.info
  - webspaces.read
settings:
  greeting: Hello from plugin!
```

## Capabilities

A plugin declares what it needs. The daemon checks these today:

| Capability | Allows |
| --- | --- |
| `routes.public` | Map routes outside /api/system |
| `routes.system` | Map routes under /api/system |
| `host.info` | Read the daemon version, UUID and uptime |
| `webspaces.read` | List WebSpaces and read their name, runtime and status |

The SDK also defines these, which no host check uses yet:`webspaces.write`, `dns.write`, `mail.write`, `ftp.admin`, `files.read`.

## Routes

`context.Routes` has `MapGet`, `MapPost`, `MapPut`, `MapDelete`and `MapPatch`. Every plugin route needs the node token, like the rest of the Quilld API. A route under`/api/system` needs `routes.system`, any other route needs `routes.public`; without it the plugin is not configured.

`Before(pattern, hook)` and `After(pattern, hook)` run around matching requests, including the daemon's own routes. A before hook that returns `HookResult.Cancel()` stops the request.

## Events

Subscribe with `context.Events.On<TEvent>(handler)` and return a `HookResult`. Most actions come as a pair: a `…BeforeEvent` and an `…AfterEvent`. From a before event,`HookResult.Cancel()` aborts the action (the API answers 403) and `HookResult.Replace(value)`skips it and uses your value as the result. After events can only observe. The list below is generated from the SDK:162 events in 17 groups.

### Access

`AccessDeauthorizeBeforeEvent`, `AccessDeauthorizeAfterEvent`, `AccessSetPermissionsBeforeEvent`, `AccessSetPermissionsAfterEvent`

### Auth

`AuthValidateBeforeEvent`, `AuthValidateAfterEvent`

### Backup

`BackupCreateBeforeEvent`, `BackupCreateAfterEvent`, `BackupRestoreBeforeEvent`, `BackupRestoreAfterEvent`, `BackupDeleteBeforeEvent`, `BackupDeleteAfterEvent`, `BackupImportBeforeEvent`, `BackupImportAfterEvent`, `BackupReconcileBeforeEvent`, `BackupReconcileAfterEvent`

### BuiltIn

`PluginConfiguredEvent`, `ApplicationStartedEvent`, `ApplicationStoppingEvent`, `HttpRequestEvent`, `RouteExecutingEvent`, `RouteExecutedEvent`, `HealthCheckEvent`

### Dns

`DnsZoneCreateBeforeEvent`, `DnsZoneCreateAfterEvent`, `DnsRecordCreateBeforeEvent`, `DnsRecordCreateAfterEvent`, `DnsRecordUpdateBeforeEvent`, `DnsRecordUpdateAfterEvent`, `DnsRecordDeleteBeforeEvent`, `DnsRecordDeleteAfterEvent`, `DnsUpsertABeforeEvent`, `DnsUpsertAAfterEvent`, `DnsTxtCreateBeforeEvent`, `DnsTxtCreateAfterEvent`, `DnsTxtDeleteBeforeEvent`, `DnsTxtDeleteAfterEvent`

### File

`FileListBeforeEvent`, `FileListAfterEvent`, `FileReadBeforeEvent`, `FileReadAfterEvent`, `FileWriteBeforeEvent`, `FileWriteAfterEvent`, `FileCreateDirectoryBeforeEvent`, `FileCreateDirectoryAfterEvent`, `FileRenameBeforeEvent`, `FileRenameAfterEvent`, `FileCopyBeforeEvent`, `FileCopyAfterEvent`, `FileDeleteBeforeEvent`, `FileDeleteAfterEvent`, `FileUploadBeforeEvent`, `FileUploadAfterEvent`, `FileCompressBeforeEvent`, `FileCompressAfterEvent`, `FileDecompressBeforeEvent`, `FileDecompressAfterEvent`, `FileChmodBeforeEvent`, `FileChmodAfterEvent`, `FilePullBeforeEvent`, `FilePullAfterEvent`, `FileSymlinkBeforeEvent`, `FileSymlinkAfterEvent`, `FileDownloadArchiveBeforeEvent`, `FileDownloadArchiveAfterEvent`

### Ftp

`FtpAuthBeforeEvent`, `FtpAuthAfterEvent`, `FtpWriteBeforeEvent`, `FtpWriteAfterEvent`, `FtpDeleteBeforeEvent`, `FtpDeleteAfterEvent`, `FtpRenameBeforeEvent`, `FtpRenameAfterEvent`, `FtpMkdirBeforeEvent`, `FtpMkdirAfterEvent`

### Mail

`MailDomainAddBeforeEvent`, `MailDomainAddAfterEvent`, `MailDomainRemoveBeforeEvent`, `MailDomainRemoveAfterEvent`, `MailProvisionBeforeEvent`, `MailProvisionAfterEvent`, `MailSpamFilterBeforeEvent`, `MailSpamFilterAfterEvent`

### Malware

`MalwareScanBeforeEvent`, `MalwareScanAfterEvent`

### Package

`PackageInstallBeforeEvent`, `PackageInstallAfterEvent`, `PackageRemoveBeforeEvent`, `PackageRemoveAfterEvent`

### Panel

`PanelSyncBeforeEvent`, `PanelSyncAfterEvent`

### Proxy

`ProxyRebuildBeforeEvent`, `ProxyRebuildAfterEvent`, `AcmeEnsureCertsBeforeEvent`, `AcmeEnsureCertsAfterEvent`, `AcmeIssueBeforeEvent`, `AcmeIssueAfterEvent`, `StaticFileSyncBeforeEvent`, `StaticFileSyncAfterEvent`

### Schedule

`ScheduleExecuteBeforeEvent`, `ScheduleExecuteAfterEvent`, `ScheduleTaskExecuteBeforeEvent`, `ScheduleTaskExecuteAfterEvent`

### Sftp

`SftpAuthBeforeEvent`, `SftpAuthAfterEvent`, `SftpSessionOpenBeforeEvent`, `SftpSessionOpenAfterEvent`, `SftpSessionCloseAfterEvent`, `SftpWriteBeforeEvent`, `SftpWriteAfterEvent`, `SftpMkdirBeforeEvent`, `SftpMkdirAfterEvent`, `SftpRmdirBeforeEvent`, `SftpRmdirAfterEvent`, `SftpRemoveBeforeEvent`, `SftpRemoveAfterEvent`, `SftpRenameBeforeEvent`, `SftpRenameAfterEvent`, `SftpSetstatBeforeEvent`, `SftpSetstatAfterEvent`, `SftpOpenBeforeEvent`, `SftpOpenAfterEvent`, `SftpReadBeforeEvent`, `SftpReadAfterEvent`, `SftpReaddirBeforeEvent`, `SftpReaddirAfterEvent`, `SftpStatBeforeEvent`, `SftpStatAfterEvent`

### Transfer

`TransferOutgoingBeforeEvent`, `TransferOutgoingAfterEvent`, `TransferIncomingBeforeEvent`, `TransferIncomingAfterEvent`

### Trash

`TrashRestoreBeforeEvent`, `TrashRestoreAfterEvent`, `TrashPurgeBeforeEvent`, `TrashPurgeAfterEvent`

### WebSpace

`WebSpaceCreateBeforeEvent`, `WebSpaceCreateAfterEvent`, `WebSpaceSyncBeforeEvent`, `WebSpaceSyncAfterEvent`, `WebSpaceDeleteBeforeEvent`, `WebSpaceDeleteAfterEvent`, `WebSpaceReinstallBeforeEvent`, `WebSpaceReinstallAfterEvent`, `WebSpaceSslRenewBeforeEvent`, `WebSpaceSslRenewAfterEvent`, `WebSpacePowerBeforeEvent`, `WebSpacePowerAfterEvent`, `WebSpaceConsoleCommandBeforeEvent`, `WebSpaceConsoleCommandAfterEvent`, `WebSpaceExecBeforeEvent`, `WebSpaceExecAfterEvent`, `WebSpaceRedisBeforeEvent`, `WebSpaceRedisAfterEvent`, `WebSpaceAbortInstallBeforeEvent`, `WebSpaceAbortInstallAfterEvent`, `WebSpaceRecreateRuntimeBeforeEvent`, `WebSpaceRecreateRuntimeAfterEvent`, `WebSpaceSslUploadBeforeEvent`, `WebSpaceSslUploadAfterEvent`, `WebSpaceSslDeleteBeforeEvent`, `WebSpaceSslDeleteAfterEvent`
