Initial commit: Windmill workspace sync
Scripts, flows, apps, resources and resource types from the Windmill workspace. API token excluded via .gitignore (config/).
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
# Windmill CLI credentials (contains API token)
|
||||||
|
config/
|
||||||
|
|
||||||
|
# Claude Code internals
|
||||||
|
.claude/
|
||||||
|
.config/
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Windmill AI Agent Instructions
|
||||||
|
|
||||||
|
You are a helpful assistant that can help with Windmill scripts, flows, apps, and resources management.
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
- Every new entity MUST be created using the skills listed below.
|
||||||
|
- Every modification of an entity MUST be done using the skills listed below.
|
||||||
|
- User MUST be asked where to create the entity. It can be in its user folder, under u/{user_name} folder, or in a new folder, /f/{folder_name}/. folder_name and user_name must be provided by the user.
|
||||||
|
|
||||||
|
## Script Writing Guide
|
||||||
|
|
||||||
|
You MUST use the `write-script-<language>` skill to write or modify scripts in the language specified by the user. Use bun by default.
|
||||||
|
|
||||||
|
## Flow Writing Guide
|
||||||
|
|
||||||
|
You MUST use the `write-flow` skill to create or modify flows.
|
||||||
|
|
||||||
|
## Raw App Development
|
||||||
|
|
||||||
|
You MUST use the `raw-app` skill to create or modify raw apps.
|
||||||
|
Whenever a new app needs to be created you MUST ask the user to run `wmill app new` in its terminal first.
|
||||||
|
|
||||||
|
## Triggers
|
||||||
|
|
||||||
|
You MUST use the `triggers` skill to configure HTTP routes, WebSocket, Kafka, NATS, SQS, MQTT, GCP, or Postgres CDC triggers.
|
||||||
|
|
||||||
|
## Schedules
|
||||||
|
|
||||||
|
You MUST use the `schedules` skill to configure cron schedules.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
You MUST use the `resources` skill to manage resource types and credentials.
|
||||||
|
|
||||||
|
## CLI Reference
|
||||||
|
|
||||||
|
You MUST use the `cli-commands` skill to use the CLI.
|
||||||
|
|
||||||
|
## Skills
|
||||||
|
|
||||||
|
For specific guidance, ALWAYS use the skills listed below.
|
||||||
|
|
||||||
|
- `.claude/skills/write-script-bash/SKILL.md` - MUST use when writing Bash scripts.
|
||||||
|
- `.claude/skills/write-script-bigquery/SKILL.md` - MUST use when writing BigQuery queries.
|
||||||
|
- `.claude/skills/write-script-bun/SKILL.md` - MUST use when writing Bun/TypeScript scripts.
|
||||||
|
- `.claude/skills/write-script-bunnative/SKILL.md` - MUST use when writing Bun Native scripts.
|
||||||
|
- `.claude/skills/write-script-csharp/SKILL.md` - MUST use when writing C# scripts.
|
||||||
|
- `.claude/skills/write-script-deno/SKILL.md` - MUST use when writing Deno/TypeScript scripts.
|
||||||
|
- `.claude/skills/write-script-duckdb/SKILL.md` - MUST use when writing DuckDB queries.
|
||||||
|
- `.claude/skills/write-script-go/SKILL.md` - MUST use when writing Go scripts.
|
||||||
|
- `.claude/skills/write-script-graphql/SKILL.md` - MUST use when writing GraphQL queries.
|
||||||
|
- `.claude/skills/write-script-java/SKILL.md` - MUST use when writing Java scripts.
|
||||||
|
- `.claude/skills/write-script-mssql/SKILL.md` - MUST use when writing MS SQL Server queries.
|
||||||
|
- `.claude/skills/write-script-mysql/SKILL.md` - MUST use when writing MySQL queries.
|
||||||
|
- `.claude/skills/write-script-nativets/SKILL.md` - MUST use when writing Native TypeScript scripts.
|
||||||
|
- `.claude/skills/write-script-php/SKILL.md` - MUST use when writing PHP scripts.
|
||||||
|
- `.claude/skills/write-script-postgresql/SKILL.md` - MUST use when writing PostgreSQL queries.
|
||||||
|
- `.claude/skills/write-script-powershell/SKILL.md` - MUST use when writing PowerShell scripts.
|
||||||
|
- `.claude/skills/write-script-python3/SKILL.md` - MUST use when writing Python scripts.
|
||||||
|
- `.claude/skills/write-script-rlang/SKILL.md` - MUST use when writing R scripts.
|
||||||
|
- `.claude/skills/write-script-rust/SKILL.md` - MUST use when writing Rust scripts.
|
||||||
|
- `.claude/skills/write-script-snowflake/SKILL.md` - MUST use when writing Snowflake queries.
|
||||||
|
- `.claude/skills/write-flow/SKILL.md` - MUST use when creating flows.
|
||||||
|
- `.claude/skills/raw-app/SKILL.md` - MUST use when creating raw apps.
|
||||||
|
- `.claude/skills/triggers/SKILL.md` - MUST use when configuring triggers.
|
||||||
|
- `.claude/skills/schedules/SKILL.md` - MUST use when configuring schedules.
|
||||||
|
- `.claude/skills/resources/SKILL.md` - MUST use when managing resources.
|
||||||
|
- `.claude/skills/cli-commands/SKILL.md` - MUST use when using the CLI.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKye:
|
||||||
|
type: string
|
||||||
|
description: API key generated against an app on the dashboard.
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
description: >-
|
||||||
|
Abstract API has a bunch of standalone API products, each with their own
|
||||||
|
endpoints and API keys.
|
||||||
|
|
||||||
|
Head to desired API page, and copy the `Primary Key`.
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: Key obtained from the dashboard page for each API product
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
clientId:
|
||||||
|
type: string
|
||||||
|
description: The client ID of your Accelo application.
|
||||||
|
clientSecret:
|
||||||
|
type: string
|
||||||
|
description: The client secret of your Accelo application.
|
||||||
|
deployment:
|
||||||
|
type: string
|
||||||
|
description: The deployment subdomain of your Accelo instance. For example,
|
||||||
|
'mydeployement' for 'https://mydeployement.accelo.com'.
|
||||||
|
required:
|
||||||
|
- deployment
|
||||||
|
- clientId
|
||||||
|
- clientSecret
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
description: >-
|
||||||
|
1. Visit https://acumbamail.com/en/apidoc/ upon successfully logging into the
|
||||||
|
Acumbamail account
|
||||||
|
|
||||||
|
2. Copy the `auth_token` under `customer identifier`
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
authToken:
|
||||||
|
type: string
|
||||||
|
description: Auth token found on the developer documentation of Acumbamail
|
||||||
|
required:
|
||||||
|
- authToken
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiToken:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- apiToken
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API key retrieved from the dashboard.
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: The airtable OAuth token
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: keyXXXXXXXXXXXXXX
|
||||||
|
required: []
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
baseId:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
tableName:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
required: []
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
description: An Ansible inventory
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- content
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
description: Text contents of the file
|
||||||
|
required: []
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- apiKey
|
||||||
|
- base_url
|
||||||
|
- platform
|
||||||
|
- enable_1M_context
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
base_url:
|
||||||
|
type: string
|
||||||
|
description: 'Optional. Only needed to overwrite the default base URL. For
|
||||||
|
google_vertex_ai:
|
||||||
|
https://{region}-aiplatform.googleapis.com/v1/projects/{project_id}/locations/{region}/publishers/anthropic/models/'
|
||||||
|
default: ''
|
||||||
|
enable_1M_context:
|
||||||
|
type: boolean
|
||||||
|
description: Enable beta header for 1M context on eligible models.
|
||||||
|
default: false
|
||||||
|
platform:
|
||||||
|
type: string
|
||||||
|
description: Platform to use for Anthropic API.
|
||||||
|
default: standard
|
||||||
|
enum:
|
||||||
|
- standard
|
||||||
|
- google_vertex_ai
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- api_key_header
|
||||||
|
- api_key_secret
|
||||||
|
properties:
|
||||||
|
api_key_header:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
api_key_secret:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
required:
|
||||||
|
- api_key_header
|
||||||
|
- api_key_secret
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
description: Credentials for the Apify API using an OAuth token.
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- token
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: Apify OAuth token. Available only on Windmill Cloud.
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
password: true
|
||||||
|
title: Apify OAuth Token
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
description: Credentials for the Apify API using a personal API Key.
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- api_key
|
||||||
|
properties:
|
||||||
|
api_key:
|
||||||
|
type: string
|
||||||
|
description: Your personal API token from your Apify account settings.
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
password: true
|
||||||
|
title: Apify API Key
|
||||||
|
required:
|
||||||
|
- api_key
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
description: Webhook config needed to create an Apify webhook with URL and token
|
||||||
|
from your Windmill flow.
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- url
|
||||||
|
- token
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: Webhook-specific token from your Windmill flow.
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
password: true
|
||||||
|
title: Token
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
description: Webhook URL from your Windmill flow.
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
title: URL
|
||||||
|
required:
|
||||||
|
- url
|
||||||
|
- token
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
description: |-
|
||||||
|
1. Visit https://developer.apollo.io/ to generate an API key.
|
||||||
|
2. Copy the API key
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API Key generated inside the settings for Apollo.
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
description: credentials to connect to an [appwrite](https://appwrite.io/docs) project
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
endpoint:
|
||||||
|
type: string
|
||||||
|
description: url of your appwrite server
|
||||||
|
default: https://
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
description: API key of your appwrite project
|
||||||
|
default: ''
|
||||||
|
project:
|
||||||
|
type: string
|
||||||
|
description: ID of your appwrite project
|
||||||
|
default: ''
|
||||||
|
self_signed:
|
||||||
|
type: boolean
|
||||||
|
description: use self signed certificates on server (only for development)
|
||||||
|
default: ''
|
||||||
|
required:
|
||||||
|
- endpoint
|
||||||
|
- project
|
||||||
|
- key
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
description: Username and password of an ArcGIS Account to be able to access
|
||||||
|
Feature Services.
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- username
|
||||||
|
- password
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
password: true
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
required: []
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API key retrieved from the dashboard.
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: Access token generated from the dashboard or the OAuth flow.
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
description: AWS credentials
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
awsAccessKeyId:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
awsSecretAccessKey:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
region:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
required:
|
||||||
|
- awsAccessKeyId
|
||||||
|
- awsSecretAccessKey
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
description: AWS Bedrock resource type
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- region
|
||||||
|
- apiKey
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: AWS bedrock API key (simplest, no need for awsAccessKeyId and
|
||||||
|
awsSecretAccessKey)
|
||||||
|
default: ''
|
||||||
|
nullable: true
|
||||||
|
password: true
|
||||||
|
awsAccessKeyId:
|
||||||
|
type: string
|
||||||
|
description: AWS IAM access key ID (alternative to apiKey)
|
||||||
|
default: ''
|
||||||
|
nullable: true
|
||||||
|
password: true
|
||||||
|
awsSecretAccessKey:
|
||||||
|
type: string
|
||||||
|
description: AWS IAM secret access key (alternative to apiKey)
|
||||||
|
default: ''
|
||||||
|
nullable: true
|
||||||
|
password: true
|
||||||
|
awsSessionToken:
|
||||||
|
type: string
|
||||||
|
description: AWS IAM session token (alternative to apiKey). You will need to
|
||||||
|
refresh this token periodically.
|
||||||
|
default: ''
|
||||||
|
nullable: true
|
||||||
|
password: true
|
||||||
|
region:
|
||||||
|
type: string
|
||||||
|
description: AWS Bedrock region (e.g., us-east-1)
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
required:
|
||||||
|
- region
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- roleArn
|
||||||
|
- region
|
||||||
|
properties:
|
||||||
|
region:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
roleArn:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
required:
|
||||||
|
- roleArn
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
description: Azure Service Principal Credentials
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
azureClientId:
|
||||||
|
type: string
|
||||||
|
description: Client ID of the Azure AD app registration
|
||||||
|
default: ''
|
||||||
|
azureClientSecret:
|
||||||
|
type: string
|
||||||
|
description: Client Secret of the Azure AD app registration
|
||||||
|
default: ''
|
||||||
|
azureTenantId:
|
||||||
|
type: string
|
||||||
|
description: Azure AD tenant ID
|
||||||
|
default: ''
|
||||||
|
required:
|
||||||
|
- azureTenantId
|
||||||
|
- azureClientId
|
||||||
|
- azureClientSecret
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
description: An Azure Blob Storage connection info
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- accountName
|
||||||
|
- containerName
|
||||||
|
- accessKey
|
||||||
|
- useSSL
|
||||||
|
- endpoint
|
||||||
|
properties:
|
||||||
|
accessKey:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
accountName:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
containerName:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
endpoint:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
useSSL:
|
||||||
|
type: boolean
|
||||||
|
description: ''
|
||||||
|
default: true
|
||||||
|
required:
|
||||||
|
- accountName
|
||||||
|
- containerName
|
||||||
|
- useSSL
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
description: Azure OAuth token
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- token
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: AAD token for authentication
|
||||||
|
default: ''
|
||||||
|
password: true
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- apiKey
|
||||||
|
- baseUrl
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
password: true
|
||||||
|
baseUrl:
|
||||||
|
type: string
|
||||||
|
description: https://<YOUR_RESOURCE_NAME>.openai.azure.com/openai
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
|
- baseUrl
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
description: |
|
||||||
|
Azure Blob Storage configuration which works with Azure Workload Identity.
|
||||||
|
|
||||||
|
Assume the servers and workers are passed the following env variable:
|
||||||
|
AZURE_FEDERATED_TOKEN_FILE
|
||||||
|
AZURE_CLIENT_ID
|
||||||
|
AZURE_TENANT_ID
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- accountName
|
||||||
|
- containerName
|
||||||
|
- useSSL
|
||||||
|
properties:
|
||||||
|
accountName:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
containerName:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
useSSL:
|
||||||
|
type: boolean
|
||||||
|
description: ''
|
||||||
|
default: true
|
||||||
|
required:
|
||||||
|
- accountName
|
||||||
|
- containerName
|
||||||
|
- useSSL
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft-07/schema#
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API key
|
||||||
|
companyDomain:
|
||||||
|
type: string
|
||||||
|
description: Company domain
|
||||||
|
required:
|
||||||
|
- companyDomain
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
description: |-
|
||||||
|
1. Once on the dashboard, open up `Settings`, and then `API`
|
||||||
|
2. Copy `Live API Key`
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: Live API key obtained from the Baremetrics dashboard
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
description: Baserow is an open-source no-code database and an alternative to Airtable.
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- base_url
|
||||||
|
- token
|
||||||
|
properties:
|
||||||
|
base_url:
|
||||||
|
type: string
|
||||||
|
description: By default, the URL points to the official Baserow site, though it
|
||||||
|
can be modified for self-hosted setups.
|
||||||
|
default: https://baserow.io
|
||||||
|
nullable: false
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: The Database token
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
password: true
|
||||||
|
required:
|
||||||
|
- base_url
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
description: Specifies a particular Baserow table, to be used in conjunction
|
||||||
|
with the 'baserow' resource that contains the access token and URL.
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- database_id
|
||||||
|
- table_id
|
||||||
|
properties:
|
||||||
|
database_id:
|
||||||
|
type: integer
|
||||||
|
description: The numeric database id
|
||||||
|
table_id:
|
||||||
|
type: integer
|
||||||
|
description: The numeric table id
|
||||||
|
required: []
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- username
|
||||||
|
- password
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
required:
|
||||||
|
- username
|
||||||
|
- password
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API key retrieved upon creating an application.
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
auth_provider_x509_cert_url:
|
||||||
|
type: string
|
||||||
|
format: url
|
||||||
|
auth_uri:
|
||||||
|
type: string
|
||||||
|
format: url
|
||||||
|
client_email:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
client_id:
|
||||||
|
type: string
|
||||||
|
client_x509_cert_url:
|
||||||
|
type: string
|
||||||
|
format: url
|
||||||
|
private_key:
|
||||||
|
type: string
|
||||||
|
private_key_id:
|
||||||
|
type: string
|
||||||
|
project_id:
|
||||||
|
type: string
|
||||||
|
token_uri:
|
||||||
|
type: string
|
||||||
|
format: url
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
- auth_uri
|
||||||
|
- client_id
|
||||||
|
- token_uri
|
||||||
|
- project_id
|
||||||
|
- private_key
|
||||||
|
- client_email
|
||||||
|
- private_key_id
|
||||||
|
- client_x509_cert_url
|
||||||
|
- auth_provider_x509_cert_url
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
required:
|
||||||
|
- username
|
||||||
|
- password
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: 'Generic access token or OAuth access token:
|
||||||
|
https://dev.bitly.com/docs/getting-started/authentication/'
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
description: Bluesky credentials
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- username
|
||||||
|
- password
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: Create an App password in Settings -> Privacy and Security -> App
|
||||||
|
passwords
|
||||||
|
default: ''
|
||||||
|
password: true
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: User handle without the @
|
||||||
|
default: ''
|
||||||
|
placeholder: johnsmith.bsky.social
|
||||||
|
title: ''
|
||||||
|
required: []
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: Access token generated using the Client ID and the Client Secret.
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API key generated from the dashboard.
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: API token retrieved from the settings page.
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: API token generated from the dashboard.
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- host
|
||||||
|
- username
|
||||||
|
- password
|
||||||
|
- transport
|
||||||
|
- use_https
|
||||||
|
- server_cert_validation
|
||||||
|
properties:
|
||||||
|
host:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
server_cert_validation:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
transport:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
use_https:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
required: []
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
certificate:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
required: []
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
description: |-
|
||||||
|
1. On the dashboard, open `Settings` and then navigate to `Account`
|
||||||
|
2. Press `Show your campayn API key` to reveal the API key and copy it
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API Key retrieved from the settings page for Campayn.
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
description: >-
|
||||||
|
1. After completing the onboarding, click the puzzle icon on the left
|
||||||
|
navigation bar to switch to the integrations tab.
|
||||||
|
|
||||||
|
2. Copy the `REST API KEY` under `Canva`.
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API key obtained from the Certopus dashboard
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
description: Open-source vector database to store, manage, and query vector embeddings.
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- host
|
||||||
|
- port
|
||||||
|
- ssl
|
||||||
|
- tenant
|
||||||
|
- database
|
||||||
|
properties:
|
||||||
|
database:
|
||||||
|
type: string
|
||||||
|
description: "Optional: The database to use. Default is 'default_database'."
|
||||||
|
default: null
|
||||||
|
nullable: true
|
||||||
|
host:
|
||||||
|
type: string
|
||||||
|
description: The host of the remote server. Default is localhost.
|
||||||
|
default: localhost
|
||||||
|
nullable: false
|
||||||
|
port:
|
||||||
|
type: integer
|
||||||
|
description: The port of the remote server. Default is 8000.
|
||||||
|
ssl:
|
||||||
|
type: boolean
|
||||||
|
description: If True, the client will use HTTPS. If not specified, the default
|
||||||
|
is False.
|
||||||
|
default: false
|
||||||
|
tenant:
|
||||||
|
type: string
|
||||||
|
description: "Optional: The tenant to use (logical grouping for a set of
|
||||||
|
databases). Default is 'default_tenant'."
|
||||||
|
default: null
|
||||||
|
nullable: true
|
||||||
|
required:
|
||||||
|
- host
|
||||||
|
- port
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: Secret Key found in configure tab of the instance.
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
host:
|
||||||
|
type: string
|
||||||
|
description: Database host
|
||||||
|
default: ''
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: Database password or API key secret (Cloud API)
|
||||||
|
default: ''
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: Database username or API key id (Cloud API)
|
||||||
|
default: ''
|
||||||
|
required: []
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: API token retrieved from the dashboard.
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
- token
|
||||||
|
- email
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: Access token retrieved from the dashboard.
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
encodedKey:
|
||||||
|
type: string
|
||||||
|
description: Encoded API key retrieved from the dashboard.
|
||||||
|
required:
|
||||||
|
- encodedKey
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API key generated from Cohere dashboard.
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
description: A server URL and access token pair to connect to a CoMapeo archive server.
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- server_url
|
||||||
|
- access_token
|
||||||
|
properties:
|
||||||
|
access_token:
|
||||||
|
type: string
|
||||||
|
description: Your access token for the CoMapeo archive server
|
||||||
|
default: ''
|
||||||
|
server_url:
|
||||||
|
type: string
|
||||||
|
description: The base URL of your CoMapeo archive server
|
||||||
|
default: ''
|
||||||
|
required:
|
||||||
|
- server_url
|
||||||
|
- access_token
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiToken:
|
||||||
|
type: string
|
||||||
|
description: Token generated from Atlassian dashboard.
|
||||||
|
baseUrl:
|
||||||
|
type: string
|
||||||
|
description: Base URL of confluence account.
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
description: Email used to register the Confluence account.
|
||||||
|
required:
|
||||||
|
- email
|
||||||
|
- apiToken
|
||||||
|
- baseUrl
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
description: |-
|
||||||
|
1. Open up the `Settings` dropdown and select `General Settings`
|
||||||
|
2. Copy the `Space ID`
|
||||||
|
3. Open up the `Settings` dropdown again and select `CMA tokens`
|
||||||
|
4. Create a new personal access token, copy the access token
|
||||||
|
5. Open up the `Settings` dropdown one last time and select `Environments`
|
||||||
|
6. Copy any `Environment ID`. The value for this will most likely be `master`
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
accessToken:
|
||||||
|
type: string
|
||||||
|
description: Access token for Content Management API (CMA).
|
||||||
|
environment:
|
||||||
|
type: string
|
||||||
|
description: Environment (e.g. master).
|
||||||
|
spaceId:
|
||||||
|
type: string
|
||||||
|
description: Unique identifier of the space.
|
||||||
|
required:
|
||||||
|
- accessToken
|
||||||
|
- environment
|
||||||
|
- spaceId
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
description: >-
|
||||||
|
1. On the dashboard, click on `generate & use revokable tokens` under the
|
||||||
|
`authenticating with revokable tokens` section
|
||||||
|
|
||||||
|
2. Provide a name for the token and create it, then copy it
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: Revokable token generated from the dashboard
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
description: >-
|
||||||
|
1. Open up `Settings` by clicking on the profile picture icon at the top-right
|
||||||
|
corner
|
||||||
|
|
||||||
|
2. Select `Advanced` from the left navigation bar.
|
||||||
|
|
||||||
|
3. View the `API Secret` and copy it
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiSecret:
|
||||||
|
type: string
|
||||||
|
description: The API Secret associated to the converkit account
|
||||||
|
required:
|
||||||
|
- apiSecret
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
description: Api key for currencyapi.com
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: the apikey for the currencyapi.com
|
||||||
|
default: ''
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
description: Any OpenAI API compatible provider
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- api_key
|
||||||
|
- base_url
|
||||||
|
properties:
|
||||||
|
api_key:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
password: true
|
||||||
|
base_url:
|
||||||
|
type: string
|
||||||
|
description: e.g. https://my.custom.provider/v1
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
placeholder: ''
|
||||||
|
required:
|
||||||
|
- base_url
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiBase:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
appKey:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
description: |-
|
||||||
|
1. On a project page, select "project settings" from the top navigation bar.
|
||||||
|
2. Switch to "api tokens" tab and copy the `full-access API token`.
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API key obtained from the DatoCMS dashboard
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft-07/schema#
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API key
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
description: Some of the APIs are accessible using the API key on the dashboard,
|
||||||
|
others require the JWT token copied from the network tab.
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API key generated on the dashboard for DeepL API product.
|
||||||
|
baseUrl:
|
||||||
|
type: string
|
||||||
|
description: Base URL for your API plan - free and pro have different API base URLs.
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
|
- baseUrl
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order:
|
||||||
|
- api_key
|
||||||
|
- base_url
|
||||||
|
properties:
|
||||||
|
api_key:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
nullable: false
|
||||||
|
password: true
|
||||||
|
base_url:
|
||||||
|
type: string
|
||||||
|
description: Optional. Only needed to overwrite the default base URL.
|
||||||
|
default: ''
|
||||||
|
required:
|
||||||
|
- api_key
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
description: 'See: https://www.windmill.dev/blog/knowledge-base-discord-bot'
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
application_id:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
public_key:
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
default: ''
|
||||||
|
required: []
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
description: >-
|
||||||
|
Discord webhook url to send messages.
|
||||||
|
|
||||||
|
|
||||||
|
See:
|
||||||
|
<https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks>
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
webhook_url:
|
||||||
|
type: string
|
||||||
|
description: The webhook url
|
||||||
|
required: []
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API key retrieved from Discourse.
|
||||||
|
apiUsername:
|
||||||
|
type: string
|
||||||
|
description: API username retrieved from Discourse.
|
||||||
|
defaultHost:
|
||||||
|
type: string
|
||||||
|
description: Base URL of the Discourse instance.
|
||||||
|
required:
|
||||||
|
- '{property1}'
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
tokenId:
|
||||||
|
type: string
|
||||||
|
tokenSecret:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- tokenId
|
||||||
|
- tokenSecret
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
description: >-
|
||||||
|
1. On the workspace console, switch to the `Build` tab, open `Developer Tools`
|
||||||
|
-> `API Keys`, and create a new API key
|
||||||
|
|
||||||
|
2. Next, open up `Settings` -> `Workspace`. The URL of this page will look
|
||||||
|
something like the following: `https://dust.tt/w/{workspace_id}/workspace`.
|
||||||
|
Copy the `workspace_id` part
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API Key generated from the dashboard
|
||||||
|
workspaceId:
|
||||||
|
type: string
|
||||||
|
description: Workspace id in the URL bar after w/
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
|
- workspaceId
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
description: Credentials for connecting to a Dynatrace tenant
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
accessToken:
|
||||||
|
type: string
|
||||||
|
description: 'Documentation:
|
||||||
|
https://www.dynatrace.com/support/help/manage/access-control/access-tokens'
|
||||||
|
default: ''
|
||||||
|
environmentId:
|
||||||
|
type: string
|
||||||
|
description: "In Dynatrace SaaS, your environment ID is the first part of your
|
||||||
|
Dynatrace environment's URL: {environmentId}.live.dynatrace.com"
|
||||||
|
default: ''
|
||||||
|
environmentUrl:
|
||||||
|
type: string
|
||||||
|
description: URL of your environment. Default is live.dynatrace.com
|
||||||
|
default: live.dynatrace.com
|
||||||
|
required:
|
||||||
|
- environmentId
|
||||||
|
- accessToken
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
description: Connection details for an EdgeDB database, either hosted on EdgeDB
|
||||||
|
Cloud or self-hosted
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
database:
|
||||||
|
type: string
|
||||||
|
description: Name of the database to connect to. (Required unless a DSN or an
|
||||||
|
EdgeDB Cloud instance are provided)
|
||||||
|
default: ''
|
||||||
|
dsn:
|
||||||
|
type: string
|
||||||
|
description: Overrides any other settings except password. (Required unless
|
||||||
|
host, port, user, and database, or an EdgeDB Cloud instance name are
|
||||||
|
provided)
|
||||||
|
default: ''
|
||||||
|
host:
|
||||||
|
type: string
|
||||||
|
description: Hostname of the EdgeDB instance. (Required unless a DSN or an
|
||||||
|
EdgeDB Cloud instance are provided)
|
||||||
|
default: ''
|
||||||
|
format: hostname
|
||||||
|
instanceName:
|
||||||
|
type: string
|
||||||
|
description: Only for use with EdgeDB Cloud instances. Format is
|
||||||
|
<github-username>/<instance-name> (Required for EdgeDB Cloud instances
|
||||||
|
unless a DSN is provided)
|
||||||
|
default: ''
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: (Required unless a secret key for an EdgeDB Cloud instance is provided)
|
||||||
|
default: ''
|
||||||
|
port:
|
||||||
|
type: number
|
||||||
|
description: (Required unless a DSN or an EdgeDB Cloud instance are provided)
|
||||||
|
secretKey:
|
||||||
|
type: string
|
||||||
|
description: Specifies the secret key to use for authentication with EdgeDB
|
||||||
|
Cloud instances. (Required for EdgeDB Cloud instance)
|
||||||
|
default: ''
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
|
description: Username to use for connecting to the database. (Required unless a
|
||||||
|
DSN or an EdgeDB Cloud instance are provided)
|
||||||
|
default: ''
|
||||||
|
required: []
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- token
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
description: ''
|
||||||
|
format_extension: null
|
||||||
|
is_fileset: false
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
apiKey:
|
||||||
|
type: string
|
||||||
|
description: API key retrieved from the dashboard.
|
||||||
|
required:
|
||||||
|
- apiKey
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
SELECT datastore FROM Kunden.`bronze.backup.server.datastore` AS kbbsd WHERE kbbsd.restore = 1
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# py: 3.12
|
||||||
|
anyio==4.12.1
|
||||||
|
bcrypt==5.0.0
|
||||||
|
certifi==2026.2.25
|
||||||
|
cffi==2.0.0
|
||||||
|
cryptography==46.0.5
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httpx==0.28.1
|
||||||
|
idna==3.11
|
||||||
|
invoke==2.2.1
|
||||||
|
mysql-connector-python==9.6.0
|
||||||
|
paramiko==4.0.0
|
||||||
|
pycparser==3.0
|
||||||
|
pynacl==1.6.2
|
||||||
|
typing-extensions==4.15.0
|
||||||
|
wmill==1.657.2
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import wmill, mysql.connector, json
|
||||||
|
|
||||||
|
def main(prev: dict):
|
||||||
|
if prev.get("mode") == "webhook":
|
||||||
|
return prev
|
||||||
|
|
||||||
|
db_cfg = json.loads(wmill.get_variable("f/Backup/mysql_config"))
|
||||||
|
conn = mysql.connector.connect(**db_cfg)
|
||||||
|
cur = conn.cursor(dictionary=True)
|
||||||
|
|
||||||
|
# FIX: max_backup_size_gb hinzugefügt
|
||||||
|
cur.execute("""
|
||||||
|
SELECT hostname, ip, free_space_gb,
|
||||||
|
script_deployed, script_version,
|
||||||
|
restore_mount, restore_path,
|
||||||
|
max_backup_size_gb,
|
||||||
|
min_backup_size_gb
|
||||||
|
FROM Kunden.`bronze.restore.server`
|
||||||
|
WHERE is_active = 1 AND current_job_uuid IS NULL
|
||||||
|
ORDER BY free_space_gb DESC
|
||||||
|
""")
|
||||||
|
servers = cur.fetchall()
|
||||||
|
cur.close(); conn.close()
|
||||||
|
|
||||||
|
if not servers:
|
||||||
|
raise Exception("Kein freier Restore-Server verfuegbar!")
|
||||||
|
|
||||||
|
for s in servers:
|
||||||
|
if not s.get("restore_mount"):
|
||||||
|
raise Exception(
|
||||||
|
f"restore_mount fuer '{s['hostname']}' nicht konfiguriert!"
|
||||||
|
)
|
||||||
|
if not s.get("restore_path"):
|
||||||
|
raise Exception(
|
||||||
|
f"restore_path fuer '{s['hostname']}' nicht konfiguriert!"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"{len(servers)} freie Restore-Server: "
|
||||||
|
f"{[s['hostname'] for s in servers]}")
|
||||||
|
return {**prev, "target_servers": servers}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
# py: 3.12
|
||||||
|
anyio==4.13.0
|
||||||
|
bcrypt==5.0.0
|
||||||
|
certifi==2026.2.25
|
||||||
|
cffi==2.0.0
|
||||||
|
cryptography==46.0.6
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httpx==0.28.1
|
||||||
|
idna==3.11
|
||||||
|
invoke==2.2.1
|
||||||
|
mysql-connector-python==9.6.0
|
||||||
|
paramiko==4.0.0
|
||||||
|
pycparser==3.0
|
||||||
|
pynacl==1.6.2
|
||||||
|
typing-extensions==4.15.0
|
||||||
|
wmill==1.666.0
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
import wmill, json, paramiko, mysql.connector, re, io
|
||||||
|
|
||||||
|
|
||||||
|
def main(prev: dict):
|
||||||
|
if prev.get("mode") == "webhook":
|
||||||
|
return prev
|
||||||
|
|
||||||
|
db_cfg = json.loads(wmill.get_variable("f/Backup/mysql_config"))
|
||||||
|
conn = mysql.connector.connect(**db_cfg)
|
||||||
|
cur = conn.cursor(dictionary=True)
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT datastore, rsync_target, retention_days
|
||||||
|
FROM Kunden.`bronze.backup.datastore.config`
|
||||||
|
WHERE rsync_target IS NOT NULL AND rsync_target != ''
|
||||||
|
""")
|
||||||
|
configs = cur.fetchall()
|
||||||
|
cur.close(); conn.close()
|
||||||
|
|
||||||
|
if not configs:
|
||||||
|
print("Keine Datastore-Configs - Cleanup uebersprungen.")
|
||||||
|
return prev
|
||||||
|
|
||||||
|
backup_server = wmill.get_variable("f/Backup/backup_server_host")
|
||||||
|
ip_match = re.search(r'https?://([0-9.]+)', backup_server)
|
||||||
|
ip = ip_match.group(1) if ip_match else backup_server
|
||||||
|
bw_pass = wmill.get_variable("f/Backup/backup_server_ssh_password")
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect(ip, username="root", password=bw_pass)
|
||||||
|
|
||||||
|
# Cleanup-Script aufbauen
|
||||||
|
# Datum-basierter Vergleich statt -mtime
|
||||||
|
script_lines = ["#!/bin/bash", "set -euo pipefail", ""]
|
||||||
|
|
||||||
|
for cfg in configs:
|
||||||
|
datastore = cfg["datastore"]
|
||||||
|
target = cfg["rsync_target"]
|
||||||
|
retention_days = cfg.get("retention_days") or 7
|
||||||
|
|
||||||
|
script_lines.append(
|
||||||
|
f"echo '[{datastore}] Cleanup {target} (Ordner aelter als {retention_days} Tage)'"
|
||||||
|
)
|
||||||
|
script_lines.append(f"if [ -d '{target}' ]; then")
|
||||||
|
script_lines.append(f" cutoff=$(date -d '{retention_days} days ago' +%Y-%m-%d)")
|
||||||
|
script_lines.append(f" find '{target}' -maxdepth 1 -type d -name '????-??-??' | while read dir; do")
|
||||||
|
script_lines.append(f" folder_date=$(basename \"$dir\")")
|
||||||
|
script_lines.append(f" if [[ \"$folder_date\" < \"$cutoff\" ]]; then")
|
||||||
|
script_lines.append(f" echo \" Loesche: $dir (Datum: $folder_date < Cutoff: $cutoff)\"")
|
||||||
|
script_lines.append(f" rm -rf \"$dir\"")
|
||||||
|
script_lines.append(f" else")
|
||||||
|
script_lines.append(f" echo \" Behalte: $dir\"")
|
||||||
|
script_lines.append(f" fi")
|
||||||
|
script_lines.append(f" done")
|
||||||
|
script_lines.append(f"else")
|
||||||
|
script_lines.append(f" echo ' WARNUNG: Verzeichnis nicht gefunden: {target}'")
|
||||||
|
script_lines.append(f"fi")
|
||||||
|
script_lines.append("")
|
||||||
|
|
||||||
|
script_lines.append("echo 'Cleanup abgeschlossen.'")
|
||||||
|
cleanup_script = "\n".join(script_lines)
|
||||||
|
|
||||||
|
# Script per SFTP hochladen statt Heredoc
|
||||||
|
sftp = ssh.open_sftp()
|
||||||
|
sftp.putfo(
|
||||||
|
io.BytesIO(cleanup_script.encode()),
|
||||||
|
"/tmp/cleanup_restore.sh"
|
||||||
|
)
|
||||||
|
sftp.close()
|
||||||
|
|
||||||
|
ssh.exec_command("chmod +x /tmp/cleanup_restore.sh")
|
||||||
|
|
||||||
|
import time; time.sleep(1)
|
||||||
|
|
||||||
|
ssh.exec_command(
|
||||||
|
"nohup /tmp/cleanup_restore.sh > /tmp/cleanup_restore.log 2>&1 &"
|
||||||
|
)
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
print(f"Cleanup auf {ip} im Hintergrund gestartet.")
|
||||||
|
print(f"Log: /tmp/cleanup_restore.log")
|
||||||
|
return {**prev, "cleanup": "started_background"}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# py: 3.12
|
||||||
|
anyio==4.13.0
|
||||||
|
bcrypt==5.0.0
|
||||||
|
certifi==2026.2.25
|
||||||
|
cffi==2.0.0
|
||||||
|
cryptography==46.0.5
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httpx==0.28.1
|
||||||
|
idna==3.11
|
||||||
|
invoke==2.2.1
|
||||||
|
mysql-connector-python==9.6.0
|
||||||
|
paramiko==4.0.0
|
||||||
|
pycparser==3.0
|
||||||
|
pynacl==1.6.2
|
||||||
|
typing-extensions==4.15.0
|
||||||
|
wmill==1.664.0
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
import wmill, json, paramiko, mysql.connector
|
||||||
|
|
||||||
|
|
||||||
|
def start_restore(server, backup, job_uuid, webhook_url, webhook_tok):
|
||||||
|
"""Startet restore.sh auf einem Server non-blocking."""
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect(
|
||||||
|
server["ip"],
|
||||||
|
username=server["ssh_creds"]["user"],
|
||||||
|
password=server["ssh_creds"]["password"]
|
||||||
|
)
|
||||||
|
safe_client = backup["client_name"].replace("/", "_").replace(":", "_")
|
||||||
|
srv_hostname = server["hostname"]
|
||||||
|
bk_size = backup.get("backup_size_bytes") or 0
|
||||||
|
cmd = (
|
||||||
|
f"nohup /opt/windmill-restore/restore.sh"
|
||||||
|
f" --job-uuid '{job_uuid}'"
|
||||||
|
f" --backup-path '{backup['backup_path']}'"
|
||||||
|
f" --client '{backup['client_name']}'"
|
||||||
|
f" --restore-mount '{server['restore_mount']}'"
|
||||||
|
f" --restore-path '{server['restore_path']}'"
|
||||||
|
f" --rsync-target '{backup['rsync_target']}'"
|
||||||
|
f" --pbs-storage '{backup['pbs_storage_id']}'"
|
||||||
|
f" --webhook-url '{webhook_url}'"
|
||||||
|
f" --webhook-token '{webhook_tok}'"
|
||||||
|
f" --server-hostname '{srv_hostname}'"
|
||||||
|
f" --backup-size '{bk_size}'"
|
||||||
|
f" > /opt/windmill-restore/logs/{safe_client}.log 2>&1 &"
|
||||||
|
)
|
||||||
|
ssh.exec_command(cmd)
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
|
|
||||||
|
def find_backup_for_server(server, backups):
|
||||||
|
"""
|
||||||
|
Sucht das erste Backup aus der Liste das zur Server-Größenklasse passt.
|
||||||
|
max_backup_size_gb = NULL -> kein oberes Limit
|
||||||
|
min_backup_size_gb = NULL -> kein unteres Limit
|
||||||
|
Gibt (index, backup) zurueck oder (None, None) wenn nichts passt.
|
||||||
|
"""
|
||||||
|
max_gb = server.get("max_backup_size_gb")
|
||||||
|
min_gb = server.get("min_backup_size_gb")
|
||||||
|
max_bytes = max_gb * 1024 * 1024 * 1024 if max_gb is not None else None
|
||||||
|
min_bytes = min_gb * 1024 * 1024 * 1024 if min_gb is not None else None
|
||||||
|
|
||||||
|
for i, backup in enumerate(backups):
|
||||||
|
size = backup.get("backup_size_bytes") or 0
|
||||||
|
if max_bytes is not None and size > max_bytes:
|
||||||
|
continue
|
||||||
|
if min_bytes is not None and size < min_bytes:
|
||||||
|
continue
|
||||||
|
return i, backup
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def main(prev: dict):
|
||||||
|
if prev.get("mode") == "webhook":
|
||||||
|
return prev
|
||||||
|
|
||||||
|
servers = prev.get("target_servers", [])
|
||||||
|
backups = list(prev.get("backups", []))
|
||||||
|
job_uuid = prev["job_uuid"]
|
||||||
|
|
||||||
|
if not backups:
|
||||||
|
return {**prev, "status": "no_backups"}
|
||||||
|
|
||||||
|
db_cfg = json.loads(wmill.get_variable("f/Backup/mysql_config"))
|
||||||
|
conn = mysql.connector.connect(**db_cfg)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
webhook_url = wmill.get_variable("f/Backup/windmill_webhook_url")
|
||||||
|
webhook_tok = wmill.get_variable("f/Backup/windmill_webhook_token")
|
||||||
|
|
||||||
|
servers_sorted = sorted(
|
||||||
|
servers,
|
||||||
|
key=lambda s: s.get("max_backup_size_gb") or 999999,
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
|
started = []
|
||||||
|
|
||||||
|
for server in servers_sorted:
|
||||||
|
if not backups:
|
||||||
|
break
|
||||||
|
|
||||||
|
idx, backup = find_backup_for_server(server, backups)
|
||||||
|
|
||||||
|
if backup is None:
|
||||||
|
print(f"Kein passendes Backup fuer '{server['hostname']}' "
|
||||||
|
f"(max: {server.get('max_backup_size_gb')} GB)")
|
||||||
|
continue
|
||||||
|
|
||||||
|
backups.pop(idx)
|
||||||
|
|
||||||
|
def fail_backup(msg, bk=backup, sv=server):
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO Kunden.`bronze.restore.result`
|
||||||
|
(job_uuid, client_name, backup_path,
|
||||||
|
backup_size_bytes, restore_server, status, error_message)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, 'failed', %s)
|
||||||
|
""", (job_uuid, bk["client_name"], bk["backup_path"],
|
||||||
|
bk.get("backup_size_bytes", 0), sv["hostname"], msg))
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE Kunden.`bronze.restore.jobs`
|
||||||
|
SET failed_count=failed_count+1 WHERE job_uuid=%s
|
||||||
|
""", (job_uuid,))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
if not backup.get("rsync_target"):
|
||||||
|
fail_backup("rsync_target fehlt"); continue
|
||||||
|
if not backup.get("pbs_storage_id"):
|
||||||
|
fail_backup("pbs_storage_id fehlt"); continue
|
||||||
|
if not server.get("restore_mount"):
|
||||||
|
fail_backup("restore_mount fehlt"); continue
|
||||||
|
if not server.get("restore_path"):
|
||||||
|
fail_backup("restore_path fehlt"); continue
|
||||||
|
|
||||||
|
client_like = f"{backup['client_name']}%"
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO Kunden.`bronze.restore.result`
|
||||||
|
(job_uuid, client_name, backup_path,
|
||||||
|
backup_size_bytes, restore_server, status, started_at)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, 'restoring', NOW())
|
||||||
|
""", (job_uuid, backup["client_name"], backup["backup_path"],
|
||||||
|
backup.get("backup_size_bytes", 0), server["hostname"]))
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE Kunden.`bronze.restore.server`
|
||||||
|
SET current_job_uuid=%s WHERE hostname=%s
|
||||||
|
""", (job_uuid, server["hostname"]))
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE Kunden.`bronze.backup.queue`
|
||||||
|
SET status='assigned'
|
||||||
|
WHERE job_uuid=%s AND backup_path LIKE %s
|
||||||
|
""", (job_uuid, client_like))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
start_restore(server, backup, job_uuid, webhook_url, webhook_tok)
|
||||||
|
size_gb = (backup.get("backup_size_bytes") or 0) / 1024 / 1024 / 1024
|
||||||
|
print(f"Restore gestartet: {backup['client_name']} "
|
||||||
|
f"({size_gb:.1f} GB) auf {server['hostname']} "
|
||||||
|
f"(max: {server.get('max_backup_size_gb')} GB)")
|
||||||
|
started.append({
|
||||||
|
"client": backup["client_name"],
|
||||||
|
"server": server["hostname"],
|
||||||
|
})
|
||||||
|
|
||||||
|
cur.close(); conn.close()
|
||||||
|
|
||||||
|
return {
|
||||||
|
**prev,
|
||||||
|
"status": "restore_started",
|
||||||
|
"started": started,
|
||||||
|
"backups": backups,
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
summary: Backup Restore Orchestrator
|
||||||
|
description: |
|
||||||
|
Startet täglich um 00:11 Uhr. Holt Backup-Liste direkt via
|
||||||
|
proxmox-backup-client, schreibt Queue nach Größe sortiert in DB,
|
||||||
|
registriert PBS-Datastores auf allen freien Restore-Servern,
|
||||||
|
startet Restores parallel auf mehreren Servern per Webhook-Chaining.
|
||||||
|
value:
|
||||||
|
modules:
|
||||||
|
- id: f
|
||||||
|
summary: Aktive Datastores aus DB holen
|
||||||
|
value:
|
||||||
|
type: rawscript
|
||||||
|
content: '!inline aktive_datastores_aus_db_holen.my.sql'
|
||||||
|
input_transforms:
|
||||||
|
database:
|
||||||
|
type: static
|
||||||
|
value: $res:u/sebastianserfling/fascinating_mysql
|
||||||
|
lock: ''
|
||||||
|
language: mysql
|
||||||
|
- id: a
|
||||||
|
summary: Job initialisieren & Backup-Queue aus PBS aufbauen
|
||||||
|
value:
|
||||||
|
type: rawscript
|
||||||
|
content: '!inline job_initialisieren_&_backup-queue_aus_pbs_aufbauen.py'
|
||||||
|
input_transforms:
|
||||||
|
datastores:
|
||||||
|
type: javascript
|
||||||
|
expr: results.f
|
||||||
|
trigger_type:
|
||||||
|
type: javascript
|
||||||
|
expr: "flow_input.job_uuid ? 'webhook' : 'schedule'"
|
||||||
|
webhook_data:
|
||||||
|
type: javascript
|
||||||
|
expr: 'flow_input.job_uuid ? flow_input : {}'
|
||||||
|
lock: '!inline job_initialisieren_&_backup-queue_aus_pbs_aufbauen.lock'
|
||||||
|
language: python3
|
||||||
|
- id: b
|
||||||
|
summary: Alle freien Restore-Server holen
|
||||||
|
value:
|
||||||
|
type: rawscript
|
||||||
|
content: '!inline alle_freien_restore-server_holen.py'
|
||||||
|
input_transforms:
|
||||||
|
prev:
|
||||||
|
type: javascript
|
||||||
|
expr: results.a
|
||||||
|
lock: '!inline alle_freien_restore-server_holen.lock'
|
||||||
|
language: python3
|
||||||
|
- id: g
|
||||||
|
summary: SSH-Credentials fuer alle Restore-Server aus Bitwarden
|
||||||
|
value:
|
||||||
|
type: rawscript
|
||||||
|
content: '!inline ssh-credentials_fuer_alle_restore-server_aus_bitwarden.py'
|
||||||
|
input_transforms:
|
||||||
|
bw_url:
|
||||||
|
type: static
|
||||||
|
value: https://bitwarden.stines.de
|
||||||
|
prev:
|
||||||
|
type: javascript
|
||||||
|
expr: results.b
|
||||||
|
lock: '!inline ssh-credentials_fuer_alle_restore-server_aus_bitwarden.lock'
|
||||||
|
language: python3
|
||||||
|
- id: c
|
||||||
|
summary: Script deployen & PBS-Datastores auf allen Servern registrieren
|
||||||
|
value:
|
||||||
|
type: rawscript
|
||||||
|
content: '!inline
|
||||||
|
script_deployen_&_pbs-datastores_auf_allen_servern_registrieren.py'
|
||||||
|
input_transforms:
|
||||||
|
bw_result:
|
||||||
|
type: javascript
|
||||||
|
expr: results.g
|
||||||
|
datastores:
|
||||||
|
type: javascript
|
||||||
|
expr: results.f
|
||||||
|
prev:
|
||||||
|
type: javascript
|
||||||
|
expr: results.g
|
||||||
|
lock: '!inline
|
||||||
|
script_deployen_&_pbs-datastores_auf_allen_servern_registrieren.lock'
|
||||||
|
language: python3
|
||||||
|
- id: h
|
||||||
|
summary: Alte Restore-Ordner auf Backup-Server loeschen
|
||||||
|
value:
|
||||||
|
type: rawscript
|
||||||
|
content: '!inline alte_restore-ordner_auf_backup-server_loeschen.py'
|
||||||
|
input_transforms:
|
||||||
|
prev:
|
||||||
|
type: javascript
|
||||||
|
expr: results.c
|
||||||
|
lock: '!inline alte_restore-ordner_auf_backup-server_loeschen.lock'
|
||||||
|
language: python3
|
||||||
|
- id: d
|
||||||
|
summary: Ersten Restore pro Server starten
|
||||||
|
value:
|
||||||
|
type: rawscript
|
||||||
|
content: '!inline ersten_restore_pro_server_starten.py'
|
||||||
|
input_transforms:
|
||||||
|
prev:
|
||||||
|
type: javascript
|
||||||
|
expr: results.h
|
||||||
|
lock: '!inline ersten_restore_pro_server_starten.lock'
|
||||||
|
language: python3
|
||||||
|
continue_on_error: false
|
||||||
|
- id: e
|
||||||
|
summary: Webhook verarbeiten & naechsten Restore auf demselben Server starten
|
||||||
|
value:
|
||||||
|
type: rawscript
|
||||||
|
content: '!inline
|
||||||
|
webhook_verarbeiten_&_naechsten_restore_auf_demselben_server_starten.py'
|
||||||
|
input_transforms:
|
||||||
|
from_init:
|
||||||
|
type: javascript
|
||||||
|
expr: results.a
|
||||||
|
lock: '!inline
|
||||||
|
webhook_verarbeiten_&_naechsten_restore_auf_demselben_server_starten.lock'
|
||||||
|
language: python3
|
||||||
|
schema:
|
||||||
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
type: object
|
||||||
|
order: []
|
||||||
|
properties: {}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# py: 3.12
|
||||||
|
anyio==4.12.1
|
||||||
|
certifi==2026.2.25
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httpx==0.28.1
|
||||||
|
idna==3.11
|
||||||
|
mysql-connector-python==9.6.0
|
||||||
|
typing-extensions==4.15.0
|
||||||
|
wmill==1.662.0
|
||||||
+161
@@ -0,0 +1,161 @@
|
|||||||
|
import wmill, mysql.connector, json, uuid, subprocess, sys, os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def main(
|
||||||
|
trigger_type: str = "schedule",
|
||||||
|
webhook_data: dict = {},
|
||||||
|
datastores: list = [],
|
||||||
|
):
|
||||||
|
# Webhook erkennen: kind=webhook ODER job_uuid im payload
|
||||||
|
if trigger_type == "webhook" or webhook_data.get("job_uuid"):
|
||||||
|
return {"mode": "webhook", "data": webhook_data}
|
||||||
|
|
||||||
|
pbs = json.loads(wmill.get_variable("f/Backup/pbs_variable"))
|
||||||
|
port = pbs.get("port", 8007)
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["PBS_PASSWORD"] = pbs["password"]
|
||||||
|
if pbs.get("fingerprint"):
|
||||||
|
env["PBS_FINGERPRINT"] = pbs["fingerprint"]
|
||||||
|
|
||||||
|
if subprocess.run(["which", "proxmox-backup-client"],
|
||||||
|
capture_output=True).returncode != 0:
|
||||||
|
print("Installiere proxmox-backup-client...", file=sys.stderr)
|
||||||
|
subprocess.run(["bash", "-c", (
|
||||||
|
"echo 'deb http://download.proxmox.com/debian/pbs bookworm pbs-no-subscription'"
|
||||||
|
" > /etc/apt/sources.list.d/pbs-client.list && "
|
||||||
|
"wget -qO /etc/apt/trusted.gpg.d/proxmox-release-bookworm.gpg "
|
||||||
|
" https://enterprise.proxmox.com/debian/proxmox-release-bookworm.gpg && "
|
||||||
|
"apt-get update -qq && apt-get install -y proxmox-backup-client"
|
||||||
|
)], check=True)
|
||||||
|
|
||||||
|
db_cfg = json.loads(wmill.get_variable("f/Backup/mysql_config"))
|
||||||
|
conn = mysql.connector.connect(**db_cfg)
|
||||||
|
cur = conn.cursor(dictionary=True)
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT datastore, rsync_target, pbs_storage_id
|
||||||
|
FROM Kunden.`bronze.backup.datastore.config`
|
||||||
|
""")
|
||||||
|
ds_config = {row["datastore"]: row for row in cur.fetchall()}
|
||||||
|
|
||||||
|
all_snaps = []
|
||||||
|
for row in datastores:
|
||||||
|
datastore = row["datastore"]
|
||||||
|
if port != 8007:
|
||||||
|
repo = f"{pbs['user']}@{pbs['host']}!{port}:{datastore}"
|
||||||
|
else:
|
||||||
|
repo = f"{pbs['user']}@{pbs['host']}:{datastore}"
|
||||||
|
|
||||||
|
env["PBS_REPOSITORY"] = repo
|
||||||
|
print(f"Hole Snapshots: {datastore}...", file=sys.stderr)
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
["proxmox-backup-client", "snapshots", "--output-format", "json"],
|
||||||
|
capture_output=True, text=True, env=env,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"WARNUNG: {datastore} fehlgeschlagen:\n{result.stderr}",
|
||||||
|
file=sys.stderr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
snaps = json.loads(result.stdout)
|
||||||
|
snaps = [s for s in snaps if s.get("backup-type") in ("vm", "ct")]
|
||||||
|
for s in snaps:
|
||||||
|
s["_datastore"] = datastore
|
||||||
|
all_snaps.extend(snaps)
|
||||||
|
print(f" -> {len(snaps)} Snapshots.", file=sys.stderr)
|
||||||
|
|
||||||
|
if not all_snaps:
|
||||||
|
raise Exception("Keine Snapshots gefunden!")
|
||||||
|
|
||||||
|
latest: dict = {}
|
||||||
|
for snap in all_snaps:
|
||||||
|
key = f"{snap['_datastore']}/{snap['backup-type']}/{snap['backup-id']}"
|
||||||
|
if key not in latest or snap["backup-time"] > latest[key]["backup-time"]:
|
||||||
|
latest[key] = snap
|
||||||
|
|
||||||
|
sorted_snaps = sorted(latest.values(), key=lambda s: s.get("size", 0), reverse=True)
|
||||||
|
print(f"{len(sorted_snaps)} Gruppen -> Queue.", file=sys.stderr)
|
||||||
|
|
||||||
|
job_uuid = str(uuid.uuid4())
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT job_uuid FROM Kunden.`bronze.restore.jobs`
|
||||||
|
WHERE status = 'running'
|
||||||
|
LIMIT 1
|
||||||
|
""")
|
||||||
|
existing = cur.fetchone()
|
||||||
|
if existing:
|
||||||
|
cur.close(); conn.close()
|
||||||
|
raise Exception(
|
||||||
|
f"Job bereits aktiv: {existing['job_uuid']} – "
|
||||||
|
f"kein neuer Job gestartet."
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.execute("SET time_zone = 'Europe/Berlin'")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO Kunden.`bronze.restore.jobs`
|
||||||
|
(job_uuid, started_at, status, restore_server)
|
||||||
|
VALUES (%s, NOW(), 'running', '')
|
||||||
|
""", (job_uuid,))
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE Kunden.`bronze.backup.queue`
|
||||||
|
SET status='obsolete' WHERE status='queued'
|
||||||
|
""")
|
||||||
|
|
||||||
|
for idx, snap in enumerate(sorted_snaps):
|
||||||
|
backup_type = snap["backup-type"]
|
||||||
|
backup_id = str(snap["backup-id"])
|
||||||
|
datastore = snap["_datastore"]
|
||||||
|
size_bytes = snap.get("size", 0)
|
||||||
|
ts = snap["backup-time"]
|
||||||
|
client_name = f"{datastore}:{backup_type}/{backup_id}"
|
||||||
|
backup_time_str = datetime.utcfromtimestamp(ts).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
backup_path = f"{datastore}:{backup_type}/{backup_id}/{backup_time_str}"
|
||||||
|
cfg = ds_config.get(datastore, {})
|
||||||
|
rsync_target = cfg.get("rsync_target")
|
||||||
|
pbs_storage_id = cfg.get("pbs_storage_id")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO Kunden.`bronze.backup.queue`
|
||||||
|
(job_uuid, client_name, backup_path, backup_size_bytes,
|
||||||
|
encrypt_key_ref, priority, rsync_target,
|
||||||
|
pbs_storage_id, status, last_seen_at)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 'queued', NOW())
|
||||||
|
""", (
|
||||||
|
job_uuid, client_name, backup_path, size_bytes,
|
||||||
|
client_name, idx, rsync_target, pbs_storage_id,
|
||||||
|
))
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO Kunden.`bronze.restore.plan`
|
||||||
|
(job_uuid, client_name, vm_name)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
""", (job_uuid, client_name, f"{backup_type}/{backup_id}"))
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE Kunden.`bronze.restore.jobs`
|
||||||
|
SET total_backups=%s WHERE job_uuid=%s
|
||||||
|
""", (len(sorted_snaps), job_uuid))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT client_name, backup_path, backup_size_bytes,
|
||||||
|
encrypt_key_ref, priority,
|
||||||
|
rsync_target, pbs_storage_id
|
||||||
|
FROM Kunden.`bronze.backup.queue`
|
||||||
|
WHERE job_uuid = %s AND status = 'queued'
|
||||||
|
ORDER BY priority ASC
|
||||||
|
""", (job_uuid,))
|
||||||
|
queued = cur.fetchall()
|
||||||
|
cur.close(); conn.close()
|
||||||
|
|
||||||
|
print(f"Queue: {len(queued)} Backups.", file=sys.stderr)
|
||||||
|
return {
|
||||||
|
"mode": "schedule",
|
||||||
|
"job_uuid": job_uuid,
|
||||||
|
"backups": queued,
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
# py: 3.12
|
||||||
|
anyio==4.12.1
|
||||||
|
bcrypt==5.0.0
|
||||||
|
certifi==2026.2.25
|
||||||
|
cffi==2.0.0
|
||||||
|
cryptography==46.0.5
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httpx==0.28.1
|
||||||
|
idna==3.11
|
||||||
|
invoke==2.2.1
|
||||||
|
mysql-connector-python==9.6.0
|
||||||
|
paramiko==4.0.0
|
||||||
|
pycparser==3.0
|
||||||
|
pynacl==1.6.2
|
||||||
|
typing-extensions==4.15.0
|
||||||
|
wmill==1.659.1
|
||||||
+245
@@ -0,0 +1,245 @@
|
|||||||
|
import wmill, json, paramiko, io, mysql.connector, re
|
||||||
|
|
||||||
|
GITEA_REPO = "http://172.17.1.251:8080/sebastian.serfling/BackupScript.git"
|
||||||
|
|
||||||
|
|
||||||
|
def deploy_to_server(ssh, server, pbs, pbs_host, pbs_user, pbs_pass,
|
||||||
|
pbs_port, ds_config, datastores, gitea_token,
|
||||||
|
backup_server_host, job_uuid, ssh_creds, db_cfg,
|
||||||
|
script_version):
|
||||||
|
|
||||||
|
hostname = server["hostname"]
|
||||||
|
|
||||||
|
_, out, _ = ssh.exec_command(
|
||||||
|
"cat /opt/windmill-restore/version.txt 2>/dev/null || echo none"
|
||||||
|
)
|
||||||
|
current_version = out.read().decode().strip()
|
||||||
|
needs_deploy = current_version != script_version \
|
||||||
|
or not server.get("script_deployed")
|
||||||
|
|
||||||
|
if needs_deploy:
|
||||||
|
print(f"[{hostname}] Deploye Script v{script_version}...")
|
||||||
|
ssh.exec_command("mkdir -p /opt/windmill-restore/logs")
|
||||||
|
ssh.exec_command("which git || apt-get install -y git 2>/dev/null")
|
||||||
|
repo_url = GITEA_REPO.replace("http://", f"http://{gitea_token}@")
|
||||||
|
|
||||||
|
_, out, err = ssh.exec_command(
|
||||||
|
"cd /opt/windmill-restore && "
|
||||||
|
"if [ -d 'BackupScript/.git' ]; then "
|
||||||
|
" cd BackupScript && git pull; "
|
||||||
|
"else "
|
||||||
|
" rm -rf BackupScript && "
|
||||||
|
" git clone '" + repo_url + "' BackupScript; "
|
||||||
|
"fi"
|
||||||
|
)
|
||||||
|
exit_code = out.channel.recv_exit_status()
|
||||||
|
stderr_out = err.read().decode().strip()
|
||||||
|
if exit_code != 0:
|
||||||
|
raise Exception(f"[{hostname}] Git fehlgeschlagen: {stderr_out}")
|
||||||
|
|
||||||
|
_, out, err = ssh.exec_command(
|
||||||
|
"cp /opt/windmill-restore/BackupScript/restore.sh "
|
||||||
|
" /opt/windmill-restore/restore.sh && "
|
||||||
|
"chmod +x /opt/windmill-restore/restore.sh && "
|
||||||
|
"echo '" + script_version + "' > /opt/windmill-restore/version.txt"
|
||||||
|
)
|
||||||
|
exit_code = out.channel.recv_exit_status()
|
||||||
|
stderr_out = err.read().decode().strip()
|
||||||
|
if exit_code != 0:
|
||||||
|
raise Exception(f"[{hostname}] Script kopieren fehlgeschlagen: {stderr_out}")
|
||||||
|
|
||||||
|
print(f"[{hostname}] Script v{script_version} deployed.")
|
||||||
|
|
||||||
|
pbs_conf = "\n".join([
|
||||||
|
f"PBS_HOST={pbs_host}",
|
||||||
|
f"PBS_PORT={pbs_port}",
|
||||||
|
f"PBS_USER={pbs_user}",
|
||||||
|
f"PBS_PASSWORD={pbs_pass}",
|
||||||
|
]) + "\n"
|
||||||
|
sftp = ssh.open_sftp()
|
||||||
|
sftp.putfo(io.BytesIO(pbs_conf.encode()),
|
||||||
|
"/opt/windmill-restore/pbs.conf")
|
||||||
|
sftp.putfo(io.BytesIO(backup_server_host.encode()),
|
||||||
|
"/opt/windmill-restore/backup_server_host")
|
||||||
|
sftp.close()
|
||||||
|
ssh.exec_command("chmod 600 /opt/windmill-restore/pbs.conf")
|
||||||
|
print(f"[{hostname}] backup_server_host: {backup_server_host}")
|
||||||
|
else:
|
||||||
|
print(f"[{hostname}] Script aktuell (v{current_version}).")
|
||||||
|
|
||||||
|
ssh.exec_command(
|
||||||
|
"mkdir -p /opt/windmill-restore/keys && "
|
||||||
|
"chmod 700 /opt/windmill-restore/keys"
|
||||||
|
)
|
||||||
|
|
||||||
|
_, out, _ = ssh.exec_command(
|
||||||
|
"pvesm status 2>/dev/null | awk 'NR>1{print $1}'"
|
||||||
|
)
|
||||||
|
existing_storages = out.read().decode().splitlines()
|
||||||
|
pbs_storages = []
|
||||||
|
|
||||||
|
for row in datastores:
|
||||||
|
datastore = row["datastore"]
|
||||||
|
storage_id = "pbs-" + datastore.lower() \
|
||||||
|
.replace(" ", "-") \
|
||||||
|
.replace("_", "-")
|
||||||
|
ds_cfg = ds_config.get(datastore, {})
|
||||||
|
fingerprint = ds_cfg.get("fingerprint", "") or ""
|
||||||
|
keyfile_path = f"/opt/windmill-restore/keys/{datastore}.keyfile"
|
||||||
|
|
||||||
|
rsync_src = f"root@{pbs_host}:/root/Scripte/{datastore}.keyfile"
|
||||||
|
_, out, err = ssh.exec_command(
|
||||||
|
"if [ -s '" + keyfile_path + "' ]; then "
|
||||||
|
" echo 'vorhanden'; "
|
||||||
|
"else "
|
||||||
|
" rsync -az -e 'ssh -o StrictHostKeyChecking=no' "
|
||||||
|
" '" + rsync_src + "' '" + keyfile_path + "' "
|
||||||
|
" && chmod 600 '" + keyfile_path + "' "
|
||||||
|
" && echo 'geholt'; "
|
||||||
|
"fi"
|
||||||
|
)
|
||||||
|
exit_code = out.channel.recv_exit_status()
|
||||||
|
stderr_out = err.read().decode().strip()
|
||||||
|
if exit_code != 0:
|
||||||
|
raise Exception(
|
||||||
|
f"[{hostname}] Keyfile fehlgeschlagen fuer '{datastore}': {stderr_out}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if storage_id in existing_storages:
|
||||||
|
print(f"[{hostname}] Storage '{storage_id}' vorhanden.")
|
||||||
|
else:
|
||||||
|
fp_part = f"--fingerprint '{fingerprint}'" if fingerprint else ""
|
||||||
|
cmd = (
|
||||||
|
f"pvesm add pbs {storage_id} "
|
||||||
|
f"--server '{pbs_host}' "
|
||||||
|
f"--datastore '{datastore}' "
|
||||||
|
f"--username '{pbs_user}' "
|
||||||
|
f"--password '{pbs_pass}' "
|
||||||
|
f"--port {pbs_port} "
|
||||||
|
f"--encryption-key '{keyfile_path}' "
|
||||||
|
f"--content backup"
|
||||||
|
)
|
||||||
|
_, out, err = ssh.exec_command(cmd)
|
||||||
|
exit_code = out.channel.recv_exit_status()
|
||||||
|
stderr = err.read().decode().strip()
|
||||||
|
if exit_code != 0:
|
||||||
|
raise Exception(
|
||||||
|
f"[{hostname}] pvesm add '{datastore}' fehlgeschlagen: {stderr}"
|
||||||
|
)
|
||||||
|
print(f"[{hostname}] -> '{storage_id}' registriert")
|
||||||
|
|
||||||
|
pbs_storages.append({"datastore": datastore, "storage_id": storage_id})
|
||||||
|
|
||||||
|
conn = mysql.connector.connect(**db_cfg)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
if needs_deploy:
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE Kunden.`bronze.restore.server`
|
||||||
|
SET script_deployed=1, script_version=%s WHERE hostname=%s
|
||||||
|
""", (script_version, hostname))
|
||||||
|
|
||||||
|
for entry in pbs_storages:
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE Kunden.`bronze.backup.datastore.config`
|
||||||
|
SET pbs_storage_id=%s WHERE datastore=%s
|
||||||
|
""", (entry["storage_id"], entry["datastore"]))
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO Kunden.`bronze.restore.session`
|
||||||
|
(job_uuid, hostname, ip, ssh_user, ssh_password)
|
||||||
|
VALUES (%s, %s, %s, %s, %s)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
ip=VALUES(ip), ssh_user=VALUES(ssh_user),
|
||||||
|
ssh_password=VALUES(ssh_password)
|
||||||
|
""", (
|
||||||
|
job_uuid, hostname,
|
||||||
|
ssh_creds["ip"], ssh_creds["user"], ssh_creds["password"],
|
||||||
|
))
|
||||||
|
|
||||||
|
conn.commit(); cur.close(); conn.close()
|
||||||
|
print(f"[{hostname}] Session-Creds gespeichert.")
|
||||||
|
|
||||||
|
return pbs_storages
|
||||||
|
|
||||||
|
|
||||||
|
def main(prev: dict, bw_result: dict = {}, datastores: list = []):
|
||||||
|
if prev.get("mode") == "webhook":
|
||||||
|
return prev
|
||||||
|
|
||||||
|
servers = prev["target_servers"]
|
||||||
|
server_creds = prev.get("server_creds", {})
|
||||||
|
job_uuid = prev["job_uuid"]
|
||||||
|
|
||||||
|
script_version = wmill.get_variable("f/Backup/restore_version").strip()
|
||||||
|
print(f"Script-Version aus Variable: {script_version}")
|
||||||
|
|
||||||
|
pbs = json.loads(wmill.get_variable("f/Backup/pbs_variable"))
|
||||||
|
pbs_host = pbs["host"]
|
||||||
|
pbs_user = pbs["user"]
|
||||||
|
pbs_pass = pbs["password"]
|
||||||
|
pbs_port = str(pbs.get("port", 8007))
|
||||||
|
|
||||||
|
db_cfg = json.loads(wmill.get_variable("f/Backup/mysql_config"))
|
||||||
|
conn = mysql.connector.connect(**db_cfg)
|
||||||
|
cur = conn.cursor(dictionary=True)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT datastore, rsync_target, pbs_storage_id, fingerprint
|
||||||
|
FROM Kunden.`bronze.backup.datastore.config`
|
||||||
|
""")
|
||||||
|
ds_config = {row["datastore"]: row for row in cur.fetchall()}
|
||||||
|
cur.close(); conn.close()
|
||||||
|
|
||||||
|
gitea_token = wmill.get_variable("f/Backup/gitea_token")
|
||||||
|
backup_server_host = wmill.get_variable("f/Backup/backup_server_host")
|
||||||
|
|
||||||
|
target_servers_out = []
|
||||||
|
|
||||||
|
for server in servers:
|
||||||
|
hostname = server["hostname"]
|
||||||
|
creds = server_creds.get(hostname, {})
|
||||||
|
|
||||||
|
url = creds.get("url", "")
|
||||||
|
ip_match = re.search(r'https?://([0-9.]+)', url)
|
||||||
|
ip = ip_match.group(1) if ip_match else server.get("ip", "")
|
||||||
|
|
||||||
|
if not ip:
|
||||||
|
print(f"WARNUNG: Keine IP fuer '{hostname}' – uebersprungen.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
ssh_creds_dict = {
|
||||||
|
"ip": ip,
|
||||||
|
"user": creds.get("username", "root"),
|
||||||
|
"password": creds.get("password", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect(ip, username=ssh_creds_dict["user"],
|
||||||
|
password=ssh_creds_dict["password"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
pbs_storages = deploy_to_server(
|
||||||
|
ssh, server, pbs, pbs_host, pbs_user, pbs_pass,
|
||||||
|
pbs_port, ds_config, datastores, gitea_token,
|
||||||
|
backup_server_host, job_uuid, ssh_creds_dict, db_cfg,
|
||||||
|
script_version
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
|
target_servers_out.append({
|
||||||
|
**server,
|
||||||
|
"ip": ip,
|
||||||
|
"ssh_creds": ssh_creds_dict,
|
||||||
|
"pbs_storages": pbs_storages,
|
||||||
|
})
|
||||||
|
|
||||||
|
if not target_servers_out:
|
||||||
|
raise Exception("Kein Server konnte vorbereitet werden!")
|
||||||
|
|
||||||
|
return {
|
||||||
|
**prev,
|
||||||
|
"target_servers": target_servers_out,
|
||||||
|
"script_version": script_version,
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
# py: 3.12
|
||||||
|
anyio==4.12.1
|
||||||
|
certifi==2026.2.25
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httpx==0.28.1
|
||||||
|
idna==3.11
|
||||||
|
typing-extensions==4.15.0
|
||||||
|
wmill==1.657.2
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user