# FileDistro Actions

FileDistro Actions runs repository-owned commands on self-hosted machines. Runs and jobs are
stored durably in SQLite. A machine uses a one-time registration token and then polls with its own
revocable credential. The machine belongs to that FileDistro account and can be enabled for one,
several, or all repositories owned by the account.

## Agent quick start

Treat this document as the authoritative FileDistro Actions contract. FileDistro intentionally
implements a small GitHub-style subset, so do not assume an arbitrary GitHub Marketplace action or
workflow feature is available.

For an install, update, or deployment task:

1. inspect the repository and the target host conventions before changing anything;
2. put operational logic in an idempotent repository script such as
   `scripts/deploy-product.ps1`;
3. keep the workflow small and call that script;
4. use explicit OS and architecture labels so a host-specific job cannot reach the wrong runner;
5. enable `workflow_dispatch` and test it manually before relying on push deployment; and
6. never commit credentials or print them in logs. Encrypted repository secrets are not currently
   implemented, so provision required credentials securely on the runner host.

```yaml
name: Deploy product

on:
  push:
    branches: [main]
  workflow_dispatch:

concurrency:
  group: product-production
  cancel-in-progress: true

jobs:
  deploy:
    runs-on: [self-hosted, windows, x64]
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
      - name: Install or update product
        shell: pwsh
        run: ./scripts/deploy-product.ps1
```

This example is native: Docker may be installed on the runner, but the job runs on the host because
it does not define `container:`.

FileDistro supplies an archive of the exact commit that triggered the run. A push or manual dispatch
on `main` therefore builds the selected `main` revision; do not add a separate `git pull` that could
silently deploy a different commit.

### Persistent Windows products

A native Windows job runs with the runner service account's permissions and can restore, build,
publish, stop a product service, replace files, configure or create the service, start it, and run a
health check. Use a staging directory and an idempotent deployment script. Treat `robocopy` exit codes
0-7 as success and fail the job for higher values.

The product executable must implement the Windows service control lifetime before it is registered
with `New-Service` or `sc.exe`. A plain console executable is not automatically a Windows service.
Add service hosting support to the product or use a deliberate service wrapper.

A console host can run in the foreground for a test or one-off task, but the Actions step waits for
it to exit. Job cancellation or timeout kills the process tree. Do not leave a detached console
process as an unmanaged production deployment; use a Windows service or another machine supervisor
for startup, restart, logs, and controlled updates.

## Add a workflow

Put YAML files in `.filedistro/workflows` or `.github/workflows`. This is a deliberately small,
GitHub-style format:

```yaml
name: Build and test

on:
  push:
    branches: [main, "release/**"]
    branches-ignore: ["release/wip/**"]
  workflow_dispatch:

env:
  CONFIGURATION: Release

concurrency:
  group: build-and-test
  cancel-in-progress: true

jobs:
  test:
    name: Test suite
    runs-on: [self-hosted, docker]
    container: mcr.microsoft.com/dotnet/sdk:10.0
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
      - name: Restore
        run: dotnet restore
      - name: Test
        run: dotnet test --no-restore -c "$CONFIGURATION"
        shell: bash
```

`push` and `workflow_dispatch` are supported. Branch filters accept `*`, `**`, and `?`. Each job
requires `runs-on`; all listed labels must exist on one runner. Jobs are independent and can run
on different matching runners.

Workflow-level `env` values are inherited by every job, and a job or step can override them.
`concurrency` accepts a literal group name or a `group` / `cancel-in-progress` mapping. Runs in the
same repository and group are serialized; with cancellation enabled, an older run is cancelled
before its replacement starts.
Supported job fields are `name`, `runs-on`, `container`, `timeout-minutes`, `env`, and `steps`.
Supported step fields are `name`, `run`, `uses`, `shell`, `working-directory`, and `env`.
`actions/checkout` is the only built-in `uses` action because FileDistro already supplies an exact
commit archive. Use `run` for repository-local scripts. Supported shells are `bash`, `sh`, `pwsh`,
`powershell`, and `cmd` where those programs exist in the selected host or image.

Matrix builds, dependencies (`needs`), services, conditions, concurrency expressions, remote
marketplace actions, artifacts, and encrypted repository secrets are not currently implemented.
Do not commit credentials to a workflow or print them in logs.

## Docker and native-host jobs

Set `container` to run each step in that image. Docker is required, the repository workspace is
mounted at `/workspace`, and files written by one step remain for later steps. Each step uses a new
ephemeral container.

Omit `container` to run commands directly on the runner host. Native jobs have the permissions of
the runner service account and should be enabled only for repositories whose writers are trusted.
FileDistro does not treat native jobs as a sandbox.

Runners advertise these labels automatically:

- `self-hosted`
- `windows`, `linux`, or `macos`
- `x64`, `arm64`, or the detected architecture
- `docker` when the Docker daemon is reachable
- any custom labels passed to the installer

Container jobs are never assigned to a runner whose Docker daemon is unavailable, even if it has a
stale custom `docker` label.

## Install a runner

Open a repository, select **Actions**, then choose **Add runner**. FileDistro creates a token that
expires after 30 minutes and offers downloadable Windows and Linux scripts plus copyable commands.
Run the script from an Administrator PowerShell window on Windows or through `sudo` on Linux.

The installer:

1. detects OS and CPU architecture;
2. tests the Docker CLI and daemon;
3. offers to install Docker Desktop with `winget` on Windows or the distribution Docker package on
   Linux;
4. downloads the matching self-contained runner package from FileDistro;
5. exchanges the one-time token for a runner credential; and
6. installs an automatically restarting Windows service or systemd unit.

Docker can be selected without a prompt by adding `-InstallDocker -NonInteractive` on Windows or
`--install-docker --non-interactive` on Linux. Use `-SkipDocker` or `--skip-docker` for a native-only
runner. Docker Desktop may need to be launched once after installation before its daemon is ready.

After installation, every repository Actions tab lists the account's machines. **This repository**
adds or removes an explicit assignment. **All repositories** lets the machine claim jobs from every
current and future repository owned by the account. Existing jobs are allowed to finish when an
assignment is removed.

Removing a runner in any Actions tab revokes its credential for the entire account immediately.
Re-running the installer with the same runner name rotates the credential, preserves its existing
assignments, assigns the repository where the new token was created, and updates the service
configuration.

## Queue and recovery behavior

A successful Git push compares branch heads before and after receive-pack. Every changed branch is
evaluated at its new commit, so the workflow used for a run is always the workflow stored in that
commit. Invalid workflow YAML creates a failed run with the validation error.

Runners heartbeat while executing. A background recovery worker returns a job to the queue when its
90-second lease expires, even if no runner is polling. Three expired attempts fail the job.
Cancellation is delivered on the next heartbeat. Completion reports are idempotent and retried
during temporary server outages. Logs are sent in ordered, idempotent batches and capped at 5 MiB
per job.

## Publish runner packages

The web application serves installers from `RunnerAssets` and prebuilt packages from
`runner/packages`. The normal Windows deployment script publishes self-contained packages for
`win-x64`, `win-arm64`, `linux-x64`, and `linux-arm64` before replacing the live site.

To build packages separately:

```powershell
.\scripts\publish-filedistro-runner.ps1 `
  -OutputDirectory C:\deploy\FileDistro\app\runner\packages
```

The package endpoint returns a clear 404 until the requested runtime has been published. This lets
the FileDistro web application build normally during development without embedding four runtime
bundles in every build output.
