Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
556ca55b18 |
@@ -1,100 +0,0 @@
|
||||
name: Template Bug Report
|
||||
description: Report a bug in one of our open source templates (Supabase, Appwrite, etc.)
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before opening a new issue, please search existing issues to see if this bug has already been reported.
|
||||
|
||||
This template is specifically for bugs in our open source templates like Supabase, Appwrite, and others.
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Template Name
|
||||
description: Which template is experiencing the bug?
|
||||
placeholder: "e.g: Supabase, Appwrite, Plausible, etc."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Relevant Logs of the Error
|
||||
description: |
|
||||
**IMPORTANT:** Go to the logs tab in your Dokploy dashboard and take a screenshot of the error.
|
||||
Please be clear and include the full error message. You can also paste text logs here.
|
||||
placeholder: |
|
||||
Please attach a clear screenshot of the error logs from the logs tab.
|
||||
|
||||
If you're pasting text logs, include them here:
|
||||
```
|
||||
[Your error logs here]
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to Reproduce the Error
|
||||
description: |
|
||||
Please provide clear, detailed steps to reproduce this bug.
|
||||
Be as specific as possible with numbered steps.
|
||||
placeholder: |
|
||||
1. Deploy the [Template Name] template
|
||||
2. Configure the following settings: [be specific]
|
||||
3. Navigate to [specific page/section]
|
||||
4. Perform [specific action]
|
||||
5. Error occurs at [specific point]
|
||||
|
||||
**Expected behavior:** What should happen
|
||||
**Actual behavior:** What actually happens
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment Information
|
||||
description: Please provide information about your environment
|
||||
render: bash
|
||||
placeholder: |
|
||||
Operating System: Ubuntu 20.04
|
||||
Dokploy version: 0.2.2
|
||||
VPS Provider: DigitalOcean, Hetzner, etc.
|
||||
Template Version: [if known]
|
||||
Browser: Chrome, Firefox, etc. [if relevant]
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: When does this error occur?
|
||||
options:
|
||||
- "During template deployment"
|
||||
- "After successful deployment"
|
||||
- "When accessing the application"
|
||||
- "During configuration"
|
||||
- "Other"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Any additional information that might help us understand and fix this bug
|
||||
placeholder: |
|
||||
- Did this work before? When did it stop working?
|
||||
- Have you made any custom modifications to the template?
|
||||
- Any workarounds you've found?
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Will you send a PR to fix it?
|
||||
description: Let us know if you are planning to submit a pull request to address this issue
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
- "Maybe, need help"
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,59 +0,0 @@
|
||||
name: New Template Request
|
||||
description: Suggest a new template for the project
|
||||
labels: ["template"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Template Name
|
||||
description: Provide a clear and descriptive name for the template
|
||||
placeholder: "e.g: Supabase, Appwrite, etc."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Template URL
|
||||
description: Link to the repository or resource where the template is located
|
||||
placeholder: "https://github.com/username/repo"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Docker Compose Link
|
||||
description: Link to docker-compose.yml file or Docker documentation (optional) but would be helpful
|
||||
placeholder: "https://github.com/username/repo/blob/main/docker-compose.yml"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Resources
|
||||
description: List the resources, links, or any other information that would be helpful to know about the template
|
||||
placeholder: |
|
||||
- Link to the template
|
||||
- Link to the documentation
|
||||
- Link to the repository
|
||||
- Link to the website
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Template Description
|
||||
description: Provide a detailed description of what this template does and when to use it
|
||||
placeholder: |
|
||||
This template is perfect for creating modern web applications with React and TypeScript...
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Will you send a PR to implement it?
|
||||
description: Let us know if you are planning to submit a pull request to add this template
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
- "Maybe, need help"
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,73 +0,0 @@
|
||||
# Dokploy Open Source Templates - Copilot Instructions
|
||||
|
||||
## Project Overview
|
||||
|
||||
This repository maintains Docker Compose templates for deploying open-source applications via Dokploy, a self-hosted PaaS alternative to Heroku. The core structure revolves around the `blueprints/` directory, where each subdirectory represents a deployable service (e.g., `blueprints/ghost/` for Ghost blogging platform).
|
||||
|
||||
Key components:
|
||||
|
||||
- **Blueprints**: Self-contained templates with `docker-compose.yml` (service definitions) and `template.toml` (Dokploy-specific configuration for domains, env vars, mounts).
|
||||
- **meta.json**: Centralized index of all templates, aggregated from blueprint metadata. Entries include `id`, `name`, `version`, `description`, `logo`, `links`, and `tags`.
|
||||
- **Scripts**: Node.js tools in root and `build-scripts/` for maintaining `meta.json` (deduplication, sorting, validation).
|
||||
|
||||
Data flow: New templates added to `blueprints/` → Metadata updated in `meta.json` → Processing scripts ensure consistency → App builds include static blueprints/meta for preview.
|
||||
|
||||
The "why": Enables rapid, standardized deployment of 200+ OSS apps on Dokploy without manual config. Structure prioritizes simplicity—each blueprint is independent, no shared state or complex interdependencies.
|
||||
|
||||
## Key Files and Directories
|
||||
|
||||
- `meta.json`: Array of template objects. Always process after edits using `node dedupe-and-sort-meta.js` to remove duplicates (by `id`) and sort alphabetically.
|
||||
- `blueprints/<service>/`:
|
||||
- `docker-compose.yml`: Standard Docker Compose v3.8. Avoid `ports`, `container_name`, `networks`—Dokploy handles isolation via internal networks.
|
||||
- `template.toml`: Defines variables (e.g., `${domain}`), domains (service:port → host), env vars, and mounts. Use helpers like `${password:32}`, `${uuid}`, `${jwt:secret_var}`.
|
||||
- `logo.svg/png`: Service icon, referenced in `meta.json`.
|
||||
- `dedupe-and-sort-meta.js`: Standalone script—reads `meta.json`, removes duplicate `id`s (keeps first), sorts by `id` (case-insensitive), creates timestamped backup.
|
||||
- `build-scripts/process-meta.js`: Advanced processor with CLI options (`--verbose`, `--no-backup`, `--input`/`--output`), JSON schema validation (required: `id`, `name`, `version`, `description`, `links.github`, `logo`, `tags` array).
|
||||
|
||||
Exemplary blueprint: `blueprints/ghost/`—`docker-compose.yml` exposes port 2368; `template.toml` maps domain to Ghost service; meta entry tags as ["blogging", "cms"].
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Add/Update Template**:
|
||||
|
||||
- **REQUIREMENT**: Service **MUST** be open source. Only add templates for applications with an open-source license (e.g., MIT, Apache, GPL, AGPL). Proprietary or closed-source services are not allowed.
|
||||
- **Verify Docker Images**: Before using any Docker image in `docker-compose.yml`, verify it exists using `docker manifest inspect <image:tag>` (e.g., `docker manifest inspect docker.io/bitnami/discourse:3.5.0`). This ensures the image is available and prevents deployment failures.
|
||||
- Create `blueprints/<id>/` (e.g., `ghost`).
|
||||
- Implement `docker-compose.yml` (single service typical; use volumes for persistence).
|
||||
- Configure `template.toml`—reference vars in `[config.domains]`, `[config.env]`, `[config.mounts]`.
|
||||
- Add/update `meta.json` entry with exact `id` matching folder.
|
||||
- Run `node dedupe-and-sort-meta.js --backup` to validate/sort.
|
||||
- Commit; PR triggers Dokploy preview (base64 import for testing).
|
||||
|
||||
2. **CI/CD**:
|
||||
- `.github/workflows/validate-meta.yml` (if present): Runs validation on push/PR—fails on duplicates, invalid JSON, missing fields.
|
||||
- Integrate processing: Add `npm run process-meta` to build steps; use `--no-backup` in CI.
|
||||
|
||||
No tests in repo—focus on manual validation via scripts and Dokploy deploys. Debug: Check console output from processing scripts for warnings (e.g., missing `id`).
|
||||
|
||||
## Conventions and Patterns
|
||||
|
||||
- **Open Source Requirement**: **ALL services MUST be open source**. Only applications with open-source licenses (MIT, Apache, GPL, AGPL, etc.) are allowed. Proprietary or closed-source services are strictly prohibited.
|
||||
- **Template IDs**: Lowercase, kebab-case (e.g., `active-pieces`); unique across repo—enforced by dedupe script.
|
||||
- **Docker Compose**: Minimal—omit `ports` (Dokploy proxies), persistent volumes (e.g., `- db-data:/var/lib/postgresql/data`). Services named after folder (e.g., `ghost` service).
|
||||
- **template.toml**:
|
||||
- Variables: `[variables] main_domain = "${domain}"`; use helpers for secrets (`${password:64}`, `${base64:32}`).
|
||||
- Domains: `[[config.domains]] serviceName = "<service>" port = 80 host = "${main_domain}"` (path="/" optional).
|
||||
- Env: `[[config.env]]` array of "KEY=VALUE" strings, interpolating vars (e.g., "DB_PASSWORD=${db_pass}").
|
||||
- **URL Variables**: When environment variables require URLs (e.g., `WEB_URL`, `NEXTAUTH_URL`, `PUBLIC_URL`), **always use HTTP by default** (e.g., `"http://${main_domain}"`). HTTPS should only be used if explicitly required by the application or when using a reverse proxy with SSL termination.
|
||||
- Mounts: `[[config.mounts]] filePath = "/etc/config" content = """multi-line\ncontent"""`.
|
||||
- JWT helper: `${jwt:secret_var:payload_var}` for auth tokens; payload as JSON string with `exp: ${timestamps:YYYY-MM-DDTHH:mm:ssZ}`.
|
||||
- **Meta.json**: Entries as JSON objects; tags array of lowercase strings (e.g., ["monitoring", "database"]); links object with `github`, `website`, `docs`.
|
||||
- **No Networks**: Rely on Dokploy's isolated deployments—avoid explicit `networks:`.
|
||||
- **Versions**: Pin images to specific versions in `docker-compose.yml` (e.g., `ghost:5.82.0-alpine`); match in `meta.json.version`. **NEVER use `latest` tag**—it can break templates when upstream images change unexpectedly. **Always verify image exists** using `docker manifest inspect <image:tag>` before committing.
|
||||
- **Logos**: SVG preferred; size ~128x128; file name in `meta.json.logo` (e.g., "ghost.svg").
|
||||
|
||||
Cross-component: Templates are independent and ship as static blueprints/meta.
|
||||
|
||||
## Integration Points
|
||||
|
||||
- **Dokploy**: Templates import via base64 (full compose + config) or URL. Test deploys validate env interpolation, domain proxying.
|
||||
- **External Deps**: Docker Compose (v3.8+); TOML parsing via `@iarna/toml` in app. No runtime deps beyond Node/pnpm for dev.
|
||||
- **PR Previews**: Auto-generated on GitHub—use for end-to-end testing without local Dokploy.
|
||||
|
||||
When editing, always re-run meta processing and validate blueprint deploy in preview.
|
||||
@@ -1,18 +0,0 @@
|
||||
## What is this PR about?
|
||||
|
||||
New PR of [Template Name]
|
||||
|
||||
## Checklist
|
||||
|
||||
Before submitting this PR, please make sure that:
|
||||
|
||||
- [ ] I have read the suggestions in the README.md file https://github.com/Dokploy/templates?tab=readme-ov-file#general-requirements-when-creating-a-template
|
||||
- [ ] I have tested the template in my instance, so the maintainers don't spend time trying to figure out what's wrong.
|
||||
- [ ] I have added tests that demonstrate that my correction works or that my new feature works.
|
||||
|
||||
## Issues related (if applicable)
|
||||
|
||||
Close automatically the related issues using the keywords: `closes #ISSUE_NUMBER`
|
||||
|
||||
|
||||
## Screenshots or Videos
|
||||
@@ -1,163 +0,0 @@
|
||||
name: Audit All Blueprints
|
||||
|
||||
# The per-PR validators only look at blueprints a PR touches, which is the right
|
||||
# call for CI cost but means a template is checked when it lands and then never
|
||||
# again. When a rule is added or tightened, every existing blueprint that breaks
|
||||
# it stays broken with nothing surfacing it (see #1047).
|
||||
#
|
||||
# This job runs the same validators over ALL blueprints on a schedule, so drift
|
||||
# is reported instead of accumulating. It never runs on pull_request, so it
|
||||
# cannot block a contributor for a pre-existing problem they did not introduce.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Mondays, 04:00 UTC
|
||||
- cron: "0 4 * * 1"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
fail_on_findings:
|
||||
description: "Exit non-zero when findings exist (default: report only)"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.16.0
|
||||
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd build-scripts && pnpm install
|
||||
|
||||
- name: Validate per-template metadata
|
||||
run: node build-scripts/generate-meta.js --check
|
||||
|
||||
- name: Audit every blueprint
|
||||
id: audit
|
||||
run: |
|
||||
# $RUNNER_TEMP is per-job; also cleared explicitly so a re-run or a
|
||||
# partially-executed earlier step can never leak into the summary.
|
||||
WORK="${RUNNER_TEMP:-/tmp}"
|
||||
OUT="$WORK/validator-out.txt"
|
||||
REPORT="$WORK/audit-report.md"
|
||||
SUMMARY="$WORK/audit-summary.md"
|
||||
rm -f "$OUT" "$REPORT" "$SUMMARY"
|
||||
|
||||
COMPOSE_FAILED=""
|
||||
TOML_FAILED=""
|
||||
|
||||
for dir in blueprints/*/; do
|
||||
id="$(basename "$dir")"
|
||||
|
||||
# Run unconditionally: a blueprint with no docker-compose.yml or no
|
||||
# template.toml is itself a finding, and the validators already
|
||||
# report a missing file clearly. Skipping them would hide exactly
|
||||
# the blueprints most likely to be broken.
|
||||
if ! (cd build-scripts && pnpm exec tsx validate-docker-compose.ts \
|
||||
--file "../$dir/docker-compose.yml") > "$OUT" 2>&1; then
|
||||
COMPOSE_FAILED="$COMPOSE_FAILED $id"
|
||||
{
|
||||
echo "### \`$id\` — docker-compose.yml"
|
||||
echo '```'
|
||||
grep '❌' "$OUT" || cat "$OUT"
|
||||
echo '```'
|
||||
} >> "$REPORT"
|
||||
fi
|
||||
|
||||
if ! (cd build-scripts && pnpm exec tsx validate-template.ts \
|
||||
--dir "../$dir") > "$OUT" 2>&1; then
|
||||
TOML_FAILED="$TOML_FAILED $id"
|
||||
{
|
||||
echo "### \`$id\` — template.toml"
|
||||
echo '```'
|
||||
grep '❌' "$OUT" || cat "$OUT"
|
||||
echo '```'
|
||||
} >> "$REPORT"
|
||||
fi
|
||||
done
|
||||
|
||||
COMPOSE_COUNT=$(echo $COMPOSE_FAILED | wc -w | tr -d ' ')
|
||||
TOML_COUNT=$(echo $TOML_FAILED | wc -w | tr -d ' ')
|
||||
TOTAL=$(ls -d blueprints/*/ | wc -l | tr -d ' ')
|
||||
|
||||
{
|
||||
echo "## Blueprint audit"
|
||||
echo ""
|
||||
echo "| check | failing | of |"
|
||||
echo "| --- | --- | --- |"
|
||||
echo "| \`validate-docker-compose.ts\` | $COMPOSE_COUNT | $TOTAL |"
|
||||
echo "| \`validate-template.ts\` | $TOML_COUNT | $TOTAL |"
|
||||
echo ""
|
||||
} > "$SUMMARY"
|
||||
|
||||
if [ -f "$REPORT" ]; then
|
||||
cat "$REPORT" >> "$SUMMARY"
|
||||
else
|
||||
echo "No findings." >> "$SUMMARY"
|
||||
fi
|
||||
|
||||
# The step summary caps at ~1MB and truncates silently past it, which
|
||||
# would hide findings exactly as the backlog grows. Bound what we
|
||||
# append and point at the artifact for the rest.
|
||||
SUMMARY_LIMIT=$((512 * 1024))
|
||||
SUMMARY_BYTES=$(wc -c < "$SUMMARY" | tr -d ' ')
|
||||
|
||||
if [ "$SUMMARY_BYTES" -gt "$SUMMARY_LIMIT" ]; then
|
||||
# Emit the header table only, never a byte-sliced prefix: cutting
|
||||
# mid-``` leaves an unclosed code fence, which swallows the notice
|
||||
# below it and makes the truncation invisible in the rendered summary.
|
||||
{
|
||||
head -n 7 "$SUMMARY"
|
||||
echo ""
|
||||
echo "_Report omitted: $SUMMARY_BYTES bytes exceeds the $SUMMARY_LIMIT byte budget for a job summary._"
|
||||
echo "_Download the \`blueprint-audit\` artifact for the complete output._"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
cat "$SUMMARY" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
# Always published, so the full report survives regardless of size.
|
||||
cp "$SUMMARY" "$WORK/blueprint-audit.md"
|
||||
echo "report_path=$WORK/blueprint-audit.md" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "compose_failed=$COMPOSE_COUNT" >> "$GITHUB_OUTPUT"
|
||||
echo "toml_failed=$TOML_COUNT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ "$COMPOSE_COUNT" != "0" ] || [ "$TOML_COUNT" != "0" ]; then
|
||||
echo "::warning::$COMPOSE_COUNT compose and $TOML_COUNT template.toml findings across $TOTAL blueprints"
|
||||
fi
|
||||
|
||||
- name: Upload the full report
|
||||
if: always() && steps.audit.outputs.report_path != ''
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: blueprint-audit
|
||||
path: ${{ steps.audit.outputs.report_path }}
|
||||
retention-days: 30
|
||||
|
||||
- name: Fail if requested
|
||||
# `inputs` is only populated for workflow_dispatch; on a schedule run it
|
||||
# is not available, so gate on the event first and read the value through
|
||||
# github.event.inputs, which is an empty string when absent.
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' &&
|
||||
github.event.inputs.fail_on_findings == 'true' &&
|
||||
(steps.audit.outputs.compose_failed != '0' || steps.audit.outputs.toml_failed != '0')
|
||||
run: |
|
||||
echo "❌ Findings present and fail_on_findings was requested."
|
||||
exit 1
|
||||
@@ -27,6 +27,15 @@ jobs:
|
||||
- name: Build
|
||||
working-directory: app
|
||||
run: pnpm build
|
||||
# - name: Deploy to Cloudflare Pages
|
||||
# uses: AdrianGonz97/refined-cf-pages-action@v1
|
||||
# with:
|
||||
# apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
# accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
# githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
# projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
||||
# deploymentName: Preview
|
||||
# directory: app/dist
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -20,7 +20,6 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
@@ -5,6 +5,7 @@ on:
|
||||
workflows: [Build Preview Deployment]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
deployments: write
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
name: Validate Docker Compose Files
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- canary
|
||||
paths:
|
||||
- 'blueprints/**/docker-compose.yml'
|
||||
- 'blueprints/**/template.toml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
validate-docker-compose:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Compose
|
||||
run: docker compose version
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.16.0
|
||||
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd build-scripts && pnpm install
|
||||
|
||||
- name: Detect changed blueprints
|
||||
id: changed
|
||||
run: |
|
||||
BASE_SHA=$(git merge-base HEAD origin/${{ github.base_ref }})
|
||||
|
||||
# Obtener todos los blueprints que tienen cambios (en docker-compose.yml o template.toml)
|
||||
CHANGED_BLUEPRINTS=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA HEAD | \
|
||||
grep -E 'blueprints/[^/]+/(docker-compose\.yml|template\.toml)$' | \
|
||||
sed 's|blueprints/\([^/]*\)/.*|\1|' | \
|
||||
sort -u)
|
||||
|
||||
echo "Changed blueprints:"
|
||||
echo "$CHANGED_BLUEPRINTS" | while read blueprint; do
|
||||
[ -n "$blueprint" ] && echo " - $blueprint"
|
||||
done
|
||||
|
||||
# Guardar lista de blueprints (una por línea)
|
||||
echo "blueprints<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CHANGED_BLUEPRINTS" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Validate blueprints
|
||||
run: |
|
||||
BLUEPRINTS="${{ steps.changed.outputs.blueprints }}"
|
||||
|
||||
if [ -z "$BLUEPRINTS" ] || [ "$BLUEPRINTS" = "" ]; then
|
||||
echo "ℹ️ No blueprints changed, skipping validation"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ERROR=0
|
||||
|
||||
# Convertir a array para evitar problemas con subshells
|
||||
mapfile -t BLUEPRINT_ARRAY <<< "$BLUEPRINTS"
|
||||
|
||||
# Iterar sobre cada blueprint
|
||||
for blueprint in "${BLUEPRINT_ARRAY[@]}"; do
|
||||
[ -z "$blueprint" ] && continue
|
||||
|
||||
BLUEPRINT_PATH="blueprints/$blueprint"
|
||||
COMPOSE_FILE="$BLUEPRINT_PATH/docker-compose.yml"
|
||||
TOML_FILE="$BLUEPRINT_PATH/template.toml"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 Validating: $blueprint"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# 1. Validar best practices de docker-compose.yml
|
||||
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||
echo "⚠️ WARNING: docker-compose.yml not found"
|
||||
ERROR=1
|
||||
continue
|
||||
fi
|
||||
echo "🔍 Validating docker-compose.yml best practices..."
|
||||
if ! (cd build-scripts && pnpm exec tsx validate-docker-compose.ts --file "../$COMPOSE_FILE" --verbose); then
|
||||
ERROR=1
|
||||
continue
|
||||
fi
|
||||
|
||||
# 3. Validar template.toml
|
||||
if [ -f "$TOML_FILE" ]; then
|
||||
echo "🔍 Validating template.toml..."
|
||||
if ! (cd build-scripts && pnpm exec tsx validate-template.ts --dir "../$BLUEPRINT_PATH" --verbose); then
|
||||
ERROR=1
|
||||
continue
|
||||
fi
|
||||
else
|
||||
echo "⚠️ WARNING: template.toml not found"
|
||||
ERROR=1
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "✅ All validations passed for $blueprint"
|
||||
done
|
||||
|
||||
if [ $ERROR -eq 1 ]; then
|
||||
echo ""
|
||||
echo "❌ Validation failed for one or more blueprints"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "✅ All blueprints validated successfully"
|
||||
fi
|
||||
@@ -1,41 +0,0 @@
|
||||
name: Validate Template Metadata
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [canary]
|
||||
paths:
|
||||
- "blueprints/**"
|
||||
- "build-scripts/generate-meta.js"
|
||||
- "meta.json"
|
||||
pull_request:
|
||||
branches: [canary]
|
||||
paths:
|
||||
- "blueprints/**"
|
||||
- "build-scripts/generate-meta.js"
|
||||
- "meta.json"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
validate-meta:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Reject root meta.json
|
||||
run: |
|
||||
if [ -f "meta.json" ]; then
|
||||
echo "❌ The root meta.json is generated at build time and must not be committed."
|
||||
echo " Add your template's metadata to blueprints/<id>/meta.json instead."
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ No root meta.json present"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Validate per-template metadata
|
||||
run: node build-scripts/generate-meta.js --check
|
||||
@@ -3,10 +3,10 @@ name: Validate Blueprints Structure and Meta
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- canary
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- canary
|
||||
- main
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
TEMPLATE_NAME=$(basename "$dir")
|
||||
|
||||
COMPOSE_FILE="$dir/docker-compose.yml"
|
||||
TEMPLATE_FILE="$dir/template.toml"
|
||||
TEMPLATE_FILE="$dir/template.yml"
|
||||
|
||||
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||
echo "❌ Missing docker-compose.yml in $TEMPLATE_NAME"
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
fi
|
||||
|
||||
if [ ! -f "$TEMPLATE_FILE" ]; then
|
||||
echo "❌ Missing template.toml in $TEMPLATE_NAME"
|
||||
echo "❌ Missing template.yml in $TEMPLATE_NAME"
|
||||
ERROR=1
|
||||
fi
|
||||
fi
|
||||
@@ -49,10 +49,57 @@ jobs:
|
||||
echo "✅ Blueprint folders validated successfully."
|
||||
fi
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
- name: Validate meta.json matches blueprint folders and logo files
|
||||
run: |
|
||||
echo "🔍 Validating meta.json against blueprint folders and logos..."
|
||||
|
||||
- name: Validate per-template metadata (blueprints/<id>/meta.json)
|
||||
run: node build-scripts/generate-meta.js --check
|
||||
ERROR=0
|
||||
|
||||
# Read all blueprint folder names into an array
|
||||
FOLDERS=($(ls -1 blueprints))
|
||||
|
||||
# Extract ids and logos from meta.json
|
||||
IDS_AND_LOGOS=$(jq -c '.[] | {id, logo}' meta.json)
|
||||
|
||||
# Validate each id in meta.json exists as a folder
|
||||
for item in $IDS_AND_LOGOS; do
|
||||
ID=$(echo "$item" | jq -r '.id')
|
||||
LOGO=$(echo "$item" | jq -r '.logo')
|
||||
|
||||
# Check if folder exists
|
||||
if [ ! -d "blueprints/$ID" ]; then
|
||||
echo "❌ meta.json id \"$ID\" does not have a matching folder in blueprints/"
|
||||
ERROR=1
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if logo file exists inside its folder
|
||||
if [ ! -f "blueprints/$ID/$LOGO" ]; then
|
||||
echo "❌ Logo \"$LOGO\" defined for \"$ID\" does not exist in blueprints/$ID/"
|
||||
ERROR=1
|
||||
fi
|
||||
done
|
||||
|
||||
# Validate each folder has a matching id in meta.json
|
||||
META_IDS=$(jq -r '.[].id' meta.json)
|
||||
for FOLDER in "${FOLDERS[@]}"; do
|
||||
FOUND=0
|
||||
for ID in $META_IDS; do
|
||||
if [ "$FOLDER" == "$ID" ]; then
|
||||
FOUND=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$FOUND" -eq 0 ]; then
|
||||
echo "❌ Folder \"$FOLDER\" has no matching id in meta.json"
|
||||
ERROR=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $ERROR -eq 1 ]; then
|
||||
echo "❌ meta.json validation failed."
|
||||
exit 1
|
||||
else
|
||||
echo "✅ meta.json validated successfully."
|
||||
fi
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
meta.json.backup.*
|
||||
@@ -1,199 +0,0 @@
|
||||
# AGENTS.md: AI Collaboration Guide
|
||||
|
||||
This document provides essential context for AI models interacting with this project. Adhering to these guidelines will ensure consistency and maintain code quality.
|
||||
|
||||
## 1. Project Overview & Purpose
|
||||
|
||||
- **Primary Goal:** This is the official repository for Dokploy Open Source Templates, maintaining Docker Compose templates for deploying 200+ open-source applications via Dokploy (a self-hosted PaaS alternative to Heroku). The project enables rapid, standardized deployment of OSS applications without manual configuration.
|
||||
- **Business Domain:** Self-hosted Platform as a Service (PaaS), DevOps tooling, containerized application deployment, and open-source software distribution.
|
||||
|
||||
## 2. Core Technologies & Stack
|
||||
|
||||
- **Languages:** JavaScript (Node.js), TypeScript (React frontend), YAML (Docker Compose), TOML (template configuration), JSON (metadata), Shell (build scripts).
|
||||
- **Frameworks & Runtimes:** Node.js runtime, Vite 5.x (for React 19.x frontend), Docker Compose v3.8+, React Router 7.x.
|
||||
- **Databases:** No direct database usage in core project (templates may include various databases like PostgreSQL, MySQL, Redis, etc.).
|
||||
- **Key Libraries/Dependencies:**
|
||||
- **Core:** `nodemon` for development, custom Node.js scripts for meta processing.
|
||||
- **Frontend:** `fuse.js` (fuzzy search), `zustand` (state management), `@iarna/toml` (TOML parsing), `shadcn/ui` components, `@radix-ui` primitives, `@codemirror` (code editor), `react-router-dom`, `tailwindcss`.
|
||||
- **Build:** `vite-plugin-static-copy` for copying blueprints to dist.
|
||||
- **Platforms:** Linux (primary), Docker containers, Dokploy PaaS, Web browsers (for frontend preview), Cloudflare Pages (for PR previews).
|
||||
- **Package Manager:** npm for root project, pnpm for frontend (`app/` directory).
|
||||
|
||||
## 3. Architectural Patterns
|
||||
|
||||
- **Overall Architecture:** Template collection and management system with decentralized blueprint architecture. Each blueprint is a self-contained Docker Compose application with Dokploy-specific configuration.
|
||||
- **Directory Structure Philosophy:**
|
||||
- `/blueprints`: Contains all deployable application templates, each as a subdirectory with `docker-compose.yml`, `template.toml`, and logo files. Each blueprint is independent with no shared state.
|
||||
- `/app`: Vite + React + TypeScript frontend for local preview and development.
|
||||
- `/src/components`: React components (UI components in `/ui` subdirectory using shadcn/ui).
|
||||
- `/src/hooks`: Custom React hooks (e.g., `useFuseSearch.ts` for fuzzy search).
|
||||
- `/src/store`: Zustand state management store.
|
||||
- `/src/lib`: Utility functions and helpers.
|
||||
- `/build-scripts`: Metadata tooling — `generate-meta.js` (aggregates and validates every `blueprints/<id>/meta.json` into the served `meta.json` artifact) and `explode-meta.js` (one-shot migration reference).
|
||||
- `/.github/workflows`: CI/CD workflows for validation, preview builds, and deployments.
|
||||
- Root level: There is NO root `meta.json` in the repo — each template's metadata lives in `blueprints/<id>/meta.json` and the global registry is generated at build time.
|
||||
- **Module Organization:** Node.js scripts for metadata processing, React components for frontend UI, Docker Compose files for service definitions, TOML files for Dokploy configuration. Frontend uses component-based architecture with hooks for business logic and Zustand for global state.
|
||||
|
||||
## 4. Coding Conventions & Style Guide
|
||||
|
||||
- **Formatting:** JavaScript follows standard conventions with 2-space indentation. TypeScript in frontend uses ESLint configuration. YAML uses 2-space indentation. TOML files use standard formatting.
|
||||
- **Naming Conventions:**
|
||||
- Variables, functions: camelCase (`myVariable`, `myFunction`)
|
||||
- React components: PascalCase (`TemplateGrid`, `SearchBar`)
|
||||
- Constants: SCREAMING_SNAKE_CASE (`MAX_BUFFER_SIZE`)
|
||||
- Template IDs: lowercase, kebab-case (`activepieces`, `ghost`) - **MUST** be unique across repository
|
||||
- Files: kebab-case for scripts (`generate-meta.js`), PascalCase for React components (`TemplateGrid.tsx`), kebab-case for directories (`build-scripts`)
|
||||
- Docker services: **MUST** match blueprint folder name exactly (e.g., `ghost` service in `blueprints/ghost/`)
|
||||
- **API Design:**
|
||||
- **Backend:** Procedural scripting approach with CLI interfaces. Template system uses declarative configuration over imperative code.
|
||||
- **Frontend:** Component-based React architecture with hooks for logic separation. Zustand for centralized state management. Custom hooks encapsulate complex logic (e.g., `useFuseSearch` for search functionality).
|
||||
- **Common Patterns & Idioms:**
|
||||
- **Metaprogramming:** Minimal use of advanced JavaScript features in scripts, focusing on simple, maintainable code.
|
||||
- **Memory Management:** Relies on Node.js garbage collection and React's automatic memory management.
|
||||
- **Polymorphism:** Uses JavaScript prototype-based objects and functional programming patterns. React components use composition over inheritance.
|
||||
- **Type Safety:**
|
||||
- Backend scripts: JavaScript with JSDoc comments for documentation.
|
||||
- Frontend: TypeScript with strict type checking enabled.
|
||||
- **Concurrency:**
|
||||
- Backend: Synchronous processing model for meta.json operations.
|
||||
- Frontend: React's concurrent rendering, `useDeferredValue` for debouncing, `useEffect` for async operations.
|
||||
- **State Management:** Zustand store pattern with selectors for optimal re-renders. URL params synced with search state via React Router.
|
||||
- **Error Handling:**
|
||||
- Backend: Node.js error-first callback pattern and try-catch blocks. Scripts validate JSON structure and fail fast on errors.
|
||||
- Frontend: Error boundaries for React components, try-catch for async operations, user-friendly error messages via toast notifications.
|
||||
|
||||
## 5. Key Files & Entrypoints
|
||||
|
||||
- **Main Entrypoints:**
|
||||
- **Backend:** `build-scripts/generate-meta.js` - Aggregates and validates all `blueprints/<id>/meta.json` files into the served `meta.json` artifact (`app/public/meta.json`, gitignored).
|
||||
- **Frontend:** `app/src/main.tsx` - React application entry point.
|
||||
- **Configuration:**
|
||||
- `app/package.json` - Frontend dependencies and pnpm scripts (`dev` and `build` run `generate-meta.js` first).
|
||||
- `blueprints/<id>/meta.json` - Per-template metadata (one object per template; 400+ templates).
|
||||
- `app/vite.config.ts` - Vite build configuration with static copy plugin.
|
||||
- `app/tsconfig.json` - TypeScript compiler configuration.
|
||||
- **CI/CD Pipeline:**
|
||||
- `.github/workflows/validate-meta.yml` - Validates every `blueprints/<id>/meta.json` (via `generate-meta.js --check`) and rejects a committed root `meta.json`.
|
||||
- `.github/workflows/build-preview.yml` - Builds preview deployments for PRs.
|
||||
- `.github/workflows/deploy-preview.yml` - Deploys previews to Cloudflare Pages.
|
||||
|
||||
## 6. Development & Testing Workflow
|
||||
|
||||
- **Local Development Environment:**
|
||||
- **Install dependencies**
|
||||
|
||||
```bash
|
||||
# Root project
|
||||
npm install
|
||||
|
||||
# Frontend (uses pnpm)
|
||||
cd app && pnpm install
|
||||
```
|
||||
|
||||
- **Validate template metadata** (CRITICAL: run after adding/editing any `blueprints/<id>/meta.json`)
|
||||
```bash
|
||||
node build-scripts/generate-meta.js --check
|
||||
```
|
||||
- **Generate the served meta.json artifact** (done automatically by `pnpm dev` / `pnpm build` in `app/`)
|
||||
```bash
|
||||
node build-scripts/generate-meta.js # writes app/public/meta.json
|
||||
```
|
||||
- **Task Configuration:**
|
||||
- **App scripts (`app/package.json`, pnpm):**
|
||||
- `dev`: Generate meta artifact + start Vite dev server
|
||||
- `build`: Generate meta artifact + typecheck + Vite build
|
||||
- `generate-meta`: Generate the meta artifact only
|
||||
- **Testing:** No formal unit testing framework. Validation occurs through:
|
||||
- Script-based validation of every `blueprints/<id>/meta.json` (`build-scripts/generate-meta.js --check`)
|
||||
- Manual testing via Dokploy preview deployments (import base64 from PR preview)
|
||||
- TypeScript compilation errors caught during build
|
||||
- ESLint for code quality in frontend
|
||||
- **CI/CD Process:**
|
||||
- **On blueprint changes:** Validates each template's metadata (required fields, id/folder match, logo file exists) and rejects a committed root `meta.json`.
|
||||
- **On PR creation:** Builds preview deployment, generates base64 import for testing in Dokploy.
|
||||
- **Preview testing:** Use PR description link → Search template → Copy base64 → Import in Dokploy instance.
|
||||
|
||||
## 7. Specific Instructions for AI Collaboration
|
||||
|
||||
- **Contribution Guidelines:**
|
||||
- Follow existing Dokploy template structure strictly. Each blueprint must be independent with no shared state.
|
||||
- **CRITICAL:** Template metadata lives in `blueprints/<id>/meta.json` (one object per template). Never create or edit a root-level `meta.json` — it is generated at build time. Run `node build-scripts/generate-meta.js --check` after any metadata edits.
|
||||
- Test templates in Dokploy preview before submitting PRs (use base64 import from PR preview).
|
||||
- Add logo file (SVG preferred, ~128x128px) to blueprint folder.
|
||||
- Ensure the `id` in `blueprints/<id>/meta.json` exactly matches the blueprint folder name (lowercase kebab-case).
|
||||
- **Docker Compose Conventions (CRITICAL):**
|
||||
|
||||
- **Version:** MUST be `3.8`
|
||||
- **NEVER include:** `ports` (use `expose` only), `container_name`, `networks` (Dokploy handles isolation)
|
||||
- **ALWAYS include:** `restart: unless-stopped` or `restart: always`, persistent volumes
|
||||
- **Service naming:** MUST match blueprint folder name exactly
|
||||
- **Example:**
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
ghost:
|
||||
image: ghost:6-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- ghost:/var/lib/ghost/content
|
||||
volumes:
|
||||
ghost:
|
||||
```
|
||||
|
||||
- **template.toml Conventions:**
|
||||
|
||||
- **Variables:** Define in `[variables]` section, use helpers for secrets
|
||||
- **Domains:** `[[config.domains]]` with `serviceName`, `port`, `host` (path optional)
|
||||
- **Env:** Array of strings: `env = ["KEY=VALUE", "DB_PASSWORD=${db_pass}"]`
|
||||
- **Available helpers:** `${domain}`, `${password:length}`, `${base64:length}`, `${hash:length}`, `${uuid}`, `${randomPort}`, `${email}`, `${username}`, `${timestamp}`, `${timestamps:datetime}`, `${timestampms:datetime}`, `${jwt:secret_var:payload_var}`
|
||||
- **JWT helper example:** `${jwt:mysecret:mypayload}` with payload containing `exp: ${timestamps:2030-01-01T00:00:00Z}`
|
||||
- **Volume bind mounts (in docker-compose.yml):** When mounting host paths, do NOT use absolute paths like `"/folder:/path/in/container"`. Use relative paths instead:
|
||||
```yaml
|
||||
# Invalid
|
||||
volumes:
|
||||
- "/folder:/path/in/container" ❌
|
||||
# Valid
|
||||
volumes:
|
||||
- "../files/my-database:/var/lib/mysql" ✅
|
||||
- "../files/my-configs:/etc/my-app/config" ✅
|
||||
```
|
||||
|
||||
- **meta.json Requirements:**
|
||||
|
||||
- **Required fields:** `id`, `name`, `version`, `description`, `links` (with `github`), `logo`, `tags` (array)
|
||||
- **Tags:** Lowercase strings (e.g., `["monitoring", "database"]`)
|
||||
- **Version:** MUST match Docker image version in docker-compose.yml
|
||||
- **Logo:** Filename only (e.g., `"ghost.jpeg"`), file must exist in blueprint folder
|
||||
|
||||
- **Frontend Development:**
|
||||
|
||||
- Use TypeScript with strict type checking
|
||||
- Follow React hooks patterns, avoid class components
|
||||
- Use Zustand selectors for state access to optimize re-renders
|
||||
- Fuse.js searches across `name`, `description`, `tags`, `id` fields
|
||||
- Use shadcn/ui components for consistency
|
||||
- Sync URL params with search state via React Router
|
||||
|
||||
- **Security:**
|
||||
|
||||
- Be mindful of security when handling template configurations
|
||||
- NEVER hardcode secrets in templates - use Dokploy's variable system with helpers
|
||||
- Pin Docker images to specific versions to avoid supply chain attacks
|
||||
- Validate user input in frontend before processing
|
||||
|
||||
- **Dependencies:**
|
||||
|
||||
- When adding new templates, ensure Docker images are pinned to specific versions
|
||||
- Update meta.json with exact version matching Docker Compose image version
|
||||
- For frontend dependencies: Use `pnpm add <package>` in `app/` directory
|
||||
- For root dependencies: Use `npm install <package>` in root directory
|
||||
|
||||
- **Commit Messages:** Follow conventional commit patterns (e.g., `feat:`, `fix:`, `docs:`, `chore:`).
|
||||
|
||||
- **Common Pitfalls to Avoid:**
|
||||
1. Forgetting to process meta.json after editing (CI will fail)
|
||||
2. Template ID mismatch between meta.json and folder name
|
||||
3. Including `ports`, `container_name`, or `networks` in docker-compose.yml
|
||||
4. Using object syntax for env vars in template.toml (must be array of strings)
|
||||
5. Logo file missing or filename mismatch in meta.json
|
||||
6. Version mismatch between meta.json and docker-compose.yml image tag
|
||||
@@ -1,213 +0,0 @@
|
||||
# Contributing to Dokploy Open Source Templates
|
||||
|
||||
Thank you for your interest in contributing to the Dokploy Open Source Templates repository! This project maintains a collection of Docker Compose templates for easy deployment via Dokploy. Contributions help expand and improve the available templates for the community.
|
||||
|
||||
We welcome contributions in the form of new templates, improvements to existing ones, bug fixes, documentation updates, and more. Please follow the guidelines below to ensure a smooth review and integration process.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project adheres to the [Contributor Covenant Code of Conduct](https://www.contributor-covenant.org/version/2/1/code_of_conduct/). By participating, you are expected to uphold this code. Reports of unacceptable behavior can be directed to the project maintainers.
|
||||
|
||||
## How to Contribute
|
||||
|
||||
1. **Fork the Repository**: Start by forking this repository to your GitHub account.
|
||||
2. **Create a New Branch**: From your fork, create a feature branch named descriptively (e.g., `add-grafana-template`).
|
||||
3. **Make Your Changes**: Follow the specific guidelines for the type of contribution (e.g., adding a template).
|
||||
4. **Commit and Push**: Commit your changes with clear, concise messages. Push the branch to your fork.
|
||||
5. **Open a Pull Request (PR)**: Create a PR from your branch to the main repository. Reference any related issues in the PR description.
|
||||
6. **Automated Preview**: Every PR automatically deploys a preview of your template to Dokploy. You can test it before merging (see "Testing Your Contribution" below).
|
||||
7. **Review and Merge**: Respond to feedback from maintainers. Once approved, your changes will be merged.
|
||||
|
||||
For larger changes or questions, open an issue first to discuss your ideas.
|
||||
|
||||
### Pull Request Guidelines
|
||||
|
||||
- **Keep PRs small and focused.** Avoid very large PRs; prefer several smaller PRs (e.g., one template or one logical change per PR). This speeds up review and keeps the history clear.
|
||||
- **Test before submitting.** Any PR that has not been tested by the contributor will be closed. This keeps the PR queue tidy and ensures that only contributions that have been verified by their authors are considered.
|
||||
|
||||
## Adding a New Template
|
||||
|
||||
To add a new template, follow these steps:
|
||||
|
||||
1. **Create a Folder in `blueprints/`**: Name it after the template (e.g., `grafana`). Use lowercase with hyphens for multi-word names.
|
||||
2. **Add `docker-compose.yml`**: Define the services, volumes, and other Compose configurations. Key guidelines:
|
||||
|
||||
- Use `version: "3.8"` or later.
|
||||
- Avoid exposing ports explicitly (e.g., no `ports: - "3000:3000"`; just `- 3000` if needed for internal reference).
|
||||
- Do not use `container_name`.
|
||||
- Avoid custom networks like `dokploy-network`; Dokploy handles isolation automatically.
|
||||
- Example for Grafana:
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
grafana:
|
||||
image: grafana/grafana-enterprise:9.5.20
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- grafana-storage:/var/lib/grafana
|
||||
volumes:
|
||||
grafana-storage: {}
|
||||
```
|
||||
|
||||
3. **Add `template.toml`**: Configure domains, environment variables, mounts, and variables. This file is crucial for Dokploy integration.
|
||||
|
||||
- Sections: `[variables]` (optional), `[config]` with `[[config.domains]]`, `[[config.env]]`, `[[config.mounts]]`.
|
||||
- Use `${variable}` syntax to reference variables or helpers (e.g., `${domain}`, `${password:32}`).
|
||||
- Match `serviceName` in domains to the service name in `docker-compose.yml`.
|
||||
- Example for Grafana:
|
||||
|
||||
```toml
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "grafana"
|
||||
port = 3000
|
||||
host = "${main_domain}"
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "/content/file.txt"
|
||||
content = """
|
||||
My content
|
||||
"""
|
||||
```
|
||||
|
||||
4. **Add `meta.json` in your template folder**: Create `blueprints/<id>/meta.json` with a single JSON object describing your template. Do NOT create or edit a root-level `meta.json` — the global registry is generated automatically at build time from every `blueprints/<id>/meta.json` (this is what makes template PRs conflict-free). Include:
|
||||
|
||||
- `id`: Folder name (e.g., "grafana") — must match the folder exactly.
|
||||
- `name`: Display name.
|
||||
- `version`: Image/tag version.
|
||||
- `description`: Brief overview.
|
||||
- `logo`: Filename of the logo (e.g., "grafana.svg").
|
||||
- `links`: Object with `github`, `website`, `docs` (URLs; use `""` if not applicable).
|
||||
- `tags`: Array of categories (e.g., ["monitoring"]).
|
||||
- Example (`blueprints/grafana/meta.json`):
|
||||
```json
|
||||
{
|
||||
"id": "grafana",
|
||||
"name": "Grafana",
|
||||
"version": "9.5.20",
|
||||
"description": "Grafana is an open source platform for data visualization and monitoring.",
|
||||
"logo": "grafana.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/grafana/grafana",
|
||||
"website": "https://grafana.com/",
|
||||
"docs": "https://grafana.com/docs/"
|
||||
},
|
||||
"tags": ["monitoring"]
|
||||
}
|
||||
```
|
||||
|
||||
5. **Add a Logo**: Place an SVG or PNG logo (e.g., `grafana.svg`) in the template folder. Keep it simple and representative (ideally 512x512 or similar).
|
||||
|
||||
6. **Run Validation**: Before pushing, run `node build-scripts/generate-meta.js --check` to validate every template's metadata (same check CI runs), and manually check for syntax errors in YAML/TOML.
|
||||
|
||||
7. **Commit and PR**: Push your branch and open a PR. Include:
|
||||
- A description of the template.
|
||||
- The preview URL (auto-generated).
|
||||
- Any testing notes.
|
||||
|
||||
## Template.toml Structure
|
||||
|
||||
- **[variables]**: Define reusable values or use helpers.
|
||||
Example:
|
||||
|
||||
```toml
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
my_password = "${password:32}"
|
||||
```
|
||||
|
||||
- **[config.domains]**: Map services to domains/ports.
|
||||
|
||||
- Required: `serviceName`, `port`, `host`.
|
||||
- Optional: `path`.
|
||||
|
||||
- **[config.env]**: Array of environment variables (strings).
|
||||
Example: `env = ["GF_SECURITY_ADMIN_PASSWORD=${password:32}"]`
|
||||
|
||||
- **[config.mounts]**: Inline files or configs.
|
||||
- `filePath`: Destination in container.
|
||||
- `content`: Multi-line string.
|
||||
|
||||
### Helpers
|
||||
|
||||
Use these in `${}` for dynamic values:
|
||||
|
||||
- `${domain}`: Random subdomain.
|
||||
- `${password:length}`: Random password (default 32 chars).
|
||||
- `${base64:length}`: Base64-encoded random string.
|
||||
- `${hash:length}`: Random hash.
|
||||
- `${uuid}`: UUID.
|
||||
- `${randomPort}`: Random port.
|
||||
- `${email}`: Random email.
|
||||
- `${username}`: Random lowercase username.
|
||||
- `${timestamp}`: Current timestamp (ms).
|
||||
- `${timestamps:datetime}` or `${timestampms:datetime}`: Timestamp at specific date (e.g., `${timestamps:2030-01-01T00:00:00Z}`).
|
||||
- `${jwt:secret_var:payload_var}`: JWT token (advanced; see README for details).
|
||||
|
||||
## Best Practices and Suggestions
|
||||
|
||||
- **Docker Compose**:
|
||||
|
||||
- Omit explicit ports; let Dokploy handle exposure.
|
||||
- Use persistent volumes for data (e.g., databases).
|
||||
|
||||
- **Template.toml**:
|
||||
|
||||
- Keep service names consistent across files.
|
||||
- Use variables/helpers for secrets (e.g., passwords, API keys).
|
||||
- Minimize env vars; only include essentials.
|
||||
|
||||
- **General**:
|
||||
|
||||
- Templates should be self-contained and deployable out-of-the-box.
|
||||
- Use official/latest stable images.
|
||||
- Avoid hardcoding domains, networks, or secrets.
|
||||
- Ensure compatibility with Dokploy's isolated deployments.
|
||||
|
||||
- **Logo and Metadata**:
|
||||
- Logos should be vector (SVG preferred) or high-res PNG.
|
||||
- Descriptions: 1-2 sentences, SEO-friendly.
|
||||
- Tags: Relevant categories (e.g., "database", "web", "monitoring").
|
||||
|
||||
## Testing Your Contribution
|
||||
|
||||
Before submitting a PR:
|
||||
|
||||
1. Run the project locally:
|
||||
```bash
|
||||
cd app
|
||||
pnpm install
|
||||
pnpm run dev
|
||||
# Visit http://localhost:5173/
|
||||
```
|
||||
2. Use the PR preview:
|
||||
- After pushing, check the PR for the auto-generated preview URL.
|
||||
- Search for your template, copy the Base64 config.
|
||||
- In your Dokploy instance: Create a Compose service > Advanced > Import > Paste Base64 > Deploy.
|
||||
- Verify the service starts and is accessible (e.g., via assigned domain).
|
||||
3. Test edge cases: Restart, volume persistence, env vars.
|
||||
|
||||
If issues arise, debug locally or in the preview. Fix and update your PR.
|
||||
|
||||
## Updating Existing Templates
|
||||
|
||||
- Follow the same structure as adding new ones.
|
||||
- Bump `version` in the template's `blueprints/<id>/meta.json` and update `docker-compose.yml` image tags.
|
||||
- Test thoroughly, as changes may affect users.
|
||||
|
||||
## Reporting Bugs or Requesting Features
|
||||
|
||||
- Open an issue with:
|
||||
- Clear title and description.
|
||||
- Steps to reproduce (for bugs).
|
||||
- Template details (for feature requests).
|
||||
- Use the issue templates in `.github/ISSUE_TEMPLATE/`.
|
||||
|
||||
## Questions?
|
||||
|
||||
If you're unsure about anything, open a discussion or issue. We're here to help!
|
||||
|
||||
Happy contributing! 🚀
|
||||
@@ -7,14 +7,14 @@ This is the official repository for the Dokploy Open Source Templates.
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a new branch
|
||||
3. Add the template to the `blueprints` folder (`docker-compose.yml`, `template.toml`)
|
||||
4. Add the template metadata (name, description, version, logo, links, tags) in a `meta.json` file inside your template folder (`blueprints/<id>/meta.json`) — do NOT create or edit a root-level `meta.json`, it is generated at build time
|
||||
3. Add the template to the `blueprints` folder (docker-compose.yml, template.yml)
|
||||
4. Add the template metadata (name, description, version, logo, links, tags) to the `meta.json` file
|
||||
5. Add the logo to the template folder
|
||||
6. Commit and push your changes
|
||||
7. Create a pull request (PR)
|
||||
8. Every PR will automatically deploy a preview of the template to Dokploy.
|
||||
9. if anyone want to test the template before merging it, you can enter to the preview URL in the PR description, and search the template, click on the Template Card, scroll down and then copy the BASE64 value, and paste in the advanced section of your compose service, in the Import section or optional you can use the preview URL and paste in the
|
||||
BASE URL when creating a template.
|
||||
BASE URL when creating a template.
|
||||
|
||||
#### Optional
|
||||
|
||||
@@ -45,24 +45,21 @@ services:
|
||||
volumes:
|
||||
grafana-storage: {}
|
||||
```
|
||||
3. Add the `template.toml` file to the folder, this is where we specify the domains, mounts and env variables, to understand more the structure of `template.toml` you can read here [Template.toml structure](#template.toml-structure)
|
||||
3. Add the `template.yml` file to the folder, this is where we specify the domains, mounts and env variables, to understand more the structure of `template.yml` you can read here [Template.yml structure](#templateyml-structure)
|
||||
|
||||
```toml
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
```yaml
|
||||
variables:
|
||||
main_domain: ${domain}
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "grafana"
|
||||
port = 3000
|
||||
host = "${main_domain}"
|
||||
|
||||
|
||||
[config.env]
|
||||
|
||||
[[config.mounts]]
|
||||
config:
|
||||
domains:
|
||||
- serviceName: grafana
|
||||
port: 3000
|
||||
host: ${main_domain}
|
||||
env: []
|
||||
mounts: []
|
||||
```
|
||||
4. Add the meta information in `blueprints/<id>/meta.json` (a single JSON object; the root `meta.json` is generated automatically at build time)
|
||||
4. Add the `meta.json` file to the folder
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -85,144 +82,67 @@ host = "${main_domain}"
|
||||
6. Commit and push your changes
|
||||
7. Create a pull request
|
||||
|
||||
### Template.toml structure
|
||||
### Template.yml structure
|
||||
|
||||
Dokploy use a defined structure for the `template.yml` file, we have 4 sections available:
|
||||
|
||||
Dokploy use a defined structure for the `template.toml` file, we have 4 sections available:
|
||||
|
||||
1. `variables`: This is where we define the variables that will be used in the `domains`, `env` and `mounts` sections.
|
||||
2. `domains`: This is where we define the configuration for the template.
|
||||
3. `env`: This is where we define the environment variables for the template.
|
||||
4. `mounts`: This is where we define the mounts for the template.
|
||||
|
||||
|
||||
- The `variables(Optional)` structure is the following:
|
||||
|
||||
```toml
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
my_domain = "https://my-domain.com"
|
||||
my_password = "${password:32}"
|
||||
any_helper = "${you-can-use-any-helper}"
|
||||
```yaml
|
||||
variables:
|
||||
main_domain: ${domain}
|
||||
my_domain: https://my-domain.com
|
||||
my_password: ${password:32}
|
||||
any_helper: ${you-can-use-any-helper}
|
||||
```
|
||||
|
||||
- The `config` structure is the following:
|
||||
|
||||
```toml
|
||||
[config]
|
||||
# Optional sections below
|
||||
```yaml
|
||||
config:
|
||||
domains: # Optional
|
||||
- serviceName: grafana # Required
|
||||
port: 3000 # Required
|
||||
host: ${main_domain} # Required
|
||||
path: / # Optional
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "grafana" # Required
|
||||
port = 3000 # Required
|
||||
host = "${main_domain}" # Required
|
||||
path = "/" # Optional
|
||||
env: # Optional
|
||||
- AP_HOST=${main_domain}
|
||||
- AP_API_KEY=${api_key}
|
||||
- AP_ENCRYPTION_KEY=${encryption_key}
|
||||
- AP_JWT_SECRET=${jwt_secret}
|
||||
- AP_POSTGRES_PASSWORD=${postgres_password}
|
||||
|
||||
env = [
|
||||
"AP_HOST=${main_domain}",
|
||||
"AP_API_KEY=${api_key}",
|
||||
"AP_ENCRYPTION_KEY=${encryption_key}",
|
||||
"AP_JWT_SECRET=${jwt_secret}",
|
||||
"AP_POSTGRES_PASSWORD=${postgres_password}"
|
||||
]
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "/content/file.txt"
|
||||
content = """
|
||||
My content
|
||||
"""
|
||||
mounts: # Optional or []
|
||||
- filePath: /content/file.txt
|
||||
content: |
|
||||
My content
|
||||
```
|
||||
|
||||
Important: you can reference any variable in the `domains`, `env` and `mounts` sections. just use the `${variable_name}` syntax, in the case you don't want to define a variable, you can use the `domain`, `base64`, `password`, `hash`, `uuid`, `randomPort`, `timestamp`, `jwt`, `email`, or `username` helpers.
|
||||
Important: you can reference any variable in the `domains`, `env` and `mounts` sections. just use the `${variable_name}` syntax, in the case you don't want to define a variable, you can use the `domain`, `base64`, `password`, `hash`, `uuid`, `randomPort` or `timestamp` helpers.
|
||||
|
||||
### Helpers
|
||||
|
||||
We have a few helpers that are very common when creating a template, these are:
|
||||
|
||||
- `domain`: This is a helper that will generate a random domain for the template.
|
||||
- `base64 or base64:length`: This is a helper that will encode a string to base64 (lenght is the number of bytes to encode not the encoded string length).
|
||||
- `base64 or base64:length`: This is a helper that will encode a string to base64.
|
||||
- `password or password:length`: This is a helper that will generate a random password for the template.
|
||||
- `hash or hash:length`: This is a helper that will generate a hash for the template
|
||||
- `hash or hash:length`: This is a helper that will generate a hash for the template.
|
||||
- `uuid`: This is a helper that will generate a uuid for the template.
|
||||
- `randomPort`: This is a helper that will generate a random port for the template.
|
||||
- `email`: This is a helper that will generate a random email for the template.
|
||||
- `username`: This is a helper that will generate a random username in lowercase for the template.
|
||||
- `timestamp`: This is a helper that will generate a timestamp for "now" in milli-second.
|
||||
- `timestampms or timestampms:datetime`: This is a helper that will generate a timestamp in milli-seconds.
|
||||
- `timestamps or timestamps:datetime`: This is a helper that will generate a timestamp in seconds.
|
||||
- `datetime` parameter for `timestamps/timestampms` helpers must be a valid value for javascript new Date() (ie: `timestamps:2030-01-01T00:00:00Z`)
|
||||
- `jwt`: This is a helper that will generate a jwt for the template.
|
||||
- `jwt:length`: will generate a random hex string of bytes length. _This should not be used in newer templates_
|
||||
- `jwt:secret_var_name`: will generate a jwt with some default values, secret var name should be the name of the variable holding the secret
|
||||
- `jwt:secret_var_name:payload_var_name`: is the same as above but you can pass partial or full payload for the jwt.
|
||||
Here's a full example
|
||||
```toml
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
mysecret = "cQsdycq1hDLopQonF6jUTqgQc5WEZTwWLL02J6XJ"
|
||||
mypayload = """
|
||||
{
|
||||
"role": "jwt-tester",
|
||||
"iss": "dokploy-templates",
|
||||
"exp": ${timestamps:2030-01-01T00:00:00Z}
|
||||
}
|
||||
"""
|
||||
jwt = "${jwt:mysecret:mypayload}"
|
||||
```
|
||||
- `timestamp`: This is a helper that will generate a timestamp.
|
||||
- `jwt or jwt:length`: This is a helper that will generate a jwt for the template.
|
||||
|
||||
|
||||
|
||||
## General Requirements when creating a template
|
||||
|
||||
- Don't use this way in your docker compose file:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
grafana:
|
||||
image: grafana/grafana-enterprise:9.5.20
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 3000:3000
|
||||
|
||||
# Instead use this way:
|
||||
expose:
|
||||
- 3000
|
||||
```
|
||||
|
||||
- Don't use this way in your template.toml file, make sure to use the same service name as the one in the docker compose file:
|
||||
|
||||
```toml
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "MyGrafanaService"
|
||||
# Instead use this way:
|
||||
serviceName = "grafana" # Make sure to use the same service name as the one in the docker compose file
|
||||
```
|
||||
|
||||
- Don't use container_name in your docker compose file, make sure to use the same service name as the one in the template.toml file:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
grafana:
|
||||
container_name: grafana # ❌ Remove this
|
||||
```
|
||||
|
||||
- Don't use dokploy-network in your docker compose file, by default all the templates have this flag enabled https://docs.dokploy.com/docs/core/docker-compose/utilities#isolated-deployments, so by default they have a internal network created, so you don't need to create a new one or use the dokploy-network name.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
grafana:
|
||||
networks:
|
||||
- dokploy-network # ❌ Remove this or any other network defined
|
||||
```
|
||||
|
||||
|
||||
- Please before submit a PR, make sure to test the template in your instance, so the maintainers don't spend time trying to figure out what's wrong.
|
||||
|
||||
1. Everytime you submit a PR, it will display a Preview Link.
|
||||
2. Enter to the Preview Link and search the template you've submitted.
|
||||
3. Click on the Template Card, and click the Copy Button in the Base64 Configuration.
|
||||
4. Go to your instance, create a new Compose Service, go to Advanced Section -> Scroll Down -> Import Section -> Paste the Base64 Value -> Click on the Import Button
|
||||
5. If everything is correct and set, you should see a modal with all the details (Compose File, Environment Variables, Mounts, Domains, etc)
|
||||
6. Now you can click on the Deploy Button and wait for the deployment to finish, and try to access to the service, if everything is correct you should access to the service and see the template working.
|
||||
|
||||
|
||||
Each template's metadata lives in `blueprints/<id>/meta.json`; the global `meta.json` is generated at build time. Use `node build-scripts/generate-meta.js --check` to validate all template metadata.
|
||||
|
||||
@@ -4,53 +4,49 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node ../build-scripts/generate-meta.js && vite",
|
||||
"build": "node ../build-scripts/generate-meta.js && tsc -b && vite build",
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"generate-meta": "node ../build-scripts/generate-meta.js"
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.19.1",
|
||||
"@codemirror/lang-json": "^6.0.2",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/language": "^6.11.3",
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-yaml": "^6.1.1",
|
||||
"@codemirror/language": "^6.10.1",
|
||||
"@codemirror/legacy-modes": "6.4.0",
|
||||
"@codemirror/view": "6.29.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@uiw/codemirror-theme-github": "^4.25.2",
|
||||
"@uiw/react-codemirror": "^4.25.2",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-popover": "^1.1.6",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-tabs": "^1.1.3",
|
||||
"@tailwindcss/vite": "^4.0.12",
|
||||
"@uiw/codemirror-theme-github": "^4.22.1",
|
||||
"@uiw/react-codemirror": "^4.22.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.0",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"lucide-react": "^0.479.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.9.4",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"next-themes": "^0.4.5",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"sonner": "^2.0.1",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss": "^4.0.12",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vite-plugin-static-copy": "^2.3.2",
|
||||
"yaml": "2.7.1",
|
||||
"zustand": "^5.0.8"
|
||||
"vite-plugin-static-copy": "2.3.0",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.19.23",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@types/node": "^20.8.2",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"globals": "^15.15.0",
|
||||
"typescript": "~5.7.3",
|
||||
"vite": "^6.4.1"
|
||||
"typescript": "~5.7.2",
|
||||
"vite": "^6.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
packages:
|
||||
- .
|
||||
allowBuilds:
|
||||
esbuild: false
|
||||
@@ -1 +0,0 @@
|
||||
meta.json
|
||||
@@ -1 +0,0 @@
|
||||
../../blueprints
|
||||
@@ -1,36 +0,0 @@
|
||||
import yaml from "yaml";
|
||||
import toml from "@iarna/toml";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
function convertYamlToToml(yamlContent) {
|
||||
const parsedYaml = yaml.parse(yamlContent);
|
||||
return toml.stringify(parsedYaml);
|
||||
}
|
||||
|
||||
function processDirectory(dirPath) {
|
||||
const files = fs.readdirSync(dirPath);
|
||||
|
||||
files.forEach((file) => {
|
||||
const filePath = path.join(dirPath, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
processDirectory(filePath);
|
||||
} else if (file === "template.yml") {
|
||||
console.log(`Converting ${filePath}`);
|
||||
const yamlContent = fs.readFileSync(filePath, "utf8");
|
||||
const tomlContent = convertYamlToToml(yamlContent);
|
||||
const tomlPath = path.join(dirPath, "template.toml");
|
||||
fs.writeFileSync(tomlPath, tomlContent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Ruta al directorio blueprints relativa al script
|
||||
const blueprintsPath = path.join(__dirname, "..", "blueprints");
|
||||
processDirectory(blueprintsPath);
|
||||
@@ -3,19 +3,16 @@ import Navigation from "./components/Navigation";
|
||||
import Search from "./components/Search";
|
||||
import { useStore } from "@/store";
|
||||
import "./App.css";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
function App() {
|
||||
const view = useStore((state) => state.view);
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div className="min-h-screen">
|
||||
<Navigation />
|
||||
<Search />
|
||||
<TemplateGrid view={view} />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
<div className="min-h-screen">
|
||||
<Navigation />
|
||||
<Search />
|
||||
<TemplateGrid view={view} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,7 @@ const Navigation = () => {
|
||||
"https://api.github.com/repos/dokploy/dokploy"
|
||||
);
|
||||
const data = await response.json();
|
||||
setGithubStars(
|
||||
Number.isFinite(data.stargazers_count) ? data.stargazers_count : 0
|
||||
);
|
||||
setGithubStars(data.stargazers_count);
|
||||
} catch (error) {
|
||||
console.error("Error fetching GitHub stars:", error);
|
||||
}
|
||||
@@ -34,7 +32,7 @@ const Navigation = () => {
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.open(
|
||||
"https://github.com/Dokploy/templates/issues/new",
|
||||
"https://github.com/Dokploy/dokploy/discussions/new?category=ideas",
|
||||
"_blank"
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -16,10 +16,9 @@ import { Check, ChevronsUpDown } from "lucide-react";
|
||||
import React from "react";
|
||||
import { Tabs, TabsList, TabsTrigger } from "./ui/tabs";
|
||||
import SelectedTags from "./SelectedTags";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
const Search = () => {
|
||||
const { templates, searchQuery, setSearchQuery, setView, templatesCount, setFilteredTemplates, setTemplatesCount } =
|
||||
const { templates, searchQuery, setSearchQuery, setView, templatesCount } =
|
||||
useStore();
|
||||
const selectedTags = useStore((state) => state.selectedTags);
|
||||
const addSelectedTag = useStore((state) => state.addSelectedTag);
|
||||
@@ -27,7 +26,6 @@ const Search = () => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [tagSearch, setTagSearch] = React.useState("");
|
||||
const view = useStore((state) => state.view);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
// Get all unique tags, safely handle empty templates
|
||||
const uniqueTags = React.useMemo(() => {
|
||||
@@ -45,54 +43,6 @@ const Search = () => {
|
||||
);
|
||||
}, [uniqueTags, tagSearch]);
|
||||
|
||||
// Initialize search query from URL params and apply filters
|
||||
React.useEffect(() => {
|
||||
const queryFromUrl = searchParams.get("q") || "";
|
||||
if (queryFromUrl !== searchQuery) {
|
||||
setSearchQuery(queryFromUrl);
|
||||
}
|
||||
|
||||
// Apply filters whenever templates, search query or selected tags change
|
||||
if (templates) {
|
||||
const filtered = templates.filter((template) => {
|
||||
// Filter by search query - search across name and description
|
||||
const searchTerm = queryFromUrl.toLowerCase();
|
||||
const matchesSearch =
|
||||
template.name.toLowerCase().includes(searchTerm) ||
|
||||
template.description.toLowerCase().includes(searchTerm);
|
||||
|
||||
// Filter by selected tags
|
||||
const matchesTags =
|
||||
selectedTags.length === 0 ||
|
||||
selectedTags.every((tag) => template.tags.includes(tag));
|
||||
|
||||
return matchesSearch && matchesTags;
|
||||
});
|
||||
|
||||
setFilteredTemplates(filtered);
|
||||
setTemplatesCount(filtered.length);
|
||||
}
|
||||
}, [searchParams, templates, selectedTags, setSearchQuery, setFilteredTemplates, setTemplatesCount]);
|
||||
|
||||
// Update URL params when search query changes
|
||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newQuery = e.target.value;
|
||||
setSearchQuery(newQuery);
|
||||
if (newQuery) {
|
||||
setSearchParams({ q: newQuery });
|
||||
} else {
|
||||
searchParams.delete("q");
|
||||
setSearchParams(searchParams);
|
||||
}
|
||||
};
|
||||
|
||||
// Clear search and URL params
|
||||
const handleClearSearch = () => {
|
||||
setSearchQuery("");
|
||||
searchParams.delete("q");
|
||||
setSearchParams(searchParams);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className=" mx-auto p-4 lg:p-12 border-b w-full">
|
||||
{/* <h1 className="text-2xl md:text-3xl xl:text-4xl font-bold text-center mb-8">
|
||||
@@ -113,11 +63,11 @@ const Search = () => {
|
||||
type="text"
|
||||
placeholder="Search templates..."
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full p-6"
|
||||
/>
|
||||
{searchQuery.length > 0 ? (
|
||||
<div className="cursor-pointer" onClick={handleClearSearch}>
|
||||
<div className="cursor-pointer" onClick={() => setSearchQuery("")}>
|
||||
<XIcon className="absolute end-3 translate-y-3.5 top-1/2 h-5 w-5 text-gray-400" />
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -14,7 +14,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
|
||||
import { Label } from "./ui/label";
|
||||
import { Clipboard } from "lucide-react";
|
||||
import { Input } from "./ui/input";
|
||||
import Markdown from "./ui/markdown";
|
||||
|
||||
interface Template {
|
||||
id: string;
|
||||
@@ -33,7 +32,6 @@ interface Template {
|
||||
interface TemplateFiles {
|
||||
dockerCompose: string | null;
|
||||
config: string | null;
|
||||
instructions: string | null;
|
||||
}
|
||||
|
||||
interface TemplateDialogProps {
|
||||
@@ -57,9 +55,7 @@ const TemplateDialog: React.FC<TemplateDialogProps> = ({
|
||||
config: templateFiles.config || "",
|
||||
};
|
||||
|
||||
// Use encodeURIComponent to handle Unicode characters properly
|
||||
const jsonString = JSON.stringify(configObj, null, 2);
|
||||
return btoa(unescape(encodeURIComponent(jsonString)));
|
||||
return btoa(JSON.stringify(configObj, null, 2));
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -171,23 +167,8 @@ const TemplateDialog: React.FC<TemplateDialogProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Tabs
|
||||
defaultValue={
|
||||
templateFiles?.instructions
|
||||
? "instructions"
|
||||
: "docker-compose"
|
||||
}
|
||||
className="w-full"
|
||||
>
|
||||
<Tabs defaultValue="docker-compose" className="w-full">
|
||||
<TabsList className="w-full justify-start">
|
||||
{templateFiles?.instructions && (
|
||||
<TabsTrigger
|
||||
value="instructions"
|
||||
className="data-[state=active]:font-bold"
|
||||
>
|
||||
Instructions
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger
|
||||
value="docker-compose"
|
||||
className="data-[state=active]:font-bold"
|
||||
@@ -201,24 +182,6 @@ const TemplateDialog: React.FC<TemplateDialogProps> = ({
|
||||
Configuration
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{templateFiles?.instructions && (
|
||||
<TabsContent value="instructions" className="mt-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="flex flex-col items-start w-fit justify-start gap-1">
|
||||
<span className="leading-tight text-xl font-semibold">
|
||||
Setup Instructions
|
||||
</span>
|
||||
<span className="leading-tight text-sm text-gray-500">
|
||||
instructions.md
|
||||
</span>
|
||||
</Label>
|
||||
<div className="rounded-md border p-4">
|
||||
<Markdown content={templateFiles.instructions} />
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
<TabsContent value="docker-compose" className="mt-4">
|
||||
{templateFiles?.dockerCompose && (
|
||||
<div className="space-y-2">
|
||||
@@ -265,7 +228,7 @@ const TemplateDialog: React.FC<TemplateDialogProps> = ({
|
||||
<div className="relative w-full rounded-md overflow-hidden border">
|
||||
<CodeEditor
|
||||
value={templateFiles.config || ""}
|
||||
language="toml"
|
||||
language="yaml"
|
||||
className="font-mono w-full [&_*]:!break-words"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -28,7 +28,6 @@ interface Template {
|
||||
interface TemplateFiles {
|
||||
dockerCompose: string | null;
|
||||
config: string | null;
|
||||
instructions: string | null;
|
||||
}
|
||||
|
||||
interface TemplateGridProps {
|
||||
@@ -81,34 +80,20 @@ const TemplateGrid: React.FC<TemplateGridProps> = ({ view }) => {
|
||||
const fetchTemplateFiles = async (templateId: string) => {
|
||||
setModalLoading(true);
|
||||
try {
|
||||
const [dockerComposeRes, configRes, instructionsRes] = await Promise.all(
|
||||
[
|
||||
fetch(`/blueprints/${templateId}/docker-compose.yml`),
|
||||
fetch(`/blueprints/${templateId}/template.toml`),
|
||||
fetch(`/blueprints/${templateId}/instructions.md`),
|
||||
]
|
||||
);
|
||||
const [dockerComposeRes, configRes] = await Promise.all([
|
||||
fetch(`/blueprints/${templateId}/docker-compose.yml`),
|
||||
fetch(`/blueprints/${templateId}/template.yml`),
|
||||
]);
|
||||
|
||||
const dockerCompose = dockerComposeRes.ok
|
||||
? await dockerComposeRes.text()
|
||||
: null;
|
||||
const config = configRes.ok ? await configRes.text() : null;
|
||||
|
||||
// Guard against SPA fallbacks that return index.html with a 200 status
|
||||
// for templates that don't have an instructions.md file.
|
||||
const instructionsIsMarkdown =
|
||||
instructionsRes.ok &&
|
||||
!(instructionsRes.headers.get("content-type") || "").includes(
|
||||
"text/html"
|
||||
);
|
||||
const instructions = instructionsIsMarkdown
|
||||
? await instructionsRes.text()
|
||||
: null;
|
||||
|
||||
setTemplateFiles({ dockerCompose, config, instructions });
|
||||
setTemplateFiles({ dockerCompose, config });
|
||||
} catch (err) {
|
||||
console.error("Error fetching template files:", err);
|
||||
setTemplateFiles({ dockerCompose: null, config: null, instructions: null });
|
||||
setTemplateFiles({ dockerCompose: null, config: null });
|
||||
} finally {
|
||||
setModalLoading(false);
|
||||
}
|
||||
@@ -135,6 +120,7 @@ const TemplateGrid: React.FC<TemplateGridProps> = ({ view }) => {
|
||||
return matchesSearch && matchesTags;
|
||||
});
|
||||
|
||||
console.log("ffiltered tem", filtered.length);
|
||||
setTemplatesCount(filtered.length);
|
||||
setFilteredTemplates(filtered);
|
||||
}, [searchQuery, selectedTags]);
|
||||
|
||||
@@ -121,7 +121,7 @@ function dockerComposeComplete(
|
||||
interface Props extends ReactCodeMirrorProps {
|
||||
wrapperClassName?: string;
|
||||
disabled?: boolean;
|
||||
language?: "yaml" | "json" | "properties" | "shell" | "toml";
|
||||
language?: "yaml" | "json" | "properties" | "shell";
|
||||
lineWrapping?: boolean;
|
||||
lineNumbers?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
// Minimal markdown renderer (no external dependencies) for the per-template
|
||||
// instructions.md files. Supports headings, paragraphs, fenced code blocks,
|
||||
// ordered/unordered lists, and inline code / bold / links.
|
||||
|
||||
const INLINE_PATTERN =
|
||||
/(`[^`]+`)|(\*\*[^*]+\*\*)|(\[[^\]]+\]\([^)\s]+\))/g;
|
||||
|
||||
const renderInline = (text: string): React.ReactNode[] => {
|
||||
const nodes: React.ReactNode[] = [];
|
||||
let lastIndex = 0;
|
||||
let key = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
INLINE_PATTERN.lastIndex = 0;
|
||||
|
||||
while ((match = INLINE_PATTERN.exec(text)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
nodes.push(text.slice(lastIndex, match.index));
|
||||
}
|
||||
const token = match[0];
|
||||
if (token.startsWith("`")) {
|
||||
nodes.push(
|
||||
<code
|
||||
key={key++}
|
||||
className="px-1.5 py-0.5 rounded bg-muted font-mono text-[0.85em]"
|
||||
>
|
||||
{token.slice(1, -1)}
|
||||
</code>
|
||||
);
|
||||
} else if (token.startsWith("**")) {
|
||||
nodes.push(<strong key={key++}>{token.slice(2, -2)}</strong>);
|
||||
} else {
|
||||
const link = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(token);
|
||||
if (link) {
|
||||
nodes.push(
|
||||
<a
|
||||
key={key++}
|
||||
href={link[2]}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 dark:text-blue-400 underline underline-offset-2"
|
||||
>
|
||||
{link[1]}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
nodes.push(token);
|
||||
}
|
||||
}
|
||||
lastIndex = match.index + token.length;
|
||||
}
|
||||
if (lastIndex < text.length) {
|
||||
nodes.push(text.slice(lastIndex));
|
||||
}
|
||||
return nodes;
|
||||
};
|
||||
|
||||
const HEADING_CLASSES: Record<number, string> = {
|
||||
1: "text-2xl font-bold mt-2",
|
||||
2: "text-xl font-semibold mt-6 border-b pb-1",
|
||||
3: "text-lg font-semibold mt-4",
|
||||
4: "text-base font-semibold mt-3",
|
||||
};
|
||||
|
||||
interface MarkdownProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const Markdown: React.FC<MarkdownProps> = ({ content }) => {
|
||||
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
||||
const blocks: React.ReactNode[] = [];
|
||||
let key = 0;
|
||||
let i = 0;
|
||||
|
||||
while (i < lines.length) {
|
||||
const line = lines[i];
|
||||
|
||||
// Blank line
|
||||
if (line.trim() === "") {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fenced code block
|
||||
if (line.trim().startsWith("```")) {
|
||||
const code: string[] = [];
|
||||
i++;
|
||||
while (i < lines.length && !lines[i].trim().startsWith("```")) {
|
||||
code.push(lines[i]);
|
||||
i++;
|
||||
}
|
||||
i++; // skip closing fence
|
||||
blocks.push(
|
||||
<pre
|
||||
key={key++}
|
||||
className="bg-muted rounded-md p-4 overflow-x-auto text-sm font-mono whitespace-pre"
|
||||
>
|
||||
<code>{code.join("\n")}</code>
|
||||
</pre>
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Heading
|
||||
const heading = /^(#{1,4})\s+(.*)$/.exec(line);
|
||||
if (heading) {
|
||||
const level = heading[1].length;
|
||||
const Tag = `h${Math.min(level + 1, 6)}` as keyof React.JSX.IntrinsicElements;
|
||||
blocks.push(
|
||||
<Tag key={key++} className={HEADING_CLASSES[level]}>
|
||||
{renderInline(heading[2])}
|
||||
</Tag>
|
||||
);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unordered list
|
||||
if (/^[-*]\s+/.test(line)) {
|
||||
const items: string[] = [];
|
||||
while (i < lines.length && /^[-*]\s+/.test(lines[i])) {
|
||||
items.push(lines[i].replace(/^[-*]\s+/, ""));
|
||||
i++;
|
||||
}
|
||||
blocks.push(
|
||||
<ul key={key++} className="list-disc pl-6 space-y-1">
|
||||
{items.map((item, idx) => (
|
||||
<li key={idx}>{renderInline(item)}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ordered list
|
||||
if (/^\d+\.\s+/.test(line)) {
|
||||
const items: string[] = [];
|
||||
while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
|
||||
items.push(lines[i].replace(/^\d+\.\s+/, ""));
|
||||
i++;
|
||||
}
|
||||
blocks.push(
|
||||
<ol key={key++} className="list-decimal pl-6 space-y-1">
|
||||
{items.map((item, idx) => (
|
||||
<li key={idx}>{renderInline(item)}</li>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Paragraph: consume consecutive plain lines
|
||||
const paragraph: string[] = [line];
|
||||
i++;
|
||||
while (
|
||||
i < lines.length &&
|
||||
lines[i].trim() !== "" &&
|
||||
!/^(#{1,4})\s+/.test(lines[i]) &&
|
||||
!/^[-*]\s+/.test(lines[i]) &&
|
||||
!/^\d+\.\s+/.test(lines[i]) &&
|
||||
!lines[i].trim().startsWith("```")
|
||||
) {
|
||||
paragraph.push(lines[i]);
|
||||
i++;
|
||||
}
|
||||
blocks.push(
|
||||
<p key={key++} className="leading-relaxed">
|
||||
{renderInline(paragraph.join(" "))}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="space-y-3 text-sm">{blocks}</div>;
|
||||
};
|
||||
|
||||
export default Markdown;
|
||||
@@ -11,9 +11,11 @@ export default defineConfig({
|
||||
{
|
||||
src: '../blueprints/*',
|
||||
dest: 'blueprints' // raíz de dist (public root)
|
||||
},
|
||||
{
|
||||
src: '../meta.json',
|
||||
dest: '' // raíz de dist
|
||||
}
|
||||
// meta.json is no longer copied from the repo root: it is
|
||||
// generated into app/public/meta.json by build-scripts/generate-meta.js
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 105 KiB |
@@ -1,32 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
9router:
|
||||
image: decolua/9router:0.5.40
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DATA_DIR: /app/data
|
||||
PORT: "20128"
|
||||
HOSTNAME: "0.0.0.0"
|
||||
NODE_ENV: production
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
INITIAL_PASSWORD: ${INITIAL_PASSWORD}
|
||||
API_KEY_SECRET: ${API_KEY_SECRET}
|
||||
MACHINE_ID_SALT: ${MACHINE_ID_SALT}
|
||||
BASE_URL: http://${ROUTER_HOST}
|
||||
NEXT_PUBLIC_BASE_URL: http://${ROUTER_HOST}
|
||||
HEADROOM_URL: http://headroom:8787
|
||||
AUTH_COOKIE_SECURE: "false"
|
||||
volumes:
|
||||
- 9router-data:/app/data
|
||||
depends_on:
|
||||
- headroom
|
||||
|
||||
# Optional token-reduction proxy that 9Router calls through HEADROOM_URL.
|
||||
# Shipped as a sidecar because the 9Router image does not bundle it.
|
||||
headroom:
|
||||
image: ghcr.io/chopratejas/headroom:0.6.7
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
9router-data:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "9router",
|
||||
"name": "9Router",
|
||||
"version": "0.5.40",
|
||||
"description": "Self-hosted AI model router that rotates requests across 40+ providers with automatic fallback, exposing an OpenAI-compatible endpoint for coding agents.",
|
||||
"logo": "9router.png",
|
||||
"links": {
|
||||
"github": "https://github.com/decolua/9router",
|
||||
"website": "https://9router.com",
|
||||
"docs": "https://github.com/decolua/9router/blob/master/DOCKER.md"
|
||||
},
|
||||
"tags": [
|
||||
"ai",
|
||||
"proxy",
|
||||
"developer-tools"
|
||||
]
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
jwt_secret = "${password:64}"
|
||||
initial_password = "${password:32}"
|
||||
api_key_secret = "${password:64}"
|
||||
machine_id_salt = "${password:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"ROUTER_HOST=${main_domain}",
|
||||
"JWT_SECRET=${jwt_secret}",
|
||||
"INITIAL_PASSWORD=${initial_password}",
|
||||
"API_KEY_SECRET=${api_key_secret}",
|
||||
"MACHINE_ID_SALT=${machine_id_salt}",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "9router"
|
||||
port = 20128
|
||||
host = "${main_domain}"
|
||||
@@ -1,24 +0,0 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
ackee:
|
||||
image: electerious/ackee:3.4.2
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- ACKEE_USERNAME=${ACKEE_USERNAME}
|
||||
- ACKEE_PASSWORD=${ACKEE_PASSWORD}
|
||||
- ACKEE_MONGODB=${ACKEE_MONGODB}
|
||||
|
||||
mongo:
|
||||
image: mongo:4
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD}
|
||||
- MONGO_INITDB_ROOT_USERNAME=mongo
|
||||
volumes:
|
||||
- mongo-data:/data/db
|
||||
|
||||
|
||||
volumes:
|
||||
mongo-data:
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB |
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"id": "ackee",
|
||||
"name": "Ackee",
|
||||
"version": "latest",
|
||||
"description": "Ackee is a self-hosted analytics tool for your website.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/electerious/Ackee",
|
||||
"website": "https://ackee.electerious.com/",
|
||||
"docs": "https://docs.ackee.electerious.com/"
|
||||
},
|
||||
"tags": [
|
||||
"analytics",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
[variables]
|
||||
ACKEE_USERNAME = "default"
|
||||
ACKEE_PASSWORD = "${password:16}"
|
||||
MONGO_INITDB_ROOT_PASSWORD = "${password:16}"
|
||||
ACKEE_MONGODB = "mongodb://mongo:${MONGO_INITDB_ROOT_PASSWORD}@mongo:27017"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "ackee"
|
||||
port = 3000
|
||||
host = "${domain}"
|
||||
|
||||
|
||||
[config.env]
|
||||
ACKEE_USERNAME = "${ACKEE_USERNAME}"
|
||||
ACKEE_PASSWORD = "${ACKEE_PASSWORD}"
|
||||
ACKEE_MONGODB = "${ACKEE_MONGODB}"
|
||||
MONGO_INITDB_ROOT_PASSWORD = "${MONGO_INITDB_ROOT_PASSWORD}"
|
||||
|
||||
@@ -61,4 +61,4 @@ services:
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
redis_data:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "activepieces",
|
||||
"name": "Activepieces",
|
||||
"version": "0.35.0",
|
||||
"description": "Open-source no-code business automation tool. An alternative to Zapier, Make.com, and Tray.",
|
||||
"logo": "activepieces.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/activepieces/activepieces",
|
||||
"website": "https://www.activepieces.com/",
|
||||
"docs": "https://www.activepieces.com/docs"
|
||||
},
|
||||
"tags": [
|
||||
"automation",
|
||||
"workflow",
|
||||
"no-code"
|
||||
]
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
api_key = "${password:32}"
|
||||
encryption_key = "${password:32}"
|
||||
jwt_secret = "${password:32}"
|
||||
postgres_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"AP_HOST=${main_domain}",
|
||||
"AP_API_KEY=${api_key}",
|
||||
"AP_ENCRYPTION_KEY=${encryption_key}",
|
||||
"AP_JWT_SECRET=${jwt_secret}",
|
||||
"AP_POSTGRES_PASSWORD=${postgres_password}",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "activepieces"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
@@ -0,0 +1,21 @@
|
||||
variables:
|
||||
main_domain: ${domain}
|
||||
api_key: ${password:32}
|
||||
encryption_key: ${password:32}
|
||||
jwt_secret: ${password:32}
|
||||
postgres_password: ${password:32}
|
||||
|
||||
config:
|
||||
domains:
|
||||
- serviceName: activepieces
|
||||
port: 80
|
||||
host: ${main_domain}
|
||||
|
||||
env:
|
||||
- AP_HOST=${main_domain}
|
||||
- AP_API_KEY=${api_key}
|
||||
- AP_ENCRYPTION_KEY=${encryption_key}
|
||||
- AP_JWT_SECRET=${jwt_secret}
|
||||
- AP_POSTGRES_PASSWORD=${postgres_password}
|
||||
|
||||
mounts: []
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "actualbudget",
|
||||
"name": "Actual Budget",
|
||||
"version": "latest",
|
||||
"description": "A super fast and privacy-focused app for managing your finances.",
|
||||
"logo": "actualbudget.png",
|
||||
"links": {
|
||||
"github": "https://github.com/actualbudget/actual",
|
||||
"website": "https://actualbudget.org",
|
||||
"docs": "https://actualbudget.org/docs"
|
||||
},
|
||||
"tags": [
|
||||
"budgeting",
|
||||
"finance",
|
||||
"money"
|
||||
]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
env = []
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "actualbudget"
|
||||
port = 5_006
|
||||
host = "${main_domain}"
|
||||
@@ -0,0 +1,12 @@
|
||||
variables:
|
||||
main_domain: ${domain}
|
||||
|
||||
config:
|
||||
domains:
|
||||
- serviceName: actualbudget
|
||||
port: 5006
|
||||
host: ${main_domain}
|
||||
|
||||
env: []
|
||||
|
||||
mounts: []
|
||||
@@ -1,20 +0,0 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
adguardhome:
|
||||
image: adguard/adguardhome:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "53:53/tcp" # DNS
|
||||
- "53:53/udp" # DNS
|
||||
- "67:67/udp" # DHCP Server
|
||||
- "68:68/tcp" # DHCP Client
|
||||
- "853:853/tcp" # DNS over TLS, DNS-over-QUIC
|
||||
- "853:853/udp" # DNS over TLS, DNS-over-QUIC
|
||||
- "6060:6060/tcp" # HTTP (pprof)
|
||||
volumes:
|
||||
- adguardhome-work:/opt/adguardhome/work
|
||||
- adguardhome-conf:/opt/adguardhome/conf
|
||||
|
||||
volumes:
|
||||
adguardhome-work: {}
|
||||
adguardhome-conf: {}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="80px" height="80px" viewBox="0 0 80 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>logo@2x</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="logo" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" fill="#FFFFFF" x="0" y="0" width="80" height="80" rx="20"></rect>
|
||||
<g id="Group-10" transform="translate(12.000000, 12.000000)">
|
||||
<g id="Group-9">
|
||||
<g id="Group-8">
|
||||
<g id="Group-7">
|
||||
<path d="M28.2221276,0 C19.4007575,0 8.75997994,2.07442553 8.65485005e-06,6.64038298 C8.65485005e-06,16.501617 -0.120909272,41.0689362 28.2221276,57.855 C56.5657909,41.0689362 56.4454995,16.501617 56.4454995,6.64038298 C47.6849017,2.07442553 37.0441241,0 28.2221276,0 L28.2221276,0 Z" id="Path" fill="#68BC71"></path>
|
||||
<path d="M28.1932991,57.8379179 C-0.120827266,41.0522735 8.65485006e-06,16.4982725 8.65485006e-06,6.64038298 C8.75043947,2.07939831 19.3775821,0.00452145957 28.1932991,7.38217799e-06 L28.1932991,57.8379237 Z" id="Combined-Shape" fill="#67B279"></path>
|
||||
</g>
|
||||
<path d="M27.1926958,38.6027397 L44.2590846,15.6010416 C43.0084943,14.5986526 41.911548,15.3061181 41.3076915,15.8538333 L41.2856573,15.8555888 L27.0557264,30.6585285 L21.6942672,24.2064902 C19.1365123,21.2514028 15.6592758,23.5054616 14.8469876,24.1011604 L27.1926958,38.6027397" id="Fill-11" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"id": "adguardhome",
|
||||
"name": "AdGuard Home",
|
||||
"version": "latest",
|
||||
"description": "AdGuard Home is a comprehensive solution designed to enhance your online browsing experience by eliminating all kinds of ads, from annoying banners and pop-ups to intrusive video ads. It provides privacy protection, browsing security, and parental control features while maintaining website functionality.",
|
||||
"logo": "logo.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/AdguardTeam/AdGuardHome",
|
||||
"website": "https://adguard.com",
|
||||
"docs": "https://github.com/AdguardTeam/AdGuardHome/wiki"
|
||||
},
|
||||
"tags": [
|
||||
"privacy",
|
||||
"security",
|
||||
"dns",
|
||||
"ad-blocking"
|
||||
]
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
env = []
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "adguardhome"
|
||||
port = 3000
|
||||
host = "${main_domain}"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
adminer:
|
||||
image: adminer:4.8.1
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 8080
|
||||
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="791px"
|
||||
height="792px" viewBox="-91.5 -0.5 791 792" enable-background="new -91.5 -0.5 791 792" xml:space="preserve">
|
||||
<g id="Layer_2">
|
||||
<rect x="-91.5" y="-0.5" fill="#005982" width="791" height="792"/>
|
||||
<rect x="-90.5" y="-0.5" fill="none" width="790" height="792"/>
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<path fill="#FFFFFF" d="M336.162,343.266c-7.003-27.503-58.849-54.543-149.432-54.543c-90.517,0-142.358,27.004-149.416,54.488
|
||||
c-0.543,1.058-0.875,2.241-0.875,3.513v249.82c0,2.393,1.101,4.505,2.794,5.937c11.992,25.549,62.822,49.176,147.496,49.176
|
||||
c84.697,0,135.532-23.639,147.507-49.199c1.679-1.428,2.768-3.531,2.768-5.913v-5.691c0-0.047,0.008-0.09,0.008-0.137
|
||||
c0-0.027-0.008-0.05-0.008-0.077v-81.224c0-0.046,0.008-0.09,0.008-0.136c0-0.028-0.008-0.051-0.008-0.078V429.61
|
||||
c0-0.048,0.008-0.09,0.008-0.137c0-0.027-0.008-0.052-0.008-0.079v-79.59c0-0.047,0.008-0.09,0.008-0.137s-0.008-0.094-0.008-0.141
|
||||
v-2.802C337.005,345.475,336.685,344.312,336.162,343.266z M321.392,509.372c-0.165,21.415-55.417,45.233-134.662,45.233
|
||||
c-79.36,0-134.668-23.889-134.668-45.33c0-0.027-0.008-0.052-0.008-0.079v-52.169c22.261,18.717,67.967,33.387,134.676,33.387
|
||||
c66.698,0,112.392-14.665,134.662-33.375V509.372z M321.392,429.567C321.227,450.98,265.974,474.8,186.73,474.8
|
||||
c-79.36,0-134.668-23.889-134.668-45.331c0-0.026-0.008-0.051-0.008-0.078v-52.169c22.261,18.716,67.967,33.386,134.676,33.386
|
||||
c66.698,0,112.392-14.661,134.662-33.374V429.567z M186.73,304.337c79.244,0,134.497,23.818,134.662,45.233v0.191
|
||||
c-0.165,21.415-55.417,45.233-134.662,45.233c-79.36,0-134.668-23.889-134.668-45.327
|
||||
C52.062,328.229,107.37,304.337,186.73,304.337z M186.73,636.043c-79.36,0-134.668-23.89-134.668-45.327
|
||||
c0-0.027-0.008-0.05-0.008-0.077v-53.802c22.261,18.717,67.967,33.387,134.676,33.387c66.698,0,112.392-14.665,134.662-33.375
|
||||
v53.961C321.227,612.228,265.974,636.043,186.73,636.043z"/>
|
||||
<g>
|
||||
<path fill="#FF0000" d="M427.38,365.951l-71.116-59.932l71.116-59.827l16.46,16.671l-52.335,42.944l52.335,43.472L427.38,365.951z
|
||||
"/>
|
||||
<path fill="#FF0000" d="M566.131,260.227c0,5.909-0.897,11.203-2.691,15.88c-1.793,4.679-4.362,8.723-7.702,12.134
|
||||
c-3.342,3.413-7.369,6.173-12.081,8.283c-4.714,2.11-9.989,3.553-15.827,4.326l-1.056,19.837h-22.052l-1.899-39.673h15.3
|
||||
c3.445,0,6.418-0.491,8.915-1.477c2.496-0.984,4.537-2.322,6.12-4.01s2.743-3.64,3.482-5.856c0.738-2.216,1.107-4.554,1.107-7.017
|
||||
c0-3.376-0.722-6.524-2.163-9.444c-1.442-2.918-3.692-5.486-6.753-7.702c-3.06-2.216-6.947-3.957-11.659-5.223
|
||||
c-4.713-1.266-10.377-1.899-16.987-1.899h-6.226v-23.952h7.28c10.832,0,20.293,1.284,28.384,3.851
|
||||
c8.088,2.568,14.841,5.979,20.258,10.235c5.416,4.257,9.479,9.127,12.188,14.614C564.775,248.62,566.131,254.317,566.131,260.227z
|
||||
M533.21,349.069c0,2.321-0.458,4.502-1.371,6.542c-0.915,2.041-2.147,3.835-3.693,5.381c-1.548,1.548-3.377,2.76-5.486,3.64
|
||||
c-2.11,0.879-4.363,1.319-6.753,1.319c-2.393,0-4.627-0.44-6.7-1.319c-2.076-0.88-3.888-2.092-5.435-3.64
|
||||
c-1.548-1.546-2.779-3.34-3.692-5.381c-0.915-2.04-1.372-4.221-1.372-6.542s0.457-4.5,1.372-6.542
|
||||
c0.913-2.04,2.145-3.833,3.692-5.381c1.547-1.546,3.358-2.76,5.435-3.64c2.073-0.879,4.308-1.319,6.7-1.319
|
||||
c2.39,0,4.643,0.44,6.753,1.319c2.109,0.88,3.938,2.094,5.486,3.64c1.546,1.548,2.778,3.342,3.693,5.381
|
||||
C532.752,344.568,533.21,346.748,533.21,349.069z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"id": "adminer",
|
||||
"name": "Adminer",
|
||||
"version": "4.8.1",
|
||||
"description": "Adminer is a comprehensive database management tool that supports MySQL, MariaDB, PostgreSQL, SQLite, MS SQL, Oracle, Elasticsearch, MongoDB and others. It provides a clean interface for efficient database operations, with strong security features and extensive customization options.",
|
||||
"logo": "logo.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/vrana/adminer",
|
||||
"website": "https://www.adminer.org/",
|
||||
"docs": "https://www.adminer.org/en/plugins/"
|
||||
},
|
||||
"tags": [
|
||||
"databases",
|
||||
"developer-tools",
|
||||
"mysql",
|
||||
"postgresql"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "adminer"
|
||||
port = 8080
|
||||
host = "${main_domain}"
|
||||
|
Before Width: | Height: | Size: 7.9 KiB |
@@ -1,51 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: ghcr.io/seanmorley15/adventurelog-frontend:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- server
|
||||
environment:
|
||||
# Frontend needs to know where the backend is
|
||||
PUBLIC_SERVER_URL: ${PUBLIC_SERVER_URL}
|
||||
ORIGIN: ${ORIGIN}
|
||||
BODY_SIZE_LIMIT: ${BODY_SIZE_LIMIT}
|
||||
|
||||
db:
|
||||
image: postgis/postgis:16-3.5
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data/
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
|
||||
server:
|
||||
image: ghcr.io/seanmorley15/adventurelog-backend:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- db
|
||||
volumes:
|
||||
- adventurelog_media:/code/media/
|
||||
environment:
|
||||
# DB settings for Django
|
||||
PGHOST: ${PGHOST}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
|
||||
# Django app settings
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
DJANGO_ADMIN_USERNAME: ${DJANGO_ADMIN_USERNAME}
|
||||
DJANGO_ADMIN_PASSWORD: ${DJANGO_ADMIN_PASSWORD}
|
||||
DJANGO_ADMIN_EMAIL: ${DJANGO_ADMIN_EMAIL}
|
||||
DEBUG: ${DEBUG}
|
||||
CSRF_TRUSTED_ORIGINS: ${CSRF_TRUSTED_ORIGINS}
|
||||
FRONTEND_URL: ${FRONTEND_URL}
|
||||
PUBLIC_URL: ${PUBLIC_URL}
|
||||
|
||||
volumes:
|
||||
postgres_data: {}
|
||||
adventurelog_media: {}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"id": "adventurelog",
|
||||
"name": "AdventureLog",
|
||||
"version": "latest",
|
||||
"description": "AdventureLog is an open-source activity tracker with maps, journaling, and Strava integration.",
|
||||
"logo": "adventurelog.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/seanmorley15/adventurelog",
|
||||
"website": "https://adventurelog.app/",
|
||||
"docs": "https://adventurelog.app/docs/intro/adventurelog_overview.html"
|
||||
},
|
||||
"tags": [
|
||||
"activity",
|
||||
"maps",
|
||||
"django",
|
||||
"react",
|
||||
"postgres"
|
||||
]
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
[variables]
|
||||
frontend_domain = "${domain}"
|
||||
backend_domain = "${domain}"
|
||||
postgres_password = "${password:16}"
|
||||
secret_key = "${password:32}"
|
||||
admin_username = "admin"
|
||||
admin_password = "${password:12}"
|
||||
admin_email = "${email}"
|
||||
|
||||
[config]
|
||||
|
||||
# Frontend domain
|
||||
[[config.domains]]
|
||||
serviceName = "web"
|
||||
port = 3000
|
||||
host = "${frontend_domain}"
|
||||
|
||||
# Backend domain
|
||||
[[config.domains]]
|
||||
serviceName = "server"
|
||||
port = 80
|
||||
host = "${backend_domain}"
|
||||
|
||||
[config.env]
|
||||
# --- Postgres (values used by both db and server services)
|
||||
PGHOST = "db"
|
||||
POSTGRES_DB = "adventure"
|
||||
POSTGRES_USER = "adventure"
|
||||
POSTGRES_PASSWORD = "${postgres_password}"
|
||||
|
||||
# --- Django Backend
|
||||
SECRET_KEY = "${secret_key}"
|
||||
DJANGO_ADMIN_USERNAME = "${admin_username}"
|
||||
DJANGO_ADMIN_PASSWORD = "${admin_password}"
|
||||
DJANGO_ADMIN_EMAIL = "${admin_email}"
|
||||
DEBUG = "False"
|
||||
CSRF_TRUSTED_ORIGINS = "http://${backend_domain},http://${frontend_domain}"
|
||||
FRONTEND_URL = "http://${frontend_domain}"
|
||||
PUBLIC_URL = "http://${backend_domain}"
|
||||
|
||||
# --- Frontend
|
||||
PUBLIC_SERVER_URL = "${backend_domain}"
|
||||
ORIGIN = "http://${frontend_domain}"
|
||||
BODY_SIZE_LIMIT = "Infinity"
|
||||
@@ -1,84 +0,0 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
affinepro:
|
||||
image: ghcr.io/toeverything/affine:stable
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 3010
|
||||
volumes:
|
||||
- affine-storage:/root/.affine/storage
|
||||
- affine-config:/root/.affine/config
|
||||
environment:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- REDIS_SERVER_PASSWORD=${REDIS_PASSWORD}
|
||||
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/affinepro
|
||||
- AFFINE_INDEXER_ENABLED=false
|
||||
- AFFINE_SERVER_HOST=${DOMAIN}
|
||||
- MAILER_HOST=${MAILER_HOST}
|
||||
- MAILER_PORT=${MAILER_PORT}
|
||||
- MAILER_USER=${MAILER_USER}
|
||||
- MAILER_PASSWORD=${MAILER_PASSWORD}
|
||||
- MAILER_SENDER=${MAILER_SENDER}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
migration:
|
||||
condition: service_completed_successfully
|
||||
|
||||
migration:
|
||||
image: ghcr.io/toeverything/affine:stable
|
||||
command: ["sh", "-c", "node ./scripts/self-host-predeploy.js"]
|
||||
environment:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- REDIS_SERVER_PASSWORD=${REDIS_PASSWORD}
|
||||
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/affinepro
|
||||
- AFFINE_INDEXER_ENABLED=false
|
||||
- AFFINE_SERVER_HOST=${DOMAIN}
|
||||
- MAILER_HOST=${MAILER_HOST}
|
||||
- MAILER_PORT=${MAILER_PORT}
|
||||
- MAILER_USER=${MAILER_USER}
|
||||
- MAILER_PASSWORD=${MAILER_PASSWORD}
|
||||
- MAILER_SENDER=${MAILER_SENDER}
|
||||
volumes:
|
||||
- affine-storage:/root/.affine/storage
|
||||
- affine-config:/root/.affine/config
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
db:
|
||||
image: pgvector/pgvector:pg16
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=affinepro
|
||||
- POSTGRES_INITDB_ARGS=--data-checksums
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "postgres", "-d", "affinepro"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli -a '${REDIS_PASSWORD}' ping | grep PONG"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
affine-storage: {}
|
||||
affine-config: {}
|
||||
postgres-data: {}
|
||||
redis-data: {}
|
||||
|
Before Width: | Height: | Size: 8.3 KiB |
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"id": "affinepro",
|
||||
"name": "Affine Pro",
|
||||
"version": "stable",
|
||||
"description": "Affine Pro is a modern, self-hosted platform designed for collaborative content creation and project management. It offers an intuitive interface, seamless real-time collaboration, and powerful tools for organizing tasks, notes, and ideas.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/toeverything/Affine",
|
||||
"website": "https://affine.pro/",
|
||||
"docs": "https://docs.affine.pro/"
|
||||
},
|
||||
"tags": [
|
||||
"collaboration",
|
||||
"self-hosted",
|
||||
"productivity",
|
||||
"project-management"
|
||||
]
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
postgres_password = "${password:16}"
|
||||
redis_password = "${password:16}"
|
||||
mailer_host = ""
|
||||
mailer_port = "587"
|
||||
mailer_user = ""
|
||||
mailer_password = ""
|
||||
mailer_sender = ""
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "affinepro"
|
||||
port = 3010
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
DOMAIN = "${main_domain}"
|
||||
POSTGRES_PASSWORD = "${postgres_password}"
|
||||
REDIS_PASSWORD = "${redis_password}"
|
||||
MAILER_HOST = "${mailer_host}"
|
||||
MAILER_PORT = "${mailer_port}"
|
||||
MAILER_USER = "${mailer_user}"
|
||||
MAILER_PASSWORD = "${mailer_password}"
|
||||
MAILER_SENDER = "${mailer_sender}"
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="a" viewBox="0 0 3271 1440"><defs><style>.st0{fill:#dedede}</style></defs><path d="M1009.95 1209.53c-125.1-214.98-250.74-430.9-378.92-651.16-126.82 219.73-251.81 436.28-376.62 652.51H60.69c190.42-329.11 570.87-981.75 570.87-981.75s382.11 651.12 572.49 980.41h-194.1Z" class="st0"/><path d="M875.46 1210.46H383.92c32.72-56.91 64.4-111.99 95.81-166.62H781.8c30.62 54.46 61.09 108.66 93.67 166.62zm1622.92-202.23c19.52 29.84 37.9 57.94 58.46 89.39h-63.61c-18.29-24.88-38.17-51.91-57.71-78.49h-72.24v78.17h-51.11V828.44c30.03 0 58.71-.22 87.38.06 23.48.24 47.32-1.26 70.37 2.21 37.67 5.66 66.94 25.71 74.9 64.9 8.22 40.54 1.62 78.35-35.52 104.62-3.14 2.22-6.21 4.54-10.93 7.99h.01Zm-135.01-40.37c34 0 66.71 1.87 99.08-.66 21.47-1.67 30.97-17.92 31.56-39.77.61-22.88-9.01-40.56-30.99-42.73-32.8-3.23-66.15-.84-99.65-.84zM1674.39 829.9v38.37c-52.36 58.82-104.25 117.13-160.14 179.91h158.93v49.49h-227.62v-51.9c46.98-52.11 95.02-105.41 147.93-164.09h-147.28V829.9zm906.29-489.95c64.48 63.91 125.11 123.99 189.35 187.65V373.68h47.4v271.19c-64.19-62.94-125.05-122.59-190.04-186.3v162.44h-46.72V339.94h.01Zm195.02 692.31c13.79-6.85 33.51-15.76 45.41-21.65 41.44 55.26 105.07 42.53 132.33 18s35.44-59.98 25.2-95.47c-13.36-34.33-42.97-61.14-86.36-59.62-43.38 1.52-81.55 32.41-85.2 84.34h-50.32c-.57-50.11 36.27-109.35 94.12-126.75 72.77-21.89 144.89 13.23 172.07 83.77 25.18 65.37-5.74 138.6-72.21 171-60.69 29.59-140.17 6.97-175.1-53.61h.04Zm-811.82-622.07c-11.07 8.4-24.55 17.68-34.13 24.95-55.99-46.33-119.62-30.88-142.33 27.28-13.63 38.17 6.27 88.2 48.2 103.62 36.45 13.4 76.82-.61 87.32-31.48-8.66-2.61-17.29-5.23-28.25-8.54-.93-12.84-1.85-25.67-2.96-40.9h96.82c7.25 56.02-37.39 118.12-94.41 132.17-63.06 15.54-129.35-19.2-152.31-79.83-22.64-59.76 4.81-131.25 61.88-161.16 55.93-29.3 126.75-15.37 160.17 33.88Zm-531.9-55.25c48.2 92.28 93.04 178.13 139.88 267.84h-53.47c-27.89-50.55-56.47-102.34-86.73-157.13-28.93 54.29-56.1 105.29-83.82 157.31h-54.54c45.87-88.68 90.79-175.5 138.65-268.02h.01Zm1642.09 60.65h-89.98v-46.22h226.22v46.24h-86.43v205.91h-49.82V415.59zM1947.89 878.34v65.77l63.09.91s-.03 30.03-.03 50.34c-25.69.89-53.1.45-78.35-5.53-21.38-5.05-36.73-22.63-37.67-46.05-1.5-36.98-.4-75.88-.4-115.24h216.51v49.81h-163.16Zm-52.88 218.57v-68.62c14.11-.65 27.22-1.25 40.97-1.87 3.47 7 3.06 19.81 9.96 20.09 6.88.29 107.67 0 164.04 0v50.41h-214.97Zm342.4-678.53v60.83l58.36.84s-.03 27.78-.03 46.57c-23.85.41-49.12.41-72.49-5.11-19.77-4.68-33.97-20.93-34.85-42.59-1.39-34.2-.37-70.19-.37-106.6h200.29v46.07H2237.4h.01Zm-48.91 202.2v-63.49c13.05-.6 25.18-1.15 37.9-1.73 3.2 6.48 2.83 18.32 9.2 18.59s99.6 0 151.75 0v46.63h-198.87.01Z" class="st0"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1,14 +0,0 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
agent-zero:
|
||||
image: agent0ai/agent-zero:v1.9
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "80"
|
||||
environment:
|
||||
- AUTH_LOGIN=${AUTH_LOGIN}
|
||||
- AUTH_PASSWORD=${AUTH_PASSWORD}
|
||||
volumes:
|
||||
- agent-zero-data:/a0/usr
|
||||
volumes:
|
||||
agent-zero-data:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "agent-zero",
|
||||
"name": "Agent Zero",
|
||||
"version": "v1.9",
|
||||
"description": "Agent Zero is an open-source autonomous AI agent framework with memory and tool use capabilities.",
|
||||
"logo": "agent-zero.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/agent0ai/agent-zero",
|
||||
"website": "https://www.agent-zero.ai",
|
||||
"docs": "https://www.agent-zero.ai/p/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"ai",
|
||||
"agent",
|
||||
"automation"
|
||||
]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
auth_login = "${username}"
|
||||
auth_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"AUTH_LOGIN=${auth_login}",
|
||||
"AUTH_PASSWORD=${auth_password}",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "agent-zero"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
|
Before Width: | Height: | Size: 23 KiB |
@@ -1,21 +0,0 @@
|
||||
services:
|
||||
agentdvr:
|
||||
image: mekayelanik/ispyagentdvr:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=${TIMEZONE:-America/New_York}
|
||||
- AGENTDVR_WEBUI_PORT=8090
|
||||
volumes:
|
||||
- agentdvr-config:/AgentDVR/Media/XML/
|
||||
- agentdvr-media:/AgentDVR/Media/WebServerRoot/Media/
|
||||
- agentdvr-commands:/AgentDVR/Commands/
|
||||
expose:
|
||||
- 8090
|
||||
- 3478/udp
|
||||
- 50000-50100/udp
|
||||
volumes:
|
||||
agentdvr-config: {}
|
||||
agentdvr-media: {}
|
||||
agentdvr-commands: {}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"id": "agentdvr",
|
||||
"name": "Agent DVR",
|
||||
"version": "latest",
|
||||
"description": "Agent DVR is a comprehensive video surveillance software with motion detection, alerts, and remote access capabilities.",
|
||||
"logo": "agentdvr.png",
|
||||
"links": {
|
||||
"github": "https://github.com/ispysoftware/AgentDVR",
|
||||
"website": "https://www.ispyconnect.com/",
|
||||
"docs": "https://www.ispyconnect.com/userguide-agent-dvr.aspx"
|
||||
},
|
||||
"tags": [
|
||||
"surveillance",
|
||||
"security",
|
||||
"video",
|
||||
"monitoring",
|
||||
"dvr",
|
||||
"camera"
|
||||
]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
timezone = "America/New_York"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "agentdvr"
|
||||
port = 8090
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
TIMEZONE = "${timezone}"
|
||||
|
||||
[[config.mounts]]
|
||||
@@ -1,55 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
akaunting:
|
||||
image: docker.io/akaunting/akaunting:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
akaunting-db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
# App config
|
||||
APP_URL: ${APP_URL}
|
||||
LOCALE: ${LOCALE}
|
||||
|
||||
# Database connection
|
||||
DB_HOST: akaunting-db
|
||||
DB_PORT: 3306
|
||||
DB_NAME: ${DB_NAME}
|
||||
DB_USERNAME: ${DB_USERNAME}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
DB_PREFIX: ${DB_PREFIX}
|
||||
|
||||
# First-run bootstrap (company + admin)
|
||||
COMPANY_NAME: ${COMPANY_NAME}
|
||||
COMPANY_EMAIL: ${COMPANY_EMAIL}
|
||||
ADMIN_EMAIL: ${ADMIN_EMAIL}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
|
||||
|
||||
# Expose only to internal network; domain routing handled by Dokploy/Traefik
|
||||
expose:
|
||||
- "80"
|
||||
|
||||
volumes:
|
||||
- akaunting-data:/var/www/html
|
||||
|
||||
akaunting-db:
|
||||
image: mariadb:10.11
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE}
|
||||
MYSQL_USER: ${MYSQL_USER}
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||
# Randomizes root password on each deployment (recommended)
|
||||
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
|
||||
volumes:
|
||||
- akaunting-db:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
akaunting-data: {}
|
||||
akaunting-db: {}
|
||||
|
Before Width: | Height: | Size: 8.3 KiB |
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"id": "akaunting",
|
||||
"name": "Akaunting",
|
||||
"version": "latest",
|
||||
"description": "Akaunting is a self-hosted, open-source accounting app for small businesses.",
|
||||
"logo": "image.png",
|
||||
"links": {
|
||||
"github": "https://github.com/akaunting/akaunting",
|
||||
"website": "https://akaunting.com",
|
||||
"docs": "https://akaunting.com/docs"
|
||||
},
|
||||
"tags": [
|
||||
"finance",
|
||||
"accounting",
|
||||
"php",
|
||||
"mariadb",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
[variables]
|
||||
# Domain & UI
|
||||
main_domain = "${domain}"
|
||||
locale = "en-US"
|
||||
|
||||
# DB credentials (safe defaults; user can override in UI)
|
||||
db_name = "akaunting"
|
||||
db_user = "admin"
|
||||
mysql_password = "${password:24}"
|
||||
|
||||
# Akaunting setup variables
|
||||
company_name = "My Company"
|
||||
company_email = "${email}"
|
||||
admin_email = "${email}"
|
||||
admin_password = "${password:24}"
|
||||
|
||||
# Akaunting table prefix: 3 hex chars + underscore (e.g., "a1f_")
|
||||
db_prefix = "${hash:3}_"
|
||||
|
||||
# Domain routing for the web UI
|
||||
[[config.domains]]
|
||||
serviceName = "akaunting"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
# App
|
||||
"APP_URL=${main_domain}",
|
||||
"LOCALE=${locale}",
|
||||
|
||||
# Database (app)
|
||||
"DB_HOST=akaunting-db",
|
||||
"DB_PORT=3306",
|
||||
"DB_NAME=${db_name}",
|
||||
"DB_USERNAME=${db_user}",
|
||||
"DB_PASSWORD=${mysql_password}",
|
||||
"DB_PREFIX=${db_prefix}",
|
||||
|
||||
# First-run bootstrap (app)
|
||||
"COMPANY_NAME=${company_name}",
|
||||
"COMPANY_EMAIL=${company_email}",
|
||||
"ADMIN_EMAIL=${admin_email}",
|
||||
"ADMIN_PASSWORD=${admin_password}",
|
||||
|
||||
# Database (server)
|
||||
"MYSQL_DATABASE=${db_name}",
|
||||
"MYSQL_USER=${db_user}",
|
||||
"MYSQL_PASSWORD=${mysql_password}",
|
||||
"MYSQL_RANDOM_ROOT_PASSWORD=yes"
|
||||
]
|
||||
|
||||
# No custom mounts needed; volumes are already defined in compose.
|
||||
[[config.mounts]]
|
||||
|
Before Width: | Height: | Size: 55 KiB |
@@ -1,30 +0,0 @@
|
||||
services:
|
||||
alarik:
|
||||
image: ghcr.io/achtungsoftware/alarik:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- alarik-storage:/app/Storage
|
||||
environment:
|
||||
- API_BASE_URL=${API_BASE_URL}
|
||||
- CONSOLE_BASE_URL=${CONSOLE_BASE_URL}
|
||||
- ADMIN_USERNAME=${ADMIN_USERNAME}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||
- JWT=${JWT}
|
||||
- ALLOW_ACCOUNT_CREATION=${ALLOW_ACCOUNT_CREATION}
|
||||
expose:
|
||||
- 8080
|
||||
|
||||
console:
|
||||
image: ghcr.io/achtungsoftware/alarik-console:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- NUXT_PUBLIC_API_BASE_URL=${API_BASE_URL}
|
||||
- NUXT_PUBLIC_CONSOLE_BASE_URL=${CONSOLE_BASE_URL}
|
||||
- NUXT_PUBLIC_ALLOW_ACCOUNT_CREATION=${ALLOW_ACCOUNT_CREATION}
|
||||
expose:
|
||||
- 3000
|
||||
depends_on:
|
||||
- alarik
|
||||
|
||||
volumes:
|
||||
alarik-storage:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "alarik",
|
||||
"name": "Alarik",
|
||||
"description": "Alarik is a high-performance, S3-compatible object storage.",
|
||||
"logo": "alarik.png",
|
||||
"version": "latest",
|
||||
"links": {
|
||||
"github": "https://github.com/achtungsoftware/alarik",
|
||||
"website": "https://alarik.io/",
|
||||
"docs": "https://alarik.io/docs"
|
||||
},
|
||||
"tags": [
|
||||
"storage",
|
||||
"s3",
|
||||
"object-storage"
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
[variables]
|
||||
console_domain = "${domain}"
|
||||
api_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
|
||||
# S3-compatible API - point AWS CLI / SDKs / rclone / backup tools at this domain
|
||||
[[config.domains]]
|
||||
serviceName = "alarik"
|
||||
port = 8_080
|
||||
host = "${api_domain}"
|
||||
|
||||
# Web management console - log in with the generated admin credentials below
|
||||
[[config.domains]]
|
||||
serviceName = "console"
|
||||
port = 3_000
|
||||
host = "${console_domain}"
|
||||
|
||||
[config.env]
|
||||
API_BASE_URL = "http://${api_domain}"
|
||||
CONSOLE_BASE_URL = "http://${console_domain}"
|
||||
ADMIN_USERNAME = "alarik"
|
||||
ADMIN_PASSWORD = "${password:16}"
|
||||
JWT = "${base64:64}"
|
||||
ALLOW_ACCOUNT_CREATION = "false"
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
alist:
|
||||
image: xhofe/alist:v3.55.0
|
||||
image: xhofe/alist:v3.41.0
|
||||
volumes:
|
||||
- alist-data:/opt/alist/data
|
||||
environment:
|
||||
@@ -11,4 +11,4 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
alist-data:
|
||||
alist-data:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "alist",
|
||||
"name": "AList",
|
||||
"version": "v3.55.0",
|
||||
"description": "🗂ï¸A file list/WebDAV program that supports multiple storages, powered by Gin and Solidjs.",
|
||||
"logo": "alist.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/AlistGo/alist",
|
||||
"website": "https://alistgo.com/",
|
||||
"docs": "https://alistgo.com/guide/install/docker.html"
|
||||
},
|
||||
"tags": [
|
||||
"file",
|
||||
"webdav",
|
||||
"storage"
|
||||
]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
env = []
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "alist"
|
||||
port = 5_244
|
||||
host = "${main_domain}"
|
||||
@@ -0,0 +1,12 @@
|
||||
variables:
|
||||
main_domain: ${domain}
|
||||
|
||||
config:
|
||||
domains:
|
||||
- serviceName: alist
|
||||
port: 5244
|
||||
host: ${main_domain}
|
||||
|
||||
env: []
|
||||
|
||||
mounts: []
|
||||
@@ -1,12 +0,0 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
alltube:
|
||||
image: dnomd343/alltube:latest
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 80
|
||||
environment:
|
||||
- TITLE=${TITLE}
|
||||
- CONVERT=${CONVERT}
|
||||
- STREAM=${STREAM}
|
||||
- REMUX=${REMUX}
|
||||
|
Before Width: | Height: | Size: 31 KiB |
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "alltube",
|
||||
"name": "AllTube",
|
||||
"version": "latest",
|
||||
"description": "AllTube Download is an application designed to facilitate the downloading of videos from YouTube and other video sites. It provides an HTML GUI for youtube-dl with video conversion capabilities and JSON API support.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/Rudloff/alltube",
|
||||
"website": "https://github.com/Rudloff/alltube",
|
||||
"docs": "https://github.com/Rudloff/alltube/wiki"
|
||||
},
|
||||
"tags": [
|
||||
"media",
|
||||
"video",
|
||||
"downloader"
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
title = "My AllTube Site"
|
||||
convert = "true"
|
||||
stream = "true"
|
||||
remux = "true"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "alltube"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
TITLE = "${title}"
|
||||
CONVERT = "${convert}"
|
||||
STREAM = "${stream}"
|
||||
REMUX = "${remux}"
|
||||
@@ -1,17 +0,0 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
ampache:
|
||||
image: ampache/ampache:latest
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 80
|
||||
volumes:
|
||||
- config:/var/www/config
|
||||
- log:/var/log/ampache
|
||||
- mysql:/var/lib/mysql
|
||||
- ${MEDIA_PATH}:/media
|
||||
|
||||
volumes:
|
||||
config: {}
|
||||
log: {}
|
||||
mysql: {}
|
||||
|
Before Width: | Height: | Size: 8.9 KiB |
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "ampache",
|
||||
"name": "Ampache",
|
||||
"version": "latest",
|
||||
"description": "Ampache is a web-based audio/video streaming application and file manager allowing you to access your music & videos from anywhere, using almost any internet enabled device.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/ampache/ampache",
|
||||
"website": "http://ampache.org/",
|
||||
"docs": "https://github.com/ampache/ampache/wiki"
|
||||
},
|
||||
"tags": [
|
||||
"media",
|
||||
"music",
|
||||
"streaming"
|
||||
]
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
media_path = "/path/to/your/media"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "ampache"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
MEDIA_PATH = "${media_path}"
|
||||
@@ -1,16 +0,0 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
anonupload:
|
||||
image: ghcr.io/supernova3339/anonfiles:1
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 80
|
||||
environment:
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||
- CONTACT_EMAIL=${CONTACT_EMAIL}
|
||||
volumes:
|
||||
- uploads:/var/www/html/uploads
|
||||
|
||||
volumes:
|
||||
uploads: {}
|
||||
|
Before Width: | Height: | Size: 258 KiB |
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"id": "anonupload",
|
||||
"name": "AnonUpload",
|
||||
"version": "1",
|
||||
"description": "AnonUpload is a secure, anonymous file sharing application that does not require a database. It is built with privacy as a priority, ensuring that the direct filename used is not displayed.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/supernova3339/anonupload",
|
||||
"docs": "https://github.com/Supernova3339/anonupload/blob/main/env.md",
|
||||
"website": "https://anonupload.com/"
|
||||
},
|
||||
"tags": [
|
||||
"file-sharing",
|
||||
"privacy"
|
||||
]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
admin_email = "${email}"
|
||||
admin_password = "${password:16}"
|
||||
contact_email = "${email}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "anonupload"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
ADMIN_EMAIL = "${admin_email}"
|
||||
ADMIN_PASSWORD = "${admin_password}"
|
||||
CONTACT_EMAIL = "${contact_email}"
|
||||
@@ -1,15 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
anse-demo:
|
||||
image: ddiu8081/anse:latest
|
||||
restart: always
|
||||
expose:
|
||||
- 3000
|
||||
environment:
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||
- OPENAI_API_BASE_URL=${OPENAI_API_BASE_URL}
|
||||
- HEAD_SCRIPTS=${HEAD_SCRIPTS}
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
- SITE_PASSWORD=${SITE_PASSWORD}
|
||||
- OPENAI_API_MODEL=${OPENAI_API_MODEL}
|
||||
|
Before Width: | Height: | Size: 42 KiB |
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"id": "anse",
|
||||
"name": "Anse",
|
||||
"version": "latest",
|
||||
"description": "Anse is an open-source alternative to ChatGPT web UI, supporting OpenAI-compatible APIs.",
|
||||
"logo": "image.png",
|
||||
"links": {
|
||||
"github": "https://github.com/ddiu8081/anse",
|
||||
"website": "https://anse.app/",
|
||||
"docs": "https://github.com/ddiu8081/anse#readme"
|
||||
},
|
||||
"tags": [
|
||||
"ai",
|
||||
"chatbot",
|
||||
"openai",
|
||||
"ui"
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
secret_key = "${password:32}"
|
||||
site_password = "${password:16}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "anse-demo"
|
||||
port = 3000
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
OPENAI_API_KEY = ""
|
||||
OPENAI_API_BASE_URL = "https://api.openai.com"
|
||||
HEAD_SCRIPTS = ""
|
||||
SECRET_KEY = "${secret_key}"
|
||||
SITE_PASSWORD = "${site_password}"
|
||||
OPENAI_API_MODEL = "gpt-4o-mini"
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
answer:
|
||||
image: apache/answer:1.4.1
|
||||
expose:
|
||||
ports:
|
||||
- '80'
|
||||
restart: on-failure
|
||||
volumes:
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"id": "answer",
|
||||
"name": "Answer",
|
||||
"version": "v1.4.1",
|
||||
"description": "Answer is an open-source Q&A platform for building a self-hosted question-and-answer service.",
|
||||
"logo": "answer.png",
|
||||
"links": {
|
||||
"github": "https://github.com/apache/answer",
|
||||
"website": "https://answer.apache.org/",
|
||||
"docs": "https://answer.apache.org/docs"
|
||||
},
|
||||
"tags": [
|
||||
"q-and-a",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
service_hash = "${hash:32}"
|
||||
|
||||
[config]
|
||||
env = ["ANSWER_HOST=http://${main_domain}", "SERVICE_HASH=${service_hash}"]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "answer"
|
||||
port = 9_080
|
||||
host = "${main_domain}"
|
||||
@@ -0,0 +1,15 @@
|
||||
variables:
|
||||
main_domain: ${domain}
|
||||
service_hash: ${hash:32}
|
||||
|
||||
config:
|
||||
domains:
|
||||
- serviceName: answer
|
||||
port: 9080
|
||||
host: ${main_domain}
|
||||
|
||||
env:
|
||||
- ANSWER_HOST=http://${main_domain}
|
||||
- SERVICE_HASH=${service_hash}
|
||||
|
||||
mounts: []
|
||||