From ae19e80dd811d8ebc8670a4bf1852e0ddbb649b0 Mon Sep 17 00:00:00 2001 From: Ryan McCullagh Date: Tue, 1 Sep 2026 10:53:10 -0500 Subject: [PATCH 1/3] excerpts --- CLAUDE.md | 40 ++++++++++- api/changelog.md | 20 +++--- api/deployments/cancel-deployment.md | 8 +-- api/deployments/get.md | 8 +-- api/deployments/post.md | 24 +++---- api/environments/get-environment.md | 8 +-- api/environments/list-environments.md | 6 +- api/environments/update-environment.md | 22 +++--- api/index.md | 51 ++++++++++--- api/instances/create-instance.md | 52 +++++++------- api/instances/get-instance.md | 10 +-- api/instances/list-instances.md | 6 +- api/instances/terminate-instance.md | 6 +- api/regions/get-region.md | 10 +-- api/regions/list-regions.md | 2 +- api/workers/get.md | 8 +-- api/workers/restart.md | 8 +-- api/workers/start.md | 8 +-- api/workers/stop.md | 8 +-- billing/index.md | 34 +++++++-- caching/index.md | 14 +++- databases/index.md | 57 ++++++++++++--- databases/ssh.md | 4 +- deployments/hooks/index.md | 46 ++++++++++-- deployments/index.md | 8 +-- domains/index.md | 41 +++++++++-- git/index.md | 36 ++++++++-- .../change-automatic-deployment-branch.md | 2 +- .../change-default-composer-version.md | 7 +- how-to-guides/changing-php-versions.md | 2 +- how-to-guides/delete-domain-name.md | 2 +- how-to-guides/deleting-applications.md | 2 +- .../disable-automatic-composer-installs.md | 2 +- .../disable-automatic-laravel-migrations.md | 2 +- .../enable-automated-server-maintenance.md | 2 +- how-to-guides/turn-off-npm-package-caching.md | 2 +- index.md | 8 +-- instances/index.md | 72 +++++++++++++++---- instances/ports-and-ip-addresses.md | 2 +- php/index.md | 22 ++++-- teams/index.md | 13 +++- 41 files changed, 485 insertions(+), 200 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 03f35ad..c6a3095 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,6 +16,29 @@ Each top-level directory is a product area (`instances/`, `databases/`, `deploym Every directory has an `index.md` that acts as its landing page and links out to the child pages in that section. When you add a new page, add a link to it from the section's `index.md` (and, for top-level sections, keep the root `index.md` and `how-to-guides/index.md` in sync where relevant) — pages are not auto-discovered by a nav generator. +### Landing Page Section Index + +A landing page's index of its child pages is a run of `###` sub-sections, one per child, not a bullet list of bare links: + +```markdown +## In This Section + +### Backup and Restore + +Amezmo takes logical `mysqldump`-style [backups](backup-restore.md) you can +restore anywhere: copy production to your machine, archive it, or move data +between environments. +``` + +Rules for that block: + +- **Give it a parent `## In This Section`** when the index sits above the page's first `##`. Without one the page jumps `#` to `###`, which is a skipped heading level. Where the list already sits under a heading of its own (`## Core Resources`, `## Managing Instances`), keep that heading and put the `###`s under it. +- **Heading text is plain, never a link.** The site's heading renderer keeps heading text outside the permalink anchor on purpose, because Safari Reader Mode discards any heading whose entire content is a single link. Put the link in the excerpt prose instead. +- **One or two sentences per child, written from that page's actual content**, not a restatement of its title. Say what the reader gets, so the excerpt earns the space the bare link used to occupy. +- **Link text describes the destination** (`[Encrypt your backups](encryption.md)`, not `[here](encryption.md)`), per WCAG 2.2 SC 2.4.4. + +This applies to a section's own child pages. **Leave "See Also" and "Resources" lists as bullet lists**: cross-references to other sections, marketing guides, YouTube, or the dashboard are not sub-sections of the page, and promoting them to headings puts them in the page outline and the "On this page" TOC as though they were. + ## Conventions (match these when editing or adding pages) - **No YAML front matter.** Each page starts directly with a single `#` H1 title, followed by prose. Use `##`/`###` for sections. @@ -28,15 +51,26 @@ Every directory has an `index.md` that acts as its landing page and links out to API endpoint docs follow a fixed layout — replicate it exactly for new endpoints: 1. `#` H1 with the human-readable action name (e.g. `# Create an instance`). 2. The method and path on one line using inline code for the verb: `` `POST` /v1/instances ``. -3. A `## Parameters for ""` section with a Markdown table: `Parameter | Type | In | Description`. Mark required params with `**Required**` in the Description cell, and cross-link related endpoints (e.g. from `api/instances/create-instance.md`, `See [Regions](../regions/list-regions.md)`). -4. A `## Code samples for ""` section with a `### Request example` and `### Response`. +3. A `## Parameters` section with a Markdown table: `Parameter | Type | In | Required | Description`. Requiredness is a property of the parameter, like Type and In, so it gets its own column (`Yes`, `No`, or `Conditional`) rather than a `**Required**` marker inside the Description cell. Bold there is presentational: screen readers do not announce ``, so the markup carries nothing, and it leaves "optional" implied by absence. Use `Conditional` when a parameter is required only in some cases, and say which in the Description. Cross-link related endpoints (e.g. from `api/instances/create-instance.md`, `See [Regions](../regions/list-regions.md)`). +4. A `## Code samples` section with a `### Request example` and `### Response`. 5. Fenced code blocks are preceded by a Leanpub-style title annotation on its own line, e.g. `{title="POST /v1/instances"}` above a ```` ```bash ```` request or `{title="201 Created"}` above a ```` ```javascript ```` JSON response. Keep this annotation — it is part of the site's rendering. +Do **not** repeat the endpoint name in these headings (`## Parameters for "Create an instance"`). That convention comes from GitHub's REST reference, where one page carries about twenty endpoints and the quoted name says which one's parameters you are reading. Each page here documents a single endpoint, so the H1, the ``, the breadcrumb, and the left rail already name it. Repeating it makes headings less distinguishing under WCAG 2.2 SC 2.4.6, puts the page title twice into "On this page", and produces anchors like `#parameters-for-create-an-instance` instead of `#parameters`. + The API base URL in examples is `https://api.amezmo.com`. Authenticate with a Bearer token from an `AMEZMO_API_KEY` environment variable, double-quoted so the shell expands it: `-H "Authorization: Bearer $AMEZMO_API_KEY"`. The `api/authentication/index.md` and `api/endpoints.md` pages show the `export` step. ### API changelog (`api/changelog.md`) -Group entries by date with a `##` heading (`## YYYY-MM-DD`, newest first). Under each date, label changes with a **bold type line** — `**Added**`, `**Changed**`, `**Deprecated**`, `**Removed**`, `**Fixed**`, or `**Breaking**` — over a bullet list. Use bold labels, **not** `###` sub-headings: repeated `### Added` headings collide into meaningless `#added-1` anchors and clutter the on-page TOC. Keep breaking changes under their own `**Breaking**` label so readers can scan for them. +Group entries by date with a `##` heading (`## YYYY-MM-DD`, newest first). Under each date, label changes with a **description list**: the type on its own line (`Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, or `Breaking`), then each change on a following `: ` line. Keep breaking changes under their own `Breaking` term so readers can scan for them. + +```markdown +## 2022-12-12 + +Added +: `current_deployment` to [Get Environment](environments/get-environment.md) +``` + +The type is a label for the changes under it, so it wants markup that says so. A `dt`/`dd` pair carries that relationship (WCAG 2.2 SC 1.3.1) where a bold paragraph only implies it. Do **not** use `###` sub-headings here: repeated `### Added` headings collide into meaningless `#added-1` anchors and put four identical entries in the on-page TOC. ## Required CommonMark extensions diff --git a/api/changelog.md b/api/changelog.md index 093b7b1..424cf5a 100644 --- a/api/changelog.md +++ b/api/changelog.md @@ -4,25 +4,21 @@ The changelog lists forward-compatible changes to the API. ## 2022-12-12 -**Added** - -- `current_deployment` to [Get Environment](environments/get-environment.md) +Added +: `current_deployment` to [Get Environment](environments/get-environment.md) ## 2022-04-18 -**Added** - -- `ssh_enabled` and `trusted_ssh_ips` parameters to [Update Environments](environments/update-environment.md). In addition to accepting these parameters in the PUT +Added +: `ssh_enabled` and `trusted_ssh_ips` parameters to [Update Environments](environments/update-environment.md). In addition to accepting these parameters in the PUT request, they are also shown in the [Get an environment](environments/get-environment.md) resource. ## 2022-01-19 -**Added** - -- `newrelic_license_key` parameter to [Update Environments](environments/update-environment.md) +Added +: `newrelic_license_key` parameter to [Update Environments](environments/update-environment.md) ## 2020-10-14 -**Added** - -- New optional request properties to [Deployments](deployments/post.md) +Added +: New optional request properties to [Deployments](deployments/post.md) diff --git a/api/deployments/cancel-deployment.md b/api/deployments/cancel-deployment.md index 60870fd..bc28dc4 100644 --- a/api/deployments/cancel-deployment.md +++ b/api/deployments/cancel-deployment.md @@ -7,10 +7,10 @@ POST /v1/instances/{instance_id}/deployments/{deployment_id}/cancel ## Parameters -Parameter | Type | In | Description -------------- | -------|------ |------------------ -instance_id | string | uri | The instance id of the environment -deployment_id | string | uri | The deployment ID +Parameter | Type | In | Required | Description +------------- | ------ | --- | -------- | ---------------------------------- +instance_id | string | uri | No | The instance id of the environment +deployment_id | string | uri | No | The deployment ID ## Response diff --git a/api/deployments/get.md b/api/deployments/get.md index 5dfa00d..0cdab1e 100644 --- a/api/deployments/get.md +++ b/api/deployments/get.md @@ -7,10 +7,10 @@ GET /v1/instances/{instance_id}/deployments/{deployment_id} ## Parameters -Parameter | Type | In | Description -------------- | -------|------ |------------------ -instance_id | string | uri | The instance id of the environment -deployment_id | string | uri | The deployment ID +Parameter | Type | In | Required | Description +------------- | ------ | --- | -------- | ---------------------------------- +instance_id | string | uri | No | The instance id of the environment +deployment_id | string | uri | No | The deployment ID ## Response diff --git a/api/deployments/post.md b/api/deployments/post.md index b5b3552..2e5eded 100644 --- a/api/deployments/post.md +++ b/api/deployments/post.md @@ -26,18 +26,18 @@ POST /v1/instances/{instance_id}/deployments ## Parameters -Parameter | Type | In | Description -------------- | ------|------ |------------------ -api_key | string | header | **Required**. Your [API key](../authentication/index.md). -environment | string | body | **Required**. The [environment](../environments/index.md) name for this deployment. This can be `production` or `staging`. -instance_id | string | uri | **Required**. The ID of the instance that this deployment will be executed on. -archive | body | uri | **Required**. The archive file that contains the source code of your application. This can be a zip or a tar archive. The maximum size of an archive file is 512MB. -repo_owner | string | body | The repository owner. Required if `repo_name` is provided. -repo_name | string | body | The repository name. Required if `repo_owner` is provided. -branch | string | body | The name of the branch -pusher | string | body | The email address of the user that invoked the deployment -commit | string | body | The git commit hash -tag | string | body | The git tag +Parameter | Type | In | Required | Description +----------- | ------ | ------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- +api_key | string | header | Yes | Your [API key](../authentication/index.md). +environment | string | body | Yes | The [environment](../environments/index.md) name for this deployment. This can be `production` or `staging`. +instance_id | string | uri | Yes | The ID of the instance that this deployment will be executed on. +archive | body | uri | Yes | The archive file that contains the source code of your application. This can be a zip or a tar archive. The maximum size of an archive file is 512MB. +repo_owner | string | body | Conditional | The repository owner. Required if `repo_name` is provided. +repo_name | string | body | Conditional | The repository name. Required if `repo_owner` is provided. +branch | string | body | No | The name of the branch +pusher | string | body | No | The email address of the user that invoked the deployment +commit | string | body | No | The git commit hash +tag | string | body | No | The git tag ## Code samples diff --git a/api/environments/get-environment.md b/api/environments/get-environment.md index 33ab580..c667810 100644 --- a/api/environments/get-environment.md +++ b/api/environments/get-environment.md @@ -4,10 +4,10 @@ `GET` /v1/instances/{instance_id}/environments/{name} ## Parameters -Parameter | Type | In | Description -------------- | ------|------ |------------------ -instance_id | string | uri | The instance id of the environment -name | string | uri | The name of the environment +Parameter | Type | In | Required | Description +----------- | ------ | --- | -------- | ---------------------------------- +instance_id | string | uri | No | The instance id of the environment +name | string | uri | No | The name of the environment ## Response diff --git a/api/environments/list-environments.md b/api/environments/list-environments.md index 32c3cc4..8513c53 100644 --- a/api/environments/list-environments.md +++ b/api/environments/list-environments.md @@ -4,9 +4,9 @@ `GET` /v1/instances/{instance_id}/environments ## Parameters -Parameter | Type | In | Description -------------- | ------|------ |------------------ -instance_id | string | uri | The instance id +Parameter | Type | In | Required | Description +----------- | ------ | --- | -------- | --------------- +instance_id | string | uri | No | The instance id ## Response diff --git a/api/environments/update-environment.md b/api/environments/update-environment.md index cb78ece..03f1639 100644 --- a/api/environments/update-environment.md +++ b/api/environments/update-environment.md @@ -6,17 +6,17 @@ patterns, SSH access and the New Relic license key. `PATCH` /v1/instances/{instance_id}/environments/{name} -## Parameters for "Update an environment" +## Parameters -Parameter | Type | In | Description ---------- | ---- | -- | ----------- -instance_id | string | uri | **Required** The instance id -name | string | uri | **Required** The environment name. See [List environments](list-environments.md) -auto_deploy_tag_patterns | array | body | Regular expressions that match a git tag -auto_deploy_branch_patterns | array | body | Regular expressions that match a git branch -newrelic_license_key | string | body | [New Relic API key](https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/) -ssh_enabled | boolean | body | Enables or disables SSH access for the environment -trusted_ssh_ips | array | body | An array of IPv4 addresses allowed to connect over SSH +Parameter | Type | In | Required | Description +--------------------------- | ------- | ---- | -------- | --------------------------------------------------------------------------------------- +instance_id | string | uri | Yes | The instance id +name | string | uri | Yes | The environment name. See [List environments](list-environments.md) +auto_deploy_tag_patterns | array | body | No | Regular expressions that match a git tag +auto_deploy_branch_patterns | array | body | No | Regular expressions that match a git branch +newrelic_license_key | string | body | No | [New Relic API key](https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/) +ssh_enabled | boolean | body | No | Enables or disables SSH access for the environment +trusted_ssh_ips | array | body | No | An array of IPv4 addresses allowed to connect over SSH When you update `newrelic_license_key`, the change takes effect on the next [deployment](../deployments/index.md). A `null` value disables the New Relic Application Performance Monitoring @@ -30,7 +30,7 @@ Amezmo supports New Relic only on Advanced instances. See When you set `ssh_enabled` to `false`, Amezmo resets `trusted_ssh_ips` to an empty array. -## Code samples for "Update an environment" +## Code samples ### Request example diff --git a/api/index.md b/api/index.md index 17ccf6b..2b0c61d 100644 --- a/api/index.md +++ b/api/index.md @@ -1,16 +1,47 @@ # Amezmo REST API The Amezmo API gives you programmatic access to Amezmo which empowers you to create deployments, -launch instances, and manage resources. Please see the Endpoints link below for more information -on accessing the Amezmo API. +launch instances, and manage resources. +## Getting Started -- [API endpoint](endpoints.md) -- [Authentication](authentication/index.md) +### API Endpoint -## Core resources -- [Workers](workers/index.md) -- [Instances](instances/index.md) -- [Environments](environments/index.md) -- [Deployments](deployments/index.md) -- [Regions](regions/index.md) +A REST API with a single base URL, `https://api.amezmo.com/v1`, returning JSON. +Start with the [API endpoint](endpoints.md) reference for the base URL and +request format. + +### Authentication + +Every request carries a Bearer token. [API authentication](authentication/index.md) +covers where to find your key and how the examples store it in an +`AMEZMO_API_KEY` environment variable. + +## Core Resources + +### Workers + +Start, stop, and restart [worker processes](workers/index.md) through the API. + +### Instances + +Your applications, along with their environments and resources. +[Instance endpoints](instances/index.md) list and get instances, list instance +types, and terminate an instance. + +### Environments + +An environment ties an instance to a Git repository and its deployment rules. +[Environment endpoints](environments/index.md) read and update those, including +automatic deployment settings. + +### Deployments + +Deploy without Git by supplying a `.zip` archive of your source. +[Deployment endpoints](deployments/index.md) create, read, and cancel +deployments in both staging and production. + +### Regions + +[Region endpoints](regions/index.md) list the regions an instance can run in +and get a single region. diff --git a/api/instances/create-instance.md b/api/instances/create-instance.md index e03bb21..e598499 100644 --- a/api/instances/create-instance.md +++ b/api/instances/create-instance.md @@ -3,38 +3,40 @@ `POST` /v1/instances -## Parameters for "Create an instance" +## Parameters -Parameter | Type | In | Description ----------------| --------|------ |------------------ -runtime | string | body | **Required** One of `php` -instance_type | string | body | **Required** One of `hobby`, `developer`, `business`. See [Instance types](list-instance-types.md) -region | string | body | **Required** The region id. See [Regions](../regions/list-regions.md) -name | string | body | The identifier for this instance. -domain | string | body | An initial domain name for the production environment -php | dictionary | body | PHP configuration. Only valid if `runtime` is `php` -php.version | string | php | The PHP version for the instance -php.composer_version | string | php | The default [composer](https://getcomposer.org) version. Either `1` or `2` -mysql | dictionary | body | MySQL configuration -mysql.version | string | mysql | `5.7` or `8` -mysql.enabled | boolean | mysql | Enables or disables the MySQL server for this instance. -mysql.database | dictionary | mysql | Initial database configuration -mysql.database.name | string | mysql.database | Initial database name -mysql.database.user | string | mysql.database | Initial database user -mysql.database.password | string | mysql.database | Initial database password -app_type | string | body | One of 'laravel', 'octane', 'other', 'symfony', 'craftcms', 'drupal', 'wordpress', 'bedrock' +Parameter | Type | In | Required | Description +----------------------- | ---------- | -------------- | -------- | -------------------------------------------------------------------------------------------- +runtime | string | body | Yes | One of `php` +instance_type | string | body | Yes | One of `hobby`, `developer`, `business`. See [Instance types](list-instance-types.md) +region | string | body | Yes | The region id. See [Regions](../regions/list-regions.md) +name | string | body | Yes | The identifier for this instance. Up to 32 characters. A unique suffix is appended, so the created instance is named `<name>-<id>` +domain | string | body | No | An initial domain name for the production environment +php | dictionary | body | No | PHP configuration. Only valid if `runtime` is `php` +php.version | string | php | No | The PHP version for the instance +php.composer_version | string | php | No | The default [composer](https://getcomposer.org) version. Either `1` or `2` +mysql | dictionary | body | No | MySQL configuration +mysql.version | string | mysql | No | `5.7` or `8.0` +mysql.enabled | boolean | mysql | No | Enables or disables the MySQL server for this instance. +mysql.database | dictionary | mysql | No | Initial database configuration +mysql.database.name | string | mysql.database | No | Initial database name +mysql.database.user | string | mysql.database | No | Initial database user +mysql.database.password | string | mysql.database | No | Initial database password +app_type | string | body | No | One of `laravel`, `octane`, `other`, `symfony`, `craftcms`, `drupal`, `wordpress`, `bedrock`, `backdrop` -## Code samples for "Create an instance" +## Code samples ### Request example {title="POST /v1/instances"} ```bash -curl https://api.amezmo.com/v1/instances -X POST -H "Authorization: Bearer $AMEZMO_API_KEY" \ - --data runtime=php - --data instance_type=business - --data region=lb2-us +curl https://api.amezmo.com/v1/instances -X POST \ + -H "Authorization: Bearer $AMEZMO_API_KEY" \ + --data name=storefront \ + --data runtime=php \ + --data instance_type=business \ + --data region=lb2-us \ --data app_type=other ``` @@ -45,7 +47,7 @@ curl https://api.amezmo.com/v1/instances -X POST -H "Authorization: Bearer $AMEZ ```javascript { "id": 1, - "name": "engage-plugandplay-564192df9c", + "name": "storefront-564192df9c", "runtime_description": "PHP", "instance_type": "business", "description": null, diff --git a/api/instances/get-instance.md b/api/instances/get-instance.md index 6a4856b..f4de670 100644 --- a/api/instances/get-instance.md +++ b/api/instances/get-instance.md @@ -6,13 +6,13 @@ and every environment attached to it. `GET` /v1/instances/{instance_id} -## Parameters for "Get an instance" +## Parameters -Parameter | Type | In | Description ---------- | ---- | -- | ----------- -instance_id | string | uri | **Required** The instance id +Parameter | Type | In | Required | Description +----------- | ------ | --- | -------- | --------------- +instance_id | string | uri | Yes | The instance id -## Code samples for "Get an instance" +## Code samples ### Request example diff --git a/api/instances/list-instances.md b/api/instances/list-instances.md index 8ab66cc..c664aae 100644 --- a/api/instances/list-instances.md +++ b/api/instances/list-instances.md @@ -7,9 +7,9 @@ GET /v1/instances ``` ## Parameters -Parameter | Type | In | Description ----------------- | --------|------- |------------------ -instance_type | string | query | [instance type](list-instance-types.md) filter +Parameter | Type | In | Required | Description +------------- | ------ | ----- | -------- | ---------------------------------------------- +instance_type | string | query | No | [instance type](list-instance-types.md) filter ## Response diff --git a/api/instances/terminate-instance.md b/api/instances/terminate-instance.md index 9b1cd5d..c5eddad 100644 --- a/api/instances/terminate-instance.md +++ b/api/instances/terminate-instance.md @@ -6,9 +6,9 @@ Queues an instance for termination. The `state` property on the instance is upda API requests for this instance will respond with 404 not found. ## Parameters -Parameter | Type | In | Description ---------------|------|----------|------------------ -instance_id | string | uri | The instance id +Parameter | Type | In | Required | Description +----------- | ------ | --- | -------- | --------------- +instance_id | string | uri | No | The instance id ## Code samples diff --git a/api/regions/get-region.md b/api/regions/get-region.md index 02a9055..62dcbdd 100644 --- a/api/regions/get-region.md +++ b/api/regions/get-region.md @@ -6,13 +6,13 @@ Use it to confirm a region before you launch an instance there. `GET` /v1/regions/{region_id} -## Parameters for "Get a region" +## Parameters -Parameter | Type | In | Description ---------- | ---- | -- | ----------- -region_id | string | uri | **Required** The region id. See [List regions](list-regions.md) +Parameter | Type | In | Required | Description +--------- | ------ | --- | -------- | -------------------------------------------------- +region_id | string | uri | Yes | The region id. See [List regions](list-regions.md) -## Code samples for "Get a region" +## Code samples ### Request example diff --git a/api/regions/list-regions.md b/api/regions/list-regions.md index dceb13e..d49bf55 100644 --- a/api/regions/list-regions.md +++ b/api/regions/list-regions.md @@ -7,7 +7,7 @@ its name and ISO country code. `GET` /v1/regions -## Code samples for "List regions" +## Code samples ### Request example diff --git a/api/workers/get.md b/api/workers/get.md index 2145e7d..b699be1 100644 --- a/api/workers/get.md +++ b/api/workers/get.md @@ -4,10 +4,10 @@ `GET` /v1/instances/{instance_id}/workers/{worker_id} ## Parameters -Parameter | Type | In | Description ---------------|------|---------|------------------ -instance_id | string | uri | The instance ID -worker_id | string | uri | The worker ID +Parameter | Type | In | Required | Description +----------- | ------ | --- | -------- | --------------- +instance_id | string | uri | No | The instance ID +worker_id | string | uri | No | The worker ID ## Response diff --git a/api/workers/restart.md b/api/workers/restart.md index 94bc71f..ae917a3 100644 --- a/api/workers/restart.md +++ b/api/workers/restart.md @@ -5,10 +5,10 @@ `POST` /v1/instances/{instance_id}/workers/{worker_id}/restart ## Parameters -Parameter | Type | In | Description ---------------|------|---------|------------------ -instance_id | string | uri | The instance ID -worker_id | string | uri | The worker ID +Parameter | Type | In | Required | Description +----------- | ------ | --- | -------- | --------------- +instance_id | string | uri | No | The instance ID +worker_id | string | uri | No | The worker ID diff --git a/api/workers/start.md b/api/workers/start.md index 9b36dcf..79c1055 100644 --- a/api/workers/start.md +++ b/api/workers/start.md @@ -4,10 +4,10 @@ ## Parameters -Parameter | Type | In | Description ---------------|------|---------|------------------ -instance_id | string | uri | The instance ID -worker_id | string | uri | The worker ID +Parameter | Type | In | Required | Description +----------- | ------ | --- | -------- | --------------- +instance_id | string | uri | No | The instance ID +worker_id | string | uri | No | The worker ID ## Response diff --git a/api/workers/stop.md b/api/workers/stop.md index 2f9bc3d..cf2d815 100644 --- a/api/workers/stop.md +++ b/api/workers/stop.md @@ -4,10 +4,10 @@ ## Parameters -Parameter | Type | In | Description ---------------|------|---------|------------------ -instance_id | string | uri | The instance ID -worker_id | string | uri | The worker ID +Parameter | Type | In | Required | Description +----------- | ------ | --- | -------- | --------------- +instance_id | string | uri | No | The instance ID +worker_id | string | uri | No | The worker ID diff --git a/billing/index.md b/billing/index.md index e318b6a..c888ca4 100644 --- a/billing/index.md +++ b/billing/index.md @@ -5,8 +5,32 @@ by the hour. The [pricing page](/pricing) lists prices per month, but the underlying basis is hourly. That way your card is charged at the same time each month, once for every instance you ran during the prior month. -- [Hourly billing](hourly.md) -- [Payment methods](payment-methods.md) -- [Advanced Pay](advanced-pay.md) -- [Credits](credits.md) -- [Evaluation period](evaluation-period.md) +## In This Section + +### Hourly Billing + +[Hourly billing](hourly.md) explains the billing period: your card is charged on +the first day of the month for every hour an instance was active during the +prior month. + +### Payment Methods + +Add cards, choose a default, and remove ones you no longer use from +[Billing > Payment methods](payment-methods.md). Amezmo charges the default +card. + +### Advanced Pay + +[Advanced Pay](advanced-pay.md) funds an account balance up front, which usage +draws down as you incur it. Keep a backup payment method so instances aren't +terminated when the balance reaches zero. + +### Credits + +[Billing credits](credits.md) arrive as a promotion code and reduce the amount +due on your invoices until you spend them in full or they expire. + +### Evaluation Period + +New accounts get a [free trial](evaluation-period.md) with unlimited deploys. +When it ends, add a payment method to keep your application hosted. diff --git a/caching/index.md b/caching/index.md index 5d8ff96..9906f9b 100644 --- a/caching/index.md +++ b/caching/index.md @@ -44,7 +44,15 @@ Your blog posts probably don't change often, so there's no reason to have PHP regenerate the page for each requests. -## See also +## In This Section -- [Purging cached pages](purging.md) -- [URL Patterns](patterns.md) +### Cache Purging + +Cached pages are purged automatically after a successful deployment. +[Cache purging](purging.md) covers when that happens. + +### URL Patterns + +Amezmo caches PHP output according to patterns you set on the Caching tab. +[Caching patterns](patterns.md) shows how the conditions match against the +request URI. diff --git a/databases/index.md b/databases/index.md index 0d287dc..f99181c 100644 --- a/databases/index.md +++ b/databases/index.md @@ -5,15 +5,54 @@ database support. From the dashboard you can back up, restore and check the status of your database, and any instance shared with the database instance can reach it. Your MySQL server is never reachable from the public internet. -- [MySQL](mysql.md) -- [Redis](redis.md) -- [Remote access](remote-access.md) -- [Remote MySQL access with SSH](ssh.md) -- [SSL connections](ssl-connections.md) -- [Passwords](passwords.md) -- [Backup and restore](backup-restore.md) -- [Instance sharing](instance-sharing.md) -- [Encryption](encryption.md) +## In This Section + +### MySQL + +[MySQL](mysql.md) is available to any instance shared with a database instance. +Back up, restore, and check its status from the dashboard. + +### Redis + +[Redis](redis.md) is available if you chose it at instance creation, reachable +from your shared instances and never exposed to the internet. Data is not +persisted, so treat it as an in-memory cache. + +### Remote Access + +Your MySQL server isn't reachable from the public internet by default. +[Remote access](remote-access.md) covers the two ways to connect a local client. + +### Remote MySQL Access With SSH + +[Connect over SSH](ssh.md) by enabling SSH on the instance and tunnelling to the +database through it. + +### SSL Connections + +Expose MySQL to the internet over [SSL only](ssl-connections.md), which requires +a set of trusted IP addresses first. + +### Passwords + +The first [database password](passwords.md) is set when the instance is created, +either by you or autogenerated, and you can change it later. + +### Backup and Restore + +Amezmo takes logical `mysqldump`-style [backups](backup-restore.md) you can +restore anywhere: copy production to your machine, archive it, or move data +between environments. + +### Instance Sharing + +[Instance sharing](instance-sharing.md) joins instances into one private +network, so another instance can reach your MySQL server and other services. + +### Encryption + +[Encrypt your backups](encryption.md) with AES-256 and a SHA256 digest by +setting an encryption key under Databases > Automation. ## Connecting diff --git a/databases/ssh.md b/databases/ssh.md index a8eefb6..1170c9e 100644 --- a/databases/ssh.md +++ b/databases/ssh.md @@ -26,8 +26,8 @@ port in the command to something else, such as 3307. ## Requirements Before you can reach your MySQL database over SSH, upload your public SSH key to -your instance. [Learn more](../instances/ssh.md) about accessing your instance's -resources through SSH. +your instance. Read more about [accessing your instance's resources through +SSH](../instances/ssh.md). ## See Also diff --git a/deployments/hooks/index.md b/deployments/hooks/index.md index 4167e93..4ac729a 100644 --- a/deployments/hooks/index.md +++ b/deployments/hooks/index.md @@ -60,13 +60,45 @@ For files, these are the permissions. ## Hook list -- [before.pull](before-pull.md) -- [after.pull](after-pull.md) -- [after.extract](after-extract.md) -- [before.deploy](before-deploy.md) -- [after.deploy](after-deploy.md) -- [deploy.success](deploy-success.md) -- [deploy.failure](deploy-failure.md) +The hooks run in the order below. Where a hook's exit status is honored, a +non-zero status fails the deployment. + +### `before.pull` + +Runs [before Amezmo fetches](before-pull.md) from your Git provider. Its exit +status is ignored. + +### `after.pull` + +Runs [after the fetch](after-pull.md). This is where you install Composer and +NPM dependencies. A non-zero exit status fails the deployment. + +### `after.extract` + +Runs after Amezmo extracts the archive from an +[API deployment](after-extract.md), so it applies to `.zip` deploys rather than +Git ones. A non-zero exit status fails the deployment. + +### `before.deploy` + +Runs [before your web server directory is updated](before-deploy.md), the last +point to act on the release before it goes live. A non-zero exit status fails +the deployment. + +### `after.deploy` + +Runs [once the web server directory points at the new release](after-deploy.md), +so your code is already live. Its exit status is ignored. + +### `deploy.success` + +The [last hook to run](deploy-success.md), after a successful deployment. Its +exit status is ignored. + +### `deploy.failure` + +Runs when [any hook or the deployment itself](deploy-failure.md) returns a +non-zero exit status. ## Environment File diff --git a/deployments/index.md b/deployments/index.md index a0c9ab4..7c5f1c3 100644 --- a/deployments/index.md +++ b/deployments/index.md @@ -30,18 +30,18 @@ any existing `.env` file your archive may have. ## Deployment directories -The deployment directory section describes the layout for zero-downtime deployments -[Learn more](directories.md). +The [deployment directory layout](directories.md) describes how zero-downtime +deployments are arranged on disk. ## Hooks Amezmo's extensible deployment systems provides you the capability to run your own code at -any step of the deployment process. [Learn more](hooks/index.md). +any step of the deployment process. See [deployment hooks](hooks/index.md). ## Releases After a successful deployment, then the deployment is considered a release. You may rollback to a previous -release at anytime. [Learn more](releases.md). +release at anytime. See [releases](releases.md). ## Fault tolerance diff --git a/domains/index.md b/domains/index.md index 5114a97..5eb91f0 100644 --- a/domains/index.md +++ b/domains/index.md @@ -5,12 +5,41 @@ you launch an instance, you provide a fully qualified domain name, and you can add more domains at any time. Each instance also gets an [Amezmo development subdomain](development-subdomain.md). -- [Development subdomain](development-subdomain.md) -- [SSL certificates](ssl-certificates.md) -- [Wildcard SSL certificates](wildcard-ssl-certificates.md) -- [Domain routing](routing.md) -- [Redirects](redirects.md) -- [Rules](rules.md) +## In This Section + +### Development Subdomains + +Every instance gets its own [development subdomain](development-subdomain.md) +on `amezmo.co`, served over HTTPS with a free +[SSL certificate](ssl-certificates.md) like any custom domain. Use it for +testing, or as your application's primary domain. + +### SSL Certificates + +Amezmo issues a [free Let's Encrypt certificate](ssl-certificates.md) for every +domain once it validates, and renews it for as long as the domain stays on the +instance. + +### Wildcard SSL Certificates + +A [wildcard certificate](wildcard-ssl-certificates.md) covers a domain and all +of its direct subdomains at once, so `*.example.com` secures `app.example.com` +and `api.example.com` together. + +### Domain Routing + +Domains reach your Nginx server by default. +[Domain routing](routing.md) covers pointing one at a worker process instead. + +### Domain Redirects + +A [redirect](redirects.md) sends visitors from one domain to another on the same +instance. Both domains have to be added and validated first. + +### Domain Rules + +A [domain rule](rules.md) attaches an action to a path on a validated domain, +such as marking a path for WebSocket connections. ## Validating a Domain diff --git a/git/index.md b/git/index.md index 3507c8a..fa71532 100644 --- a/git/index.md +++ b/git/index.md @@ -6,14 +6,36 @@ BitBucket, with automatic deployments and a set of starts a new deployment. You can also turn off automatic deployments and deploy the branches you choose. -- [Branches](branches.md) -- [Git providers](providers.md) -- [Repos](repos.md) -- [Tag-based deployments](tag-based-deployments.md) -- [Troubleshooting](troubleshooting.md) +## In This Section + +### Branches + +A deployment starts from a repository and the branch you choose for automatic +deployment. [Branches](branches.md) covers picking and changing it. + +### Git Providers + +[Connect GitHub, GitLab, or Bitbucket](providers.md) from the Git tab. Amezmo +stores an access token for that account and uses it to list your repositories. + +### Repos + +Every deployment runs from a [Git repository](repos.md) attached to your +instance once you have connected a provider. + +### Tag-Based Deployments + +Deploy from [tags matching a regular expression](tag-based-deployments.md) +instead of a fixed branch name. + +### Troubleshooting + +[Git troubleshooting](troubleshooting.md) covers the problems you can hit +partway through connecting a provider, choosing a repository, or adding a deploy +key. ## Automation -[Learn more](../deployments/automatic-composer-installs.md) about installing -Composer packages and running migrations automatically, without a custom +Read more about [installing Composer packages and running migrations +automatically](../deployments/automatic-composer-installs.md), without a custom script. diff --git a/how-to-guides/change-automatic-deployment-branch.md b/how-to-guides/change-automatic-deployment-branch.md index 78b2e35..f8ac83c 100644 --- a/how-to-guides/change-automatic-deployment-branch.md +++ b/how-to-guides/change-automatic-deployment-branch.md @@ -5,7 +5,7 @@ branch is used for [automatic deployments](../deployments/index.md). ## How to change the default branch -1. Open the Amezmo dashboard at [https://www.amezmo.com/sites](/sites) +1. Open the [Amezmo dashboard](/sites) 2. Choose the name of your application. 3. Above the horizontal tab navigation menu, click the Production **or** Staging tab. 4. In the horizontal tab navigation menu, choose the **Git** tab. diff --git a/how-to-guides/change-default-composer-version.md b/how-to-guides/change-default-composer-version.md index 720e6da..64c3fa9 100644 --- a/how-to-guides/change-default-composer-version.md +++ b/how-to-guides/change-default-composer-version.md @@ -5,7 +5,7 @@ your automatic deployments. ## How to change the default Composer version -1. Open the Amezmo dashboard at [https://www.amezmo.com/sites](/sites) +1. Open the [Amezmo dashboard](/sites) 2. Choose the name of the application. 3. Above the horizontal tab navigation menu, click the Production **or** Staging tab. 4. In the horizontal tab navigation menu, choose the **Deployments** tab. @@ -17,5 +17,6 @@ your automatic deployments. 9. Click **Save** -After changing the default Composer version, [composer](../deployments/automatic-composer-installs.md), the version you selected is available -in your [deployment hooks](../deployments/hooks/index.md). +After you change it, the version you selected is used for +[automatic Composer installs](../deployments/automatic-composer-installs.md) +and is available in your [deployment hooks](../deployments/hooks/index.md). diff --git a/how-to-guides/changing-php-versions.md b/how-to-guides/changing-php-versions.md index aa751bd..c2ed6de 100644 --- a/how-to-guides/changing-php-versions.md +++ b/how-to-guides/changing-php-versions.md @@ -6,7 +6,7 @@ PHP version restarts the instance. ## How to change the PHP version -1. Open the Amezmo dashboard at [https://www.amezmo.com/sites](/sites) +1. Open the [Amezmo dashboard](/sites) 2. Choose the name of the application 3. Above the horizontal tab navigation menu, click the Production **or** Staging tab. 4. In the horizontal tab navigation menu, choose the **Overview** tab. diff --git a/how-to-guides/delete-domain-name.md b/how-to-guides/delete-domain-name.md index b94e17a..1efcc50 100644 --- a/how-to-guides/delete-domain-name.md +++ b/how-to-guides/delete-domain-name.md @@ -4,7 +4,7 @@ You may remove a domain name if you no longer need it, or if you've migrated the ## How to delete a domain name -1. Open the Amezmo dashboard at [https://www.amezmo.com/sites](/sites) +1. Open the [Amezmo dashboard](/sites) 2. Choose the name of the application. 3. Above the horizontal tab navigation menu, click the Production **or** Staging tab. 4. In the horizontal tab navigation menu, choose the **Domains** tab. diff --git a/how-to-guides/deleting-applications.md b/how-to-guides/deleting-applications.md index 7db56e1..d1811fd 100644 --- a/how-to-guides/deleting-applications.md +++ b/how-to-guides/deleting-applications.md @@ -7,7 +7,7 @@ you've terminated your application, you will no longer be charged for it. [Termi You can terminate an application using the Amezmo dashboard. **To terminate an application**: -1. Open the Amezmo dashboard at [https://www.amezmo.com/sites](/sites) +1. Open the [Amezmo dashboard](/sites) 2. Choose the name of the application. 3. In the horizontal tab navigation menu, choose **Overview**. 4. Scroll down to the **Application details** section. diff --git a/how-to-guides/disable-automatic-composer-installs.md b/how-to-guides/disable-automatic-composer-installs.md index 39ff224..834cf48 100644 --- a/how-to-guides/disable-automatic-composer-installs.md +++ b/how-to-guides/disable-automatic-composer-installs.md @@ -3,7 +3,7 @@ You can disable [automatic Composer installations](../deployments/automatic-composer-installs.md) if you'd like more control over installing Composer packages. ## How to disable automatic Composer installations -1. Open the Amezmo dashboard at [https://www.amezmo.com/sites](/sites) +1. Open the [Amezmo dashboard](/sites) 2. Choose the name of the application. 3. Above the horizontal tab navigation menu, click the Production **or** Staging tab. 4. In the horizontal tab navigation menu, choose the **Deployments** tab. diff --git a/how-to-guides/disable-automatic-laravel-migrations.md b/how-to-guides/disable-automatic-laravel-migrations.md index 2c3630c..cca5f1f 100644 --- a/how-to-guides/disable-automatic-laravel-migrations.md +++ b/how-to-guides/disable-automatic-laravel-migrations.md @@ -6,7 +6,7 @@ automatic migrations for Laravel applications. ## Disabling Automatic Laravel Migrations -1. Open the Amezmo dashboard at [https://www.amezmo.com/sites](/sites) +1. Open the [Amezmo dashboard](/sites) 2. Choose the name of your application. 3. Above the horizontal tab navigation menu, click the Production **or** Staging tab. diff --git a/how-to-guides/enable-automated-server-maintenance.md b/how-to-guides/enable-automated-server-maintenance.md index 430f782..fef59e3 100644 --- a/how-to-guides/enable-automated-server-maintenance.md +++ b/how-to-guides/enable-automated-server-maintenance.md @@ -5,7 +5,7 @@ This is an automated process that Amezmo handles for you. ## Enable automatic maintenance -1. Open the Amezmo dashboard at [https://www.amezmo.com/sites](/sites) +1. Open the [Amezmo dashboard](/sites) 2. Choose the name of the application 3. Above the horizontal tab navigation menu, click the Production **or** Staging tab. 4. In the horizontal tab navigation menu, choose the **Overview** tab. diff --git a/how-to-guides/turn-off-npm-package-caching.md b/how-to-guides/turn-off-npm-package-caching.md index 407dd2f..27e7921 100644 --- a/how-to-guides/turn-off-npm-package-caching.md +++ b/how-to-guides/turn-off-npm-package-caching.md @@ -6,7 +6,7 @@ scratch on every deployment. ## Disable NPM package caching -1. Open the Amezmo dashboard at [https://www.amezmo.com/sites](/sites) +1. Open the [Amezmo dashboard](/sites) 2. Choose the name of the application. 3. Above the horizontal tab navigation menu, click the Production **or** Staging tab. 4. In the horizontal tab navigation menu, choose the **Deployments** tab. diff --git a/index.md b/index.md index 6b8b44b..c8c6847 100644 --- a/index.md +++ b/index.md @@ -24,23 +24,23 @@ and we'll customize your image to install any custom packages with ## Amezmo PHP Hosting Components for Your Applications Get details about setting up your PHP applications and server infrastructure on Amezmo. Learn about -automated deployments, and more. For specific questions that are not answered here, we encourage you to join us on [Slack](/goslack). +automated deployments, and more. For specific questions that are not answered here, we encourage you to join us on [Slack](https://www.amezmo.com/goslack). ### Instances Launch a dedicated service instance, or a full stack instance. Scale up or down at anytime. -[Learn more](instances/index.md). +Read more [about instances](instances/index.md). ### Databases Run a fully managed dedicated instance of MySQL on Amezmo. Launch a database instance into your private network and let your other instances connect to it. -[Learn more](databases/index.md). +Read more [about databases](databases/index.md). ### Deployments Automatically deploy your PHP application with zero-downtime using Git. -[Learn more](deployments/index.md). +Read more [about deployments](deployments/index.md). ## See Also diff --git a/instances/index.md b/instances/index.md index f93ff5c..4bec3b1 100644 --- a/instances/index.md +++ b/instances/index.md @@ -14,18 +14,66 @@ For pricing and features, see the [pricing](/pricing) page. ## Managing Instances -- [Scaling](scaling.md) -- [Pausing](pausing.md) -- [Terminating](terminating.md) -- [Sharing](sharing.md) -- [Enable or disable SSH](enable-or-disable-ssh.md) -- [SSH access](ssh.md) -- [SSH troubleshooting](ssh-troubleshooting.md) -- [Trusted IP addresses](trusted-ip-addresses.md) -- [Ports and IP addresses](ports-and-ip-addresses.md) -- [Instance limits](limits.md) -- [Regions](regions.md) -- [Troubleshooting](troubleshooting.md) +### Scaling + +[Scale an instance](scaling.md) up when you need more memory or computing +power, or back down afterward. + +### Pausing + +[Pausing](pausing.md) takes an instance offline without deleting it. It shuts +down and stops accepting requests, and you can start it again. + +### Terminating + +[Terminating](terminating.md) removes an application from Amezmo permanently. +Pause instead if you only need it offline for a while. + +### Sharing + +[Sharing](sharing.md) joins your instance to another instance's private network +and gives it an address inside that network. + +### Enable or Disable SSH + +Toggle [SSH access](enable-or-disable-ssh.md) from the Overview tab. With it +off, the instance doesn't answer SSH at all. + +### SSH Access + +[SSH](ssh.md) runs through a secure entry-point host that routes you from the +load balancer to your instance on a pre-allocated port. + +### SSH Troubleshooting + +[SSH troubleshooting](ssh-troubleshooting.md) covers connection failures, +particularly "Permission denied (publickey)". + +### Trusted IP Addresses + +Restrict which machines can open an SSH connection with +[trusted IP addresses](trusted-ip-addresses.md). These apply to SSH only, not to +web traffic. + +### Ports and IP Addresses + +Amezmo leaves the [default ports](ports-and-ip-addresses.md) in place for Nginx, +MySQL, and Redis. + +### Instance Limits + +Your account can run a [set number of applications](limits.md) at once. The +limit starts low on a new account and rises as the account establishes itself. + +### Regions + +Your [region](regions.md) sets where the application runs. Amezmo defaults to +North America and you can change it on the launch screen. + +### Troubleshooting + +[Instance troubleshooting](troubleshooting.md) covers a launch that is refused +or seems to stall. ## Features diff --git a/instances/ports-and-ip-addresses.md b/instances/ports-and-ip-addresses.md index b672d8f..48dcf41 100644 --- a/instances/ports-and-ip-addresses.md +++ b/instances/ports-and-ip-addresses.md @@ -10,4 +10,4 @@ The following table maps common services to port numbers. | Redis | 6379 | | MySQL | 3306 | | Nginx | 80, 443 | -| SSH | [Learn more](enable-or-disable-ssh.md#ssh-port) | +| SSH | [See your SSH port](enable-or-disable-ssh.md#ssh-port) | diff --git a/php/index.md b/php/index.md index df68300..d3f91f2 100644 --- a/php/index.md +++ b/php/index.md @@ -4,10 +4,20 @@ PHP on Amezmo comes with FastCGI Process Manager (<abbr title="FastCGI Process M expect are installed by default, so you can focus on your code, not configuration. -- [PHP versions](versions.md) -- [Extensions](extensions.md) -- [Composer](composer.md) +## In This Section -You pick your PHP version at launch and can change it later. The version applies -to both FPM and the command line. See [PHP versions](versions.md) for the list -of versions and how to change them. +### PHP Versions + +Choose a [PHP version](versions.md) at launch and change it later. It applies to +both FPM and the command line, so your web requests and scripts run the same +PHP. + +### Extensions + +The [common extensions](extensions.md) ship installed, so most applications run +without extra setup. There is no per-application install step. + +### Composer + +Every PHP instance has [both Composer versions installed](composer.md), as the +`composer` and `composer2` commands, for use in deployment hooks or over SSH. diff --git a/teams/index.md b/teams/index.md index dbb4434..22ef8f6 100644 --- a/teams/index.md +++ b/teams/index.md @@ -9,5 +9,14 @@ without logging out. Administrators and Team owners may launch instances under t Each team you create has its own payment method. Instances that are launched from within a team will be billed to the default payment method for the team. -- [Team owners](team-owners.md) -- [Member roles](team-member-roles.md) +## In This Section + +### Team Owners + +A [team owner](team-owners.md) invites and revokes members and reads the +security log of what members have done. + +### Member Roles + +Every member has a [role](team-member-roles.md) that limits what they can do in +the dashboard and the API. From e9ccdc1db5488025eac1a04dfdd50a97145ed00b Mon Sep 17 00:00:00 2001 From: Ryan McCullagh <me@ryanmccullagh.com> Date: Sat, 12 Sep 2026 09:11:07 -0500 Subject: [PATCH 2/3] Cron --- .github/backlink-ignore.txt | 13 ++- .github/scripts/check_backlinks.py | 9 +- .github/scripts/check_links.py | 7 +- README.md | 110 +++++++++++++++++++ api/changelog.md | 40 +++++++ api/cron/create-cron-entry.md | 95 ++++++++++++++++ api/cron/get-cron-entry.md | 46 ++++++++ api/cron/index.md | 12 +++ api/cron/list-cron-entries.md | 47 ++++++++ api/cron/update-cron-entry.md | 96 +++++++++++++++++ api/endpoints.md | 20 ++++ api/environments/update-environment.md | 4 +- api/errors.md | 96 +++++++++++++++++ api/index.md | 16 ++- api/instances/get-instance.md | 2 +- api/regions/get-region.md | 2 +- api/regions/list-regions.md | 2 +- billing/credits.md | 2 +- billing/payment-methods.md | 8 +- cron/custom-expressions.md | 143 +++++++++++++++++++++++++ cron/editing-entries.md | 64 +++++++++++ cron/index.md | 26 ++++- databases/backup-restore.md | 2 +- databases/index.md | 4 +- databases/ssh.md | 2 +- domains/rules.md | 2 +- domains/wildcard-ssl-certificates.md | 2 +- environments/cloning.md | 4 +- git/providers.md | 6 +- instances/index.md | 2 +- metrics/index.md | 2 +- nginx/compression-and-caching.md | 2 +- nginx/location-blocks.md | 6 +- 33 files changed, 859 insertions(+), 35 deletions(-) create mode 100644 README.md create mode 100644 api/cron/create-cron-entry.md create mode 100644 api/cron/get-cron-entry.md create mode 100644 api/cron/index.md create mode 100644 api/cron/list-cron-entries.md create mode 100644 api/cron/update-cron-entry.md create mode 100644 api/errors.md create mode 100644 cron/custom-expressions.md create mode 100644 cron/editing-entries.md diff --git a/.github/backlink-ignore.txt b/.github/backlink-ignore.txt index 01a549b..6ba3660 100644 --- a/.github/backlink-ignore.txt +++ b/.github/backlink-ignore.txt @@ -9,9 +9,18 @@ /docs/api/changelog -> /docs/api/deployments/post /docs/api/changelog -> /docs/api/environments/get-environment /docs/api/changelog -> /docs/api/environments/update-environment +/docs/api/changelog -> /docs/api/cron/create-cron-entry +/docs/api/changelog -> /docs/api/cron/get-cron-entry +/docs/api/changelog -> /docs/api/cron/list-cron-entries +/docs/api/changelog -> /docs/api/cron/update-cron-entry +/docs/api/changelog -> /docs/api/errors # API endpoints cross-link the lookup endpoints they depend on (see the API -# convention in CLAUDE.md); those lookups don't link back to every caller. +# convention in CLAUDE.md); those lookups don't link back to every caller. The +# errors reference is cited the same way: by everything, citing nothing back. +/docs/api/endpoints -> /docs/api/errors +/docs/api/cron/create-cron-entry -> /docs/api/errors +/docs/api/cron/update-cron-entry -> /docs/api/errors /docs/api/environments/update-environment -> /docs/api/environments/list-environments /docs/api/environments/update-environment -> /docs/api/instances/list-instance-types /docs/api/instances/create-instance -> /docs/api/instances/list-instance-types @@ -40,6 +49,7 @@ /docs/billing/payment-methods -> /docs/billing/advanced-pay /docs/php/versions -> /docs/how-to-guides/changing-php-versions /docs/php/composer -> /docs/instances/ssh +/docs/cron/custom-expressions -> /docs/instances/ssh # Nginx location-blocks references the domain routing/rules and http-auth pages; # those describe separate features and don't need to point back. @@ -65,6 +75,7 @@ /docs/configuration/logging -> /docs/deployments/directories /docs/npm/package-caching -> /docs/deployments/directories /docs/workers/reloading -> /docs/deployments/directories +/docs/cron/editing-entries -> /docs/deployments/directories /docs/deployments/directories -> /docs/deployments/hooks/deploy-success /docs/configuration/dotenv -> /docs/deployments/releases /docs/deployments/hooks/before-pull -> /docs/deployments/releases diff --git a/.github/scripts/check_backlinks.py b/.github/scripts/check_backlinks.py index 7b773cf..a592b81 100644 --- a/.github/scripts/check_backlinks.py +++ b/.github/scripts/check_backlinks.py @@ -13,7 +13,7 @@ Excluded: - index/landing pages (index.md) -- their nav is one-directional by design - - non-published files (CLAUDE.md, anything under .github/) + - non-published files (CLAUDE.md, README.md, anything under .github/) - pairs listed in .github/backlink-ignore.txt (known-intentional one-ways) Exit code is always 0. @@ -28,6 +28,11 @@ EXTERNAL = ("http://", "https://", "mailto:", "//") +# Repo meta files, not published doc pages: skipped as both link sources +# and link targets. +META_FILES = {"CLAUDE.md", "README.md"} + + def doc_files(root): files = set() for dirpath, _dirs, names in os.walk(root): @@ -35,7 +40,7 @@ def doc_files(root): if ".git" in parts or ".github" in parts: continue for name in names: - if name.endswith(".md") and name != "CLAUDE.md": + if name.endswith(".md") and name not in META_FILES: files.add(os.path.relpath(os.path.join(dirpath, name), root)) return files diff --git a/.github/scripts/check_links.py b/.github/scripts/check_links.py index df10878..b232c7c 100644 --- a/.github/scripts/check_links.py +++ b/.github/scripts/check_links.py @@ -22,6 +22,11 @@ EXTERNAL = ("http://", "https://", "mailto:", "//") +# Repo meta files, not published doc pages: skipped as both link sources +# and link targets. +META_FILES = {"CLAUDE.md", "README.md"} + + def doc_files(root): files = set() for dirpath, _dirs, names in os.walk(root): @@ -29,7 +34,7 @@ def doc_files(root): if ".git" in parts or ".github" in parts: continue for name in names: - if name.endswith(".md") and name != "CLAUDE.md": + if name.endswith(".md") and name not in META_FILES: files.add(os.path.relpath(os.path.join(dirpath, name), root)) return files diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc23f32 --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +# Amezmo Documentation + +[![Docs lint](https://github.com/amezmo/docs/actions/workflows/docs-lint.yml/badge.svg)](https://github.com/amezmo/docs/actions/workflows/docs-lint.yml) +[![Pages](https://img.shields.io/badge/pages-146-blue)](https://www.amezmo.com/docs/) +[![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://github.com/amezmo/docs/pulls) + +The source for the [Amezmo](https://www.amezmo.com) docs: 146 pages on +zero-downtime PHP hosting, from your first `git push` to tuning Nginx, OPcache +and Redis on a production instance. Published at +[amezmo.com/docs](https://www.amezmo.com/docs/). + +**This repo is Markdown and nothing else.** No framework, no package manager, +no build step, no dependencies to install. Clone it, open a `.md` file in your +editor's preview, and you are looking at the page. That means the distance +between "this paragraph is wrong" and a merged fix is about four minutes. + +## Contribute + +You already know something we got wrong. Ship it: + +```bash +$ git clone git@github.com:amezmo/docs.git +$ cd docs +$ git checkout -b fix-cron-timezone-note +$ $EDITOR cron/index.md +$ python3 .github/scripts/check_links.py +``` + +Then open a pull request. Typo fixes, a missing flag, a code sample that no +longer runs, an entire new how-to guide: all of it is welcome, and small PRs +get merged fast. + +Good first contributions: + +- Fix a code sample you tried and found stale. +- Add the gotcha that cost you an hour, as a `> [!WARNING]` callout. +- Write the how-to guide for your framework in + [how-to-guides/](how-to-guides/index.md). +- Document an endpoint the [REST API](api/index.md) section is missing. + +Read the +[contributor rulebook](CLAUDE.md) +before a larger change. It covers page layout, the API reference format, the +changelog format and the house writing style. It's written for Claude Code, and +it's the same rulebook a human needs. + +## Local Checks + +CI runs these four on every push and pull request. Run them from the repo root +first: + +```bash +$ python3 .github/scripts/check_links.py # internal links resolve +$ python3 .github/scripts/check_image_paths.py # images use absolute URLs +$ codespell --config .codespellrc # spelling +$ python3 .github/scripts/check_backlinks.py # backlinks (warn-only) +``` + +The first three gate the build. The backlink check only reports. + +## Conventions Worth Knowing Up Front + +Internal links are relative and keep their `.md` suffix, so they work in the +GitHub file browser and on the rendered site: `releases.md` for a sibling, +`../instances/scaling.md` across sections. Never use a `/docs/`-absolute or +extensionless path. + +Links are reciprocal. When one content page links to another, the target links +back. Landing pages are exempt, and deliberate one-way links are recorded in +[`.github/backlink-ignore.txt`](.github/backlink-ignore.txt) +with a comment explaining why. + +Pages render through league/commonmark with the attributes and description list +extensions, plus GitHub alert callouts: + +```markdown +> [!WARNING] +> Restoring a backup overwrites the target database. +``` + +New pages are linked from their section's `index.md`. Nothing is +auto-discovered by a nav generator. + +## The Map + +[index.md](index.md) is the home page. The big sections: + +| Section | What's in it | +| --- | --- | +| [Instances](instances/index.md) | Dedicated and full-stack servers, scaling, private networking | +| [Deployments](deployments/index.md) | Atomic zero-downtime releases, hooks, instant rollbacks | +| [Databases](databases/index.md) | Managed MySQL, backups, restores between environments | +| [REST API](api/index.md) | 32 endpoint pages for automating the dashboard | +| [How-to guides](how-to-guides/index.md) | Laravel, Craft CMS, Drupal and friends | +| [Domains](domains/index.md), [Cron](cron/index.md), [Workers](workers/index.md) | The rest of the platform surface | + +## Contributors + +Built by the people who use it. + +[![Contributors](https://contrib.rocks/image?repo=amezmo/docs)](https://github.com/amezmo/docs/graphs/contributors) + +Your avatar goes here on your first merged PR. + +## See Also + +- [Amezmo on YouTube](https://www.youtube.com/@AmezmoPHP) +- [PHP hosting guides](https://www.amezmo.com/guides) +- [Amezmo on GitHub](https://www.github.com/amezmo) +- [Slack community](https://www.amezmo.com/goslack) diff --git a/api/changelog.md b/api/changelog.md index 424cf5a..0d951a1 100644 --- a/api/changelog.md +++ b/api/changelog.md @@ -2,6 +2,46 @@ The changelog lists forward-compatible changes to the API. +## 2026-09-12 + +Added +: [Create a cron entry](cron/create-cron-entry.md) creates an entry on an +instance: a permanent name, a bash script, a schedule, and the environment to +run it in. Deleting an entry stays in the dashboard. + +## 2026-09-10 + +Breaking +: `type` on an error response is now the coarse class of the error +(`invalid_request_error`, `authentication_error`, `rate_limit_error`, +`api_error`) rather than the specific reason. The specific reason moved to the +new `code` field, keeping the strings it used before, so a client matching on +`unknown_resource_error` or `validation_error` should read `code` instead of +`type`. See [Errors](errors.md). + +Added +: `code` to every error response: the specific, machine-readable reason, where +`type` is the coarse class. See [Errors](errors.md). + +Fixed +: Errors that carry their own response body (no payment method, unverified +email, an instance that is paused or still launching, a feature your instance +type does not include) returned a generic `500` with no usable message. They now +return their real status, message, and `code`. + +Fixed +: Error messages written for users were replaced with "An unexpected API error +occurred." outside development. The real message is now returned, and only +genuinely unexpected failures fall back to a generic one. + +Added +: [Cron endpoints](cron/index.md). [List cron entries](cron/list-cron-entries.md) +lists every entry on an instance, and +[Get a cron entry](cron/get-cron-entry.md) gets one by ID. +: [Update a cron entry](cron/update-cron-entry.md) updates the `command`, the +`expression`, or both, on an existing entry. An entry's `name` stays fixed, +because Amezmo derives its log file name from it. + ## 2022-12-12 Added diff --git a/api/cron/create-cron-entry.md b/api/cron/create-cron-entry.md new file mode 100644 index 0000000..438e020 --- /dev/null +++ b/api/cron/create-cron-entry.md @@ -0,0 +1,95 @@ +# Create a cron entry + +{.lead} +Create a **cron entry** on an instance: a name, a bash script, and the +schedule to run it on. + +`POST` /v1/instances/{instance_id}/cron + +## Parameters + +Parameter | Type | In | Required | Description +----------- | ------ | ---- | -------- | ------------------------------------------------------- +instance_id | string | uri | Yes | The instance ID +name | string | body | Yes | A permanent name for the entry, unique in its environment +command | string | body | Yes | The bash script to run +expression | string | body | Yes | The schedule +environment | string | body | Yes | The environment the entry runs in: `production` or `staging` + +`name` accepts letters, numbers, and hyphens, starts with a letter or a +number, and is at most 63 characters. Pick it carefully: Amezmo derives the +entry's log file name from it, so the name is fixed after creation. +[Update a cron entry](update-cron-entry.md) updates the script and the +schedule, never the name. + +Use `--data-urlencode` rather than `--data` for both `command` and +`expression`. A script contains newlines and an expression contains spaces and +`*`, and only the encoding form sends those intact. + +`expression` accepts a five-field POSIX cron expression or one of the aliases +below. Not every expression the API accepts runs on a Linux instance: see +[custom cron expressions](../../cron/custom-expressions.md) for the extensions +to avoid. + +Alias | Expression | Runs +------------------ | ------------- | ------------------------- +`@minutely` | `* * * * *` | Every minute +`@every_5_minutes` | `*/5 * * * *` | Every five minutes +`@hourly` | `0 * * * *` | At the top of every hour +`@daily` | `0 0 * * *` | Every day at midnight +`@weekly` | `0 0 * * 0` | Every Sunday at midnight +`@monthly` | `0 0 1 * *` | On the first of every month +`@yearly` | `0 0 1 1 *` | On January 1 +`@annually` | `0 0 1 1 *` | On January 1 + +The response reports the resolved five-field `expression` along with the +matching `expression_alias`, so an alias you send comes out as both. + +Amezmo applies the entry on your instance asynchronously: it writes the script +and the crontab entry, and runs begin at the next time the schedule matches. +The response carries the entry immediately, including the `id` you use with +[Get a cron entry](get-cron-entry.md) and +[Update a cron entry](update-cron-entry.md). + +A name already taken in the same environment returns `409 Conflict` with the +code `cron_entry_exists`. An environment your instance does not have returns +`422` with `business_logic_error`. An instance whose type does not include +cron returns `422` with `feature_not_supported`. See [Errors](../errors.md) +for the shape of all three. + +Delete a cron entry from the dashboard, under **Cron** on your instance. + +## Code samples + +### Request example + +{title="POST /v1/instances/{instance_id}/cron"} +```bash +curl https://api.amezmo.com/v1/instances/{instance_id}/cron \ + -X POST \ + -H "Authorization: Bearer $AMEZMO_API_KEY" \ + --data-urlencode name='Laravel-task-scheduler' \ + --data-urlencode command="#!/bin/bash +php artisan schedule:run +" \ + --data-urlencode expression='@minutely' \ + --data-urlencode environment='production' +``` + +### Response + +{title="201 Created"} +```javascript +{ + "id": 412, + "name": "Laravel-task-scheduler", + "command": "#!/bin/bash\nphp artisan schedule:run", + "expression": "* * * * *", + "expression_alias": "@minutely", + "status": "Created", + "environment_name": "production", + "log_file_path": "/home/deployer/cron/logs/cron.Laravel-task-scheduler.log", + "created_at": "2026-02-11T18:04:22.000000Z", + "updated_at": "2026-02-11T18:04:22.000000Z" +} +``` diff --git a/api/cron/get-cron-entry.md b/api/cron/get-cron-entry.md new file mode 100644 index 0000000..4859599 --- /dev/null +++ b/api/cron/get-cron-entry.md @@ -0,0 +1,46 @@ +# Get a cron entry + +{.lead} +Get a single **cron entry** by ID, including its schedule and its script. + +`GET` /v1/instances/{instance_id}/cron/{cron_id} + +## Parameters + +Parameter | Type | In | Required | Description +----------- | ------ | --- | -------- | ------------------------------------------------------- +instance_id | string | uri | Yes | The instance ID +cron_id | string | uri | Yes | The cron entry ID. See [List cron entries](list-cron-entries.md) + +The response carries the entry's full `command`, so this is how you read a +script before updating it with [Update a cron entry](update-cron-entry.md). A +freshly created entry needs no follow-up request: the response of +[Create a cron entry](create-cron-entry.md) carries the same fields. + +## Code samples + +### Request example + +{title="GET /v1/instances/{instance_id}/cron/{cron_id}"} +```bash +curl https://api.amezmo.com/v1/instances/{instance_id}/cron/412 \ + -H "Authorization: Bearer $AMEZMO_API_KEY" +``` + +### Response + +{title="200 OK"} +```javascript +{ + "id": 412, + "name": "Laravel-task-scheduler", + "command": "#!/bin/bash\nphp artisan schedule:run", + "expression": "* * * * *", + "expression_alias": "@minutely", + "status": "Created", + "environment_name": "production", + "log_file_path": "/home/deployer/cron/logs/cron.Laravel-task-scheduler.log", + "created_at": "2026-02-11T18:04:22.000000Z", + "updated_at": "2026-09-10T14:31:07.000000Z" +} +``` diff --git a/api/cron/index.md b/api/cron/index.md new file mode 100644 index 0000000..59bde2c --- /dev/null +++ b/api/cron/index.md @@ -0,0 +1,12 @@ +# Cron + +[Cron entries](../../cron/index.md) run a bash script on your instance on a +schedule. The API lists, gets, creates, and updates them. The following +endpoints are supported: + +- `GET` [List cron entries](list-cron-entries.md) +- `GET` [Get a cron entry](get-cron-entry.md) +- `POST` [Create a cron entry](create-cron-entry.md) +- `PATCH` [Update a cron entry](update-cron-entry.md) + +Delete a cron entry from the dashboard, under **Cron** on your instance. diff --git a/api/cron/list-cron-entries.md b/api/cron/list-cron-entries.md new file mode 100644 index 0000000..8476dd3 --- /dev/null +++ b/api/cron/list-cron-entries.md @@ -0,0 +1,47 @@ +# List cron entries + +{.lead} +List every **cron entry** on an instance, across all of its environments. + +`GET` /v1/instances/{instance_id}/cron + +## Parameters + +Parameter | Type | In | Required | Description +----------- | ------ | --- | -------- | --------------- +instance_id | string | uri | Yes | The instance ID + +Each entry reports the environment it belongs to in `environment_name`, so one +call covers both staging and production. To get a single entry, see +[Get a cron entry](get-cron-entry.md). To update one, see +[Update a cron entry](update-cron-entry.md). + +## Code samples + +### Request example + +{title="GET /v1/instances/{instance_id}/cron"} +```bash +curl https://api.amezmo.com/v1/instances/{instance_id}/cron \ + -H "Authorization: Bearer $AMEZMO_API_KEY" +``` + +### Response + +{title="200 OK"} +```javascript +[ + { + "id": 412, + "name": "Laravel-task-scheduler", + "command": "#!/bin/bash\nphp artisan schedule:run", + "expression": "* * * * *", + "expression_alias": "@minutely", + "status": "Created", + "environment_name": "production", + "log_file_path": "/home/deployer/cron/logs/cron.Laravel-task-scheduler.log", + "created_at": "2026-02-11T18:04:22.000000Z", + "updated_at": "2026-09-10T14:31:07.000000Z" + } +] +``` diff --git a/api/cron/update-cron-entry.md b/api/cron/update-cron-entry.md new file mode 100644 index 0000000..bd37819 --- /dev/null +++ b/api/cron/update-cron-entry.md @@ -0,0 +1,96 @@ +# Update a cron entry + +{.lead} +Update the **script** a cron entry runs, its **schedule**, or both, without +recreating the entry. + +`PATCH` /v1/instances/{instance_id}/cron/{cron_id} + +## Parameters + +Parameter | Type | In | Required | Description +----------- | ------ | ---- | ----------- | ------------------------------------------------------- +instance_id | string | uri | Yes | The instance ID +cron_id | string | uri | Yes | The cron entry ID. See [List cron entries](list-cron-entries.md) +command | string | body | Conditional | The bash script to run. Required when you omit `expression` +expression | string | body | Conditional | The schedule. Required when you omit `command` + +Send `command`, `expression`, or both. A field you leave out keeps its current +value, and a request that carries neither returns a validation error. + +Use `--data-urlencode` rather than `--data` for both fields. A script contains +newlines and an expression contains spaces and `*`, and only the encoding form +sends those intact. + +Read the current values first with +[Get a cron entry](get-cron-entry.md), so a partial update starts from what is +actually on the instance. + +`expression` accepts a five-field POSIX cron expression or one of the aliases +below. Not every expression the API accepts runs on a Linux instance: see +[custom cron expressions](../../cron/custom-expressions.md) for the extensions to +avoid. + +Alias | Expression | Runs +------------------ | ------------- | ------------------------- +`@minutely` | `* * * * *` | Every minute +`@every_5_minutes` | `*/5 * * * *` | Every five minutes +`@hourly` | `0 * * * *` | At the top of every hour +`@daily` | `0 0 * * *` | Every day at midnight +`@weekly` | `0 0 * * 0` | Every Sunday at midnight +`@monthly` | `0 0 1 * *` | On the first of every month +`@yearly` | `0 0 1 1 *` | On January 1 +`@annually` | `0 0 1 1 *` | On January 1 + +The response reports the resolved five-field `expression` along with the +matching `expression_alias`, so an alias you send back comes out as both. + +> [!NOTE] +> A cron entry's `name` is fixed. Amezmo derives the entry's log file name from +> it, so renaming would leave the existing log behind under the old name. To +> change a name, delete the entry from the dashboard and +> [create a new one](create-cron-entry.md). +> [Editing a cron entry](../../cron/editing-entries.md) covers the same +> limitations for the dashboard form. + +Amezmo applies the change on your instance asynchronously: it rewrites the +script and the crontab entry in place, so the entry keeps its ID and its log +file. A run already in flight finishes under the old script. + +Updating an entry that is being deleted returns `409 Conflict` with the code +`cron_entry_deleting`. An instance whose type does not include cron returns +`422` with `feature_not_supported`. See [Errors](../errors.md) for the shape of +both. + +## Code samples + +### Request example + +{title="PATCH /v1/instances/{instance_id}/cron/{cron_id}"} +```bash +curl https://api.amezmo.com/v1/instances/{instance_id}/cron/412 \ + -X PATCH \ + -H "Authorization: Bearer $AMEZMO_API_KEY" \ + --data-urlencode command="#!/bin/bash +php artisan schedule:run --no-interaction +" \ + --data-urlencode expression='@every_5_minutes' +``` + +### Response + +{title="200 OK"} +```javascript +{ + "id": 412, + "name": "Laravel-task-scheduler", + "command": "#!/bin/bash\nphp artisan schedule:run --no-interaction", + "expression": "*/5 * * * *", + "expression_alias": "@every_5_minutes", + "status": "Created", + "environment_name": "production", + "log_file_path": "/home/deployer/cron/logs/cron.Laravel-task-scheduler.log", + "created_at": "2026-02-11T18:04:22.000000Z", + "updated_at": "2026-09-10T14:31:07.000000Z" +} +``` diff --git a/api/endpoints.md b/api/endpoints.md index de86e7b..7eca8ff 100644 --- a/api/endpoints.md +++ b/api/endpoints.md @@ -27,6 +27,26 @@ so you set it once instead of pasting it into every command: export AMEZMO_API_KEY="your-api-key" ``` +## Errors + +A failing request returns a JSON body with the same fields every time: a `type` +to branch on, a specific `code`, a human `message`, and a `request_id` to quote +when you report a problem. See [Errors](errors.md). + +{title="404 Not Found"} +```javascript +{ + "http_status": 404, + "type": "invalid_request_error", + "code": "unknown_resource_error", + "message": "The requested resource does not exist or you do not have permission to access it.", + "errors": {}, + "doc_url": "https://www.amezmo.com/docs/api", + "request_id": "req_01HZX9C4Q2", + "error": null +} +``` + ## Example Request This request lists the regions where you can launch an instance. The double diff --git a/api/environments/update-environment.md b/api/environments/update-environment.md index 03f1639..54e5ee1 100644 --- a/api/environments/update-environment.md +++ b/api/environments/update-environment.md @@ -2,7 +2,7 @@ {.lead} Update the settings of an **environment** on an instance, such as auto-deploy -patterns, SSH access and the New Relic license key. +patterns, SSH access, and the New Relic license key. `PATCH` /v1/instances/{instance_id}/environments/{name} @@ -20,7 +20,7 @@ trusted_ssh_ips | array | body | No | An array of IPv4 addre When you update `newrelic_license_key`, the change takes effect on the next [deployment](../deployments/index.md). A `null` value disables the New Relic Application Performance Monitoring -(<abbr title="Application Performance Monitoring">APM</abbr>) integration. Amezmo encrypts the key at rest, decrypts it at instance creation +(<abbr title="Application Performance Monitoring">APM</abbr>) integration. Amezmo encrypts the key at rest, decrypts it at instance creation, and stores it in the `newrelic.ini` PHP configuration file. You can read the stored value with `php --ri newrelic | grep newrelic.license`. diff --git a/api/errors.md b/api/errors.md new file mode 100644 index 0000000..5d1b8f2 --- /dev/null +++ b/api/errors.md @@ -0,0 +1,96 @@ +# Errors + +{.lead} +Every Amezmo API error returns the same JSON body, whatever went wrong. Branch on +`type` to decide how to handle it and on `code` when you need the exact reason. + +{title="422 Unprocessable Entity"} +```javascript +{ + "http_status": 422, + "type": "invalid_request_error", + "code": "validation_error", + "message": "The request you provided failed validation.", + "errors": { + "expression": "This is an invalid cron expression." + }, + "doc_url": "https://www.amezmo.com/docs/api/cron/update-cron-entry", + "request_id": "req_01HZX9C4Q2", + "error": null +} +``` + +Field | Description +------------ | ----------------------------------------------------------------- +`http_status`| The HTTP status, repeated in the body so it survives logging +`type` | The coarse class of error. A short, closed set, listed below +`code` | The specific reason. Open-ended, so treat an unfamiliar one as its `type` +`message` | A human-readable explanation. Not a stable interface: don't match on it +`errors` | Per-parameter messages, keyed by parameter name. `{}` when not a validation failure +`doc_url` | The reference page for whatever failed +`request_id` | Identifies this request. Quote it when you contact support +`error` | Debugging detail, always `null` outside development + +## Types + +`invalid_request_error` +: Something about the request is wrong: a bad parameter, a resource that doesn't +exist, a state that forbids the action, or an unmet account requirement. Retrying +the same request unchanged fails the same way. + +`authentication_error` +: Your API key is missing, malformed, disabled, or not allowed to do this. See +[Authentication](authentication/index.md). + +`rate_limit_error` +: You sent too many requests. Back off and retry. + +`api_error` +: Something failed on Amezmo's side. These are safe to retry. + +## Codes + +The codes you're most likely to handle. Individual endpoints add their own for +situations only they can hit, documented on that endpoint's page. New codes get +added as the API grows, so treat an unrecognized one as its `type` rather than +failing. + +Code | Status | Meaning +---------------------------- | ------ | ------------------------------------------- +`validation_error` | 422 | One or more parameters failed validation. See `errors` +`unknown_resource_error` | 404 | No such resource, or your key can't see it +`method_not_allowed` | 405 | Wrong HTTP method for this path +`conflict` | 409 | The resource's current state forbids this +`unauthenticated` | 401 | No usable API key on the request +`invalid_credentials` | 401 | The API key was rejected +`forbidden` | 403 | The key is valid but not allowed to do this +`email_verification_required`| 403 | Verify your email address first +`payment_method_required` | 402 | Add a payment method first +`payment_method_flagged` | 402 | Your payment method could not be verified +`account_past_due` | 402 | Settle an outstanding balance first +`feature_not_supported` | 422 | Your instance type doesn't include this feature +`invalid_instance_state` | 409 | The environment is paused, launching, or failed +`instance_in_progress` | 422 | Another instance is still being created +`instance_limit_reached` | 422 | You're at your account's instance limit +`rate_limit_exceeded` | 429 | Too many requests +`internal_error` | 500 | Something failed on Amezmo's side + +## Validation errors + +A `validation_error` fills in `errors` with one message per rejected parameter, +keyed by the parameter's name. Everything else leaves `errors` as `{}`. + +{title="Reading a validation failure"} +```bash +$ curl -s https://api.amezmo.com/v1/instances/{instance_id}/cron/412 \ + -X PATCH \ + -H "Authorization: Bearer $AMEZMO_API_KEY" \ + --data-urlencode expression='every other tuesday' \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["errors"])' +``` + +## Reporting a problem + +`request_id` identifies one request on Amezmo's side. Include it when you write +to [support](../support/index.md), along with the endpoint and roughly when you +called it. diff --git a/api/index.md b/api/index.md index 2b0c61d..8629d56 100644 --- a/api/index.md +++ b/api/index.md @@ -17,6 +17,11 @@ Every request carries a Bearer token. [API authentication](authentication/index. covers where to find your key and how the examples store it in an `AMEZMO_API_KEY` environment variable. +### Errors + +Every failure returns the same JSON body. [Errors](errors.md) documents that +envelope, the error types worth branching on, and the codes behind them. + ## Core Resources ### Workers @@ -32,15 +37,20 @@ types, and terminate an instance. ### Environments An environment ties an instance to a Git repository and its deployment rules. -[Environment endpoints](environments/index.md) read and update those, including -automatic deployment settings. +[Environment endpoints](environments/index.md) list, get, and update those, +including automatic deployment settings. ### Deployments Deploy without Git by supplying a `.zip` archive of your source. -[Deployment endpoints](deployments/index.md) create, read, and cancel +[Deployment endpoints](deployments/index.md) create, get, and cancel deployments in both staging and production. +### Cron + +List, get, create, and update the [cron entries](cron/index.md) on an +instance. + ### Regions [Region endpoints](regions/index.md) list the regions an instance can run in diff --git a/api/instances/get-instance.md b/api/instances/get-instance.md index f4de670..6c8ebe9 100644 --- a/api/instances/get-instance.md +++ b/api/instances/get-instance.md @@ -1,7 +1,7 @@ # Get an instance {.lead} -Retrieve a single **instance** by its id, including its runtime configuration +Get a single **instance** by its id, including its runtime configuration and every environment attached to it. `GET` /v1/instances/{instance_id} diff --git a/api/regions/get-region.md b/api/regions/get-region.md index 62dcbdd..184f4d7 100644 --- a/api/regions/get-region.md +++ b/api/regions/get-region.md @@ -1,7 +1,7 @@ # Get a region {.lead} -Retrieve a single **region** by its id, including its name and ISO country code. +Get a single **region** by its id, including its name and ISO country code. Use it to confirm a region before you launch an instance there. `GET` /v1/regions/{region_id} diff --git a/api/regions/list-regions.md b/api/regions/list-regions.md index d49bf55..fc6b629 100644 --- a/api/regions/list-regions.md +++ b/api/regions/list-regions.md @@ -19,7 +19,7 @@ curl https://api.amezmo.com/v1/regions \ ### Response -The response is an array of regions. Retrieve one region with +The response is an array of regions. To get a single region, see [Get a region](get-region.md). {title="200 OK"} diff --git a/billing/credits.md b/billing/credits.md index b974905..abbf4cd 100644 --- a/billing/credits.md +++ b/billing/credits.md @@ -22,7 +22,7 @@ your next monthly invoice. See [Hourly billing](hourly.md) for when invoices go out. To review the promotions you've redeemed, open Billing > Credits. Each one -lists its date, code, amount and expiration. +lists its date, code, amount, and expiration. ## Why a Code Won't Apply diff --git a/billing/payment-methods.md b/billing/payment-methods.md index 4424db3..1cfbb5c 100644 --- a/billing/payment-methods.md +++ b/billing/payment-methods.md @@ -1,11 +1,11 @@ # Payment Methods Manage the cards on your account from Billing > Payment methods. You can add a -card, choose your default and remove cards you no longer use. +card, choose your default, and remove cards you no longer use. ## Adding a Card -Open Billing > Payment methods, enter your card details and save. Your first +Open Billing > Payment methods, enter your card details, and save. Your first card becomes your default automatically. Amezmo charges the default card for your monthly [hourly usage](hourly.md). @@ -21,7 +21,7 @@ add the new card and set it as the default first, then remove the old one. ## Fixing a Past-Due or Failed Payment -A charge can fail for a few reasons, like an expired card, insufficient funds +A charge can fail for a few reasons, like an expired card, insufficient funds, or a charge your bank blocked. When that happens, your account becomes past due and the dashboard shows a banner asking you to update your billing details. @@ -33,7 +33,7 @@ there's no separate pay button. Once a retry succeeds, the past-due state clears on its own. If the same card keeps failing, add a different card instead of retrying it. -While your account is past due you can't launch, start or resize +While your account is past due you can't launch, start, or resize [instances](../instances/index.md), and Amezmo blocks API access. > [!WARNING] diff --git a/cron/custom-expressions.md b/cron/custom-expressions.md new file mode 100644 index 0000000..c6f9c23 --- /dev/null +++ b/cron/custom-expressions.md @@ -0,0 +1,143 @@ +# Custom cron expressions + +{.lead} +When none of the [schedule aliases](index.md#schedules) fits, write a five-field +cron expression instead. This page covers the syntax Amezmo accepts, the parts +of it that look valid but never run, and a set of expressions you can copy. + +In the dashboard, open a cron entry's schedule dropdown, choose **Custom**, and +type the expression into the dialog. Over the API, send the same string as the +`expression` field of +[Create a cron entry](../api/cron/create-cron-entry.md) or +[Update a cron entry](../api/cron/update-cron-entry.md). + +## The five fields + +An expression is five fields separated by spaces. Each field says which values of +that unit the entry runs on, and the entry runs when every field matches. + +{title="Field order"} +```bash +# minute hour day-of-month month day-of-week + 30 2 * * * +``` + +Field | Range | Notes +------------ | ------- | ----------------------------------------------- +minute | `0-59` | +hour | `0-23` | Midnight is `0`, not `24` +day of month | `1-31` | +month | `1-12` | Or `JAN` through `DEC` +day of week | `0-7` | `0` and `7` are both Sunday. Or `SUN` through `SAT` + +Names are not case sensitive, so `mon` and `MON` both work. You can't use a name +in a step, so write `1-5` rather than `MON-FRI/1`. + +## Operators + +`*` +: Every value of the field. `* * * * *` is every minute of every day. + +`,` +: A list. `0 8,12,18 * * *` runs at 08:00, 12:00 and 18:00. + +`-` +: A range. `0 9-17 * * *` runs hourly from 09:00 through 17:00, inclusive. + +`/` +: A step through a range. `*/15 * * * *` runs at minutes 0, 15, 30 and 45. +`0 9-17/2 * * *` runs at 09:00, 11:00, 13:00, 15:00 and 17:00. + +A step counts from the start of its range, not from the current time. `*/40` in +the minute field fires at minute 0 and minute 40, then waits 20 minutes, because +the range restarts every hour. Steps that don't divide evenly into their range +are uneven like this, which is why `*/15` is a safer habit than `*/40`. + +## Examples + +Expression | Runs +--------------- | ------------------------------------------------ +`*/10 * * * *` | Every ten minutes +`0 * * * *` | At the top of every hour +`30 2 * * *` | Every day at 02:30 +`0 9-17 * * 1-5`| Hourly from 09:00 to 17:00, Monday through Friday +`0 3 * * 0` | Sundays at 03:00 +`0 0 1 * *` | Midnight on the first of the month +`0 0 1 1 *` | Midnight on January 1 +`15 4 1,15 * *` | 04:15 on the 1st and the 15th + +## Limitations + +### Amezmo accepts syntax your instance won't run + +The dashboard validates your expression with a PHP parser that understands +Quartz-style extensions. Standard Linux cron, which is what actually runs the +entry on your instance, does not. So these save without complaint and then never +fire: + +Not supported | What it means elsewhere +------------- | ------------------------------------- +`L` | Last day of the month or the week +`W` | Nearest weekday to a date, as in `15W` +`#` | Nth weekday of the month, as in `5#3` +`?` | No specific value + +Stick to `*`, `,`, `-`, `/`, numbers, and month or weekday names. If you need +"last day of the month", schedule the entry daily and exit early from your +script instead: + +{title="Run only on the last day of the month"} +```bash +#!/bin/bash +# Tomorrow is the 1st, so today is the last day of this month. +if [ "$(date -d tomorrow +%d)" != "01" ]; then + exit 0 +fi + +php artisan billing:close-month +``` + +### Day of month and day of week are an OR, not an AND + +When both the day-of-month and the day-of-week fields are restricted, cron runs +the entry when **either** matches, not when both do. `0 0 13 * 5` runs on the +13th of every month *and* on every Friday, not only on Friday the 13th. Leave one +of the two as `*` unless you want that behavior. + +### `@reboot` and seconds are not supported + +Amezmo rejects `@reboot`: a cron entry is a schedule, not a startup hook. Use a +[worker](../workers/index.md) for a process that should run continuously and come +back after a restart. + +There is no seconds field. One minute is the shortest interval an expression can +express. If you need to act more often than that, loop inside your script or run +a worker. + +### Schedules follow the instance clock + +Cron matches your expression against your instance's system clock, and you can't +set a timezone per entry. Check what your instance thinks the time is over +[SSH](../instances/ssh.md): + +{title="Check the instance clock"} +```bash +$ date +``` + +If your task's timing has to follow a specific timezone, set `TZ` inside the +script for the commands it runs, and pick the expression to match the instance +clock. + +### Editing the schedule doesn't reschedule a running task + +Saving a new expression rewrites the entry on your instance in the background. A +run already underway finishes on the old schedule's terms. See +[Editing a cron entry](editing-entries.md). + +## Checking your work + +The entry page streams the task's log, so the fastest way to confirm an +expression is to save it, wait for the first window, and watch the log. Give a +new script a frequent schedule such as `@minutely` while you're testing it, then +switch to the real one. diff --git a/cron/editing-entries.md b/cron/editing-entries.md new file mode 100644 index 0000000..75a7fe2 --- /dev/null +++ b/cron/editing-entries.md @@ -0,0 +1,64 @@ +# Editing a cron entry + +{.lead} +Change the script a [cron entry](index.md) runs, or how often it runs, without +recreating the entry. + +## Editing from the dashboard + +Open your instance, go to **Cron**, and open the entry you want to change. Click +**Edit** in the entry header, or pick **Edit** from the entry's menu in the cron +list. + +The edit form is the same one you used to create the entry. Change the schedule, +the script, or both, then click **Save**. + +## What you can change + +Schedule +: Pick an alias such as `@hourly` from the dropdown, or choose **Custom** and +type an expression. See [custom cron expressions](custom-expressions.md) for the +syntax. An entry created with a custom expression comes back with that +expression already selected. + +Script +: Edit the bash script in place, or drag a `.sh` file onto the editor to replace +it. Scripts still run from your +[current release](../deployments/directories.md) directory, so a script that +worked before an edit works after one. + +## What you can't change + +**The name is fixed.** Amezmo derives the entry's log file name from the name you +gave it, so a rename would leave the existing log stranded under the old name and +start a fresh, empty one. The name field is read-only on the edit form. If you +need a different name, delete the entry and create a new one, and keep in mind +that deleting removes the old log file with it. + +**Editing doesn't move an entry between environments.** An entry belongs to the +environment it was created in. To run the same task in staging and production, +create an entry in each. + +**An entry being deleted can't be edited.** Once you confirm a delete, the entry +is on its way out and the **Edit** action disappears from it. + +## What happens when you save + +Amezmo applies the change on your instance in the background: it rewrites your +script and the crontab entry in place. The entry keeps its ID and keeps writing +to the same log file, so the history you already have stays where it is. + +The change is not instantaneous, and it is not transactional with a run in +flight. A run that has already started finishes under the old script. The next +run after the update lands uses the new one. + +> [!NOTE] +> Editing the schedule doesn't run the task. If you want to confirm a new script +> works, give it a frequent schedule such as `@minutely`, watch the log on the +> entry page, then set the schedule you actually want. + +## Editing over the API + +[Update a cron entry](../api/cron/update-cron-entry.md) does the same thing over +the REST API, with the same limitations. It takes `command`, `expression`, or +both, and leaves out anything you omit. diff --git a/cron/index.md b/cron/index.md index 7201440..3c661b2 100644 --- a/cron/index.md +++ b/cron/index.md @@ -7,6 +7,21 @@ that is required to execute your task, and the logging of the task output so you see the results in your Amezmo dashboard. Crons are executed from your applications [current release](../deployments/directories.md) directory. +## In This Section + +### Custom Cron Expressions + +When no alias fits, write a five-field expression instead. +[Custom cron expressions](custom-expressions.md) covers the field order and the +operators, a table of ready-made schedules, and the syntax that saves but never +runs on your instance. + +### Editing a Cron Entry + +Change an entry's script or its schedule after you create it, from the dashboard +or the API. [Editing a cron entry](editing-entries.md) covers what you can change, +why the name is fixed, and when the change reaches your instance. + ## Schedules A cron schedule represents when the task will be executed. You may use an alias for the following cron expressions. @@ -27,13 +42,22 @@ A cron schedule represents when the task will be executed. You may use an alias For advanced use cases, Amezmo supports [POSIX cron syntax](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07). +See [custom cron expressions](custom-expressions.md) for the field order, the +operators and the extensions to avoid. ## Scripts A cron *script* is a bash script where you define your task logic. In this script, you may call into other -services such as PHP, Node.js and anything you'd normally do from the command line. Scripts run on your instance and in the current working +services such as PHP, Node.js, and anything you'd normally do from the command line. Scripts run on your instance and in the current working directory as your most recent [release](../deployments/releases.md). ## Best practices It's best to use a Cron job as a means to invoke another script. Don't put any logic into your Cron job. Keep your business logic in your [git](../git/index.md) repository so that any updates won't require an update to your Cron job as well. +That pays off when a task changes: you deploy the new logic instead of +[editing the cron entry](editing-entries.md). + +## API + +The Amezmo API lists, gets, creates, and updates your cron entries. See +[Cron endpoints](../api/cron/index.md). diff --git a/databases/backup-restore.md b/databases/backup-restore.md index 9d6a274..2af8bf2 100644 --- a/databases/backup-restore.md +++ b/databases/backup-restore.md @@ -2,7 +2,7 @@ Amezmo takes logical backups of your MySQL database, the kind you'd get from ``mysqldump``: a file of SQL statements you can restore anywhere. Use a backup -to copy production to your machine, to archive or to move data between +to copy production to your machine, to archive, or to move data between environments. ## On-Demand Backups diff --git a/databases/index.md b/databases/index.md index f99181c..6bc982b 100644 --- a/databases/index.md +++ b/databases/index.md @@ -1,7 +1,7 @@ # Databases Amezmo runs a dedicated MySQL server (5.7 or 8.0) for instances launched with -database support. From the dashboard you can back up, restore and check the +database support. From the dashboard you can back up, restore, and check the status of your database, and any instance shared with the database instance can reach it. Your MySQL server is never reachable from the public internet. @@ -15,7 +15,7 @@ Back up, restore, and check its status from the dashboard. ### Redis [Redis](redis.md) is available if you chose it at instance creation, reachable -from your shared instances and never exposed to the internet. Data is not +from your shared instances, and never exposed to the internet. Data is not persisted, so treat it as an in-memory cache. ### Remote Access diff --git a/databases/ssh.md b/databases/ssh.md index 1170c9e..709fe4b 100644 --- a/databases/ssh.md +++ b/databases/ssh.md @@ -2,7 +2,7 @@ If you launched an instance with MySQL support, the database is reachable from that instance and from any instance shared with it. From the Amezmo dashboard -you can back up, restore and check its status. +you can back up, restore, and check its status. First make sure [SSH is enabled](../instances/enable-or-disable-ssh.md) on your instance, then go to Overview, then Server Details, then find your SSH port. An diff --git a/domains/rules.md b/domains/rules.md index 6821298..e78ed2b 100644 --- a/domains/rules.md +++ b/domains/rules.md @@ -12,7 +12,7 @@ process. See 2. Open the create-rule form and enter the path. 3. Choose the Websocket action. -The path accepts letters, digits, ``_``, ``-`` and ``*``, with no slashes. +The path accepts letters, digits, ``_``, ``-``, and ``*``, with no slashes. Amezmo adds the leading slash for you. To remove a rule, delete it from the domain's rules table. diff --git a/domains/wildcard-ssl-certificates.md b/domains/wildcard-ssl-certificates.md index bd58d8c..30f25b0 100644 --- a/domains/wildcard-ssl-certificates.md +++ b/domains/wildcard-ssl-certificates.md @@ -3,7 +3,7 @@ {.lead} A **wildcard SSL certificate** secures a domain and all of its direct subdomains with one certificate, so `*.example.com` covers `app.example.com`, -`api.example.com` and every other subdomain at that level. Amezmo issues +`api.example.com`, and every other subdomain at that level. Amezmo issues wildcard certificates from Let's Encrypt, the same authority it uses for [standard certificates](ssl-certificates.md). diff --git a/environments/cloning.md b/environments/cloning.md index a699089..610c98c 100644 --- a/environments/cloning.md +++ b/environments/cloning.md @@ -9,10 +9,10 @@ On Advanced instances you can clone a full environment from the instance Actions menu. Pick the environment to copy from, give the new environment a unique name, and choose its branch. Amezmo copies: -- The Git connection, deploy key and chosen branch. +- The Git connection, deploy key, and chosen branch. - The environment variables. - The database, including schema and data. -- Deployment settings, health checks, alerts and trusted IP addresses. +- Deployment settings, health checks, alerts, and trusted IP addresses. The new environment then runs a deployment. Cloning a full environment is only available on Advanced instances. diff --git a/git/providers.md b/git/providers.md index 7ef5bd3..ecb0f74 100644 --- a/git/providers.md +++ b/git/providers.md @@ -1,11 +1,11 @@ # Git Providers -Amezmo is a Git-native deployment platform. You can deploy from GitHub, GitLab +Amezmo is a Git-native deployment platform. You can deploy from GitHub, GitLab, and Bitbucket. You connect a provider from the Git tab. Amezmo asks you to authorize with the -provider, stores an access token for that account and uses it to list your -repositories, add a deploy key and create a webhook for automatic deployments. +provider, stores an access token for that account, and uses it to list your +repositories, add a deploy key, and create a webhook for automatic deployments. ## Permissions Amezmo Needs diff --git a/instances/index.md b/instances/index.md index 4bec3b1..da9ba7d 100644 --- a/instances/index.md +++ b/instances/index.md @@ -8,7 +8,7 @@ action in the dashboard runs in the context of one environment. Everything you need to run a production PHP app is set up by default, with little or no custom configuration. Amezmo includes framework-specific -configuration for CraftCMS, Laravel and Yii. +configuration for CraftCMS, Laravel, and Yii. For pricing and features, see the [pricing](/pricing) page. diff --git a/metrics/index.md b/metrics/index.md index 9e2513f..79e65aa 100644 --- a/metrics/index.md +++ b/metrics/index.md @@ -1,6 +1,6 @@ # Metrics -The Metrics tab shows how your environment is performing: CPU, memory and HTTP +The Metrics tab shows how your environment is performing: CPU, memory, and HTTP request stats for the environment you're viewing. ## CPU diff --git a/nginx/compression-and-caching.md b/nginx/compression-and-caching.md index b32a77c..22f57de 100644 --- a/nginx/compression-and-caching.md +++ b/nginx/compression-and-caching.md @@ -4,7 +4,7 @@ The Nginx tab has two switches that speed up how your site serves assets. ## Gzip Compression -Turn on Gzip to compress text assets like JavaScript, CSS and HTML before Nginx +Turn on Gzip to compress text assets like JavaScript, CSS, and HTML before Nginx sends them, which cuts download size. Amezmo supports Gzip. There's no Brotli option. diff --git a/nginx/location-blocks.md b/nginx/location-blocks.md index 0370462..5438232 100644 --- a/nginx/location-blocks.md +++ b/nginx/location-blocks.md @@ -2,11 +2,11 @@ A location block is an Nginx ``location`` directive scoped to your app. Amezmo generates a default set based on your app type (Laravel, Craft, WordPress, -Drupal or plain PHP), and you can edit them or add your own from the Nginx tab. +Drupal, or plain PHP), and you can edit them or add your own from the Nginx tab. ## Editing a Block -You can edit the body of a block, add a block or delete one. The ``location`` +You can edit the body of a block, add a block, or delete one. The ``location`` line itself is fixed, so to change the match you delete the block and add a new one. When you add a block, put the body on its own line after the opening ``{``. @@ -39,6 +39,6 @@ To route a whole domain to a worker process instead, see ## Coming from .htaccess Amezmo runs Nginx, not Apache, so it ignores ``.htaccess`` files. Move your -rewrite, redirect, deny and expires rules into location blocks or your custom +rewrite, redirect, deny, and expires rules into location blocks or your custom [Nginx configuration](config.md). For password protection, use [HTTP authentication](http-authentication.md). From 70dedd0208cbec4557b9f08cb504eff5ea6a5d16 Mon Sep 17 00:00:00 2001 From: Ryan McCullagh <me@ryanmccullagh.com> Date: Tue, 15 Sep 2026 06:28:10 -0500 Subject: [PATCH 3/3] fix --- .claude/skills/add-doc-page/SKILL.md | 107 ++++++++++++++++++++++++++ .github/scripts/check_links.py | 24 +++++- README.md | 11 ++- api/cron/create-cron-entry.md | 16 +--- api/cron/update-cron-entry.md | 21 ++--- api/deployments/cancel-deployment.md | 4 +- api/deployments/get.md | 4 +- api/deployments/post.md | 2 +- api/environments/get-environment.md | 4 +- api/environments/list-environments.md | 2 +- api/index.md | 5 ++ api/instances/terminate-instance.md | 2 +- api/workers/get.md | 4 +- api/workers/restart.md | 4 +- api/workers/start.md | 4 +- api/workers/stop.md | 4 +- cron/custom-expressions.md | 8 +- cron/index.md | 29 ++----- cron/schedules.md | 38 +++++++++ 19 files changed, 213 insertions(+), 80 deletions(-) create mode 100644 .claude/skills/add-doc-page/SKILL.md create mode 100644 cron/schedules.md diff --git a/.claude/skills/add-doc-page/SKILL.md b/.claude/skills/add-doc-page/SKILL.md new file mode 100644 index 0000000..2263304 --- /dev/null +++ b/.claude/skills/add-doc-page/SKILL.md @@ -0,0 +1,107 @@ +--- +name: add-doc-page +description: Add a new documentation page to this repo and register it so it + renders on amezmo.com. Use when creating any new .md page, including API + endpoint pages. Covers the section index entry, reciprocal backlinks, the + nav registration in the dashboard app, and the lint checks. +--- + +# Add a Documentation Page + +Adding a page is a two-repo change. The Markdown lives here, but the page +only renders on amezmo.com after it is registered in the dashboard app at +`~/Source/dashboard.amezmo.com`. A page that exists only in this repo is +unreachable: the menu, the prev/next nav, the sitemap, and the JSON-LD dates +all come from the dashboard's hand-maintained tree, not from file discovery. + +## 1. Write the Page + +Follow the conventions in [CLAUDE.md](../../../CLAUDE.md): a single `#` H1, +no front matter, relative `.md`-suffixed internal links, GitHub alert +callouts. API endpoint pages follow the fixed layout in the "API reference +pages" section of CLAUDE.md. + +An optional lead paragraph directly under the H1 can carry `{.lead}` on its +own line above the paragraph (see `cron/schedules.md`). The site styles it +as the page intro. + +## 2. Link It from Its Section Index + +Pages are not auto-discovered. Add a `###` entry for the page under the +section index's `## In This Section` run, with a one-or-two sentence excerpt +written from the page's content. The heading text is plain, never a link; +the link goes in the excerpt prose. Keep the entries in reading order: this +order should match the menu order you register in step 4. + +For top-level sections, check whether the root `index.md` or +`how-to-guides/index.md` should also point at the page. + +## 3. Make Content Links Reciprocal + +Every content page you link from the new page should link back with a +contextual link. Index pages are exempt. Record deliberate one-ways in +`.github/backlink-ignore.txt` as `SRC -> DST` with a comment. + +## 4. Register the Page in the Dashboard App + +Edit `app/Http/Docs/Services/DocumentationMenuService.php` in +`~/Source/dashboard.amezmo.com`. Find the section's +`DocumentationCategory` and add a child entry in the same position the page +has in the section index (menu order is also prev/next reading order). + +The constructor is: + +```php +// app/Http/Docs/Models/DocumentationCategory.php +public function __construct( + string $name, // menu label; also the slug source + ?string $description, // meta description for the page + ?string $parent, // parent category name, e.g. 'Cron' + ?array $children, // [] for a leaf page + bool $lineBreak, // false for leaf pages + $slug, // only when the filename differs from the name + ?string $title // <title> override when it differs from name +) +``` + +The slug defaults to `Str::slug(strtolower($name))`, so `'Schedules'` +resolves to `schedules`. Pass an explicit slug when the filename does not +match the name (`'Create a deployment'` renders `post.md`, so it passes +`'post'`). The slug plus the parent chain must equal the file path in this +repo without `.md`: the `Cron` category's child with slug `schedules` renders +`cron/schedules.md`. + +Write the description with care: it becomes the page's meta description, +and the search engine snippet. + +## 5. Page Dates + +`app/Http/Docs/Generated/DocsPageDates.php` maps each page to its git +commit dates for the Article JSON-LD. It is generated by +`php artisan sitemap:generate` through the regenerate-sitemap workflow. Do +not edit it by hand. `DocsPageDatesTest` fails the dashboard CI when a menu +page has no dates, so the docs commit needs to land and the sitemap needs +regenerating after the menu entry is added. + +## 6. Validate + +Run from this repo's root: + +```bash +$ python3 .github/scripts/check_links.py +$ python3 .github/scripts/check_backlinks.py +$ python3 .github/scripts/check_image_paths.py +$ codespell --config .codespellrc +``` + +## Checklist + +- [ ] Page written per CLAUDE.md conventions +- [ ] `###` excerpt entry in the section's `index.md` +- [ ] Root `index.md` / `how-to-guides/index.md` updated if relevant +- [ ] Backlinks reciprocal, or the one-way recorded in + `.github/backlink-ignore.txt` +- [ ] `DocumentationCategory` entry added in `DocumentationMenuService.php`, + in the same order as the section index +- [ ] Sitemap regenerated after both changes land (page dates) +- [ ] Lint scripts pass diff --git a/.github/scripts/check_links.py b/.github/scripts/check_links.py index b232c7c..ced84c7 100644 --- a/.github/scripts/check_links.py +++ b/.github/scripts/check_links.py @@ -22,10 +22,15 @@ EXTERNAL = ("http://", "https://", "mailto:", "//") -# Repo meta files, not published doc pages: skipped as both link sources -# and link targets. +# Repo meta files, not published doc pages: excluded as link sources and +# link targets for the doc-page check. META_FILES = {"CLAUDE.md", "README.md"} +# Meta files whose own outgoing links are still checked. They may point at +# any existing repo file, not only published .md pages. CLAUDE.md is not +# here: its links are illustrative examples, not real paths. +CHECKED_META_FILES = {"README.md"} + def doc_files(root): files = set() @@ -58,6 +63,21 @@ def main(): if not path.endswith(".md") or target not in files: broken.append((rel, lineno, url)) + for rel in sorted(CHECKED_META_FILES): + full = os.path.join(ROOT, rel) + if not os.path.isfile(full): + continue + with open(full, encoding="utf-8") as handle: + for lineno, line in enumerate(handle, 1): + for match in LINK_RE.finditer(line): + url = match.group(1) + if not is_internal(url): + continue + path = url.split("#", 1)[0] + target = os.path.normpath(os.path.join(os.path.dirname(rel), path)) + if path and not os.path.isfile(os.path.join(ROOT, target)): + broken.append((rel, lineno, url)) + if broken: print(f"Found {len(broken)} broken internal link(s):") for rel, lineno, url in broken: diff --git a/README.md b/README.md index cc23f32..3df629b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # Amezmo Documentation [![Docs lint](https://github.com/amezmo/docs/actions/workflows/docs-lint.yml/badge.svg)](https://github.com/amezmo/docs/actions/workflows/docs-lint.yml) -[![Pages](https://img.shields.io/badge/pages-146-blue)](https://www.amezmo.com/docs/) [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://github.com/amezmo/docs/pulls) -The source for the [Amezmo](https://www.amezmo.com) docs: 146 pages on -zero-downtime PHP hosting, from your first `git push` to tuning Nginx, OPcache +The source for the [Amezmo](https://www.amezmo.com) docs: everything on +zero-downtime PHP hosting, from your first `git push` to tuning Nginx, OPcache, and Redis on a production instance. Published at [amezmo.com/docs](https://www.amezmo.com/docs/). @@ -41,7 +40,7 @@ Good first contributions: Read the [contributor rulebook](CLAUDE.md) before a larger change. It covers page layout, the API reference format, the -changelog format and the house writing style. It's written for Claude Code, and +changelog format, and the house writing style. It's written for Claude Code, and it's the same rulebook a human needs. ## Local Checks @@ -90,8 +89,8 @@ auto-discovered by a nav generator. | [Instances](instances/index.md) | Dedicated and full-stack servers, scaling, private networking | | [Deployments](deployments/index.md) | Atomic zero-downtime releases, hooks, instant rollbacks | | [Databases](databases/index.md) | Managed MySQL, backups, restores between environments | -| [REST API](api/index.md) | 32 endpoint pages for automating the dashboard | -| [How-to guides](how-to-guides/index.md) | Laravel, Craft CMS, Drupal and friends | +| [REST API](api/index.md) | Endpoint reference for automating the dashboard | +| [How-to guides](how-to-guides/index.md) | Laravel, Craft CMS, Drupal, and friends | | [Domains](domains/index.md), [Cron](cron/index.md), [Workers](workers/index.md) | The rest of the platform surface | ## Contributors diff --git a/api/cron/create-cron-entry.md b/api/cron/create-cron-entry.md index 438e020..e251599 100644 --- a/api/cron/create-cron-entry.md +++ b/api/cron/create-cron-entry.md @@ -26,22 +26,12 @@ Use `--data-urlencode` rather than `--data` for both `command` and `expression`. A script contains newlines and an expression contains spaces and `*`, and only the encoding form sends those intact. -`expression` accepts a five-field POSIX cron expression or one of the aliases -below. Not every expression the API accepts runs on a Linux instance: see +`expression` accepts a five-field POSIX cron expression or one of the +[schedule aliases](../../cron/schedules.md), such as `@minutely` or +`@daily`. Not every expression the API accepts runs on a Linux instance: see [custom cron expressions](../../cron/custom-expressions.md) for the extensions to avoid. -Alias | Expression | Runs ------------------- | ------------- | ------------------------- -`@minutely` | `* * * * *` | Every minute -`@every_5_minutes` | `*/5 * * * *` | Every five minutes -`@hourly` | `0 * * * *` | At the top of every hour -`@daily` | `0 0 * * *` | Every day at midnight -`@weekly` | `0 0 * * 0` | Every Sunday at midnight -`@monthly` | `0 0 1 * *` | On the first of every month -`@yearly` | `0 0 1 1 *` | On January 1 -`@annually` | `0 0 1 1 *` | On January 1 - The response reports the resolved five-field `expression` along with the matching `expression_alias`, so an alias you send comes out as both. diff --git a/api/cron/update-cron-entry.md b/api/cron/update-cron-entry.md index bd37819..5d973e4 100644 --- a/api/cron/update-cron-entry.md +++ b/api/cron/update-cron-entry.md @@ -26,21 +26,12 @@ Read the current values first with [Get a cron entry](get-cron-entry.md), so a partial update starts from what is actually on the instance. -`expression` accepts a five-field POSIX cron expression or one of the aliases -below. Not every expression the API accepts runs on a Linux instance: see -[custom cron expressions](../../cron/custom-expressions.md) for the extensions to -avoid. - -Alias | Expression | Runs ------------------- | ------------- | ------------------------- -`@minutely` | `* * * * *` | Every minute -`@every_5_minutes` | `*/5 * * * *` | Every five minutes -`@hourly` | `0 * * * *` | At the top of every hour -`@daily` | `0 0 * * *` | Every day at midnight -`@weekly` | `0 0 * * 0` | Every Sunday at midnight -`@monthly` | `0 0 1 * *` | On the first of every month -`@yearly` | `0 0 1 1 *` | On January 1 -`@annually` | `0 0 1 1 *` | On January 1 +`expression` accepts a five-field POSIX cron expression or one of the +[schedule aliases](../../cron/schedules.md), such as `@minutely` or +`@every_5_minutes`. Not every expression the API accepts runs on a Linux +instance: see +[custom cron expressions](../../cron/custom-expressions.md) for the extensions +to avoid. The response reports the resolved five-field `expression` along with the matching `expression_alias`, so an alias you send back comes out as both. diff --git a/api/deployments/cancel-deployment.md b/api/deployments/cancel-deployment.md index bc28dc4..d787eef 100644 --- a/api/deployments/cancel-deployment.md +++ b/api/deployments/cancel-deployment.md @@ -9,8 +9,8 @@ POST /v1/instances/{instance_id}/deployments/{deployment_id}/cancel Parameter | Type | In | Required | Description ------------- | ------ | --- | -------- | ---------------------------------- -instance_id | string | uri | No | The instance id of the environment -deployment_id | string | uri | No | The deployment ID +instance_id | string | uri | Yes | The instance id of the environment +deployment_id | string | uri | Yes | The deployment ID ## Response diff --git a/api/deployments/get.md b/api/deployments/get.md index 0cdab1e..d93d7ab 100644 --- a/api/deployments/get.md +++ b/api/deployments/get.md @@ -9,8 +9,8 @@ GET /v1/instances/{instance_id}/deployments/{deployment_id} Parameter | Type | In | Required | Description ------------- | ------ | --- | -------- | ---------------------------------- -instance_id | string | uri | No | The instance id of the environment -deployment_id | string | uri | No | The deployment ID +instance_id | string | uri | Yes | The instance id of the environment +deployment_id | string | uri | Yes | The deployment ID ## Response diff --git a/api/deployments/post.md b/api/deployments/post.md index 2e5eded..32b30af 100644 --- a/api/deployments/post.md +++ b/api/deployments/post.md @@ -31,7 +31,7 @@ Parameter | Type | In | Required | Description api_key | string | header | Yes | Your [API key](../authentication/index.md). environment | string | body | Yes | The [environment](../environments/index.md) name for this deployment. This can be `production` or `staging`. instance_id | string | uri | Yes | The ID of the instance that this deployment will be executed on. -archive | body | uri | Yes | The archive file that contains the source code of your application. This can be a zip or a tar archive. The maximum size of an archive file is 512MB. +archive | file | body | Yes | The archive file that contains the source code of your application. This can be a zip or a tar archive. The maximum size of an archive file is 512MB. repo_owner | string | body | Conditional | The repository owner. Required if `repo_name` is provided. repo_name | string | body | Conditional | The repository name. Required if `repo_owner` is provided. branch | string | body | No | The name of the branch diff --git a/api/environments/get-environment.md b/api/environments/get-environment.md index c667810..de0aa16 100644 --- a/api/environments/get-environment.md +++ b/api/environments/get-environment.md @@ -6,8 +6,8 @@ ## Parameters Parameter | Type | In | Required | Description ----------- | ------ | --- | -------- | ---------------------------------- -instance_id | string | uri | No | The instance id of the environment -name | string | uri | No | The name of the environment +instance_id | string | uri | Yes | The instance id of the environment +name | string | uri | Yes | The name of the environment ## Response diff --git a/api/environments/list-environments.md b/api/environments/list-environments.md index 8513c53..f860293 100644 --- a/api/environments/list-environments.md +++ b/api/environments/list-environments.md @@ -6,7 +6,7 @@ ## Parameters Parameter | Type | In | Required | Description ----------- | ------ | --- | -------- | --------------- -instance_id | string | uri | No | The instance id +instance_id | string | uri | Yes | The instance id ## Response diff --git a/api/index.md b/api/index.md index 8629d56..3b2b1e8 100644 --- a/api/index.md +++ b/api/index.md @@ -24,6 +24,11 @@ envelope, the error types worth branching on, and the codes behind them. ## Core Resources +### Accounts + +The user behind your API key. [Account endpoints](accounts/index.md) get the +current user. + ### Workers Start, stop, and restart [worker processes](workers/index.md) through the API. diff --git a/api/instances/terminate-instance.md b/api/instances/terminate-instance.md index c5eddad..64e5eb0 100644 --- a/api/instances/terminate-instance.md +++ b/api/instances/terminate-instance.md @@ -8,7 +8,7 @@ API requests for this instance will respond with 404 not found. ## Parameters Parameter | Type | In | Required | Description ----------- | ------ | --- | -------- | --------------- -instance_id | string | uri | No | The instance id +instance_id | string | uri | Yes | The instance id ## Code samples diff --git a/api/workers/get.md b/api/workers/get.md index b699be1..50ea596 100644 --- a/api/workers/get.md +++ b/api/workers/get.md @@ -6,8 +6,8 @@ ## Parameters Parameter | Type | In | Required | Description ----------- | ------ | --- | -------- | --------------- -instance_id | string | uri | No | The instance ID -worker_id | string | uri | No | The worker ID +instance_id | string | uri | Yes | The instance ID +worker_id | string | uri | Yes | The worker ID ## Response diff --git a/api/workers/restart.md b/api/workers/restart.md index ae917a3..fb9b803 100644 --- a/api/workers/restart.md +++ b/api/workers/restart.md @@ -7,8 +7,8 @@ ## Parameters Parameter | Type | In | Required | Description ----------- | ------ | --- | -------- | --------------- -instance_id | string | uri | No | The instance ID -worker_id | string | uri | No | The worker ID +instance_id | string | uri | Yes | The instance ID +worker_id | string | uri | Yes | The worker ID diff --git a/api/workers/start.md b/api/workers/start.md index 79c1055..6f6698e 100644 --- a/api/workers/start.md +++ b/api/workers/start.md @@ -6,8 +6,8 @@ ## Parameters Parameter | Type | In | Required | Description ----------- | ------ | --- | -------- | --------------- -instance_id | string | uri | No | The instance ID -worker_id | string | uri | No | The worker ID +instance_id | string | uri | Yes | The instance ID +worker_id | string | uri | Yes | The worker ID ## Response diff --git a/api/workers/stop.md b/api/workers/stop.md index cf2d815..3b6cca4 100644 --- a/api/workers/stop.md +++ b/api/workers/stop.md @@ -6,8 +6,8 @@ ## Parameters Parameter | Type | In | Required | Description ----------- | ------ | --- | -------- | --------------- -instance_id | string | uri | No | The instance ID -worker_id | string | uri | No | The worker ID +instance_id | string | uri | Yes | The instance ID +worker_id | string | uri | Yes | The worker ID diff --git a/cron/custom-expressions.md b/cron/custom-expressions.md index c6f9c23..a859995 100644 --- a/cron/custom-expressions.md +++ b/cron/custom-expressions.md @@ -1,7 +1,7 @@ # Custom cron expressions {.lead} -When none of the [schedule aliases](index.md#schedules) fits, write a five-field +When none of the [schedule aliases](schedules.md) fits, write a five-field cron expression instead. This page covers the syntax Amezmo accepts, the parts of it that look valid but never run, and a set of expressions you can copy. @@ -39,14 +39,14 @@ in a step, so write `1-5` rather than `MON-FRI/1`. : Every value of the field. `* * * * *` is every minute of every day. `,` -: A list. `0 8,12,18 * * *` runs at 08:00, 12:00 and 18:00. +: A list. `0 8,12,18 * * *` runs at 08:00, 12:00, and 18:00. `-` : A range. `0 9-17 * * *` runs hourly from 09:00 through 17:00, inclusive. `/` -: A step through a range. `*/15 * * * *` runs at minutes 0, 15, 30 and 45. -`0 9-17/2 * * *` runs at 09:00, 11:00, 13:00, 15:00 and 17:00. +: A step through a range. `*/15 * * * *` runs at minutes 0, 15, 30, and 45. +`0 9-17/2 * * *` runs at 09:00, 11:00, 13:00, 15:00, and 17:00. A step counts from the start of its range, not from the current time. `*/40` in the minute field fires at minute 0 and minute 40, then waits 20 minutes, because diff --git a/cron/index.md b/cron/index.md index 3c661b2..2be15cd 100644 --- a/cron/index.md +++ b/cron/index.md @@ -9,6 +9,12 @@ Crons are executed from your applications [current release](../deployments/direc ## In This Section +### Cron Schedules + +An alias such as `@daily` or a five-field expression decides when an entry +runs. [Cron schedules](schedules.md) lists every alias with the expression it +resolves to, in the dashboard and over the API. + ### Custom Cron Expressions When no alias fits, write a five-field expression instead. @@ -22,29 +28,6 @@ Change an entry's script or its schedule after you create it, from the dashboard or the API. [Editing a cron entry](editing-entries.md) covers what you can change, why the name is fixed, and when the change reaches your instance. -## Schedules -A cron schedule represents when the task will be executed. You may use an alias for the following cron expressions. - -`@minutely` -: Execute the task once every minute. - -`@hourly` -: Execute the task once every hour. - -`@monthly` -: Execute the task once every month. - -`@weekly` -: Execute the task once every week. - -`@yearly` -: Execute the task once every year. - -For advanced use cases, -Amezmo supports [POSIX cron syntax](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07). -See [custom cron expressions](custom-expressions.md) for the field order, the -operators and the extensions to avoid. - ## Scripts A cron *script* is a bash script where you define your task logic. In this script, you may call into other diff --git a/cron/schedules.md b/cron/schedules.md new file mode 100644 index 0000000..71a4f52 --- /dev/null +++ b/cron/schedules.md @@ -0,0 +1,38 @@ +# Cron Schedules + +{.lead} +A cron schedule says when a [cron entry](index.md) runs: an alias such as +`@daily`, or a five-field POSIX cron expression. + +## Aliases + +Use an alias in place of its cron expression: + +Alias | Expression | Runs +------------------ | ------------- | --------------------------- +`@minutely` | `* * * * *` | Every minute +`@every_5_minutes` | `*/5 * * * *` | Every five minutes +`@hourly` | `0 * * * *` | At the top of every hour +`@daily` | `0 0 * * *` | Every day at midnight +`@weekly` | `0 0 * * 0` | Every Sunday at midnight +`@monthly` | `0 0 1 * *` | On the first of every month +`@yearly` | `0 0 1 1 *` | On January 1 +`@annually` | `0 0 1 1 *` | On January 1 + +`@yearly` and `@annually` resolve to the same expression, so pick either. +The dashboard's schedule dropdown offers the same aliases. + +## Custom Expressions + +When no alias fits, +Amezmo supports [POSIX cron syntax](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07). +See [custom cron expressions](custom-expressions.md) for the field order, the +operators, and the extensions to avoid. + +## Schedules over the API + +[Create a cron entry](../api/cron/create-cron-entry.md) and +[update a cron entry](../api/cron/update-cron-entry.md) take a schedule in +their `expression` parameter, as an alias or a five-field expression. The +response reports the resolved five-field `expression` along with the matching +`expression_alias`, so an alias you send comes out as both.