diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3c6f39c67..3dbde68d1 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -2,30 +2,58 @@ # # Version source of truth: docs/image-tag-spec.md. # -# Tag namespaces (same repository, tag differs by branch): -# main branch -> stable tags from W9_VERSION -# dev branch -> dev- (primary) + dev-latest (rolling alias) +# Tag namespaces (same repository, tag differs by branch/event): +# dev push -> dev- (primary) + dev-latest (rolling alias) +# amd64 is required; arm64 is best-effort and warns on failure +# final tags are assembled from digests; no temporary arch tags are published +# PR merged to main -> promote the PR head's dev- to stable tags (no rebuild) +# main push -> not wired; main promotion happens only on a merged PR # # Build contract: the Dockerfile MUST declare a version ARG whose name is # ${APP^^}_VERSION (e.g. apps/akeneo -> AKENEO_VERSION). CI reads W9_VERSION # from apps//.env, injects it as that build-arg, and tags from W9_VERSION. # Apps that do not declare this ARG are not built by CI; migrate them when touched. # +# Manual runs (workflow_dispatch) target one app: +# run on dev -> build dev- + dev-latest for the chosen app +# run on main -> promote the chosen app; source_sha (the validated dev-) is required +# # If you add more paths for trigger, please update app_list= at set-matrix for it also. name: Build image to DockerHub on: push: - branches: [main, dev] + branches: [dev] + paths: + - "apps/*/Dockerfile" + - "apps/*/.env" + - "apps/*/src/**" + - "apps/*/cmd.sh" + - "apps/*/entrypoint.sh" + pull_request: + types: [closed] + branches: [main] paths: - "apps/*/Dockerfile" - "apps/*/.env" + - "apps/*/src/**" - "apps/*/cmd.sh" - "apps/*/entrypoint.sh" + workflow_dispatch: + inputs: + app: + description: "App name to build or promote (e.g. strapi)" + required: true + type: string + source_sha: + description: "Validated dev commit SHA to promote (required for manual promote on main)" + required: false + type: string jobs: setup: + if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.merged && github.event.pull_request.base.ref == 'main') }} runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} @@ -33,15 +61,33 @@ jobs: - name: Checkout code uses: actions/checkout@v7 with: - fetch-depth: 2 + # Pin the PR head (dev commit) because the pull_request merge ref can be + # gone by the time a merged PR's `closed` event runs. + ref: ${{ github.event.pull_request.head.sha || github.sha }} + fetch-depth: 0 - id: set-matrix + env: + EVENT_NAME: ${{ github.event_name }} + INPUT_APP: ${{ inputs.app }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: | - changed_files=$(git diff --name-only HEAD^ HEAD) - app_list=$(echo "$changed_files" | grep -E 'apps/.*/(Dockerfile|.env|cmd.sh|entrypoint.sh)$' | awk -F'/' '{print $2}' | sort | uniq) + if [ "$EVENT_NAME" = "workflow_dispatch" ]; then + app_list="$INPUT_APP" + elif [ "$EVENT_NAME" = "pull_request" ]; then + app_list=$(git diff --name-only "$PR_BASE_SHA" "$PR_HEAD_SHA" | grep -E 'apps/.*/(Dockerfile|.env|cmd.sh|entrypoint.sh|src/.*)$' | awk -F'/' '{print $2}' | sort | uniq) + else + changed_files=$(git diff --name-only HEAD^ HEAD) + app_list=$(echo "$changed_files" | grep -E 'apps/.*/(Dockerfile|.env|cmd.sh|entrypoint.sh|src/.*)$' | awk -F'/' '{print $2}' | sort | uniq) + fi valid_list=() for app in $app_list; do + if [ -z "$app" ]; then + continue + fi if [ ! -f "apps/$app/Dockerfile" ]; then + echo "skip (no Dockerfile): $app" continue fi app_upper=$(echo "$app" | tr '[:lower:]' '[:upper:]') @@ -52,17 +98,24 @@ jobs: fi valid_list+=("$app") done + if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "${#valid_list[@]}" -eq 0 ]; then + echo "error: app '$INPUT_APP' is not buildable (missing Dockerfile or _VERSION ARG)" >&2 + exit 1 + fi app_list_json=$(jq -cn '$ARGS.positional' --args "${valid_list[@]}") - echo "::set-output name=matrix::{\"app\": $app_list_json}" + echo "matrix={\"app\": $app_list_json}" >> "$GITHUB_OUTPUT" build: needs: setup + if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.merged && github.event.pull_request.base.ref == 'main') }} runs-on: ubuntu-latest strategy: matrix: ${{fromJson(needs.setup.outputs.matrix)}} steps: - name: Checkout code uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Set up Python uses: actions/setup-python@v7 @@ -75,17 +128,45 @@ jobs: pip install pyyaml - name: Resolve channel + env: + EVENT_NAME: ${{ github.event_name }} + REF_NAME: ${{ github.ref_name }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + INPUT_SOURCE_SHA: ${{ inputs.source_sha }} run: | echo "APP=${{ matrix.app }}" >> $GITHUB_ENV - if [ "${{ github.ref_name }}" = "main" ]; then + if [ "$EVENT_NAME" = "pull_request" ]; then + # main promotion: promote the exact dev commit that was merged and built. echo "CHANNEL=promote" >> $GITHUB_ENV + echo "SOURCE_SHA=$PR_HEAD_SHA" >> $GITHUB_ENV + elif [ "$EVENT_NAME" = "workflow_dispatch" ]; then + case "$REF_NAME" in + dev) + echo "CHANNEL=dev" >> $GITHUB_ENV + echo "SOURCE_SHA=" >> $GITHUB_ENV + ;; + main) + if [ -z "$INPUT_SOURCE_SHA" ]; then + echo "error: manual promote on main requires source_sha (the validated dev-)" >&2 + exit 1 + fi + echo "CHANNEL=promote" >> $GITHUB_ENV + echo "SOURCE_SHA=$INPUT_SOURCE_SHA" >> $GITHUB_ENV + ;; + *) + echo "error: manual runs must target the dev or main branch (got '$REF_NAME')" >&2 + exit 1 + ;; + esac else + # push to dev: build candidate images. echo "CHANNEL=dev" >> $GITHUB_ENV + echo "SOURCE_SHA=" >> $GITHUB_ENV fi - name: Generate build/promote plan run: | - PYTHONPATH=cli python -c "import json,sys; from libs.app_build import build_plan; print(json.dumps(build_plan(app_name=sys.argv[1], channel=sys.argv[2], git_sha=sys.argv[3]), indent=2, ensure_ascii=False))" "${{ env.APP }}" "${{ env.CHANNEL }}" "${{ github.sha }}" > plan.json + PYTHONPATH=cli python -c "import json,os,sys; from libs.app_build import build_plan; print(json.dumps(build_plan(app_name=sys.argv[1], channel=sys.argv[2], git_sha=sys.argv[3], source_sha=(os.environ.get('SOURCE_SHA') or None)), indent=2, ensure_ascii=False))" "${{ env.APP }}" "${{ env.CHANNEL }}" "${{ github.sha }}" > plan.json cat plan.json echo "CHANNEL=$(jq -r '.channel' plan.json)" >> $GITHUB_ENV echo "DIRECTORY=$(jq -r '.context' plan.json)" >> $GITHUB_ENV @@ -122,26 +203,72 @@ jobs: - name: Build and push Docker image (dev) if: env.CHANNEL == 'dev' - uses: docker/build-push-action@v7 - with: - context: ${{env.DIRECTORY}} - file: ${{env.DOCKERFILE}} - push: true - tags: ${{env.TAGS}} - platforms: linux/amd64 - build-args: | - ${{ env.VERSION_ARG }}=${{ env.W9_VERSION }} + shell: bash + run: | + IFS=',' read -r -a TAG_ARRAY <<< "${{ env.TAGS }}" + + IMAGE_REPO="${TAG_ARRAY[0]%:*}" + AMD64_META=$(mktemp) + ARM64_META=$(mktemp) + trap 'rm -f "$AMD64_META" "$ARM64_META"' EXIT + + docker buildx build \ + --platform linux/amd64 \ + -f "${{ env.DOCKERFILE }}" \ + --build-arg "${{ env.VERSION_ARG }}=${{ env.W9_VERSION }}" \ + --metadata-file "$AMD64_META" \ + --output "type=image,name=${IMAGE_REPO},push-by-digest=true,name-canonical=true,push=true" \ + "${{ env.DIRECTORY }}" + + AMD64_DIGEST=$(jq -r '."containerimage.digest" // empty' "$AMD64_META") + if [ -z "$AMD64_DIGEST" ]; then + echo "ERROR: amd64 build completed but no digest was recorded for ${{ matrix.app }}" >&2 + exit 1 + fi + + ARM64_OK=true + if ! docker buildx build \ + --platform linux/arm64 \ + -f "${{ env.DOCKERFILE }}" \ + --build-arg "${{ env.VERSION_ARG }}=${{ env.W9_VERSION }}" \ + --metadata-file "$ARM64_META" \ + --output "type=image,name=${IMAGE_REPO},push-by-digest=true,name-canonical=true,push=true" \ + "${{ env.DIRECTORY }}"; then + ARM64_OK=false + echo "::warning::arm64 image build failed for ${{ matrix.app }}; published amd64-only tags" + fi + + ARM64_DIGEST="" + if [ "$ARM64_OK" = true ]; then + ARM64_DIGEST=$(jq -r '."containerimage.digest" // empty' "$ARM64_META") + if [ -z "$ARM64_DIGEST" ]; then + ARM64_OK=false + echo "::warning::arm64 build completed but no digest was recorded for ${{ matrix.app }}; published amd64-only tags" + fi + fi + + for tag in "${TAG_ARRAY[@]}"; do + if [ "$ARM64_OK" = true ]; then + docker buildx imagetools create --tag "$tag" "${IMAGE_REPO}@${AMD64_DIGEST}" "${IMAGE_REPO}@${ARM64_DIGEST}" + else + docker buildx imagetools create --tag "$tag" "${IMAGE_REPO}@${AMD64_DIGEST}" + fi + done - name: Promote dev image to stable tags (main) if: env.CHANNEL == 'promote' run: | - if ! docker buildx imagetools inspect "${{ env.SOURCE_IMAGE }}" >/dev/null 2>&1; then + if ! SOURCE_DIGEST=$(docker buildx imagetools inspect "${{ env.SOURCE_IMAGE }}" --format '{{.Manifest.Digest}}' 2>/dev/null); then echo "ERROR: source dev image missing: ${{ env.SOURCE_IMAGE }}" >&2 echo "Expected this image to be built and pushed by the dev branch workflow before main promotion." >&2 exit 1 fi TAG_ARGS="" for tag in $(echo "${{ env.TAGS }}" | tr ',' ' '); do + existing=$(docker buildx imagetools inspect "$tag" --format '{{.Manifest.Digest}}' 2>/dev/null || true) + if [ -n "$existing" ] && [ "$existing" != "$SOURCE_DIGEST" ]; then + echo "::warning::overwriting existing $tag ($existing) with ${{ env.SOURCE_IMAGE }} ($SOURCE_DIGEST)" + fi TAG_ARGS="$TAG_ARGS --tag $tag" done docker buildx imagetools create $TAG_ARGS "${{ env.SOURCE_IMAGE }}" diff --git a/AGENTS.md b/AGENTS.md index 92c7ef1a3..90adeea89 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,6 +28,7 @@ Read in this order: - Follow env conventions. The canonical `W9_*` reference is `docs/w9-env-spec.md` (semantics + decision rules); use `metadata/templates/new-app/.env.tmpl` for layout. Read both before editing `.env` or `docker-compose.yml`. - Runtime/scaffold apps follow `docs/runtime-app-spec.md` (entrypoint hooks + optional `DATABASE_URL`). - Validate by deployment when the task changes runnable behavior. +- When `.secrets/remote.env` exists, prefer remote-first for all runtime, deployment, debugging, and validation work; do not default to local container execution unless the user explicitly asks for local reproduction. ## i18n diff --git a/CHANGELOG.md b/CHANGELOG.md index 22dc44d1d..327aa4842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and versions prior to 0.8.0 do not strictly follow this format. ## [0.8.0] - Unreleased ### Added +- `libs dns-bind` / `libs dns-delete` for Aliyun DNS wildcard records, plus the `aliyun` provider in `make connector` - Appstore Publish workflow with v2/catalog/library/manifest output model - Channel-aware distribution merge for dev channel - `workflow_dispatch` support for manual dev/rc/release publishing diff --git a/Makefile b/Makefile index 48e8b3165..6d73021bf 100644 --- a/Makefile +++ b/Makefile @@ -93,24 +93,48 @@ connector: @bash -lc 'set -e; \ current_choice=1; \ if [ "${PROVIDER:-}" = "cloudflare" ] || [ "${PROVIDER:-}" = "2" ]; then current_choice=2; fi; \ - printf "Available providers:\n 1) contentful\n 2) cloudflare\n 3) dockerhub\n"; \ + if [ "${PROVIDER:-}" = "dockerhub" ] || [ "${PROVIDER:-}" = "3" ]; then current_choice=3; fi; \ + if [ "${PROVIDER:-}" = "aliyun" ] || [ "${PROVIDER:-}" = "4" ]; then current_choice=4; fi; \ + printf "Available providers:\n 1) contentful\n 2) cloudflare\n 3) dockerhub\n 4) aliyun (DNS)\n"; \ read -r -p "provider [$$current_choice]: " input_choice; input_choice="$${input_choice:-$$current_choice}"; \ case "$$input_choice" in \ 1|contentful) provider="contentful"; file=".secrets/contentful.env"; key="CONTENTFUL_ACCESS_TOKEN" ;; \ 2|cloudflare) provider="cloudflare"; file=".secrets/cloudflare.env"; key="CLOUDFLARE_API_TOKEN" ;; \ 3|dockerhub) provider="dockerhub"; file=".secrets/dockerhub.env"; key="DOCKERHUB_TOKEN" ;; \ + 4|aliyun) provider="aliyun"; file=".secrets/aliyun.env"; key="ALIYUN_ACCESS_KEY_SECRET" ;; \ *) echo "unsupported provider selection: $$input_choice" >&2; exit 1 ;; \ esac; \ if [ -f "$$file" ]; then echo "updating $$file"; else echo "creating $$file"; fi; \ - if [ "$$provider" = "dockerhub" ]; then \ - read -r -p "DOCKERHUB_USERNAME: " input_user; \ - read -r -s -p "DOCKERHUB_PASSWORD (leave empty to use token): " input_password; echo; \ - if [ -n "$$input_password" ]; then \ - if [ -z "$$input_user" ]; then echo "username is required" >&2; exit 1; fi; \ - printf "DOCKERHUB_USERNAME=%s\nDOCKERHUB_PASSWORD=%s\n" "$$input_user" "$$input_password" > "$$file"; \ + if [ "$$provider" = "aliyun" ]; then \ + cur_id=""; cur_secret=""; cur_domain=""; \ + if [ -f "$$file" ]; then \ + cur_id="$$(grep -E '^ALIYUN_ACCESS_KEY_ID=' "$$file" | cut -d= -f2-)"; \ + cur_secret="$$(grep -E '^ALIYUN_ACCESS_KEY_SECRET=' "$$file" | cut -d= -f2-)"; \ + cur_domain="$$(grep -E '^ALIYUN_DNS_DOMAIN=' "$$file" | cut -d= -f2-)"; \ + fi; \ + read -r -p "ALIYUN_ACCESS_KEY_ID [$$cur_id]: " input_id; input_id="$${input_id:-$$cur_id}"; \ + read -r -s -p "ALIYUN_ACCESS_KEY_SECRET [keep existing]: " input_secret; echo; input_secret="$${input_secret:-$$cur_secret}"; \ + read -r -p "ALIYUN_DNS_DOMAIN (wildcard base, e.g. libs.websoft9.cn) [$$cur_domain]: " input_domain; input_domain="$${input_domain:-$$cur_domain}"; \ + if [ -z "$$input_id" ] || [ -z "$$input_secret" ]; then echo "access key id and secret are required" >&2; exit 1; fi; \ + if [ -n "$$input_domain" ]; then \ + printf "ALIYUN_ACCESS_KEY_ID=%s\nALIYUN_ACCESS_KEY_SECRET=%s\nALIYUN_DNS_DOMAIN=%s\n" "$$input_id" "$$input_secret" "$$input_domain" > "$$file"; \ + else \ + printf "ALIYUN_ACCESS_KEY_ID=%s\nALIYUN_ACCESS_KEY_SECRET=%s\n" "$$input_id" "$$input_secret" > "$$file"; \ + fi; \ + elif [ "$$provider" = "dockerhub" ]; then \ + cur_user=""; cur_token=""; cur_org=""; \ + if [ -f "$$file" ]; then \ + cur_user="$$(grep -E '^DOCKERHUB_USERNAME=' "$$file" | cut -d= -f2-)"; \ + cur_token="$$(grep -E '^DOCKERHUB_TOKEN=' "$$file" | cut -d= -f2-)"; \ + cur_org="$$(grep -E '^DOCKERHUB_ORG=' "$$file" | cut -d= -f2-)"; \ + fi; \ + read -r -p "DOCKERHUB_USERNAME [$$cur_user]: " input_user; input_user="$${input_user:-$$cur_user}"; \ + read -r -s -p "DOCKERHUB_TOKEN [keep existing]: " input_token; echo; input_token="$${input_token:-$$cur_token}"; \ + read -r -p "DOCKERHUB_ORG (optional default push namespace) [$$cur_org]: " input_org; input_org="$${input_org:-$$cur_org}"; \ + if [ -z "$$input_user" ] || [ -z "$$input_token" ]; then echo "username and token are required" >&2; exit 1; fi; \ + if [ -n "$$input_org" ]; then \ + printf "DOCKERHUB_USERNAME=%s\nDOCKERHUB_TOKEN=%s\nDOCKERHUB_ORG=%s\n" "$$input_user" "$$input_token" "$$input_org" > "$$file"; \ else \ - read -r -s -p "DOCKERHUB_TOKEN: " input_token; echo; \ - if [ -z "$$input_user" ] || [ -z "$$input_token" ]; then echo "username and token are required" >&2; exit 1; fi; \ printf "DOCKERHUB_USERNAME=%s\nDOCKERHUB_TOKEN=%s\n" "$$input_user" "$$input_token" > "$$file"; \ fi; \ else \ diff --git a/Notes.md b/Notes.md index 6ded29c8d..78aa9c256 100644 --- a/Notes.md +++ b/Notes.md @@ -29,5 +29,17 @@ Docker Model Runner InfluxDB 开源时序数据库 OpenClaw Canvas LMS -laravel Semaphore + + +cloudreve,compreface,commafeed, coze,dashy,ejbca, frigate, falcon + +Qdrant +e2e test for: vaultwarden, varnish +varnish not have config file +pangolin.net +RustDesk +elizaOS +Nuclear + +port define? \ No newline at end of file diff --git a/apps/activemq/variables.json b/apps/activemq/variables.json index 387e92728..154533fab 100644 --- a/apps/activemq/variables.json +++ b/apps/activemq/variables.json @@ -20,6 +20,17 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8161, + "path": "/" + }, + "admin": { + "port": 8161, + "path": "/admin" + } + }, "requirements": { "cpu": "1", "memory": "2", diff --git a/apps/affine/variables.json b/apps/affine/variables.json index 645c02af1..24ba3801c 100644 --- a/apps/affine/variables.json +++ b/apps/affine/variables.json @@ -23,6 +23,22 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3010, + "path": "/" + }, + "admin": { + "port": 3010, + "path": "/admin" + } + }, + "requirements": { + "cpu": "2", + "memory": "4", + "disk": "8" + }, "database": { "type": "postgresql", "supported_modes": [ @@ -38,11 +54,6 @@ "password_env": "POSTGRES_PASSWORD" } }, - "requirements": { - "cpu": "2", - "memory": "4", - "disk": "8" - }, "env": { "first_startup_only": [] } diff --git a/apps/airflow/variables.json b/apps/airflow/variables.json index 4c561a2a4..4d48a56d7 100644 --- a/apps/airflow/variables.json +++ b/apps/airflow/variables.json @@ -19,6 +19,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8080, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", diff --git a/apps/anythingllm/variables.json b/apps/anythingllm/variables.json index 276fa3d43..5ac946391 100644 --- a/apps/anythingllm/variables.json +++ b/apps/anythingllm/variables.json @@ -24,6 +24,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3001, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", diff --git a/apps/appsmith/variables.json b/apps/appsmith/variables.json index 59a13c22a..e66b07588 100644 --- a/apps/appsmith/variables.json +++ b/apps/appsmith/variables.json @@ -24,6 +24,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 80, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", diff --git a/apps/bitwarden/variables.json b/apps/bitwarden/variables.json index 481db95f2..391f956b3 100644 --- a/apps/bitwarden/variables.json +++ b/apps/bitwarden/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8080, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", diff --git a/apps/bookstack/variables.json b/apps/bookstack/variables.json index 41cb0ec12..bba5f4c5c 100644 --- a/apps/bookstack/variables.json +++ b/apps/bookstack/variables.json @@ -2,6 +2,9 @@ "name": "bookstack", "trademark": "BookStack", "release": true, + "upstream": { + "image": "https://github.com/linuxserver/docker-bookstack/pkgs/container/bookstack" + }, "edition": [ { "dist": "community", @@ -11,12 +14,20 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 80, + "path": "/" + }, + "admin": { + "port": 80, + "path": "/login" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "10" - }, - "upstream": { - "image": "https://github.com/linuxserver/docker-bookstack/pkgs/container/bookstack" } } diff --git a/apps/browserless/variables.json b/apps/browserless/variables.json index 6ab09c23d..24526d94a 100644 --- a/apps/browserless/variables.json +++ b/apps/browserless/variables.json @@ -2,6 +2,9 @@ "name": "browserless", "trademark": "Browserless", "release": true, + "upstream": { + "image": "ghcr.io/browserless/chromium" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3000, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "2" - }, - "upstream": { - "image": "ghcr.io/browserless/chromium" } } diff --git a/apps/budibase/variables.json b/apps/budibase/variables.json index 72d2e7de5..0b9430c8e 100644 --- a/apps/budibase/variables.json +++ b/apps/budibase/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 10000, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "8", diff --git a/apps/bunkerweb/variables.json b/apps/bunkerweb/variables.json index 28e81e03d..bde383ee1 100644 --- a/apps/bunkerweb/variables.json +++ b/apps/bunkerweb/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "https", + "web": { + "port": 8443, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", diff --git a/apps/bytebase/variables.json b/apps/bytebase/variables.json index 9445b7fa2..621056099 100644 --- a/apps/bytebase/variables.json +++ b/apps/bytebase/variables.json @@ -2,6 +2,9 @@ "name": "bytebase", "trademark": "Bytebase", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/bytebase/bytebase" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8080, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "2" - }, - "upstream": { - "image": "https://hub.docker.com/r/bytebase/bytebase" } } diff --git a/apps/canvas/variables.json b/apps/canvas/variables.json index cf3b72c4a..fe82a55df 100644 --- a/apps/canvas/variables.json +++ b/apps/canvas/variables.json @@ -2,6 +2,17 @@ "name": "canvas", "trademark": "Canvas", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/websoft9dev/canvas", + "releases": "https://github.com/instructure/canvas-lms", + "compose": { + "compose": "https://raw.githubusercontent.com/instructure/canvas-lms/master/docker-compose.yml" + }, + "docs": [ + "https://github.com/instructure/canvas-lms/wiki/Production-Start", + "https://raw.githubusercontent.com/instructure/canvas-lms/master/Dockerfile.production" + ] + }, "edition": [ { "dist": "community", @@ -10,22 +21,22 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 80, + "path": "/" + }, + "admin": { + "port": 80, + "path": "/login/canvas" + } + }, "requirements": { "cpu": "4", "memory": "8", "disk": "20" }, - "upstream": { - "image": "https://hub.docker.com/r/websoft9dev/canvas", - "releases": "https://github.com/instructure/canvas-lms", - "compose": { - "compose": "https://raw.githubusercontent.com/instructure/canvas-lms/master/docker-compose.yml" - }, - "docs": [ - "https://github.com/instructure/canvas-lms/wiki/Production-Start", - "https://raw.githubusercontent.com/instructure/canvas-lms/master/Dockerfile.production" - ] - }, "env": { "first_startup_only": [ "CANVAS_LMS_ADMIN_EMAIL", diff --git a/apps/chatwoot/variables.json b/apps/chatwoot/variables.json index 08f4036f8..9ba3e9f7f 100644 --- a/apps/chatwoot/variables.json +++ b/apps/chatwoot/variables.json @@ -2,6 +2,9 @@ "name": "chatwoot", "trademark": "Chatwoot", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/chatwoot/chatwoot" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3000, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", "disk": "1" - }, - "upstream": { - "image": "https://hub.docker.com/r/chatwoot/chatwoot" } } diff --git a/apps/cloudbeaver/.env b/apps/cloudbeaver/.env index 80105c23d..386428787 100644 --- a/apps/cloudbeaver/.env +++ b/apps/cloudbeaver/.env @@ -1,22 +1,49 @@ -W9_VERSION='25.2.2' -W9_DIST='community' W9_REPO=dbeaver/cloudbeaver +W9_DIST=community +W9_VERSION=26.2 + W9_POWER_PASSWORD='Bwmj2DDuZoM!Q08G' +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='cloudbeaver' + +W9_ID=cloudbeaver + +# Web/internal ports: uncomment the ones the package actually uses. W9_HTTP_PORT=8978 -W9_HTTP_PORT_SET='9090' -W9_URL='appname.example.com' +W9_HTTP_PORT_SET=9090 -# dont't user [admin] which is cloudbeaver system variable +# Built-in login and URL helpers: keep these in the protected block because the appstore parser +# reads them from the app package metadata surface. +# Do not use [admin], which is reserved by CloudBeaver. W9_LOGIN_USER=cbadmin -W9_LOGIN_PASSWORD=$W9_POWER_PASSWORD +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} +W9_URL=appname.example.com +W9_URL_REPLACE=true + W9_NETWORK=websoft9 -#### --------------------------------------------------------------------------------------- #### -# CloudBeaver environments: https://github.com/dbeaver/cloudbeaver/wiki/Server-configuration#automatic-server-configuration +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# CloudBeaver image environment variables +# Docs: https://github.com/dbeaver/cloudbeaver/wiki/Server-configuration#automatic-server-configuration +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: CB_SERVER_NAME="CloudBeaver Server" -CB_SERVER_URL=$W9_URL -CB_ADMIN_NAME=$W9_LOGIN_USER -CB_ADMIN_PASSWORD=$W9_POWER_PASSWORD +CB_SERVER_URL=${W9_URL} +CB_ADMIN_NAME=${W9_LOGIN_USER} +CB_ADMIN_PASSWORD=${W9_POWER_PASSWORD} + +# Not used by default; enable only when needed: +# CLOUDBEAVER_WEB_SERVER_PORT=8978 +# CLOUDBEAVER_APP_ANONYMOUS_ACCESS_ENABLED=false +# CLOUDBEAVER_APP_SUPPORTS_CUSTOM_CONNECTIONS=false +# CLOUDBEAVER_DB_DRIVER=postgres-jdbc +# CLOUDBEAVER_DB_URL=jdbc:postgresql://postgres:5432/cloudbeaver diff --git a/apps/cloudbeaver/CHANGELOG.md b/apps/cloudbeaver/CHANGELOG.md index 582cf46c5..922e2ad71 100644 --- a/apps/cloudbeaver/CHANGELOG.md +++ b/apps/cloudbeaver/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-17 +- Updated `W9_VERSION` from `25.2.2` to `26.2` and aligned `variables.json.edition` with the current upstream `x.x` tag. +- Normalized `.env` and `docker-compose.yml` to current repository policy: braced `${VAR}` references, a port purpose comment, no image/docs source comments, and no obsolete compose `version` key. +- Added `W9_URL_REPLACE=true` because `CB_SERVER_URL` references `W9_URL`. +- Added `upstream.docs`, `variables.json.access`, and `env.first_startup_only`; regenerated the README. diff --git a/apps/cloudbeaver/Notes.md b/apps/cloudbeaver/Notes.md deleted file mode 100644 index 6fd49fd5f..000000000 --- a/apps/cloudbeaver/Notes.md +++ /dev/null @@ -1,3 +0,0 @@ -# CloudBeaver - -Not found any enviroments diff --git a/apps/cloudbeaver/README.md b/apps/cloudbeaver/README.md index c9a339e33..f40b98ae8 100644 --- a/apps/cloudbeaver/README.md +++ b/apps/cloudbeaver/README.md @@ -1,26 +1,86 @@ -# CloudBeaver on Docker +# CloudBeaver on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for CloudBeaver: +## Quick Start +### Deploy Verification - - community: 25.1.0, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **CloudBeaver**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. CloudBeaver creates the administrator account from `W9_LOGIN_USER` / `W9_LOGIN_PASSWORD` on first start. +2. Sign in and create a database connection. +3. Try a core feature, such as the SQL editor. -The following are the minimal [recommended requirements](https://github.com/dbeaver/cloudbeaver/wiki): +### Change Password -* **RAM**: 2 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 4 GB of free space -* **bandwidth**: more fluent experience over 100M +1. Sign in to CloudBeaver and change the password from the user profile. +2. `W9_LOGIN_PASSWORD` seeds the administrator password on first startup; changing `.env` later does not change an existing password. +3. If you cannot sign in, reset the password with the upstream admin password recovery procedure. + -## Install +## Configuration Reference -You can install this CloudBeaver by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [CloudBeaver Docker image](https://hub.docker.com/r/dbeaver/cloudbeaver) and makes some improvements below. -If you want use CloudBeaver with **Websoft9 Business Support** free, you can [subscribe CloudBeaver](https://www.websoft9.com/apps) on Cloud platform + +The package passes the server name, the public server URL, and the initial administrator credentials through `.env` (`CB_*`). The administrator account is created on first start, and `CB_SERVER_URL` is wired to `W9_URL`. + -## Documentation +Apps run as containers; rebuild after any configuration change. -[CloudBeaver Administrator Guide](https://support.websoft9.com/docs/cloudbeaver) powered by Websoft9 \ No newline at end of file +### Version Support + +Supported versions: 26.2, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 8978 | + + +### Data Directory + + +Data is persisted in the `cloudbeaver` volume, mounted at `/opt/cloudbeaver/workspace`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +Note: `W9_LOGIN_PASSWORD` take effect on first startup only; changing them after deployment may not take effect until the app is re-initialized. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [CloudBeaver Administrator Guide](https://support.websoft9.com/docs/cloudbeaver) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/dbeaver/cloudbeaver) + +- [GitHub docs](https://github.com/dbeaver/cloudbeaver/wiki/Server-configuration) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/cloudbeaver/docker-compose.yml b/apps/cloudbeaver/docker-compose.yml index 7ecb5b29f..d069e07e5 100644 --- a/apps/cloudbeaver/docker-compose.yml +++ b/apps/cloudbeaver/docker-compose.yml @@ -1,18 +1,13 @@ -# image: https://hub.docker.com/r/dbeaver/cloudbeaver -# config docs: https://github.com/dbeaver/cloudbeaver/wiki/Server-configuration#automatic-server-configuration - -version: "3.8" - services: cloudbeaver: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID - restart: unless-stopped - env_file: .env - ports: - - '$W9_HTTP_PORT_SET:8978' - volumes: - - cloudbeaver:/opt/cloudbeaver/workspace + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} + restart: unless-stopped + env_file: .env + ports: + - "${W9_HTTP_PORT_SET}:8978" # Web Console + volumes: + - cloudbeaver:/opt/cloudbeaver/workspace networks: default: diff --git a/apps/cloudbeaver/src/filelist b/apps/cloudbeaver/src/README.md similarity index 100% rename from apps/cloudbeaver/src/filelist rename to apps/cloudbeaver/src/README.md diff --git a/apps/cloudbeaver/src/after_up.sh b/apps/cloudbeaver/src/after_up.sh deleted file mode 100644 index 8b1378917..000000000 --- a/apps/cloudbeaver/src/after_up.sh +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/cloudbeaver/src/get_version.sh b/apps/cloudbeaver/src/get_version.sh deleted file mode 100644 index 42c4975e5..000000000 --- a/apps/cloudbeaver/src/get_version.sh +++ /dev/null @@ -1 +0,0 @@ -sudo echo "cloudbeaver version: $(docker exec -i $1 sed -n '3p' /opt/cloudbeaver/server/readme.txt)" 1>> /data/logs/install_version.txt diff --git a/apps/cloudbeaver/tests/cases.yml b/apps/cloudbeaver/tests/cases.yml new file mode 100644 index 000000000..fe6388e32 --- /dev/null +++ b/apps/cloudbeaver/tests/cases.yml @@ -0,0 +1,5 @@ +optional: + - id: status-endpoint + type: web-access + path: /status + expect_status: 200 diff --git a/apps/cloudbeaver/variables.json b/apps/cloudbeaver/variables.json index 162b63976..f76cfc528 100644 --- a/apps/cloudbeaver/variables.json +++ b/apps/cloudbeaver/variables.json @@ -2,21 +2,35 @@ "name": "cloudbeaver", "trademark": "CloudBeaver", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/dbeaver/cloudbeaver", + "docs": [ + "https://github.com/dbeaver/cloudbeaver/wiki/Server-configuration" + ] + }, "edition": [ { "dist": "community", "version": [ - "25.2.2", + "26.2", "latest" ] } ], + "access": { + "web": { + "port": 8978, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "4" }, - "upstream": { - "image": "https://hub.docker.com/r/dbeaver/cloudbeaver" + "env": { + "first_startup_only": [ + "W9_LOGIN_PASSWORD" + ] } } diff --git a/apps/cloudreve/variables.json b/apps/cloudreve/variables.json index dfe92391f..fd8c9ae78 100644 --- a/apps/cloudreve/variables.json +++ b/apps/cloudreve/variables.json @@ -2,6 +2,9 @@ "name": "cloudreve", "trademark": "Cloudreve", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/cloudreve/cloudreve" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 5212, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "10" - }, - "upstream": { - "image": "https://hub.docker.com/r/cloudreve/cloudreve" } } diff --git a/apps/codeserver/variables.json b/apps/codeserver/variables.json index e007b47c2..48eece766 100644 --- a/apps/codeserver/variables.json +++ b/apps/codeserver/variables.json @@ -2,6 +2,9 @@ "name": "codeserver", "trademark": "CodeServer", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/linuxserver/code-server" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8443, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "2" - }, - "upstream": { - "image": "https://hub.docker.com/r/linuxserver/code-server" } } diff --git a/apps/collabora/variables.json b/apps/collabora/variables.json index a3675c94e..1e58fb1a1 100644 --- a/apps/collabora/variables.json +++ b/apps/collabora/variables.json @@ -2,6 +2,9 @@ "name": "collabora", "trademark": "Collabora", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/collabora/code" + }, "edition": [ { "dist": "community", @@ -11,12 +14,20 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 9980, + "path": "/" + }, + "admin": { + "port": 9980, + "path": "/browser/dist/admin/admin.html" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "4" - }, - "upstream": { - "image": "https://hub.docker.com/r/collabora/code" } } diff --git a/apps/consul/variables.json b/apps/consul/variables.json index 7529302b8..2c9e43822 100644 --- a/apps/consul/variables.json +++ b/apps/consul/variables.json @@ -2,6 +2,9 @@ "name": "consul", "trademark": "Consul", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/hashicorp/consul" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8500, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", "disk": "10" - }, - "upstream": { - "image": "https://hub.docker.com/r/hashicorp/consul" } } diff --git a/apps/couchdb/variables.json b/apps/couchdb/variables.json index 784a6374e..23b509da7 100644 --- a/apps/couchdb/variables.json +++ b/apps/couchdb/variables.json @@ -2,6 +2,9 @@ "name": "couchdb", "trademark": "CouchDB", "release": true, + "upstream": { + "image": "https://hub.docker.com/_/couchdb" + }, "edition": [ { "dist": "community", @@ -11,12 +14,20 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 5984, + "path": "/" + }, + "admin": { + "port": 5984, + "path": "/_utils" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "4" - }, - "upstream": { - "image": "https://hub.docker.com/_/couchdb" } } diff --git a/apps/databasus/.env b/apps/databasus/.env index f9d777cb7..b15fdaf84 100644 --- a/apps/databasus/.env +++ b/apps/databasus/.env @@ -1,11 +1,41 @@ -W9_REPO="databasus/databasus" +W9_REPO=databasus/databasus W9_DIST=community -W9_VERSION="latest" +W9_VERSION=v3.57.1 + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + +#### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_HTTP_PORT_SET=9001 W9_ID=databasus + +# Web/internal ports: uncomment the ones the package actually uses. W9_HTTP_PORT=4005 -W9_URL=example.youdomain.com +W9_HTTP_PORT_SET=9001 + +# Built-in login and URL helpers: keep these in the protected block because the appstore parser +# reads them from the app package metadata surface. +W9_URL=appname.example.com +W9_URL_REPLACE=true + W9_NETWORK=websoft9 #### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Databasus image environment variables +# Docs: https://databasus.com/advanced-config +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +DATABASUS_URL=http://${W9_URL} + +# Not used by default; enable only when needed: +# PUID=999 +# PGID=999 +# LOG_LEVEL=info +# IS_DISABLE_ANONYMOUS_TELEMETRY=false +# OPEN_TELEMETRY_URL= diff --git a/apps/databasus/CHANGELOG.md b/apps/databasus/CHANGELOG.md index 582cf46c5..41955e4d3 100644 --- a/apps/databasus/CHANGELOG.md +++ b/apps/databasus/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-17 +- Updated `W9_VERSION` from `latest` to `v3.57.1` and aligned `variables.json.edition` with the current upstream tag. +- Normalized `.env` and `docker-compose.yml` to current repository policy: braced `${VAR}` references, a port purpose comment, and no image/docs source comments. +- Added `DATABASUS_URL` wired to `W9_URL` and set `W9_URL_REPLACE=true`. +- Added `upstream.docs`, `upstream.releases`, and `variables.json.access`; added `tests/cases.yml` with the system health check; generated the README. diff --git a/apps/databasus/README.md b/apps/databasus/README.md new file mode 100644 index 000000000..cd11f5c46 --- /dev/null +++ b/apps/databasus/README.md @@ -0,0 +1,90 @@ +# Databasus on Docker + +## Quick Start + +### Deploy Verification + +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Databasus**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + + +### Usage + +1. Open the Databasus URL and create the first administrator account (email and password) on the sign-up screen. +2. Add a database connection, choose a storage destination, and create a backup job. +3. Run the first backup and check its status on the dashboard. + +### Change Password + +1. Sign in to Databasus and change the password from the user profile. +2. If you cannot sign in, reset it from the host: + `docker exec -it databasus ./main --new-password="YourNewPassword" --email="admin"`. + + +## Configuration Reference + +Websoft9 packages this app from the official [Databasus Docker image](https://hub.docker.com/r/databasus/databasus) and makes some improvements below. + + +The package wires `DATABASUS_URL` to `W9_URL` so links Databasus generates use the public address. Optional SMTP, OAuth, telemetry, and logging settings are listed as comments at the end of `.env`. + + +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: v3.57.1, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 4005 | + + +### Data Directory + + +Data is persisted in the `databasus-data` volume, mounted at `/databasus-data`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [Databasus Administrator Guide](https://support.websoft9.com/docs/databasus) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/databasus/databasus) + +- [Releases](https://github.com/databasus/databasus/releases) + +- [Official docs](https://databasus.com/installation) + +- [Official docs](https://databasus.com/advanced-config) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`; the first startup can take up to two minutes. + +**First backup fails?** +- Ensure the target database accepts connections from the container and the credentials have the required dump privileges. + +**Permission denied on the data volume?** +- Set `PUID` / `PGID` in `.env` to match the mount owner and rebuild the app. + diff --git a/apps/databasus/docker-compose.yml b/apps/databasus/docker-compose.yml index 8560432fb..ea0224b79 100644 --- a/apps/databasus/docker-compose.yml +++ b/apps/databasus/docker-compose.yml @@ -1,20 +1,18 @@ -# image,docs: https://databasus.com/installation - services: databasus: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} + restart: unless-stopped env_file: .env ports: - - "$W9_HTTP_PORT_SET:4005" + - "${W9_HTTP_PORT_SET}:4005" # Web Console volumes: - databasus-data:/databasus-data - restart: unless-stopped volumes: databasus-data: - + networks: default: - name: $W9_NETWORK + name: ${W9_NETWORK} external: true diff --git a/apps/databasus/tests/cases.yml b/apps/databasus/tests/cases.yml new file mode 100644 index 000000000..05a327445 --- /dev/null +++ b/apps/databasus/tests/cases.yml @@ -0,0 +1,5 @@ +optional: + - id: system-health + type: web-access + path: /api/v1/system/health + expect_status: 200 diff --git a/apps/databasus/variables.json b/apps/databasus/variables.json index 33b4ae78e..edf3931ad 100644 --- a/apps/databasus/variables.json +++ b/apps/databasus/variables.json @@ -2,21 +2,35 @@ "name": "databasus", "trademark": "Databasus", "release": false, + "upstream": { + "image": "https://hub.docker.com/r/databasus/databasus", + "releases": "https://github.com/databasus/databasus/releases", + "docs": [ + "https://databasus.com/installation", + "https://databasus.com/advanced-config" + ] + }, "edition": [ { "dist": "community", "version": [ - "v3.9.0", + "v3.57.1", "latest" ] } ], + "access": { + "web": { + "port": 4005, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", "disk": "10" }, - "upstream": { - "image": "https://hub.docker.com/r/databasus/databasus" + "env": { + "first_startup_only": [] } } diff --git a/apps/directus/variables.json b/apps/directus/variables.json index 81593beed..3a458ed9b 100644 --- a/apps/directus/variables.json +++ b/apps/directus/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8055, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", diff --git a/apps/discourse/variables.json b/apps/discourse/variables.json index 9cb270106..3b93ff8f2 100644 --- a/apps/discourse/variables.json +++ b/apps/discourse/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3000, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", diff --git a/apps/docuseal/variables.json b/apps/docuseal/variables.json index 66fe6a195..2d37098bc 100644 --- a/apps/docuseal/variables.json +++ b/apps/docuseal/variables.json @@ -2,6 +2,9 @@ "name": "docuseal", "trademark": "DocuSeal", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/docuseal/docuseal" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3000, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "6" - }, - "upstream": { - "image": "https://hub.docker.com/r/docuseal/docuseal" } } diff --git a/apps/dolibarr/.env b/apps/dolibarr/.env index 22e5195d3..502e68ec3 100644 --- a/apps/dolibarr/.env +++ b/apps/dolibarr/.env @@ -1,32 +1,56 @@ W9_REPO=tuxgasy/dolibarr W9_DIST=community -W9_VERSION=18 +W9_VERSION=19.0.2 -W9_POWER_PASSWORD=spJNF09yzwWJaG! +W9_POWER_PASSWORD="spJNF09yzwWJaG!" + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. #### -- Not allowed to edit below environments when recreate app based on existing data -- #### + W9_ID=dolibarr -W9_HTTP_PORT_SET=9001 + +# Web/internal ports: uncomment the ones the package actually uses. W9_HTTP_PORT=80 +W9_HTTP_PORT_SET=9001 + +# Dependency helpers: uncomment when the package bundles a dependency service. +W9_DB_EXPOSE=mariadb +W9_DB_VERSION=12.3 + +# Built-in login and URL helpers: keep these in the protected block because the appstore parser +# reads them from the app package metadata surface. W9_LOGIN_USER=admin -W9_LOGIN_PASSWORD=$W9_POWER_PASSWORD -W9_DB_EXPOSE="mariadb" -W9_MARIADB_VERSION=latest -W9_URL=example.youdomain.com +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} +W9_URL=appname.example.com + W9_NETWORK=websoft9 #### ----------------------------------------------------------------------------------------- #### -# Below envs is from official Image +# ============================================================ +# Dolibarr image environment variables +# Docs: https://github.com/tuxgasy/docker-dolibarr +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ -DOLI_URL_ROOT='http://0.0.0.0' -PHP_INI_DATE_TIMEZONE='Europe/Paris' +# Used by docker-compose.yml: +DOLI_URL_ROOT=http://0.0.0.0 +DOLI_DB_HOST=${W9_ID}-mariadb +DOLI_DB_USER=${W9_ID} +DOLI_DB_PASSWORD=${W9_POWER_PASSWORD} +DOLI_DB_NAME=${W9_ID} +DOLI_ADMIN_LOGIN=${W9_LOGIN_USER} +DOLI_ADMIN_PASSWORD=${W9_POWER_PASSWORD} +PHP_INI_DATE_TIMEZONE=Europe/Paris PHP_INI_MEMORY_LIMIT=512M -DOLI_AUTH=dolibarr - -DOLI_DB_HOST=$W9_ID-mariadb -DOLI_DB_USER=$W9_ID -DOLI_DB_PASSWORD=$W9_POWER_PASSWORD -DOLI_DB_NAME=$W9_ID -DOLI_ADMIN_LOGIN=$W9_LOGIN_USER -DOLI_ADMIN_PASSWORD=$W9_POWER_PASSWORD \ No newline at end of file + +# Not used by default; enable only when needed: +# DOLI_AUTH=dolibarr +# DOLI_INSTALL_AUTO=1 +# DOLI_DB_PORT=3306 +# PHP_INI_UPLOAD_MAX_FILESIZE=2M +# PHP_INI_MAX_EXECUTION_TIME=120 diff --git a/apps/dolibarr/CHANGELOG.md b/apps/dolibarr/CHANGELOG.md index 582cf46c5..4c8ca2ac9 100644 --- a/apps/dolibarr/CHANGELOG.md +++ b/apps/dolibarr/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-17 +- Fixed the version drift: pinned `W9_VERSION` from `18` to `19.0.2`, matching `variables.json.edition` and the current upstream release. +- Replaced the non-standard `W9_MARIADB_VERSION` with `W9_DB_VERSION` and pinned the bundled MariaDB to `12.3` (LTS); `11.4` and `11.8` were also verified against Dolibarr 19.0.2. +- Normalized `.env` and `docker-compose.yml` to current repository policy: braced `${VAR}` references, a port purpose comment, no image/docs source comments, and no obsolete compose `version` key. +- Replaced the legacy `links` dependency with the shared `websoft9` network plus `depends_on`. +- Added `upstream.docs`, `variables.json.access`, `help.db`, and a login-page functional check in `tests/cases.yml`. +- Regenerated the README. diff --git a/apps/dolibarr/README.md b/apps/dolibarr/README.md index b3385bc4f..5258b02eb 100644 --- a/apps/dolibarr/README.md +++ b/apps/dolibarr/README.md @@ -1,26 +1,91 @@ -# Dolibarr on Docker +# Dolibarr on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Dolibarr: +## Quick Start +### Deploy Verification - - community: 19.0.2, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Dolibarr**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Dolibarr runs its installer on first start and creates the administrator account from `W9_LOGIN_USER` / `W9_LOGIN_PASSWORD`. +2. Sign in at `/index.php` and complete the initial company setup. +3. Try a core feature. -The following are the minimal [recommended requirements](https://github.com/tuxgasy/docker-dolibarr): +### Change Password -* **RAM**: 1 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +1. Sign in to Dolibarr and change the password from the user profile. +2. `W9_LOGIN_PASSWORD` seeds the administrator password on first startup; changing `.env` later does not change an existing password. +3. If you cannot sign in, reset the password in the database. + -## Install +## Configuration Reference -You can install this Dolibarr by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Dolibarr Docker image](https://hub.docker.com/r/tuxgasy/dolibarr) and makes some improvements below. -If you want use Dolibarr with **Websoft9 Business Support** free, you can [subscribe Dolibarr](https://www.websoft9.com/apps) on Cloud platform + +The package bundles MariaDB and passes the database connection plus the initial administrator credentials through `.env` (`DOLI_*`). The image installs Dolibarr automatically on first start. + -## Documentation +Apps run as containers; rebuild after any configuration change. -[Dolibarr Administrator Guide](https://support.websoft9.com/docs/dolibarr) powered by Websoft9 \ No newline at end of file +### Version Support + +Supported versions: 19.0.2, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 80 | + + +### Data Directory + + +- `dolibarr_html` → `/var/www/html` +- `dolibarr_documents` → `/var/www/documents` +- `mariadb` → `/var/lib/mysql` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +Note: `W9_LOGIN_PASSWORD` take effect on first startup only; changing them after deployment may not take effect until the app is re-initialized. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [Dolibarr Administrator Guide](https://support.websoft9.com/docs/dolibarr) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/tuxgasy/dolibarr) + +- [GitHub docs](https://github.com/tuxgasy/docker-dolibarr) + +- [GitHub docs](https://github.com/Dolibarr/dolibarr) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/dolibarr/docker-compose.yml b/apps/dolibarr/docker-compose.yml index 81f93f02a..e7f08f283 100644 --- a/apps/dolibarr/docker-compose.yml +++ b/apps/dolibarr/docker-compose.yml @@ -1,41 +1,35 @@ -# image: https://hub.docker.com/r/tuxgasy/dolibarr -# docs: https://github.com/tuxgasy/docker-dolibarr - -version: '3.8' - services: - dolibarr: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID - restart: unless-stopped - env_file: - - .env - ports: - - "$W9_HTTP_PORT_SET:80" - volumes: - - dolibarr_html:/var/www/html - - dolibarr_documents:/var/www/documents - links: - - mariadb + dolibarr: + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} + restart: unless-stopped + env_file: .env + ports: + - "${W9_HTTP_PORT_SET}:80" # Web Console + volumes: + - dolibarr_html:/var/www/html + - dolibarr_documents:/var/www/documents + depends_on: + - mariadb - mariadb: - image: mariadb:$W9_MARIADB_VERSION - container_name: $W9_ID-mariadb - restart: unless-stopped - environment: - - MARIADB_DATABASE=$W9_ID - - MARIADB_USER=$W9_ID - - MARIADB_PASSWORD=$W9_POWER_PASSWORD - - MARIADB_ROOT_PASSWORD=$W9_POWER_PASSWORD - volumes: - - mariadb:/var/lib/mysql + mariadb: + image: mariadb:${W9_DB_VERSION} + container_name: ${W9_ID}-mariadb + restart: unless-stopped + environment: + MARIADB_DATABASE: ${W9_ID} + MARIADB_USER: ${W9_ID} + MARIADB_PASSWORD: ${W9_POWER_PASSWORD} + MARIADB_ROOT_PASSWORD: ${W9_POWER_PASSWORD} + volumes: + - mariadb:/var/lib/mysql networks: default: - name: $W9_NETWORK + name: ${W9_NETWORK} external: true volumes: mariadb: dolibarr_html: - dolibarr_documents: \ No newline at end of file + dolibarr_documents: diff --git a/apps/dolibarr/tests/cases.yml b/apps/dolibarr/tests/cases.yml new file mode 100644 index 000000000..98008132e --- /dev/null +++ b/apps/dolibarr/tests/cases.yml @@ -0,0 +1,5 @@ +optional: + - id: login-page + type: web-access + path: /index.php + expect_status: 200 diff --git a/apps/dolibarr/variables.json b/apps/dolibarr/variables.json index 228d12c9e..3aeaa586a 100644 --- a/apps/dolibarr/variables.json +++ b/apps/dolibarr/variables.json @@ -2,6 +2,13 @@ "name": "dolibarr", "trademark": "Dolibarr", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/tuxgasy/dolibarr", + "docs": [ + "https://github.com/tuxgasy/docker-dolibarr", + "https://github.com/Dolibarr/dolibarr" + ] + }, "edition": [ { "dist": "community", @@ -11,12 +18,27 @@ ] } ], + "access": { + "web": { + "port": 80, + "path": "/" + }, + "admin": { + "port": 80, + "path": "/admin" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "1" }, - "upstream": { - "image": "https://hub.docker.com/r/tuxgasy/dolibarr" + "env": { + "first_startup_only": [ + "W9_LOGIN_PASSWORD" + ] + }, + "help": { + "db": "Dolibarr requires MariaDB or MySQL. This package bundles MariaDB for a single-node deployment." } } diff --git a/apps/dsh/.env b/apps/dsh/.env new file mode 100644 index 000000000..64f1dd37f --- /dev/null +++ b/apps/dsh/.env @@ -0,0 +1,66 @@ +W9_REPO=websoft9dev/dsh +W9_DIST=community +W9_VERSION=0.1.7-rc.2 + +# Optional password seed: enable only when the package actually controls a DB or built-in login. +# See docs/w9-env-spec.md for when W9_POWER_PASSWORD is appropriate. +# W9_POWER_PASSWORD="{power_password}" + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + +#### -- Not allowed to edit below environments when recreate app based on existing data -- #### + +W9_ID=dsh + +# Web/internal ports: uncomment the ones the package actually uses. +W9_HTTP_PORT=3080 +W9_HTTP_PORT_SET=9001 +W9_DEEPSEEK_API_KEY_SET= +# W9_HTTPS_PORT=443 +# W9_HTTPS_PORT_SET=9002 + +# Dependency helpers: uncomment when the package bundles a dependency service. +# W9_DB_EXPOSE=postgresql +# W9_DB_VERSION=16 + +# Built-in login and URL helpers: keep these in the protected block because the appstore parser +# reads them from the app package metadata surface. +# Use a domain-style placeholder for W9_URL, e.g. appname.example.com or example.youdomain.com. +# Do NOT use internet_ip:${W9_HTTP_PORT_SET}; host substitution is the consumer's concern. +W9_URL=dsh.example.com +W9_URL_REPLACE=true +W9_ADMIN_PATH="/?token=xxxx" + +W9_NETWORK=websoft9 + +#### ----------------------------------------------------------------------------------------- #### + +#W9_NAME="" +#W9_RCODE="" + +# ============================================================ +# DeepSeek Harness image environment variables +# Docs: https://deepseek-harness.github.io/deepseek-harness/en/guide/quickstart +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +DSH_HOME=/var/lib/dsh +DSH_INTERNAL_PORT=3081 +DSH_WORKSPACE=/workspace +DSH_TRUSTED_HOSTS=${W9_URL} +DEEPSEEK_API_KEY=${W9_DEEPSEEK_API_KEY_SET} +DSH_FORCE_LOOPBACK_UI=true +# Extra browser authorities trusted by DSH for Host/Origin checks. +# Comma-separated, for example: host1.example.com,host2.example.com:9001,47.76.100.241:9001 +# DSH_TRUSTED_HOSTS=${W9_URL},47.76.100.241:9001 + +# Not used by default; enable only when needed: +# DEEPSEEK_BASE_URL= +# DSH_FORCE_LOOPBACK_UI=false +# HTTP_PROXY= +# HTTPS_PROXY= +# NO_PROXY= diff --git a/apps/dsh/CHANGELOG.md b/apps/dsh/CHANGELOG.md new file mode 100644 index 000000000..25180b81c --- /dev/null +++ b/apps/dsh/CHANGELOG.md @@ -0,0 +1,15 @@ +# CHANGELOG + +## 2026-09-28 +- Add the initial DeepSeek Harness app package. +- Build a custom image from the official `node:22-slim` base image and the upstream `@deepseek-ai/dsh` npm package. +- Package the Web UI as a single-container deployment with persistent `DSH_HOME` and workspace volumes. +- Add an internal Nginx reverse proxy because upstream intentionally refuses direct `0.0.0.0` binding for `dsh web`. +- Document the token-based first login flow and record it as a container-log credential hint. +- Preserve external Host/Origin headers and pass `W9_URL` as a trusted host so browser API calls do not fail with HTTP 403. +- Add optional `DSH_TRUSTED_HOSTS` support for multi-domain or forwarded-port access. +- Enable `W9_URL_REPLACE=true` and default `DSH_TRUSTED_HOSTS` to `${W9_URL}` so Websoft9 URL changes flow into DSH's browser trust configuration. +- Add install-time `W9_DEEPSEEK_API_KEY_SET` and map it to `DEEPSEEK_API_KEY` for the container. +- Rename the app id from `deepseekharness` to `dsh`, including the package path, `W9_ID`, image repo, and named volumes. +- Patch the installed DSH web client and inject `window.__DSH_LOCAL_APP__` so public-browser access can open Models/settings pages without an SSH tunnel. +- Remove `build` from `docker-compose.yml` so deployment consumes a prebuilt image and keeps image build responsibility separate from runtime compose usage. diff --git a/apps/dsh/Dockerfile b/apps/dsh/Dockerfile new file mode 100644 index 000000000..615749a27 --- /dev/null +++ b/apps/dsh/Dockerfile @@ -0,0 +1,37 @@ +ARG DSH_VERSION=0.1.7-rc.2 + +FROM node:22-slim + +ARG DSH_VERSION + +LABEL org.opencontainers.image.authors="https://www.websoft9.com" \ + org.opencontainers.image.description="DeepSeek Harness packaged by Websoft9" \ + org.opencontainers.image.source="https://github.com/Websoft9/docker-library/tree/main/apps/dsh" \ + org.opencontainers.image.title="dsh" \ + org.opencontainers.image.vendor="Websoft9" \ + org.opencontainers.image.version="${DSH_VERSION}" + +ENV DSH_HOME=/var/lib/dsh \ + DSH_WORKSPACE=/workspace \ + HOME=/var/lib/dsh \ + DSH_INTERNAL_PORT=3081 \ + DSH_FORCE_LOOPBACK_UI=true \ + NODE_ENV=production \ + NPM_CONFIG_UPDATE_NOTIFIER=false \ + NPM_CONFIG_FUND=false + +RUN apt-get update \ + && apt-get install -y --no-install-recommends bash ca-certificates curl git nginx-light openssh-client procps ripgrep \ + && rm -rf /var/lib/apt/lists/* \ + && npm install -g @deepseek-ai/dsh@${DSH_VERSION} \ + && node -e "const fs=require('fs'); const p='/usr/local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-client-connection/lib/client.js'; let s=fs.readFileSync(p,'utf8'); const old='isLoopback: transport?.ownsHost === true || pageLocation === void 0 || isLoopbackHostname(pageLocation.hostname),'; const rep='isLoopback: transport?.ownsHost === true || pageLocation === void 0 || isLoopbackHostname(pageLocation.hostname) || (typeof window !== \'undefined\' && window.__DSH_LOCAL_APP__ === true),'; if(!s.includes(old)) throw new Error('dsh-client-connection loopback marker not found'); s=s.replace(old,rep); fs.writeFileSync(p,s);" \ + && mkdir -p ${DSH_HOME} ${DSH_WORKSPACE} + +COPY src/entrypoint.sh /usr/local/bin/entrypoint.sh +COPY src/nginx.conf /etc/nginx/nginx.conf + +RUN chmod 0755 /usr/local/bin/entrypoint.sh + +EXPOSE 3080 + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/apps/dsh/README.md b/apps/dsh/README.md new file mode 100644 index 000000000..7d64525c8 --- /dev/null +++ b/apps/dsh/README.md @@ -0,0 +1,138 @@ +# DeepSeek Harness on Docker + +## Quick Start + +### Deploy Verification + +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **DeepSeek Harness**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + + +### Usage + +1. Open the Web UI from the **Access** tab. +2. Add `/workspace` as the initial workspace directory. +3. Configure a model provider in **Settings -> Models** before starting agent tasks. +4. Set `W9_URL` to the real external hostname you use to open the app, otherwise DSH may reject browser API calls with HTTP 403. +5. `DSH_TRUSTED_HOSTS` defaults to `${W9_URL}`. If the app is reachable by more than one browser authority, extend it as a comma-separated list. +6. Fill `W9_DEEPSEEK_API_KEY_SET` during installation if you want the built-in DeepSeek provider to work immediately after the first boot. +7. This package enables `DSH_FORCE_LOOPBACK_UI=true` by default so the public Websoft9 URL can open the Models/settings pages without requiring an SSH tunnel. + +### First Access + +1. DeepSeek Harness prints a one-time login URL with a `token=` query parameter in the container logs on startup. +2. If the root page returns `401 Unauthorized`, open the container logs and copy the latest `dsh web: http://127.0.0.1:3081/?token=...` URL suffix. +3. Replace `http://127.0.0.1:3081` with your Websoft9 access URL, then open that URL in the browser. + +### Loopback Management Workaround + +1. DeepSeek Harness currently keeps some Host-backed settings unavailable on non-loopback browser pages by design. +2. If you need the full **Settings -> Models** experience, create an SSH tunnel to the server and open the Web UI through a loopback address. +3. Example: + +```bash +ssh -N -L 3081:127.0.0.1:3081 root@YOUR_SERVER_IP +``` + +4. Then open `http://127.0.0.1:3081/?token=...` with the latest token printed in the container logs. +5. This workaround is mainly needed for Host-persisted settings such as provider/model configuration; ordinary chat and agent sessions can still run through the normal published Websoft9 URL. + +### Safety Notes + +1. DeepSeek Harness is developer-preview software and should be run with the least privileges possible. +2. Do not mount sensitive host paths or credentials into `/workspace` unless you accept the risk. +3. Review provider keys, plugins, and model-generated commands before allowing broad access. + + +## Configuration Reference + +Websoft9 packages this app from the official [DeepSeek Harness Docker image](https://www.npmjs.com/package/@deepseek-ai/dsh) and makes some improvements below. + + +### Package Notes + +- This package builds a local image because upstream currently distributes DeepSeek Harness primarily as a Node/npm application instead of an official container image. +- The Web UI listens on port 3080 and stores persistent state in `/var/lib/dsh`. +- The default workspace path inside the container is `/workspace` and is backed by a dedicated named volume. +- This package exposes `W9_DEEPSEEK_API_KEY_SET` as the install-time input and maps it to the container's `DEEPSEEK_API_KEY` environment variable. +- Upstream marks DeepSeek Harness as experimental developer-preview software that has not completed a security audit. +- The first browser login is token-based; the current token can be read from the container logs. +- `W9_URL` is used as the trusted browser host for DSH's Host/Origin security checks and should match the real access domain or host:port. +- `DSH_TRUSTED_HOSTS` can add extra allowed browser authorities when the app is accessed through multiple domains, IPs, or forwarded ports. +- `DEEPSEEK_BASE_URL` is unrelated to browser access. It overrides the outbound LLM API endpoint used when DSH talks to a DeepSeek-compatible model provider. +- This package patches the installed DSH web client so `DSH_FORCE_LOOPBACK_UI=true` treats the public browser page like a loopback management surface, enabling Host-backed settings such as Models/provider configuration. +- Runtime deployment consumes the prebuilt `${W9_REPO}:${W9_VERSION}` image; the local `Dockerfile` is kept for repository-controlled image builds, not for `docker compose up` on the target host. + + +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 0.1.7-rc.2, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 3080 | + + +### Data Directory + + +Data is kept inside the container; a named volume is recommended for persistence. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [DeepSeek Harness Administrator Guide](https://support.websoft9.com/docs/dsh) by Websoft9 + +- [Docker Hub image](https://www.npmjs.com/package/@deepseek-ai/dsh) + +- [Releases](https://github.com/deepseek-ai/deepseek-harness/releases) + +- [GitHub docs](https://github.com/deepseek-ai/deepseek-harness) + +- [Official docs](https://deepseek-harness.github.io/deepseek-harness/en/guide/quickstart) + +- [Official docs](https://raw.githubusercontent.com/deepseek-ai/deepseek-harness/master/SAFETY.md) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + +**Root page returns 401 Unauthorized?** +- Copy the latest `token=` login URL from `docker compose logs dsh` and open it through the published Websoft9 URL. + +**UI keeps showing Connecting or `/api/...` returns 403?** +- Set `W9_URL` to the exact external hostname or `host:port` used by the browser, then redeploy so DSH trusts that Host/Origin. +- If you use more than one access address, add all of them to `DSH_TRUSTED_HOSTS` and redeploy. + +**Models page says no API key for `deepseek-official`?** +- Fill `W9_DEEPSEEK_API_KEY_SET` and redeploy, or enter the key later through **Settings -> Models** when the current upstream build allows the onboarding flow. + +**Settings page says `settings are unavailable in this browser` or `加载提供商目录失败`?** +- This package enables a compatibility patch by default with `DSH_FORCE_LOOPBACK_UI=true`, so the public Websoft9 URL should be able to open Host-backed settings such as the Models/provider directory. +- If you explicitly disabled that patch, re-enable `DSH_FORCE_LOOPBACK_UI=true` and redeploy, or use loopback access through an SSH tunnel. + diff --git a/apps/dsh/docker-compose.yml b/apps/dsh/docker-compose.yml new file mode 100644 index 000000000..f98b00d5b --- /dev/null +++ b/apps/dsh/docker-compose.yml @@ -0,0 +1,28 @@ +services: + + dsh: + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} + restart: unless-stopped + env_file: + - .env + ports: + - "${W9_HTTP_PORT_SET}:3080" # Web Console + volumes: + - dsh_home:${DSH_HOME} + - dsh_workspace:${DSH_WORKSPACE} + healthcheck: + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3080/').then((r) => process.exit(r.status === 200 || r.status === 401 ? 0 : 1)).catch(() => process.exit(1))"] + interval: 30s + timeout: 10s + retries: 10 + start_period: 120s + +volumes: + dsh_home: + dsh_workspace: + +networks: + default: + name: ${W9_NETWORK} + external: true diff --git a/apps/rocketmq/Notes.md b/apps/dsh/src/.gitkeep similarity index 100% rename from apps/rocketmq/Notes.md rename to apps/dsh/src/.gitkeep diff --git a/apps/dsh/src/entrypoint.sh b/apps/dsh/src/entrypoint.sh new file mode 100644 index 000000000..b344656b4 --- /dev/null +++ b/apps/dsh/src/entrypoint.sh @@ -0,0 +1,76 @@ +#!/bin/sh +set -eu + +: "${W9_HTTP_PORT:=3080}" +: "${DSH_INTERNAL_PORT:=3081}" +: "${DSH_HOME:=/var/lib/dsh}" +: "${DSH_WORKSPACE:=/workspace}" +: "${DSH_FORCE_LOOPBACK_UI:=true}" +: "${W9_URL:=}" +: "${DSH_TRUSTED_HOSTS:=}" + +mkdir -p "${DSH_HOME}" "${DSH_WORKSPACE}" + +cd "${DSH_WORKSPACE}" + +trusted_host_args="" +if [ -n "${W9_URL}" ]; then + trusted_host=$(printf '%s' "${W9_URL}" | sed -E 's#^[a-zA-Z]+://##; s#/.*$##') + if [ -n "${trusted_host}" ]; then + trusted_host_args="--trusted-host ${trusted_host}" + fi +fi + +if [ -n "${DSH_TRUSTED_HOSTS}" ]; then + OLD_IFS=${IFS} + IFS=, + set -- ${DSH_TRUSTED_HOSTS} + IFS=${OLD_IFS} + for raw_host in "$@"; do + extra_host=$(printf '%s' "${raw_host}" | sed -E 's#^[[:space:]]+##; s#[[:space:]]+$##; s#^[a-zA-Z]+://##; s#/.*$##') + if [ -n "${extra_host}" ]; then + trusted_host_args="${trusted_host_args} --trusted-host ${extra_host}" + fi + done +fi + +local_app_flag=false +case "$(printf '%s' "${DSH_FORCE_LOOPBACK_UI}" | tr '[:upper:]' '[:lower:]')" in + true|1|yes|on) + local_app_flag=true + ;; +esac + +local_app_script="" +escaped_local_app_script=$(printf '%s' "${local_app_script}" | sed 's/[\/&]/\\&/g') +sed "s/__DSH_LOCAL_APP_SCRIPT__/${escaped_local_app_script}/g" /etc/nginx/nginx.conf > /tmp/nginx.conf +mv /tmp/nginx.conf /etc/nginx/nginx.conf + +# Preserve the external browser authority through the reverse proxy. DSH rejects +# non-loopback API requests unless their Host/Origin are listed as trusted. +# shellcheck disable=SC2086 +dsh web --host 127.0.0.1 --port "${DSH_INTERNAL_PORT}" --no-open ${trusted_host_args} & +dsh_pid=$! + +ready=false +for _ in $(seq 1 60); do + status_code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${DSH_INTERNAL_PORT}/" || true) + if [ "${status_code}" = "200" ] || [ "${status_code}" = "401" ]; then + ready=true + break + fi + if ! kill -0 "${dsh_pid}" >/dev/null 2>&1; then + wait "${dsh_pid}" + exit 1 + fi + sleep 1 +done + +if [ "${ready}" != "true" ]; then + echo "dsh web did not become ready on 127.0.0.1:${DSH_INTERNAL_PORT} within 60 seconds" >&2 + kill "${dsh_pid}" >/dev/null 2>&1 || true + wait "${dsh_pid}" >/dev/null 2>&1 || true + exit 1 +fi + +exec nginx -g 'daemon off;' diff --git a/apps/dsh/src/nginx.conf b/apps/dsh/src/nginx.conf new file mode 100644 index 000000000..e769e8aad --- /dev/null +++ b/apps/dsh/src/nginx.conf @@ -0,0 +1,34 @@ +worker_processes auto; + +events { + worker_connections 1024; +} + +http { + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + server { + listen 3080; + server_name _; + + location / { + sub_filter_once off; + sub_filter '' '__DSH_LOCAL_APP_SCRIPT__'; + proxy_pass http://127.0.0.1:3081; + proxy_http_version 1.1; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + proxy_set_header Accept-Encoding ""; + proxy_set_header Host $http_host; + proxy_set_header Origin $http_origin; + proxy_set_header Referer $http_referer; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + } +} diff --git a/apps/dsh/tests/cases.yml b/apps/dsh/tests/cases.yml new file mode 100644 index 000000000..2d0b1aad2 --- /dev/null +++ b/apps/dsh/tests/cases.yml @@ -0,0 +1 @@ +optional: [] diff --git a/apps/dsh/variables.json b/apps/dsh/variables.json new file mode 100644 index 000000000..45dcd1814 --- /dev/null +++ b/apps/dsh/variables.json @@ -0,0 +1,50 @@ +{ + "name": "dsh", + "trademark": "DeepSeek Harness", + "release": false, + "upstream": { + "image": "https://www.npmjs.com/package/@deepseek-ai/dsh", + "releases": "https://github.com/deepseek-ai/deepseek-harness/releases", + "docs": [ + "https://github.com/deepseek-ai/deepseek-harness", + "https://deepseek-harness.github.io/deepseek-harness/en/guide/quickstart", + "https://raw.githubusercontent.com/deepseek-ai/deepseek-harness/master/SAFETY.md" + ] + }, + "edition": [ + { + "dist": "community", + "version": ["0.1.7-rc.2", "latest"] + } + ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3080, + "path": "/" + }, + "admin": { + "port": 3080, + "path": "/?token=xxxx" + } + }, + "requirements": { + "cpu": "2", + "memory": "4", + "disk": "10" + }, + "credentials": { + "token": { + "source": "container-log", + "match": "regex", + "pattern": "dsh web: http://127\\.0\\.0\\.1:3081/\\?token=([^\\s]+)", + "group": 1 + } + }, + "env": { + "first_startup_only": [] + }, + "help": { + "db": "No bundled database is required. DeepSeek Harness stores sessions, attachments, and local state in the dsh_home volume mounted at /var/lib/dsh. The dsh_workspace volume mounted at /workspace is the default project area exposed to the Web UI." + } +} diff --git a/apps/elasticsearch/variables.json b/apps/elasticsearch/variables.json index 96caf591c..6b1d42afd 100644 --- a/apps/elasticsearch/variables.json +++ b/apps/elasticsearch/variables.json @@ -10,6 +10,13 @@ ] } ], + "access": { + "defaultScheme": "https", + "api": { + "port": 9200, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", diff --git a/apps/emqx/variables.json b/apps/emqx/variables.json index 67b893b05..1cae084ac 100644 --- a/apps/emqx/variables.json +++ b/apps/emqx/variables.json @@ -2,6 +2,9 @@ "name": "emqx", "trademark": "EMQX", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/emqx/emqx" + }, "edition": [ { "dist": "community", @@ -18,12 +21,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 18083, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "2" - }, - "upstream": { - "image": "https://hub.docker.com/r/emqx/emqx" } } diff --git a/apps/flowise/variables.json b/apps/flowise/variables.json index 5bff2426c..22513deec 100644 --- a/apps/flowise/variables.json +++ b/apps/flowise/variables.json @@ -2,6 +2,9 @@ "name": "flowise", "trademark": "Flowise", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/flowiseai/flowise" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3000, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "2" - }, - "upstream": { - "image": "https://hub.docker.com/r/flowiseai/flowise" } } diff --git a/apps/frp/variables.json b/apps/frp/variables.json index 750483177..e501daebb 100644 --- a/apps/frp/variables.json +++ b/apps/frp/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 7500, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", diff --git a/apps/ghost/variables.json b/apps/ghost/variables.json index 1bd944812..a3b6e1f82 100644 --- a/apps/ghost/variables.json +++ b/apps/ghost/variables.json @@ -2,6 +2,9 @@ "name": "ghost", "trademark": "Ghost", "release": true, + "upstream": { + "image": "https://hub.docker.com/_/ghost" + }, "edition": [ { "dist": "community", @@ -11,12 +14,20 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 2368, + "path": "/" + }, + "admin": { + "port": 2368, + "path": "/ghost" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "4" - }, - "upstream": { - "image": "https://hub.docker.com/_/ghost" } } diff --git a/apps/gitlab/variables.json b/apps/gitlab/variables.json index 64190d0fa..d68545937 100644 --- a/apps/gitlab/variables.json +++ b/apps/gitlab/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 80, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "8", diff --git a/apps/grafana/variables.json b/apps/grafana/variables.json index 83580e31a..7a4f5c46f 100644 --- a/apps/grafana/variables.json +++ b/apps/grafana/variables.json @@ -2,6 +2,9 @@ "name": "grafana", "trademark": "Grafana", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/grafana/grafana" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3000, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "2" - }, - "upstream": { - "image": "https://hub.docker.com/r/grafana/grafana" } } diff --git a/apps/haproxy/variables.json b/apps/haproxy/variables.json index eaf709a8f..6af17ebc6 100644 --- a/apps/haproxy/variables.json +++ b/apps/haproxy/variables.json @@ -2,6 +2,9 @@ "name": "haproxy", "trademark": "HAProxy", "release": true, + "upstream": { + "image": "https://hub.docker.com/_/haproxy" + }, "edition": [ { "dist": "community", @@ -20,12 +23,20 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 80, + "path": "/" + }, + "admin": { + "port": 80, + "path": "/stats" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "4" - }, - "upstream": { - "image": "https://hub.docker.com/_/haproxy" } } diff --git a/apps/homeassistant/variables.json b/apps/homeassistant/variables.json index 34c1fe7d0..639e32a22 100644 --- a/apps/homeassistant/variables.json +++ b/apps/homeassistant/variables.json @@ -2,6 +2,9 @@ "name": "homeassistant", "trademark": "Home Assistant", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/homeassistant/home-assistant" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8123, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "4" - }, - "upstream": { - "image": "https://hub.docker.com/r/homeassistant/home-assistant" } } diff --git a/apps/jenkins/README.md b/apps/jenkins/README.md index 1d236d919..a104bb2dd 100644 --- a/apps/jenkins/README.md +++ b/apps/jenkins/README.md @@ -28,7 +28,7 @@ Websoft9 packages this app from the official [Jenkins Docker image](https://hub. Jenkins generates a one-time initial admin password at first startup. The value is stored in `/var/jenkins_home/secrets/initialAdminPassword` inside the container. -Appstore consumers that support `variables.json.credentials.password` can resolve and display this value automatically. If your consumer does not support that metadata yet, read the file manually from the Jenkins container. +Appstore consumers that support `variables.json.credentials` metadata can resolve and display this value automatically. If your consumer does not support that metadata yet, read the file manually from the Jenkins container. Apps run as containers; rebuild after any configuration change. diff --git a/apps/jenkins/variables.json b/apps/jenkins/variables.json index c03a4bc93..8c95143cd 100644 --- a/apps/jenkins/variables.json +++ b/apps/jenkins/variables.json @@ -2,6 +2,15 @@ "name": "jenkins", "trademark": "Jenkins", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/jenkins/jenkins", + "releases": "https://www.jenkins.io/changelog/", + "docs": [ + "https://github.com/jenkinsci/docker", + "https://www.jenkins.io/doc/", + "https://www.jenkins.io/doc/book/installing/docker/" + ] + }, "edition": [ { "dist": "community", @@ -11,6 +20,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8080, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", @@ -19,16 +35,8 @@ "credentials": { "password": { "source": "container-file", - "path": "/var/jenkins_home/secrets/initialAdminPassword" + "path": "/var/jenkins_home/secrets/initialAdminPassword", + "format": "text" } - }, - "upstream": { - "image": "https://hub.docker.com/r/jenkins/jenkins", - "releases": "https://www.jenkins.io/changelog/", - "docs": [ - "https://github.com/jenkinsci/docker", - "https://www.jenkins.io/doc/", - "https://www.jenkins.io/doc/book/installing/docker/" - ] } } diff --git a/apps/kasmweb/variables.json b/apps/kasmweb/variables.json index 8daf271db..70868afe2 100644 --- a/apps/kasmweb/variables.json +++ b/apps/kasmweb/variables.json @@ -10,6 +10,13 @@ ] } ], + "access": { + "defaultScheme": "https", + "web": { + "port": 6901, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", diff --git a/apps/keycloak/variables.json b/apps/keycloak/variables.json index 8e00de931..789ebb737 100644 --- a/apps/keycloak/variables.json +++ b/apps/keycloak/variables.json @@ -2,6 +2,9 @@ "name": "keycloak", "trademark": "Keycloak", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/keycloak/keycloak" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8080, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", "disk": "10" - }, - "upstream": { - "image": "https://hub.docker.com/r/keycloak/keycloak" } } diff --git a/apps/kibana/variables.json b/apps/kibana/variables.json index 0d34d51aa..e03cdfba3 100644 --- a/apps/kibana/variables.json +++ b/apps/kibana/variables.json @@ -2,6 +2,9 @@ "name": "kibana", "trademark": "Kibana", "release": true, + "upstream": { + "image": "https://www.docker.elastic.co/r/kibana" + }, "edition": [ { "dist": "community", @@ -10,12 +13,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 5601, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", "disk": "5" - }, - "upstream": { - "image": "https://www.docker.elastic.co/r/kibana" } } diff --git a/apps/knowage/variables.json b/apps/knowage/variables.json index c2ec0fbec..8355e8fc4 100644 --- a/apps/knowage/variables.json +++ b/apps/knowage/variables.json @@ -2,6 +2,9 @@ "name": "knowage", "trademark": "Knowage", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/knowagelabs/knowage-server-docker" + }, "edition": [ { "dist": "community", @@ -10,12 +13,20 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8080, + "path": "/" + }, + "admin": { + "port": 8080, + "path": "/knowage" + } + }, "requirements": { "cpu": "1", "memory": "4", "disk": "3" - }, - "upstream": { - "image": "https://hub.docker.com/r/knowagelabs/knowage-server-docker" } } diff --git a/apps/linkwarden/variables.json b/apps/linkwarden/variables.json index a747ca4d1..57c34b023 100644 --- a/apps/linkwarden/variables.json +++ b/apps/linkwarden/variables.json @@ -2,6 +2,9 @@ "name": "linkwarden", "trademark": "Linkwarden", "release": true, + "upstream": { + "image": "https://ghcr.io/linkwarden/linkwarden" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3000, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "1" - }, - "upstream": { - "image": "https://ghcr.io/linkwarden/linkwarden" } } diff --git a/apps/mattermost/variables.json b/apps/mattermost/variables.json index 93e7de55a..6584f0e0b 100644 --- a/apps/mattermost/variables.json +++ b/apps/mattermost/variables.json @@ -2,6 +2,9 @@ "name": "mattermost", "trademark": "Mattermost", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/mattermost/mattermost-team-edition" + }, "edition": [ { "dist": "community", @@ -10,12 +13,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8065, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "8" - }, - "upstream": { - "image": "https://hub.docker.com/r/mattermost/mattermost-team-edition" } } diff --git a/apps/metabase/variables.json b/apps/metabase/variables.json index 9dc7130ef..6d7716197 100644 --- a/apps/metabase/variables.json +++ b/apps/metabase/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3000, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", diff --git a/apps/milvus/variables.json b/apps/milvus/variables.json index 6330ab808..18813e7e3 100644 --- a/apps/milvus/variables.json +++ b/apps/milvus/variables.json @@ -2,6 +2,9 @@ "name": "milvus", "trademark": "Milvus", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/milvusdb/milvus" + }, "edition": [ { "dist": "community", @@ -11,12 +14,20 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 9091, + "path": "/" + }, + "admin": { + "port": 9091, + "path": "/webui" + } + }, "requirements": { "cpu": "8", "memory": "32", "disk": "4" - }, - "upstream": { - "image": "https://hub.docker.com/r/milvusdb/milvus" } } diff --git a/apps/n8n/variables.json b/apps/n8n/variables.json index ab0f90cdf..4d6b57ed4 100644 --- a/apps/n8n/variables.json +++ b/apps/n8n/variables.json @@ -2,6 +2,15 @@ "name": "n8n", "trademark": "n8n", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/n8nio/n8n", + "releases": "https://github.com/n8n-io/n8n", + "docs": [ + "https://docs.n8n.io/deploy/host-n8n/install-options/install-with-docker.md", + "https://docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/use-environment-variables/deployment.md", + "https://docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/configuration-examples/configure-webhook-urls-with-reverse-proxy.md" + ] + }, "edition": [ { "dist": "community", @@ -11,18 +20,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 5678, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "1" - }, - "upstream": { - "image": "https://hub.docker.com/r/n8nio/n8n", - "releases": "https://github.com/n8n-io/n8n", - "docs": [ - "https://docs.n8n.io/deploy/host-n8n/install-options/install-with-docker.md", - "https://docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/use-environment-variables/deployment.md", - "https://docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/configuration-examples/configure-webhook-urls-with-reverse-proxy.md" - ] } } diff --git a/apps/nextcloud/variables.json b/apps/nextcloud/variables.json index e11da94fe..d902eeb93 100644 --- a/apps/nextcloud/variables.json +++ b/apps/nextcloud/variables.json @@ -2,6 +2,14 @@ "name": "nextcloud", "trademark": "Nextcloud", "release": true, + "upstream": { + "image": "https://hub.docker.com/_/nextcloud", + "releases": "https://nextcloud.com/changelog/", + "docs": [ + "https://github.com/nextcloud/docker", + "https://docs.nextcloud.com/server/latest/admin_manual/installation/system_requirements.html" + ] + }, "edition": [ { "dist": "community", @@ -11,19 +19,18 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 80, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "4" }, - "upstream": { - "image": "https://hub.docker.com/_/nextcloud", - "releases": "https://nextcloud.com/changelog/", - "docs": [ - "https://github.com/nextcloud/docker", - "https://docs.nextcloud.com/server/latest/admin_manual/installation/system_requirements.html" - ] - }, "env": { "first_startup_only": [ "NEXTCLOUD_ADMIN_USER", diff --git a/apps/nginxproxymanager/variables.json b/apps/nginxproxymanager/variables.json index c287336d6..d0a99d316 100644 --- a/apps/nginxproxymanager/variables.json +++ b/apps/nginxproxymanager/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 81, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", diff --git a/apps/odoo/variables.json b/apps/odoo/variables.json index b9d0b7d84..010613f75 100644 --- a/apps/odoo/variables.json +++ b/apps/odoo/variables.json @@ -23,6 +23,17 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8069, + "path": "/" + }, + "admin": { + "port": 8069, + "path": "/web/login" + } + }, "requirements": { "cpu": "1", "memory": "2", diff --git a/apps/oneapi/variables.json b/apps/oneapi/variables.json index 660d38fca..6c4d9f629 100644 --- a/apps/oneapi/variables.json +++ b/apps/oneapi/variables.json @@ -2,6 +2,9 @@ "name": "oneapi", "trademark": "One API", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/justsong/one-api" + }, "edition": [ { "dist": "community", @@ -11,12 +14,20 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3000, + "path": "/" + }, + "admin": { + "port": 3000, + "path": "/login" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "2" - }, - "upstream": { - "image": "https://hub.docker.com/r/justsong/one-api" } } diff --git a/apps/onyx/variables.json b/apps/onyx/variables.json index f587ed2a5..5af13c38b 100644 --- a/apps/onyx/variables.json +++ b/apps/onyx/variables.json @@ -2,6 +2,9 @@ "name": "onyx", "trademark": "Onyx", "release": false, + "upstream": { + "image": "https://hub.docker.com/r/onyxdotapp/onyx-backend" + }, "edition": [ { "dist": "community", @@ -11,12 +14,20 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 80, + "path": "/" + }, + "admin": { + "port": 80, + "path": "/admin/indexing/status" + } + }, "requirements": { "cpu": "4", "memory": "10", "disk": "50" - }, - "upstream": { - "image": "https://hub.docker.com/r/onyxdotapp/onyx-backend" } } diff --git a/apps/openclaw/.env b/apps/openclaw/.env index 76cba1af7..ee8e940d1 100644 --- a/apps/openclaw/.env +++ b/apps/openclaw/.env @@ -1,57 +1,58 @@ -# OpenClaw on Docker - Environment Configuration -# Edit this file to customize the deployment settings. -# Full documentation: https://docs.openclaw.ai/install/docker - -# ========================================================= -# Image -# version tags: https://github.com/openclaw/openclaw/releases -# ========================================================= W9_REPO=ghcr.io/openclaw/openclaw -W9_DIST='community' -W9_VERSION='2026.3.13-1' - -# ========================================================= -# Authentication -# W9_POWER_PASSWORD is used as OPENCLAW_GATEWAY_TOKEN -# After startup, paste this token in the Control UI (Settings → Token) -# ========================================================= -W9_POWER_PASSWORD=1PrMxExC45LsCT - -# ========================================================= -# Ports -# W9_HTTP_PORT_SET: host port for Gateway Control UI + WS API (internal: 18789) -# W9_BRIDGE_PORT_SET: host port for browser/bridge control service (internal: 18790) -# Access Control UI: http://localhost:$W9_HTTP_PORT_SET/ -# ========================================================= -W9_HTTP_PORT_SET='9001' -W9_BRIDGE_PORT_SET='18790' +W9_DIST=community +W9_VERSION=2026.9.6 + +# Optional password seed: enable only when the package actually controls a DB or built-in login. +# See docs/w9-env-spec.md for when W9_POWER_PASSWORD is appropriate. +# The Gateway token is wired from W9_LOGIN_PASSWORD and surfaced in the Access tab. +W9_POWER_PASSWORD='1PrMxExC45LsCT' + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -# ========================================================= -# System (managed by Websoft9, do not modify) -# ========================================================= -W9_ID='openclaw' +W9_ID=openclaw W9_HTTP_PORT=18789 -W9_URL='example.youdomain.com' -W9_LOGIN_PASSWORD=$W9_POWER_PASSWORD -W9_NETWORK=websoft9 +W9_HTTP_PORT_SET=9001 +W9_BRIDGE_PORT_SET=18790 + +# Built-in login and URL helpers: keep these in the protected block because the appstore parser +# reads them from the app package metadata surface. +W9_LOGIN_USER=token +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} +W9_URL=openclaw.example.com -#### ---------------------------------------------------------------------------- #### +W9_NETWORK=websoft9 -# ========================================================= -# Application-specific settings -# ========================================================= +#### ----------------------------------------------------------------------------------------- #### -# Gateway token (= W9_POWER_PASSWORD). Paste in Control UI → Settings → Token -OPENCLAW_GATEWAY_TOKEN=$W9_POWER_PASSWORD +# ============================================================ +# OpenClaw image environment variables +# Docs: https://docs.openclaw.ai/install/docker +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ -# Gateway bind mode. 'lan' is required for Docker port mapping to work. +# Used by docker-compose.yml: +OPENCLAW_GATEWAY_TOKEN=${W9_LOGIN_PASSWORD} OPENCLAW_GATEWAY_BIND=lan - -# Allow insecure private WebSocket connections (leave empty unless needed) -# Set to 'true' when accessing via HTTP on a non-localhost address (no HTTPS). -# This lets the server handle device identity instead of the browser's crypto.subtle API. OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=true - - +# Leave both vars empty for direct IP/LAN access. +# For domain access, set OPENCLAW_PUBLIC_SCHEME and let the package derive +# ://${W9_URL}, or set OPENCLAW_PUBLIC_ORIGIN to override it exactly. +OPENCLAW_PUBLIC_SCHEME= +OPENCLAW_PUBLIC_ORIGIN= + +# Optional reverse-proxy source allowlist for forwarded-header attribution. +# Space- or comma-separated IPs/CIDRs seen by the Gateway, for example: +# OPENCLAW_TRUSTED_PROXIES=172.17.0.1 127.0.0.1 +OPENCLAW_TRUSTED_PROXIES= + +# Not used by default; enable only when needed: +# OPENCLAW_GATEWAY_PORT=18789 +# OPENCLAW_TZ=UTC +# OPENCLAW_SANDBOX=1 +# OPENCLAW_SKIP_ONBOARDING=1 +# OPENCLAW_DISABLE_BONJOUR=1 diff --git a/apps/openclaw/CHANGELOG.md b/apps/openclaw/CHANGELOG.md index 582cf46c5..2caa6c523 100644 --- a/apps/openclaw/CHANGELOG.md +++ b/apps/openclaw/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG -## Release -### Fixes and Enhancements - +## 2026-09-24 +- Update the OpenClaw community package from `2026.3.13-1` to `2026.9.6` (`ghcr.io/openclaw/openclaw`). +- Normalize `.env` and `docker-compose.yml` to current repository policy: braced `${VAR}` references, inline port purpose comments, and removal of inline image/docs source comments. +- Fix the `W9_LOGIN*` declaration by pairing `W9_LOGIN_USER=token` with `W9_LOGIN_PASSWORD`, and wire the Gateway token from `W9_LOGIN_PASSWORD` so the Access tab surfaces a stable token. +- Fill missing `variables.json` fields: `upstream.releases`, `upstream.docs`, `access`, `credentials`, `env`, and `help`. +- Add `tests/cases.yml` with a dedicated Gateway health-endpoint check. +- Regenerate `README.md` from the current repository template. +- Add `OPENCLAW_PUBLIC_ORIGIN` and patch `gateway.publicOrigin` plus `gateway.controlUi.allowedOrigins` during init so domain settings are package-managed and re-applied on every recreate without changing the upstream gateway entrypoint. +- Remove the retired inert `gateway.controlUi.dangerouslyDisableDeviceAuth` seed key from the packaged config. +- Let `OPENCLAW_PUBLIC_ORIGIN` override the exact external origin, and otherwise derive it from `OPENCLAW_PUBLIC_SCHEME` plus `W9_URL` so domain access can follow standard Websoft9 host metadata without hardcoding. +- Add `OPENCLAW_TRUSTED_PROXIES` and re-apply `gateway.trustedProxies` on each recreate so reverse-proxy forwarded-header attribution (`proxy_attribution_required`) can be configured without hardcoding proxy IPs. diff --git a/apps/openclaw/README.md b/apps/openclaw/README.md index 1bd4745b5..508be89fc 100644 --- a/apps/openclaw/README.md +++ b/apps/openclaw/README.md @@ -1,26 +1,85 @@ -# OpenClaw on Docker +# OpenClaw on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for OpenClaw: +## Quick Start +### Deploy Verification - - community: 2026.3.13-1 +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **OpenClaw**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Make sure you are signed in to the OpenClaw admin console. +2. Try a core feature. -The following are the minimal [recommended requirements](https://openclaw.ai): +### Change Password -* **RAM**: 2 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 10 GB of free space -* **bandwidth**: more fluent experience over 100M +1. In the [Websoft9](https://www.websoft9.com) console, open the app's **Compose** tab. +2. Update the password in `.env` and save. +3. Rebuild the app. + -## Install +## Configuration Reference -You can install this OpenClaw by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [OpenClaw Docker image](https://ghcr.io/openclaw/openclaw) and makes some improvements below. -If you want use OpenClaw with **Websoft9 Business Support** free, you can [subscribe OpenClaw](https://www.websoft9.com/apps) on Cloud platform + +- Leave `OPENCLAW_PUBLIC_SCHEME` and `OPENCLAW_PUBLIC_ORIGIN` empty for direct IP/LAN access. For domain access, set `OPENCLAW_PUBLIC_SCHEME=https` to derive the exact browser origin from `W9_URL`, or set `OPENCLAW_PUBLIC_ORIGIN` to an exact override before recreating the app. +- When a reverse proxy terminates the request, set `OPENCLAW_TRUSTED_PROXIES` to the proxy source IP(s) the Gateway sees (space- or comma-separated). Without it, forwarded headers are rejected with `proxy_attribution_required`; keep the list narrow and make the proxy overwrite `X-Forwarded-*`. +- The package re-applies `gateway.publicOrigin`, `gateway.controlUi.allowedOrigins`, and `gateway.trustedProxies` on every recreate through `openclaw-init`, so domain-related config changes do not get stuck in the persisted volume. + -## Documentation +Apps run as containers; rebuild after any configuration change. -[OpenClaw Administrator Guide](https://support.websoft9.com/docs/openclaw) powered by Websoft9 \ No newline at end of file +### Version Support + +Supported versions: 2026.9.6. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Gateway Control UI and WebSocket API | 18789 | +| Browser/bridge control service | 18790 | + + +### Data Directory + + +Data is persisted in the `openclaw_data` volume, mounted at `/home/node/.openclaw`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration is overridden by mounting `./src/openclaw.json` to `/seed/openclaw.json`. + + +## References + +- [OpenClaw Administrator Guide](https://support.websoft9.com/docs/openclaw) by Websoft9 + +- [GHCR image](https://ghcr.io/openclaw/openclaw) + +- [Releases](https://github.com/openclaw/openclaw/releases) + +- [Official docs](https://docs.openclaw.ai/install/docker) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/openclaw/docker-compose.yml b/apps/openclaw/docker-compose.yml index 5af45a076..bd3b9a894 100644 --- a/apps/openclaw/docker-compose.yml +++ b/apps/openclaw/docker-compose.yml @@ -1,28 +1,53 @@ -# image: https://github.com/openclaw/openclaw/pkgs/container/openclaw -# docs: https://docs.openclaw.ai/install/docker - services: # One-time permission fix so the named volume is writable by uid 1000 (node). # Mirrors what the official docker-setup.sh does before starting the gateway. openclaw-init: - image: $W9_REPO:$W9_VERSION + image: ${W9_REPO}:${W9_VERSION} container_name: ${W9_ID}-init restart: "no" user: "0" + env_file: .env + environment: + - HOME=/home/node + - OPENCLAW_CONFIG_PATH=/home/node/.openclaw/openclaw.json + - OPENCLAW_PUBLIC_SCHEME=${OPENCLAW_PUBLIC_SCHEME:-} + - OPENCLAW_PUBLIC_ORIGIN=${OPENCLAW_PUBLIC_ORIGIN:-} + - OPENCLAW_TRUSTED_PROXIES=${OPENCLAW_TRUSTED_PROXIES:-} entrypoint: - "sh" - "-c" - | mkdir -p /home/node/.openclaw [ ! -s /home/node/.openclaw/openclaw.json ] && cp /seed/openclaw.json /home/node/.openclaw/openclaw.json + rm -f /tmp/w9-origin.json5 /tmp/w9-proxies.json5 + node -e ' + const fs = require("fs"); + const scheme = process.env.OPENCLAW_PUBLIC_SCHEME || ""; + const host = process.env.W9_URL || ""; + const origin = (process.env.OPENCLAW_PUBLIC_ORIGIN || "").trim() || (scheme && host ? scheme + "://" + host : ""); + if (origin) fs.writeFileSync("/tmp/w9-origin.json5", JSON.stringify({ gateway: { publicOrigin: origin, controlUi: { allowedOrigins: [origin] } } })); + const proxies = (process.env.OPENCLAW_TRUSTED_PROXIES || "").split(/[\s,]+/).filter(Boolean); + if (proxies.length) fs.writeFileSync("/tmp/w9-proxies.json5", JSON.stringify({ gateway: { trustedProxies: proxies } })); + ' + if [ -f /tmp/w9-origin.json5 ]; then + node dist/index.js config patch --file /tmp/w9-origin.json5 + else + node dist/index.js config unset gateway.publicOrigin || true + node dist/index.js config unset gateway.controlUi.allowedOrigins || true + fi + if [ -f /tmp/w9-proxies.json5 ]; then + node dist/index.js config patch --file /tmp/w9-proxies.json5 + else + node dist/index.js config unset gateway.trustedProxies || true + fi chown -R 1000:1000 /home/node/.openclaw volumes: - openclaw_data:/home/node/.openclaw - ./src/openclaw.json:/seed/openclaw.json:ro openclaw-gateway: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} restart: unless-stopped init: true depends_on: @@ -30,14 +55,15 @@ services: condition: service_completed_successfully command: [ "node", "dist/index.js", "gateway", "--allow-unconfigured", "--bind", "${OPENCLAW_GATEWAY_BIND:-lan}", "--port", "18789" ] ports: - - $W9_HTTP_PORT_SET:18789 - - $W9_BRIDGE_PORT_SET:18790 + - "${W9_HTTP_PORT_SET}:18789" # Gateway Control UI and WebSocket API + - "${W9_BRIDGE_PORT_SET}:18790" # Browser/bridge control service env_file: .env environment: - HOME=/home/node - NODE_ENV=production - TERM=xterm-256color - - OPENCLAW_GATEWAY_TOKEN=$W9_POWER_PASSWORD + - OPENCLAW_CONFIG_PATH=/home/node/.openclaw/openclaw.json + - OPENCLAW_GATEWAY_TOKEN=${W9_LOGIN_PASSWORD} - OPENCLAW_GATEWAY_BIND=${OPENCLAW_GATEWAY_BIND} - OPENCLAW_GATEWAY_PORT=18789 - OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-} @@ -60,7 +86,7 @@ services: # docker compose --profile cli run --rm openclaw-cli config set # docker compose --profile cli run --rm openclaw-cli devices list openclaw-cli: - image: $W9_REPO:$W9_VERSION + image: ${W9_REPO}:${W9_VERSION} container_name: ${W9_ID}-cli network_mode: "service:openclaw-gateway" profiles: @@ -74,7 +100,8 @@ services: - HOME=/home/node - NODE_ENV=production - TERM=xterm-256color - - OPENCLAW_GATEWAY_TOKEN=$W9_POWER_PASSWORD + - OPENCLAW_CONFIG_PATH=/home/node/.openclaw/openclaw.json + - OPENCLAW_GATEWAY_TOKEN=${W9_LOGIN_PASSWORD} - OPENCLAW_GATEWAY_BIND=${OPENCLAW_GATEWAY_BIND} - OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-} - BROWSER=echo diff --git a/apps/openclaw/src/openclaw.json b/apps/openclaw/src/openclaw.json index a00028f8d..5e2dd959c 100644 --- a/apps/openclaw/src/openclaw.json +++ b/apps/openclaw/src/openclaw.json @@ -1,8 +1,7 @@ { "gateway": { "controlUi": { - "dangerouslyAllowHostHeaderOriginFallback": true, - "dangerouslyDisableDeviceAuth": true + "dangerouslyAllowHostHeaderOriginFallback": true } } -} \ No newline at end of file +} diff --git a/apps/openclaw/tests/cases.yml b/apps/openclaw/tests/cases.yml new file mode 100644 index 000000000..d0877faf3 --- /dev/null +++ b/apps/openclaw/tests/cases.yml @@ -0,0 +1,8 @@ +# The default adaptive checks cover compose-config, container-up, +# container-healthy, and the Control UI root path. Add a dedicated check for +# the Gateway health endpoint so the core runtime path is exercised explicitly. +optional: + - id: gateway-health + type: web-access + path: /healthz + expect_status: 200 diff --git a/apps/openclaw/variables.json b/apps/openclaw/variables.json index ced0197d7..160223048 100644 --- a/apps/openclaw/variables.json +++ b/apps/openclaw/variables.json @@ -2,20 +2,38 @@ "name": "openclaw", "trademark": "OpenClaw", "release": true, + "upstream": { + "image": "ghcr.io/openclaw/openclaw", + "releases": "https://github.com/openclaw/openclaw/releases", + "docs": [ + "https://docs.openclaw.ai/install/docker" + ] + }, "edition": [ { "dist": "community", "version": [ - "2026.3.13-1" + "2026.9.6" ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 18789, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "2", "disk": "10" }, - "upstream": { - "image": "https://github.com/openclaw/openclaw/releases" + "credentials": {}, + "env": { + "first_startup_only": [] + }, + "help": { + "db": "No bundled database. The Gateway token is seeded from W9_LOGIN_PASSWORD; the package derives the external Control UI origin from OPENCLAW_PUBLIC_ORIGIN or OPENCLAW_PUBLIC_SCHEME plus W9_URL, and applies OPENCLAW_TRUSTED_PROXIES for reverse-proxy forwarded-header attribution on each recreate." } } diff --git a/apps/openproject/variables.json b/apps/openproject/variables.json index 492181349..a5501e657 100644 --- a/apps/openproject/variables.json +++ b/apps/openproject/variables.json @@ -10,6 +10,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 80, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", diff --git a/apps/opensearch/variables.json b/apps/opensearch/variables.json index f302c4c8b..60d8c9a73 100644 --- a/apps/opensearch/variables.json +++ b/apps/opensearch/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "https", + "api": { + "port": 9200, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "8", diff --git a/apps/openwebui/variables.json b/apps/openwebui/variables.json index 754454281..c1a268d6a 100644 --- a/apps/openwebui/variables.json +++ b/apps/openwebui/variables.json @@ -20,6 +20,17 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8080, + "path": "/" + }, + "admin": { + "port": 8080, + "path": "/admin" + } + }, "requirements": { "cpu": "2", "memory": "4", diff --git a/apps/oracle/variables.json b/apps/oracle/variables.json index 94d6f3084..20f5741e3 100644 --- a/apps/oracle/variables.json +++ b/apps/oracle/variables.json @@ -2,6 +2,9 @@ "name": "oracle", "trademark": "Oracle Database", "release": true, + "upstream": { + "image": "https://container-registry.oracle.com/ords/ocr/ba/database/express" + }, "edition": [ { "dist": "community", @@ -12,12 +15,20 @@ ] } ], + "access": { + "defaultScheme": "https", + "web": { + "port": 5500, + "path": "/" + }, + "admin": { + "port": 5500, + "path": "/em" + } + }, "requirements": { "cpu": "2", "memory": "8", "disk": "1" - }, - "upstream": { - "image": "https://container-registry.oracle.com/ords/ocr/ba/database/express" } } diff --git a/apps/outline/variables.json b/apps/outline/variables.json index 0e0c5c9ea..79cfee4f4 100644 --- a/apps/outline/variables.json +++ b/apps/outline/variables.json @@ -2,6 +2,9 @@ "name": "outline", "trademark": "Outline", "release": false, + "upstream": { + "image": "https://hub.docker.com/r/outlinewiki/outline" + }, "edition": [ { "dist": "community", @@ -10,12 +13,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3000, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", "disk": "1" - }, - "upstream": { - "image": "https://hub.docker.com/r/outlinewiki/outline" } } diff --git a/apps/pgadmin/variables.json b/apps/pgadmin/variables.json index 510f5a6bb..2ee3a04ae 100644 --- a/apps/pgadmin/variables.json +++ b/apps/pgadmin/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 80, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", diff --git a/apps/pmm/variables.json b/apps/pmm/variables.json index a99047a94..8b846dd07 100644 --- a/apps/pmm/variables.json +++ b/apps/pmm/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "https", + "web": { + "port": 8443, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", diff --git a/apps/pocketbase/CHANGELOG.md b/apps/pocketbase/CHANGELOG.md index 582cf46c5..b6d786358 100644 --- a/apps/pocketbase/CHANGELOG.md +++ b/apps/pocketbase/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG +## 2026-09-28 + +- Add declarative `variables.json.credentials.username` / `password` metadata for PocketBase while keeping legacy `W9_LOGIN_GET_PASSWORD` for compatibility. + ## Release ### Fixes and Enhancements - diff --git a/apps/pocketbase/variables.json b/apps/pocketbase/variables.json index 9a7088ed4..cea5d6be7 100644 --- a/apps/pocketbase/variables.json +++ b/apps/pocketbase/variables.json @@ -2,6 +2,9 @@ "name": "pocketbase", "trademark": "PocketBase", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/spectado/pocketbase" + }, "edition": [ { "dist": "community", @@ -11,12 +14,31 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8090, + "path": "/" + }, + "admin": { + "port": 8090, + "path": "/_/" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "1" }, - "upstream": { - "image": "https://hub.docker.com/r/spectado/pocketbase" + "credentials": { + "username": { + "source": "inline", + "value": "admin@example.com" + }, + "password": { + "source": "container-env", + "name": "W9_LOGIN_PASSWORD", + "format": "text" + } } } diff --git a/apps/portainer/variables.json b/apps/portainer/variables.json index f04a671db..a8b0d1c6c 100644 --- a/apps/portainer/variables.json +++ b/apps/portainer/variables.json @@ -2,6 +2,9 @@ "name": "portainer", "trademark": "Portainer", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/portainer/portainer-ce" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 9000, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "4" - }, - "upstream": { - "image": "https://hub.docker.com/r/portainer/portainer-ce" } } diff --git a/apps/portkey/variables.json b/apps/portkey/variables.json index 5bc544df3..55a732cd1 100644 --- a/apps/portkey/variables.json +++ b/apps/portkey/variables.json @@ -2,6 +2,9 @@ "name": "portkey", "trademark": "Portkey", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/portkeyai/gateway" + }, "edition": [ { "dist": "community", @@ -11,12 +14,20 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8787, + "path": "/" + }, + "admin": { + "port": 8787, + "path": "/public" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "1" - }, - "upstream": { - "image": "https://hub.docker.com/r/portkeyai/gateway" } } diff --git a/apps/prestashop/variables.json b/apps/prestashop/variables.json index 58b64ca71..4abd92762 100644 --- a/apps/prestashop/variables.json +++ b/apps/prestashop/variables.json @@ -24,6 +24,17 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 80, + "path": "/" + }, + "admin": { + "port": 80, + "path": "/psadmin" + } + }, "requirements": { "cpu": "1", "memory": "2", diff --git a/apps/prometheus/.env b/apps/prometheus/.env index 1817dadf4..f5db020d8 100644 --- a/apps/prometheus/.env +++ b/apps/prometheus/.env @@ -1,6 +1,6 @@ W9_REPO=prom/prometheus W9_DIST=community -W9_VERSION=v3.14.0 +W9_VERSION=v3.15.0 # Optional password seed: enable only when the package actually controls a DB or built-in login. # See docs/w9-env-spec.md for when W9_POWER_PASSWORD is appropriate. diff --git a/apps/prometheus/CHANGELOG.md b/apps/prometheus/CHANGELOG.md index 456600202..a85e0daa4 100644 --- a/apps/prometheus/CHANGELOG.md +++ b/apps/prometheus/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 2026-09-28 + +- Update Prometheus to `v3.15.0` (alias `latest`). + ## 2026-09-15 - Update Prometheus to `v3.14.0` (alias `latest`). diff --git a/apps/prometheus/variables.json b/apps/prometheus/variables.json index b7cf3d31e..a6bea4270 100644 --- a/apps/prometheus/variables.json +++ b/apps/prometheus/variables.json @@ -14,7 +14,7 @@ { "dist": "community", "version": [ - "v3.14.0", + "v3.15.0", "latest" ] } diff --git a/apps/rancher/variables.json b/apps/rancher/variables.json index 5198f430b..cce850643 100644 --- a/apps/rancher/variables.json +++ b/apps/rancher/variables.json @@ -2,6 +2,9 @@ "name": "rancher", "trademark": "Rancher", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/rancher/rancher" + }, "edition": [ { "dist": "community", @@ -10,6 +13,13 @@ ] } ], + "access": { + "defaultScheme": "https", + "web": { + "port": 443, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", @@ -18,10 +28,9 @@ "credentials": { "password": { "source": "container-log", - "pattern": "Bootstrap Password:" + "match": "regex", + "pattern": "Bootstrap Password:\\s*(.+)", + "group": 1 } - }, - "upstream": { - "image": "https://hub.docker.com/r/rancher/rancher" } } diff --git a/apps/redisinsight/variables.json b/apps/redisinsight/variables.json index 5a4a9f4c8..910c347df 100644 --- a/apps/redisinsight/variables.json +++ b/apps/redisinsight/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 5540, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", diff --git a/apps/redmine/variables.json b/apps/redmine/variables.json index 5a7cab16e..6127393e0 100644 --- a/apps/redmine/variables.json +++ b/apps/redmine/variables.json @@ -2,6 +2,9 @@ "name": "redmine", "trademark": "Redmine", "release": true, + "upstream": { + "image": "https://hub.docker.com/_/redmine" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3000, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "1" - }, - "upstream": { - "image": "https://hub.docker.com/_/redmine" } } diff --git a/apps/rocketchat/variables.json b/apps/rocketchat/variables.json index c087a0030..5d5275c3e 100644 --- a/apps/rocketchat/variables.json +++ b/apps/rocketchat/variables.json @@ -11,6 +11,13 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 3000, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", diff --git a/apps/seafile/.env b/apps/seafile/.env index 3c998f5d7..f66c8633b 100644 --- a/apps/seafile/.env +++ b/apps/seafile/.env @@ -1,48 +1,55 @@ +W9_REPO=seafileltd/seafile-mc +W9_DIST=community +W9_VERSION=13.0-latest + +# Optional password seed: enable only when the package actually controls a DB or built-in login. +# See docs/w9-env-spec.md for when W9_POWER_PASSWORD is appropriate. W9_POWER_PASSWORD='ZuWADQIBji4k!a4G' -W9_DIST='community' -W9_VERSION='13.0.8' -W9_REPO=seafileltd/seafile-mc +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='seafile' -W9_HTTP_PORT=80 -W9_HTTP_PORT_SET='9001' -W9_LOGIN_USER=me@example.com -W9_LOGIN_PASSWORD=$W9_POWER_PASSWORD -W9_URL='appname.example.com' -W9_DB_EXPOSE="mariadb" -W9_DB_VERSION="10.5" -W9_NETWORK=websoft9 -#### --------------------------------------------------------------------------------------- #### +W9_ID=seafile -# DB_HOST=$W9_ID-mariadb -# DB_ROOT_PASSWD=$W9_POWER_PASSWORD +W9_HTTP_PORT=80 +W9_HTTP_PORT_SET=9001 +W9_DB_EXPOSE=mariadb +W9_DB_VERSION=10.11 +W9_LOGIN_USER=me@example.com +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} +W9_URL=seafile.example.com +W9_URL_REPLACE=true -## Persistent Storage -BASIC_STORAGE_PATH=/opt -SEAFILE_VOLUME=$BASIC_STORAGE_PATH/seafile-data -SEAFILE_MYSQL_VOLUME=$BASIC_STORAGE_PATH/seafile-mysql/db -SEAFILE_CADDY_VOLUME=$BASIC_STORAGE_PATH/seafile-caddy -SEADOC_VOLUME=$BASIC_STORAGE_PATH/seadoc-data +W9_NETWORK=websoft9 -################################# -# Startup parameters # -################################# -SEAFILE_SERVER_HOSTNAME=${W9_URL} -SEADOC_SERVER_URL=http://${SEAFILE_SERVER_HOSTNAME}/sdoc-server +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Seafile image environment variables +# Docs: https://manual.seafile.com/latest/setup/setup_ce_by_docker/ +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +# Public host[:port] used by Seafile and notification callbacks. +# Domain mode: keep the default and point DNS to your gateway; set SEAFILE_SERVER_PROTOCOL=https. +# No-domain mode: set this to a reachable IP:port (for example 203.0.113.10:9001). +SEAFILE_EXTERNAL_HOSTPORT=${W9_URL} +SEAFILE_SERVER_HOSTNAME=${SEAFILE_EXTERNAL_HOSTPORT} +# Use https when the app is published behind an HTTPS gateway or domain, otherwise the web UI stays blank. SEAFILE_SERVER_PROTOCOL=http +SITE_ROOT=/ TIME_ZONE=Etc/UTC NON_ROOT=false -SEAFILE_SERVER_LETSENCRYPT=false JWT_PRIVATE_KEY=8423n98dshof43?1oidjdssdf -##################################### -# Third-party service configuration # -##################################### - -## Database +SEAFILE_LOG_TO_STDOUT=false +ENABLE_GO_FILESERVER=true SEAFILE_MYSQL_DB_HOST=${W9_ID}-db SEAFILE_MYSQL_DB_PORT=3306 SEAFILE_MYSQL_DB_USER=seafile @@ -50,32 +57,21 @@ SEAFILE_MYSQL_DB_PASSWORD=${W9_POWER_PASSWORD} SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=seahub_db - -## Cache -CACHE_PROVIDER=redis # or memcached - -### Redis +CACHE_PROVIDER=redis REDIS_HOST=redis REDIS_PORT=6379 - -###################################### -# Initial variables # -# (Only valid in first-time startup) # -###################################### - -## Database root password, Used to create Seafile users +REDIS_PASSWORD= INIT_SEAFILE_MYSQL_ROOT_PASSWORD=${W9_POWER_PASSWORD} - -## Seafile admin user INIT_SEAFILE_ADMIN_EMAIL=${W9_LOGIN_USER} INIT_SEAFILE_ADMIN_PASSWORD=${W9_LOGIN_PASSWORD} - -############################################ -# Additional configurations for extensions # -############################################ - -## SeaDoc service -ENABLE_SEADOC=true - -## Metadata server -MD_FILE_COUNT_LIMIT=100000 \ No newline at end of file +ENABLE_NOTIFICATION_SERVER=false +INNER_NOTIFICATION_SERVER_URL=http://notification-server:8083 +NOTIFICATION_SERVER_URL=${SEAFILE_SERVER_PROTOCOL}://${SEAFILE_EXTERNAL_HOSTPORT}/notification +MD_FILE_COUNT_LIMIT=100000 + +# Not used by default; enable only when needed: +# BASIC_STORAGE_PATH=/opt +# SEAFILE_VOLUME=${BASIC_STORAGE_PATH}/seafile-data +# SEAFILE_MYSQL_VOLUME=${BASIC_STORAGE_PATH}/seafile-mysql/db +# ENABLE_SEAFILE_AI=false +# ENABLE_FACE_RECOGNITION=false diff --git a/apps/seafile/CHANGELOG.md b/apps/seafile/CHANGELOG.md index 582cf46c5..7ed32cf7c 100644 --- a/apps/seafile/CHANGELOG.md +++ b/apps/seafile/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-24 +- updated Seafile community package target to `13.0-latest` +- aligned `.env` and `docker-compose.yml` with the official Seafile 13.0 CE Docker env and service shape +- normalized braced variable references and inline port comments for current repository policy +- added app metadata for access paths, first-start-only envs, and upstream docs +- added app-specific test coverage for the login page +- added `SEAFILE_EXTERNAL_HOSTPORT` so domain mode and direct `IP:port` mode can share the same package +- removed the bundled SeaDoc service and its gateway rules; `.sdoc` is a Seafile-specific format with recurring routing issues +- no bundled online-office extension; OnlyOffice and similar integrations are configured directly in `seahub_settings.py` +- added `src/nginx-proxy.conf` with the official reverse-proxy settings (buffering off, HTTP/1.1), fixing `ERR_INCOMPLETE_CHUNKED_ENCODING` and blank admin pages behind the gateway diff --git a/apps/seafile/Notes.md b/apps/seafile/Notes.md deleted file mode 100644 index 019e39af6..000000000 --- a/apps/seafile/Notes.md +++ /dev/null @@ -1,3 +0,0 @@ -# Notes - -Not test it, need research and complete it again diff --git a/apps/seafile/README.md b/apps/seafile/README.md index c5139e230..7752dc91e 100644 --- a/apps/seafile/README.md +++ b/apps/seafile/README.md @@ -1,26 +1,94 @@ -# Seafile on Docker +# Seafile on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Seafile: +## Quick Start +### Deploy Verification - - community: 11.0.12, 12.0-latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Seafile**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Make sure you are signed in to the Seafile admin console. +2. Try a core feature. -The following are the minimal [recommended requirements](https://cloud.seafile.com/published/seafile-manual-cn/docker): +### Change Password -* **RAM**: 2 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 4 GB of free space -* **bandwidth**: more fluent experience over 100M +1. In the [Websoft9](https://www.websoft9.com) console, open the app's **Compose** tab. +2. Update the password in `.env` and save. +3. Rebuild the app. + -## Install +## Configuration Reference -You can install this Seafile by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Seafile Docker image](https://hub.docker.com/r/seafileltd/seafile-mc) and makes some improvements below. -If you want use Seafile with **Websoft9 Business Support** free, you can [subscribe Seafile](https://www.websoft9.com/apps) on Cloud platform + +Notes: -## Documentation +- Domain mode: set `W9_URL` and `SEAFILE_SERVER_PROTOCOL=https` when the app is served behind an HTTPS gateway. A wrong protocol makes the web UI render blank. +- No-domain mode: set `SEAFILE_EXTERNAL_HOSTPORT` to a reachable `IP:port`, for example `203.0.113.10:9001`. +- The package ships `src/nginx-proxy.conf`, which the Websoft9 Gateway injects to disable response buffering for Seafile's chunked assets. This fixes `ERR_INCOMPLETE_CHUNKED_ENCODING` and blank admin pages behind a domain. +- SeaDoc is not bundled. `.sdoc` is a Seafile-specific format; use OnlyOffice for online editing of office files. OnlyOffice is configured directly in `seahub_settings.py`. + -[Seafile Administrator Guide](https://support.websoft9.com/docs/seafile) powered by Websoft9 \ No newline at end of file +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 13.0-latest. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 80 | + + +### Data Directory + + +- `seafile-data` → `/shared` +- `mysql-data` → `/var/lib/mysql` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +Note: `INIT_SEAFILE_MYSQL_ROOT_PASSWORD`, `INIT_SEAFILE_ADMIN_EMAIL`, `INIT_SEAFILE_ADMIN_PASSWORD` take effect on first startup only; changing them after deployment may not take effect until the app is re-initialized. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [Seafile Administrator Guide](https://support.websoft9.com/docs/seafile) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/seafileltd/seafile-mc) + +- [Official docs](https://manual.seafile.com/latest/setup/setup_ce_by_docker/) + +- [Official docs](https://manual.seafile.com/latest/extension/only_office/) + +- [Official docs](https://manual.seafile.com/latest/upgrade/upgrade_notes_for_13.0.x/) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/seafile/docker-compose-pro.yml b/apps/seafile/docker-compose-pro.yml deleted file mode 100644 index b44931ed5..000000000 --- a/apps/seafile/docker-compose-pro.yml +++ /dev/null @@ -1,69 +0,0 @@ -version: "3.8" -services: - mariadb: - image: mariadb:10.5 - container_name: $W9_ID-mariadb - restart: unless-stopped - environment: - - MYSQL_ROOT_PASSWORD=$W9_POWER_PASSWORD - - MYSQL_LOG_CONSOLE=true - volumes: - - mysql_data:/var/lib/mysql - - memcached: - image: memcached:1.6 - container_name: $W9_ID-memcached - restart: unless-stopped - entrypoint: memcached -m 256 - - elasticsearch: - image: seafileltd/elasticsearch-with-ik:5.6.16 - container_name: $W9_ID-elasticsearch - restart: unless-stopped - environment: - - discovery.type=single-node - - bootstrap.memory_lock=true - - "ES_JAVA_OPTS=-Xms1g -Xmx1g" - ulimits: - memlock: - soft: -1 - hard: -1 - deploy: - resources: - limits: - memory: 2G - volumes: - - elasticsearch_data:/usr/share/elasticsearch/data # Requested, specifies the path to Elasticsearch data persistent store. - - seafile-pro: - image: docker.seafile.top/seafileltd/seafile-pro-mc:$W9_VERSION - container_name: $W9_ID - restart: unless-stopped - ports: - - "$W9_HTTP_PORT_SET:$W9_HTTP_PORT" - # - "443:443" # If https is enabled, cancel the comment. - volumes: - - seafile_data:/shared # Requested, specifies the path to Seafile data persistent store. - env_file: .env - environment: - - DB_HOST=$W9_ID-mariadb - - DB_ROOT_PASSWD=$W9_POWER_PASSWORD - - TIME_ZONE=$W9_TIME_ZONE - - SEAFILE_ADMIN_EMAIL=$W9_LOGIN_USER - - SEAFILE_ADMIN_PASSWORD=$W9_LOGIN_PASSWORD - - SEAFILE_SERVER_LETSENCRYPT=false - - SEAFILE_SERVER_HOSTNAME=$W9_URL - depends_on: - - mariadb - - memcached - - elasticsearch - -networks: - default: - name: ${W9_NETWORK} - external: true - -volumes: - seafile_data: - elasticsearch_data: - mysql_data: diff --git a/apps/seafile/docker-compose.yml b/apps/seafile/docker-compose.yml index 22dd7ff48..08022a517 100644 --- a/apps/seafile/docker-compose.yml +++ b/apps/seafile/docker-compose.yml @@ -1,6 +1,3 @@ -# image: https://hub.docker.com/r/seafileltd/seafile-mc -# docs: https://cloud.seafile.com/published/seafile-manual-cn/ - services: seafile: image: ${W9_REPO}:${W9_VERSION} @@ -9,43 +6,23 @@ services: env_file: - .env ports: - - ${W9_HTTP_PORT_SET}:80 - # - ${W9_HTTPS_PORT_SET}:443 + - "${W9_HTTP_PORT_SET}:80" # Web Console volumes: - seafile-data:/shared - environment: - - SITE_ROOT=/ - - NON_ROOT=false - - SEAFILE_LOG_TO_STDOUT=false - - ENABLE_NOTIFICATION_SERVER=false - - ENABLE_SEAFILE_AI=false + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s depends_on: db: condition: service_healthy redis: condition: service_started - seadoc: - image: seafileltd/sdoc-server:2.0-latest - container_name: ${W9_ID}-seadoc - volumes: - - seadoc-data:/shared - environment: - - DB_HOST=${SEAFILE_MYSQL_DB_HOST} - - DB_PORT=${SEAFILE_MYSQL_DB_PORT} - - DB_USER=${SEAFILE_MYSQL_DB_USER} - - DB_PASSWORD=${SEAFILE_MYSQL_DB_PASSWORD} - - DB_NAME=${SEADOC_MYSQL_DB_NAME} - - TIME_ZONE=${TIME_ZONE} - - JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY} - - NON_ROOT=${NON_ROOT} - - SEAHUB_SERVICE_URL=http://${W9_ID} - depends_on: - db: - condition: service_healthy - db: - image: mariadb:10.11 + image: mariadb:${W9_DB_VERSION} container_name: ${W9_ID}-db restart: unless-stopped environment: @@ -53,7 +30,7 @@ services: - MYSQL_LOG_CONSOLE=true - MARIADB_AUTO_UPGRADE=1 volumes: - - mysql_data:/var/lib/mysql + - mysql-data:/var/lib/mysql healthcheck: test: [ @@ -61,7 +38,7 @@ services: "/usr/local/bin/healthcheck.sh", "--connect", "--mariadbupgrade", - "--innodb_initialized", + "--innodb_initialized" ] interval: 20s start_period: 30s @@ -72,6 +49,12 @@ services: image: redis container_name: ${W9_ID}-redis restart: unless-stopped + command: + - /bin/sh + - -c + - exec redis-server --requirepass "$${REDIS_PASSWORD}" --save "" --appendonly no + environment: + - REDIS_PASSWORD=${REDIS_PASSWORD} networks: default: @@ -79,6 +62,5 @@ networks: external: true volumes: - mysql_data: + mysql-data: seafile-data: - seadoc-data: \ No newline at end of file diff --git a/apps/seafile/src/after_up.sh b/apps/seafile/src/after_up.sh deleted file mode 100644 index 8b1378917..000000000 --- a/apps/seafile/src/after_up.sh +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/seafile/src/get_version.sh b/apps/seafile/src/get_version.sh deleted file mode 100644 index 35b682e4f..000000000 --- a/apps/seafile/src/get_version.sh +++ /dev/null @@ -1 +0,0 @@ -sudo echo "seafile version:" $(docker inspect seafile |grep -i seafile_version |cut -d= -f2) |sudo tee -a /data/logs/install_version.txt diff --git a/apps/seafile/src/nginx-proxy.conf b/apps/seafile/src/nginx-proxy.conf new file mode 100644 index 000000000..bca19c378 --- /dev/null +++ b/apps/seafile/src/nginx-proxy.conf @@ -0,0 +1,23 @@ +# Websoft9 Gateway (Nginx Proxy Manager) advanced config. +# The platform reads this file from src/nginx-proxy.conf and injects it into the +# Proxy Host server{} block. It is NOT mounted into any container. +# +# Seafile serves its web assets with chunked transfer encoding. The gateway's +# default proxy buffering can truncate those responses, which shows up in the +# browser as ERR_INCOMPLETE_CHUNKED_ENCODING and a blank page after login. The +# directives below follow the official Seafile "Use other reverse proxy" guide. + +location / { + proxy_pass $forward_scheme://$server:$port$request_uri; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Connection ""; + proxy_buffering off; + proxy_request_buffering off; + proxy_read_timeout 36000s; + proxy_send_timeout 36000s; + client_max_body_size 0; +} diff --git a/apps/seafile/tests/cases.yml b/apps/seafile/tests/cases.yml new file mode 100644 index 000000000..317b11a34 --- /dev/null +++ b/apps/seafile/tests/cases.yml @@ -0,0 +1,4 @@ +custom: + - id: login-page + type: script + script: check.sh diff --git a/apps/seafile/tests/check.sh b/apps/seafile/tests/check.sh new file mode 100755 index 000000000..754ca2105 --- /dev/null +++ b/apps/seafile/tests/check.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +base="${BASE_URL:?BASE_URL is required}" +body="$(mktemp)" +trap 'rm -f "$body"' EXIT + +code="$(curl -sS -o "$body" -w '%{http_code}' "${base}/accounts/login/" || true)" + +case "$code" in + 200|301|302) ;; + *) + echo "unexpected HTTP ${code} from Seafile login page" >&2 + exit 1 + ;; +esac + +if ! grep -qi 'seafile' "$body"; then + echo "response does not look like a Seafile login page" >&2 + exit 1 +fi + +echo "Seafile login page responded with HTTP ${code}" diff --git a/apps/seafile/variables.json b/apps/seafile/variables.json index 8cc53e5ed..e0b6a6283 100644 --- a/apps/seafile/variables.json +++ b/apps/seafile/variables.json @@ -6,17 +6,44 @@ { "dist": "community", "version": [ - "13.0.8", - "12.0-latest" + "13.0-latest" ] } ], + "access": { + "web": { + "port": 80, + "path": "/" + }, + "admin": { + "port": 80, + "path": "/accounts/login/" + } + }, "requirements": { - "cpu": "1", + "cpu": "2", "memory": "2", "disk": "4" }, + "env": { + "first_startup_only": [ + "INIT_SEAFILE_MYSQL_ROOT_PASSWORD", + "INIT_SEAFILE_ADMIN_EMAIL", + "INIT_SEAFILE_ADMIN_PASSWORD" + ] + }, + "help": { + "db": "Bundled MariaDB and Redis are initialized from the package env on first deployment.", + "login": "The initial administrator account comes from W9_LOGIN_USER and W9_LOGIN_PASSWORD on first startup; later edits do not backfill existing data.", + "url": "Set SEAFILE_SERVER_PROTOCOL=https when the app is published behind an HTTPS gateway or domain, and set SEAFILE_EXTERNAL_HOSTPORT to a reachable IP:port when no domain is bound." + }, "upstream": { - "image": "https://hub.docker.com/r/seafileltd/seafile-mc" + "image": "https://hub.docker.com/r/seafileltd/seafile-mc", + "docs": [ + "https://manual.seafile.com/latest/setup/setup_ce_by_docker/", + "https://manual.seafile.com/latest/extension/only_office/", + "https://manual.seafile.com/latest/upgrade/upgrade_notes_for_13.0.x/", + "https://manual.seafile.com/latest/setup/use_other_reverse_proxy/" + ] } } diff --git a/apps/selenium/variables.json b/apps/selenium/variables.json index da4efa4b7..df7097304 100644 --- a/apps/selenium/variables.json +++ b/apps/selenium/variables.json @@ -2,6 +2,9 @@ "name": "selenium", "trademark": "selenium", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/selenium/standalone-chrome" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 4444, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "1" - }, - "upstream": { - "image": "https://hub.docker.com/r/selenium/standalone-chrome" } } diff --git a/apps/signoz/variables.json b/apps/signoz/variables.json index 8b1b7fbae..3ee689164 100644 --- a/apps/signoz/variables.json +++ b/apps/signoz/variables.json @@ -2,6 +2,9 @@ "name": "signoz", "trademark": "SigNoz", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/signoz/signoz" + }, "edition": [ { "dist": "community", @@ -11,12 +14,16 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8080, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", "disk": "10" - }, - "upstream": { - "image": "https://hub.docker.com/r/signoz/signoz" } } diff --git a/apps/sqlserver/.env b/apps/sqlserver/.env index 8f9dee2cb..bc63e9680 100644 --- a/apps/sqlserver/.env +++ b/apps/sqlserver/.env @@ -1,12 +1,36 @@ -W9_VERSION=2022 -W9_PID=Express W9_REPO=mcr.microsoft.com/mssql/server -W9_POWER_PASSWORD=spJNF09yzwWJaG! +W9_DIST=community +W9_VERSION=2025 +W9_POWER_PASSWORD="spJNF09yzwWJaG!" + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + #### -- Not allowed to edit below environments when recreate app based on existing data -- #### + W9_ID=sqlserver W9_DB_PORT=1433 W9_DB_PORT_SET=1433 W9_LOGIN_USER=sa -W9_LOGIN_PASSWORD=$W9_POWER_PASSWORD +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} W9_NETWORK=websoft9 -#### --------------------------------------------------------------------------------------- #### \ No newline at end of file + +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# SQL Server image environment variables +# Docs: https://learn.microsoft.com/en-us/sql/linux/install-upgrade/quickstart-install-docker?view=sql-server-ver17 +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +MSSQL_PID=Express + +# Not used by default; enable only when needed: +# MSSQL_AGENT_ENABLED=true +# MSSQL_COLLATION=SQL_Latin1_General_CP1_CI_AS +# MSSQL_ENABLE_HADR=0 +# MSSQL_MEMORY_LIMIT_MB= +# MSSQL_TCP_PORT=1433 diff --git a/apps/sqlserver/CHANGELOG.md b/apps/sqlserver/CHANGELOG.md index 582cf46c5..4d087957d 100644 --- a/apps/sqlserver/CHANGELOG.md +++ b/apps/sqlserver/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-22 +- Update the default SQL Server image target from `2022` to `2025`. +- Keep the packaged version list limited to `2025` and `2022`. +- Replace deprecated `SA_PASSWORD` wiring with `MSSQL_SA_PASSWORD` and align `.env`/Compose files with current repository policy. +- Add upstream metadata and a sqlcmd-based smoke test for deployment validation. diff --git a/apps/sqlserver/README.md b/apps/sqlserver/README.md index 3bf42d20e..45bc8e871 100644 --- a/apps/sqlserver/README.md +++ b/apps/sqlserver/README.md @@ -3,16 +3,16 @@ This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for SQL Server: - - community: 2022, 2019, 2017 + - community: 2025, 2022 ## System Requirements -The following are the minimal [recommended requirements](https://github.com/onlyoffice/docker#recommended-system-requirements): +The following are the minimal recommended requirements for SQL Server containers: * **RAM**: 4 GB or more * **CPU**: 2 cores or higher -* **Disk**: at least 1 GB of free space +* **Disk**: at least 2 GB of free space * **bandwidth**: more fluent experience over 100M ## Install @@ -21,6 +21,18 @@ You can install this SQL Server by [How to use it?](https://github.com/Websoft9/ If you want use SQL Server with **Websoft9 Business Support** free, you can [subscribe SQL Server](https://www.websoft9.com/apps) on Cloud platform +## Change Password + +`MSSQL_SA_PASSWORD` is only applied when SQL Server initializes a new data directory. If you deploy with an existing `mssql_data` volume, changing `W9_LOGIN_PASSWORD` in `.env` does not rotate the `sa` password inside SQL Server. + +To rotate the password on a running container, execute: + +```bash +docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U sa -P '' \ + -Q "ALTER LOGIN sa WITH PASSWORD=''" +``` + ## Documentation -[SQL Server Administrator Guide](https://support.websoft9.com/docs/sqlserver) powered by Websoft9 \ No newline at end of file +[SQL Server Administrator Guide](https://support.websoft9.com/docs/sqlserver) powered by Websoft9 diff --git a/apps/sqlserver/docker-compose.yml b/apps/sqlserver/docker-compose.yml index ec076795c..fa06b5fe9 100644 --- a/apps/sqlserver/docker-compose.yml +++ b/apps/sqlserver/docker-compose.yml @@ -1,19 +1,14 @@ -# image: https://hub.docker.com/_/microsoft-mssql-server -# docs: https://docs.microsoft.com/en-us/sql/linux/quickstart-install-connect-docker - -version: "3.8" - services: sqlserver: - image: $W9_REPO:${W9_VERSION}-latest + image: ${W9_REPO}:${W9_VERSION}-latest container_name: ${W9_ID} env_file: .env environment: - ACCEPT_EULA=Y - - SA_PASSWORD=${W9_LOGIN_PASSWORD} - - MSSQL_PID=${W9_PID} + - MSSQL_SA_PASSWORD=${W9_LOGIN_PASSWORD} + - MSSQL_PID=${MSSQL_PID} ports: - - ${W9_DB_PORT_SET}:1433 + - "${W9_DB_PORT_SET}:1433" # SQL Server volumes: - mssql_data:/var/opt/mssql restart: unless-stopped diff --git a/apps/sqlserver/tests/cases.yml b/apps/sqlserver/tests/cases.yml new file mode 100644 index 000000000..98745b90e --- /dev/null +++ b/apps/sqlserver/tests/cases.yml @@ -0,0 +1,4 @@ +custom: + - id: sqlcmd-query + type: script + script: check.sh diff --git a/apps/sqlserver/tests/check.sh b/apps/sqlserver/tests/check.sh new file mode 100755 index 000000000..435d8b8a6 --- /dev/null +++ b/apps/sqlserver/tests/check.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +container="${W9_ID:?W9_ID is required}" +user="${W9_LOGIN_USER:?W9_LOGIN_USER is required}" +password="${W9_LOGIN_PASSWORD:?W9_LOGIN_PASSWORD is required}" +query="SELECT @@VERSION;" +tool="" + +for candidate in /opt/mssql-tools18/bin/sqlcmd /opt/mssql-tools/bin/sqlcmd; do + if docker exec "${container}" test -x "${candidate}" >/dev/null 2>&1; then + tool="${candidate}" + break + fi +done + +if [ -z "${tool}" ]; then + echo "sqlcmd not found in ${container}" >&2 + exit 1 +fi + +deadline=$((SECONDS + 180)) +while [ "${SECONDS}" -lt "${deadline}" ]; do + if docker exec "${container}" "${tool}" -S localhost -U "${user}" -P "${password}" -Q "${query}" -C >/dev/null 2>&1 || \ + docker exec "${container}" "${tool}" -S localhost -U "${user}" -P "${password}" -Q "${query}" >/dev/null 2>&1; then + echo "SQL Server accepted a local sqlcmd query" + exit 0 + fi + sleep 5 +done + +echo "sqlcmd query did not succeed before timeout" >&2 +exit 1 diff --git a/apps/sqlserver/variables.json b/apps/sqlserver/variables.json index 70f194cf1..2d4c54fc8 100644 --- a/apps/sqlserver/variables.json +++ b/apps/sqlserver/variables.json @@ -6,15 +6,27 @@ { "dist": "community", "version": [ - "2022", - "2019", - "2017" + "2025", + "2022" ] } ], "requirements": { "cpu": "2", "memory": "4", - "disk": "1" + "disk": "2" + }, + "upstream": { + "image": "https://mcr.microsoft.com/product/mssql/server/about", + "docs": [ + "https://learn.microsoft.com/en-us/sql/linux/install-upgrade/quickstart-install-docker?view=sql-server-ver17", + "https://learn.microsoft.com/en-us/sql/linux/quickstart-install-connect-docker?view=sql-server-ver16", + "https://mcr.microsoft.com/product/mssql/server/tags" + ] + }, + "env": { + "first_startup_only": [ + "MSSQL_SA_PASSWORD" + ] } } diff --git a/apps/strapi/.env b/apps/strapi/.env index 50a4ea7b2..1d424e040 100644 --- a/apps/strapi/.env +++ b/apps/strapi/.env @@ -1,15 +1,61 @@ -W9_VERSION=3.6.8 -W9_REPO=strapi/strapi +W9_REPO=websoft9dev/strapi W9_DIST=community +W9_VERSION=5.54.0 + +W9_POWER_PASSWORD='UGz0IARz117ssO%' + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. -W9_POWER_PASSWORD=UGz0IARz117ssO% #### -- Not allowed to edit below environments when recreate app based on existing data -- #### + W9_ID=strapi W9_HTTP_PORT=1337 -W9_HTTP_PORT_SET=1337 -W9_URL=appname.example.com -W9_ADMIN_PATH="/admin" -W9_DB_EXPOSE="mysql" -W9_DB_VERSION="5.7" +W9_HTTP_PORT_SET=9001 +W9_LOGIN_USER=admin@example.com +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} +W9_URL=strapi.example.com +W9_ADMIN_PATH=/admin W9_NETWORK=websoft9 -#### --------------------------------------------------------------------------------------- #### \ No newline at end of file + +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Strapi image environment variables +# Docs: https://docs.strapi.io/cms/installation/docker +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +HOST=0.0.0.0 +PORT=${W9_HTTP_PORT} +APP_KEYS=${W9_POWER_PASSWORD},${W9_POWER_PASSWORD}-1,${W9_POWER_PASSWORD}-2,${W9_POWER_PASSWORD}-3 +API_TOKEN_SALT=${W9_POWER_PASSWORD}-api +ADMIN_JWT_SECRET=${W9_POWER_PASSWORD}-admin +TRANSFER_TOKEN_SALT=${W9_POWER_PASSWORD}-transfer +JWT_SECRET=${W9_POWER_PASSWORD}-jwt +ENCRYPTION_KEY=${W9_POWER_PASSWORD}-enc +DATABASE_FILENAME=.tmp/data.db +ADMIN_PATH=${W9_ADMIN_PATH} +NODE_ENV=production +BROWSER=false +STRAPI_TELEMETRY_DISABLED=true + +# External database (optional; default is the bundled SQLite file above). +# Uncomment and set these to run Strapi on an external PostgreSQL or MySQL +# database instead. The external database must be empty on first start; +# Strapi does not migrate the bundled SQLite data automatically. +# DATABASE_CLIENT=postgres # sqlite (default) | postgres | mysql +# DATABASE_HOST= +# DATABASE_NAME= +# DATABASE_USERNAME= +# DATABASE_PASSWORD= + +# Not used by default; enable only when needed: +# STRAPI_ADMIN_BACKEND_URL=http://${W9_URL} +# STRAPI_PLUGIN_I18N_INIT_LOCALE_CODE=en +# STRAPI_ENFORCE_SOURCEMAPS=false +# FLAG_DOC_LINKS=true +# FLAG_PROMOTE_EE=false diff --git a/apps/strapi/CHANGELOG.md b/apps/strapi/CHANGELOG.md index 582cf46c5..d74b7be4c 100644 --- a/apps/strapi/CHANGELOG.md +++ b/apps/strapi/CHANGELOG.md @@ -1,5 +1,18 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-22 +- Rebuild the Strapi package from the official Strapi 5 npm distribution because upstream no longer publishes official container images. +- Replace the legacy Strapi 3 + MySQL package with a self-built Strapi 5.54.0 application on Node 22. +- Switch storage to bundled SQLite persisted in `strapi_data`, and persist uploads separately in `strapi_uploads`. +- Auto-create the first administrator on first start from `W9_LOGIN_USER` and `W9_LOGIN_PASSWORD`. +- Add `tests/cases.yml` to validate the Strapi admin route instead of the root path. +- Note: this package is for fresh Strapi 5 deployments; existing Strapi 3 data is not a drop-in in-place upgrade target. +## 2026-09-23 +- Remove the `build` section from `docker-compose.yml` so runtime deployment consumes a prebuilt `websoft9dev/strapi` image instead of building during `docker compose up`. +- Keep `Dockerfile` and package sources in-repo as the image build source for maintainers and image publishing workflows. + +## 2026-09-24 +- Make `config/database.js` select the database client from `DATABASE_CLIENT` (sqlite default, postgres, mysql) with env-driven connection settings. +- Bundle the `pg` and `mysql2` drivers in the `websoft9dev/strapi` image so external PostgreSQL/MySQL works without a custom build. +- Document the optional external database and its empty-database/data-migration caveat in `.env`, `README.md`, `Notes.md`, and `variables.json`. diff --git a/apps/strapi/Dockerfile b/apps/strapi/Dockerfile new file mode 100644 index 000000000..7a60b6193 --- /dev/null +++ b/apps/strapi/Dockerfile @@ -0,0 +1,39 @@ +FROM node:22-slim AS build + +ARG STRAPI_VERSION=5.54.0 + +ENV NODE_ENV=production \ + BROWSER=false \ + STRAPI_TELEMETRY_DISABLED=true + +RUN apt-get update \ + && apt-get install -y --no-install-recommends build-essential python3 git ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/app + +COPY package.json ./ +RUN npm install --omit=dev + +COPY config ./config +COPY public ./public +COPY src ./src + +RUN mkdir -p .tmp public/uploads \ + && npm run build + +FROM node:22-slim + +ENV NODE_ENV=production \ + BROWSER=false \ + STRAPI_TELEMETRY_DISABLED=true + +WORKDIR /opt/app + +COPY --from=build /opt/app /opt/app + +RUN mkdir -p /opt/app/.tmp /opt/app/public/uploads + +EXPOSE 1337 + +CMD ["npm", "run", "start"] diff --git a/apps/strapi/Notes.md b/apps/strapi/Notes.md index 7f34ece04..fbf554df2 100644 --- a/apps/strapi/Notes.md +++ b/apps/strapi/Notes.md @@ -1,2 +1,7 @@ ## Strapi +- Strapi 5 is packaged here as a prebuilt `websoft9dev/strapi` image because upstream no longer publishes official container images. +- The package now targets fresh Strapi 5 deployments; existing Strapi 3 volumes or databases are not directly upgrade-compatible. +- The first administrator is seeded during the initial boot only, using `W9_LOGIN_USER` and `W9_LOGIN_PASSWORD`. +- The database is chosen at runtime by `config/database.js` from `DATABASE_CLIENT`; SQLite is the default and PostgreSQL/MySQL are external-only options. The `pg` and `mysql2` drivers are preinstalled in the image. +- The external database must be empty on first start; the bundled SQLite file is not migrated into it automatically. diff --git a/apps/strapi/README.md b/apps/strapi/README.md index 80b1fdc78..b7b1a7fd0 100644 --- a/apps/strapi/README.md +++ b/apps/strapi/README.md @@ -1,26 +1,98 @@ -# Strapi on Docker +# Strapi on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Strapi: +## Quick Start +### Deploy Verification - - community: 3.6.8, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Strapi**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Open the Strapi admin console from the **Access** tab. +2. Sign in with the initial administrator account. +3. Create a content type or a test entry to confirm the CMS is writable. -The following are the minimal [recommended requirements](https://hub.docker.com/r/strapi/strapi): +### Change Password -* **RAM**: 1 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +1. Change the administrator password from the profile menu inside the Strapi admin console. +2. `W9_LOGIN_USER` and `W9_LOGIN_PASSWORD` create the first administrator on first start only; changing them later requires using the Strapi CLI or removing the persisted data volume and rebuilding. + -## Install +## Configuration Reference -You can install this Strapi by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Strapi Docker image](https://github.com/strapi/strapi) and makes some improvements below. -If you want use Strapi with **Websoft9 Business Support** free, you can [subscribe Strapi](https://www.websoft9.com/apps) on Cloud platform + +### Package Notes -## Documentation +- Strapi 5 no longer publishes official container images, so Websoft9 provides and consumes a prebuilt `websoft9dev/strapi` image for this package. +- The bundled SQLite database is stored in the `strapi_data` volume. +- The first administrator is created automatically on first start from `W9_LOGIN_USER` and `W9_LOGIN_PASSWORD`. +- An external PostgreSQL or MySQL database can be used instead of the bundled SQLite by setting `DATABASE_CLIENT` and the `DATABASE_*` connection variables in `.env`. The external database must be empty on first start; existing SQLite data is not migrated automatically. + -[Strapi Administrator Guide](https://support.websoft9.com/docs/strapi) powered by Websoft9 \ No newline at end of file +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 5.54.0, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 1337 | + + +### Data Directory + + +- `strapi_data` → `/opt/app/.tmp` +- `strapi_uploads` → `/opt/app/public/uploads` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +Note: `W9_LOGIN_USER`, `W9_LOGIN_PASSWORD` take effect on first startup only; changing them after deployment may not take effect until the app is re-initialized. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [Strapi Administrator Guide](https://support.websoft9.com/docs/strapi) by Websoft9 + +- [Docker Hub image](https://github.com/strapi/strapi) + +- [Releases](https://github.com/strapi/strapi/releases) + +- [Official docs](https://docs.strapi.io/cms/installation/docker) + +- [Official docs](https://docs.strapi.io/cms/configurations/environment) + +- [Official docs](https://docs.strapi.io/cms/migration/v4-to-v5/introduction-and-faq) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Admin page not reachable?** +- Confirm the published port is open and the container healthcheck reaches `/_health`. + diff --git a/apps/strapi/config/admin.js b/apps/strapi/config/admin.js new file mode 100644 index 000000000..cc72f3369 --- /dev/null +++ b/apps/strapi/config/admin.js @@ -0,0 +1,21 @@ +module.exports = ({ env }) => ({ + auth: { + secret: env('ADMIN_JWT_SECRET'), + }, + apiToken: { + salt: env('API_TOKEN_SALT'), + }, + transfer: { + token: { + salt: env('TRANSFER_TOKEN_SALT'), + }, + }, + secrets: { + encryptionKey: env('ENCRYPTION_KEY'), + }, + flags: { + nps: env.bool('FLAG_NPS', true), + promoteEE: env.bool('FLAG_PROMOTE_EE', false), + docLinks: env.bool('FLAG_DOC_LINKS', true), + }, +}); diff --git a/apps/strapi/config/api.js b/apps/strapi/config/api.js new file mode 100644 index 000000000..0ac67fa75 --- /dev/null +++ b/apps/strapi/config/api.js @@ -0,0 +1,12 @@ +module.exports = { + rest: { + defaultLimit: 25, + maxLimit: 100, + withCount: true, + strictParams: true, + }, + documents: { + strictParams: true, + strictRelations: true, + }, +}; diff --git a/apps/strapi/config/database.js b/apps/strapi/config/database.js new file mode 100644 index 000000000..d06f714ff --- /dev/null +++ b/apps/strapi/config/database.js @@ -0,0 +1,71 @@ +const path = require('path'); + +// Database selection is driven by environment variables (see .env). +// Default: bundled SQLite. To use an external database set DATABASE_CLIENT to +// "postgres" or "mysql" and provide DATABASE_HOST, DATABASE_NAME, +// DATABASE_USERNAME and DATABASE_PASSWORD (DATABASE_PORT defaults per client). +// Docs: https://docs.strapi.io/cms/configurations/environment +module.exports = ({ env }) => { + const client = env('DATABASE_CLIENT', 'sqlite'); + + const connections = { + sqlite: { + client: 'sqlite', + connection: { + filename: path.join(__dirname, '..', env('DATABASE_FILENAME', '.tmp/data.db')), + }, + useNullAsDefault: true, + }, + postgres: { + client: 'postgres', + connection: { + connectionString: env('DATABASE_URL'), + host: env('DATABASE_HOST', 'localhost'), + port: env.int('DATABASE_PORT', 5432), + database: env('DATABASE_NAME', 'strapi'), + user: env('DATABASE_USERNAME', 'strapi'), + password: env('DATABASE_PASSWORD', 'strapi'), + ssl: env.bool('DATABASE_SSL', false) && { + key: env('DATABASE_SSL_KEY', undefined), + cert: env('DATABASE_SSL_CERT', undefined), + ca: env('DATABASE_SSL_CA', undefined), + capath: env('DATABASE_SSL_CAPATH', undefined), + cipher: env('DATABASE_SSL_CIPHER', undefined), + rejectUnauthorized: env.bool('DATABASE_SSL_REJECT_UNAUTHORIZED', true), + }, + schema: env('DATABASE_SCHEMA', 'public'), + }, + pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) }, + }, + mysql: { + client: 'mysql', + connection: { + host: env('DATABASE_HOST', 'localhost'), + port: env.int('DATABASE_PORT', 3306), + database: env('DATABASE_NAME', 'strapi'), + user: env('DATABASE_USERNAME', 'strapi'), + password: env('DATABASE_PASSWORD', 'strapi'), + ssl: env.bool('DATABASE_SSL', false) && { + key: env('DATABASE_SSL_KEY', undefined), + cert: env('DATABASE_SSL_CERT', undefined), + ca: env('DATABASE_SSL_CA', undefined), + capath: env('DATABASE_SSL_CAPATH', undefined), + cipher: env('DATABASE_SSL_CIPHER', undefined), + rejectUnauthorized: env.bool('DATABASE_SSL_REJECT_UNAUTHORIZED', true), + }, + }, + pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) }, + }, + }; + + if (!connections[client]) { + throw new Error(`Unsupported DATABASE_CLIENT: ${client}. Use "postgres", "mysql", or "sqlite".`); + } + + return { + connection: { + ...connections[client], + acquireConnectionTimeout: env.int('DATABASE_CONNECTION_TIMEOUT', 60000), + }, + }; +}; diff --git a/apps/strapi/config/middlewares.js b/apps/strapi/config/middlewares.js new file mode 100644 index 000000000..6eaf586ac --- /dev/null +++ b/apps/strapi/config/middlewares.js @@ -0,0 +1,12 @@ +module.exports = [ + 'strapi::logger', + 'strapi::errors', + 'strapi::security', + 'strapi::cors', + 'strapi::poweredBy', + 'strapi::query', + 'strapi::body', + 'strapi::session', + 'strapi::favicon', + 'strapi::public', +]; diff --git a/apps/strapi/config/plugins.js b/apps/strapi/config/plugins.js new file mode 100644 index 000000000..7c88d51f5 --- /dev/null +++ b/apps/strapi/config/plugins.js @@ -0,0 +1,41 @@ +const allowedMediaTypes = [ + 'image/*', + 'video/*', + 'audio/*', + 'application/pdf', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.*', + 'text/plain', + 'text/csv', +]; + +const deniedTypes = [ + 'image/svg+xml', + 'application/vnd.microsoft.portable-executable', + 'application/x-msdownload', + 'application/x-msdos-program', + 'application/x-executable', + 'application/x-dosexec', + 'application/x-sh', + 'text/x-shellscript', + 'application/x-mach-binary', +]; + +module.exports = () => ({ + 'users-permissions': { + config: { + jwtManagement: 'refresh', + sessions: { + httpOnly: true, + }, + }, + }, + upload: { + config: { + security: { + allowedTypes: allowedMediaTypes, + deniedTypes, + }, + }, + }, +}); diff --git a/apps/strapi/config/server.js b/apps/strapi/config/server.js new file mode 100644 index 000000000..039daec9c --- /dev/null +++ b/apps/strapi/config/server.js @@ -0,0 +1,10 @@ +module.exports = ({ env }) => ({ + host: env('HOST', '0.0.0.0'), + port: env.int('PORT', 1337), + app: { + keys: env.array('APP_KEYS'), + }, + webhooks: { + populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', false), + }, +}); diff --git a/apps/strapi/docker-compose.yml b/apps/strapi/docker-compose.yml index 9a7f8240e..a4c0386c4 100644 --- a/apps/strapi/docker-compose.yml +++ b/apps/strapi/docker-compose.yml @@ -1,46 +1,21 @@ -# image:https://hub.docker.com/r/strapi/strapi -# github: https://github.com/strapi/strapi-docker - -version: '3.8' - services: strapi: image: ${W9_REPO}:${W9_VERSION} container_name: ${W9_ID} + restart: unless-stopped env_file: - .env ports: - - ${W9_HTTP_PORT_SET}:${W9_HTTP_PORT} + - "${W9_HTTP_PORT_SET}:1337" # Web Console volumes: - - strapi:/srv/app - environment: - DATABASE_CLIENT: mysql - DATABASE_HOST: ${W9_ID}-mysql - DATABASE_PORT: 3306 - DATABASE_NAME: ${W9_ID} - DATABASE_USERNAME: ${W9_ID} - DATABASE_PASSWORD: ${W9_POWER_PASSWORD} + - strapi_data:/opt/app/.tmp + - strapi_uploads:/opt/app/public/uploads healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:1337"] + test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:1337/_health', (res) => process.exit(res.statusCode === 200 || res.statusCode === 204 ? 0 : 1)).on('error', () => process.exit(1))"] interval: 30s timeout: 10s - retries: 3 - links: - - mysql - restart: unless-stopped - - mysql: - image: mysql:$W9_DB_VERSION - container_name: ${W9_ID}-mysql - restart: unless-stopped - command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci - volumes: - - mysql:/var/lib/mysql - environment: - MYSQL_DATABASE: ${W9_ID} - MYSQL_USER: ${W9_ID} - MYSQL_PASSWORD: ${W9_POWER_PASSWORD} - MYSQL_ROOT_PASSWORD: ${W9_POWER_PASSWORD} + retries: 5 + start_period: 120s networks: default: @@ -48,7 +23,5 @@ networks: external: true volumes: - strapi: - driver: local - mysql: - driver: local \ No newline at end of file + strapi_data: + strapi_uploads: diff --git a/apps/strapi/package.json b/apps/strapi/package.json new file mode 100644 index 000000000..d70e6cf1b --- /dev/null +++ b/apps/strapi/package.json @@ -0,0 +1,26 @@ +{ + "name": "strapi", + "version": "0.1.0", + "private": true, + "description": "A Strapi 5 application packaged by Websoft9", + "scripts": { + "build": "strapi build", + "start": "strapi start", + "strapi": "strapi" + }, + "dependencies": { + "@strapi/plugin-users-permissions": "5.54.0", + "@strapi/strapi": "5.54.0", + "better-sqlite3": "12.8.0", + "mysql2": "3.20.0", + "pg": "8.20.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "react-router-dom": "^6.30.3", + "styled-components": "^6.0.0" + }, + "engines": { + "node": ">=20.0.0 <=26.x.x", + "npm": ">=10.0.0" + } +} diff --git a/apps/strapi/public/robots.txt b/apps/strapi/public/robots.txt new file mode 100644 index 000000000..ff5d3164e --- /dev/null +++ b/apps/strapi/public/robots.txt @@ -0,0 +1,3 @@ +# To prevent search engines from seeing the site altogether, uncomment the next two lines: +# User-Agent: * +# Disallow: / diff --git a/apps/runtime/src/9panel/docs/PRD.md b/apps/strapi/public/uploads/.gitkeep similarity index 100% rename from apps/runtime/src/9panel/docs/PRD.md rename to apps/strapi/public/uploads/.gitkeep diff --git a/apps/strapi/src/filelist b/apps/strapi/src/filelist deleted file mode 100644 index 341240aed..000000000 --- a/apps/strapi/src/filelist +++ /dev/null @@ -1,3 +0,0 @@ -docker-compose.yml -script/test.sh -docker \ No newline at end of file diff --git a/apps/strapi/src/index.js b/apps/strapi/src/index.js new file mode 100644 index 000000000..78b9fec42 --- /dev/null +++ b/apps/strapi/src/index.js @@ -0,0 +1,33 @@ +'use strict'; + +module.exports = { + register() {}, + + async bootstrap({ strapi }) { + const email = process.env.W9_LOGIN_USER; + const password = process.env.W9_LOGIN_PASSWORD; + + if (!email || !password) { + return; + } + + const userService = strapi.admin?.services?.user; + if (!userService) { + return; + } + + const adminCount = await userService.count(); + if (adminCount > 0) { + return; + } + + await userService.createFirstAdmin({ + email, + password, + firstname: 'Strapi', + lastname: 'Admin', + }); + + strapi.log.info(`Created initial Strapi administrator: ${email}`); + }, +}; diff --git a/apps/strapi/tests/cases.yml b/apps/strapi/tests/cases.yml new file mode 100644 index 000000000..4051e73a5 --- /dev/null +++ b/apps/strapi/tests/cases.yml @@ -0,0 +1,9 @@ +skip: + - id: web-access + reason: validate the Strapi admin console instead of the root path. + +optional: + - id: admin-console + type: web-access + path: /admin + expect_status: 200 diff --git a/apps/strapi/variables.json b/apps/strapi/variables.json index 4315065f3..f03f5b786 100644 --- a/apps/strapi/variables.json +++ b/apps/strapi/variables.json @@ -2,21 +2,51 @@ "name": "strapi", "trademark": "Strapi", "release": true, + "upstream": { + "image": "https://github.com/strapi/strapi", + "releases": "https://github.com/strapi/strapi/releases", + "docs": [ + "https://docs.strapi.io/cms/installation/docker", + "https://docs.strapi.io/cms/configurations/environment", + "https://docs.strapi.io/cms/migration/v4-to-v5/introduction-and-faq" + ] + }, "edition": [ { "dist": "community", "version": [ - "3.6.8", + "5.54.0", "latest" ] } ], + "access": { + "web": { + "port": 1337, + "path": "/" + }, + "admin": { + "port": 1337, + "path": "/admin" + }, + "api": { + "port": 1337, + "path": "/api" + } + }, "requirements": { - "cpu": "1", - "memory": "1", - "disk": "1" + "cpu": "2", + "memory": "4", + "disk": "5" }, - "upstream": { - "image": "https://hub.docker.com/r/strapi/strapi" + "credentials": {}, + "env": { + "first_startup_only": [ + "W9_LOGIN_USER", + "W9_LOGIN_PASSWORD" + ] + }, + "help": { + "db": "Bundled SQLite database stored in the strapi_data volume. An external PostgreSQL or MySQL database can be selected with DATABASE_CLIENT and the DATABASE_* connection variables in .env; the external database must be empty on first start because existing SQLite data is not migrated automatically. Strapi 5 does not support attaching directly to a Strapi v3 database." } } diff --git a/apps/supabase/.env b/apps/supabase/.env index 60746dd9e..4a0ec45fe 100644 --- a/apps/supabase/.env +++ b/apps/supabase/.env @@ -20,6 +20,7 @@ W9_LOGIN_USER=supabase W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} W9_URL=example.youdomain.com W9_URL_REPLACE=true +W9_ADMIN_PATH=/ W9_NETWORK=websoft9 #### ----------------------------------------------------------------------------------------- #### diff --git a/apps/supabase/variables.json b/apps/supabase/variables.json index 680de32da..18d12fec9 100644 --- a/apps/supabase/variables.json +++ b/apps/supabase/variables.json @@ -2,19 +2,6 @@ "name": "supabase", "trademark": "Supabase", "release": true, - "edition": [ - { - "dist": "community", - "version": [ - "0.8.1" - ] - } - ], - "requirements": { - "cpu": "2", - "memory": "4", - "disk": "40" - }, "upstream": { "image": "https://github.com/supabase/supabase/releases/tag/self-hosted/v0.8.1", "releases": "https://github.com/supabase/supabase/tags", @@ -28,6 +15,30 @@ "https://github.com/supabase/supabase/blob/master/docker/versions.md" ] }, + "edition": [ + { + "dist": "community", + "version": [ + "0.8.1" + ] + } + ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8000, + "path": "/" + }, + "api": { + "port": 8000, + "path": "/" + } + }, + "requirements": { + "cpu": "2", + "memory": "4", + "disk": "40" + }, "env": { "first_startup_only": [ "POSTGRES_PASSWORD" diff --git a/apps/syncthing/.env b/apps/syncthing/.env deleted file mode 100644 index 01bc17aa5..000000000 --- a/apps/syncthing/.env +++ /dev/null @@ -1,15 +0,0 @@ -W9_DIST='community' -W9_VERSION='2.0' -W9_REPO=syncthing/syncthing - -#### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='syncthing' -W9_HTTP_PORT=8384 -W9_HTTP_PORT_SET='8384' -W9_URL='appname.example.com' -W9_NETWORK=websoft9 -#### --------------------------------------------------------------------------------------- #### - -PUID=1000 -PGID=1000 -SYNC_PATH="/data/websoft9/syncthing" diff --git a/apps/syncthing/README.md b/apps/syncthing/README.md deleted file mode 100644 index bafb6a0fc..000000000 --- a/apps/syncthing/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Syncthing on Docker - -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Syncthing: - - - - community: 1.29.4, latest - - -## System Requirements - -The following are the minimal [recommended requirements](https://github.com/syncthing/syncthing/blob/main/README-Docker.md): - -* **RAM**: 2 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 2 GB of free space -* **bandwidth**: more fluent experience over 100M - -## Install - -You can install this Syncthing by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). - -If you want use Syncthing with **Websoft9 Business Support** free, you can [subscribe Syncthing](https://www.websoft9.com/apps) on Cloud platform - -## Documentation - -[Syncthing Administrator Guide](https://support.websoft9.com/docs/syncthing) powered by Websoft9 \ No newline at end of file diff --git a/apps/syncthing/docker-compose.yml b/apps/syncthing/docker-compose.yml deleted file mode 100644 index b3ed613b9..000000000 --- a/apps/syncthing/docker-compose.yml +++ /dev/null @@ -1,28 +0,0 @@ -# image: https://hub.docker.com/r/syncthing/syncthing -# compose: https://github.com/syncthing/syncthing/blob/main/README-Docker.md -# docs: https://docs.syncthing.net/intro/getting-started.html - -version: "3.8" - -services: - syncthing: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID - hostname: syncthing - restart: unless-stopped - env_file: .env - volumes: - - $SYNC_PATH/$W9_ID:/var/syncthing - ports: - - ${W9_HTTP_PORT_SET}:8384 - # - 22000:22000/tcp # TCP file transfers - # - 22000:22000/udp # QUIC file transfers - # - 21027:21027/udp # Receive local discovery broadcasts - -networks: - default: - name: ${W9_NETWORK} - external: true - -volumes: - sync: diff --git a/apps/syncthing/variables.json b/apps/syncthing/variables.json deleted file mode 100644 index 8f5ee9001..000000000 --- a/apps/syncthing/variables.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "syncthing", - "trademark": "Syncthing", - "release": true, - "edition": [ - { - "dist": "community", - "version": [ - "2.0", - "latest" - ] - } - ], - "requirements": { - "cpu": "1", - "memory": "2", - "disk": "2" - }, - "upstream": { - "image": "https://hub.docker.com/r/syncthing/syncthing" - } -} diff --git a/apps/teamcity/.env b/apps/teamcity/.env index b0759fcfa..ef1104df8 100644 --- a/apps/teamcity/.env +++ b/apps/teamcity/.env @@ -1,18 +1,42 @@ -W9_VERSION='2025.07' -W9_DIST='community' W9_REPO=jetbrains/teamcity-server +W9_DIST=community +W9_VERSION=2026.2 + +# Optional password seed: enable only when the package actually controls a DB or built-in login. +# See docs/w9-env-spec.md for when W9_POWER_PASSWORD is appropriate. W9_POWER_PASSWORD='!cQT8pI4iLmcsnlU' + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='teamcity' -W9_HTTP_PORT_SET='8111' + +W9_ID=teamcity W9_HTTP_PORT=8111 -W9_URL='example.youdomain.com' -W9_DB_EXPOSE="mysql" -W9_DB_VERSION="5.7" +W9_HTTP_PORT_SET=8111 + +W9_DB_EXPOSE=mysql +W9_DB_VERSION=8.4 + +# Shown in the Websoft9 console as reference for the TeamCity database setup wizard. +W9_LOGIN_MYSQL_CONNECTION_STRING_PASSWORD="jdbc:mysql://${W9_ID}-mysql:3306/teamcity?user=teamcity&password=${W9_POWER_PASSWORD}" + +W9_URL=example.youdomain.com + +# Random hostname used by the TeamCity server and build agent (no underscores allowed). W9_RCODE='YNUOeQHG8rNmT' + W9_NETWORK=websoft9 -#### --------------------------------------------------------------------------------------- #### -# Below is Teamcity environments: +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# TeamCity image environment variables +# Docs: https://hub.docker.com/r/jetbrains/teamcity-server +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# ============================================================ + +# Used by docker-compose.yml: +TEAMCITY_SERVER_MEM_OPTS="-Xmx2g -XX:ReservedCodeCacheSize=640m" -TEAMCITY_SERVER_MEM_OPTS="-Xmx2g -XX:MaxPermSize=270m -XX:ReservedCodeCacheSize=640m" +# Not used by default; enable only when needed: diff --git a/apps/teamcity/CHANGELOG.md b/apps/teamcity/CHANGELOG.md index 582cf46c5..e0d4d4f7d 100644 --- a/apps/teamcity/CHANGELOG.md +++ b/apps/teamcity/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-21 +- Update TeamCity to 2026.2 and the bundled MySQL to 8.4. +- Add upstream releases and official docs references to `variables.json`. +- Refresh `.env` to the current template layout; drop the obsolete `-XX:MaxPermSize` JVM flag. +- Normalize `docker-compose.yml` references to `${VAR}`, remove image source comments, and annotate the published port. +- Regenerate `README.md` so the advertised version matches `2026.2` and document first-run setup and agent authorization. +- Add `tests/cases.yml` and `tests/check.sh` covering the TeamCity HTTP endpoint. +- Add `W9_LOGIN_MYSQL_CONNECTION_STRING` so the Websoft9 console shows the MySQL connection string during first-run setup. diff --git a/apps/teamcity/README.md b/apps/teamcity/README.md index c5f3817aa..ac9379c2c 100644 --- a/apps/teamcity/README.md +++ b/apps/teamcity/README.md @@ -1,26 +1,106 @@ -# TeamCity on Docker +# TeamCity on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for TeamCity: +## Quick Start +### Deploy Verification - - community: 2025.03.1, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **TeamCity**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### First-run setup -## System Requirements +1. Open the app URL; TeamCity shows a maintenance page titled **Confirming TeamCity first start**. +2. Click **I'm a server administrator, show me the details**, then **Proceed**. +3. On **Setting up database connection**, choose **MySQL** and use the **Download** button to fetch the JDBC driver (the image does not bundle it). To install it manually, put `mysql-connector-j-*.jar` in the `teamcity_data` volume under `lib/jdbc`, then click **Refresh JDBC drivers**. +4. Enter the connection details: host `teamcity-mysql`, port `3306`, database `teamcity`, user `teamcity`, password from `W9_POWER_PASSWORD` in `.env`. +5. Accept the driver license, then create the first administrator account. -The following are the minimal [recommended requirements](https://github.com/JetBrains/teamcity-docker-server): +### Build agents -* **RAM**: 4 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +The bundled `teamcity-agent` connects to the server automatically but must be authorized: after signing in, open **Agents → Unauthorized** and click **Authorize** for the agent. -## Install +### Change Password -You can install this TeamCity by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +TeamCity accounts are managed inside the application. Change the administrator password from the account profile, or reset other users under **Administration → Users**. + -If you want use TeamCity with **Websoft9 Business Support** free, you can [subscribe TeamCity](https://www.websoft9.com/apps) on Cloud platform +## Configuration Reference -## Documentation +Websoft9 packages this app from the official [TeamCity Docker image](https://hub.docker.com/r/jetbrains/teamcity-server) and makes some improvements below. -[TeamCity Administrator Guide](https://support.websoft9.com/docs/teamcity) powered by Websoft9 \ No newline at end of file + + + + +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 2026.2, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 8111 | + + +### Data Directory + + +- `teamcity_data` → `/data/teamcity_server/datadir` +- `teamcity_logs` → `/opt/teamcity/logs` +- `teamcity_temp` → `/opt/teamcity/temp` +- `agent_conf` → `/data/teamcity_agent/conf` +- `/var/run/docker.sock` → `/var/run/docker.sock` +- `agent_work` → `/opt/buildagent/work` +- `agent_temp` → `/opt/buildagent/temp` +- `agent_tools` → `/opt/buildagent/tools` +- `agent_plugins` → `/opt/buildagent/plugins` +- `agent_system` → `/opt/buildagent/system` +- `agent_logs` → `/opt/buildagent/logs` +- `agent_docker` → `/var/lib/docker` +- `mysql_data` → `/var/lib/mysql` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [TeamCity Administrator Guide](https://support.websoft9.com/docs/teamcity) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/jetbrains/teamcity-server) + +- [Releases](https://www.jetbrains.com/teamcity/download/) + +- [Official docs](https://www.jetbrains.com/help/teamcity/teamcity-documentation.html) + +- [GitHub docs](https://github.com/JetBrains/teamcity-docker-server) + +- [GitHub docs](https://github.com/JetBrains/teamcity-docker-samples) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/teamcity/docker-compose.yml b/apps/teamcity/docker-compose.yml index aec625570..5ec9ab265 100644 --- a/apps/teamcity/docker-compose.yml +++ b/apps/teamcity/docker-compose.yml @@ -1,13 +1,6 @@ -# image: https://hub.docker.com/r/jetbrains/teamcity-server -# docs: https://www.jetbrains.com/help/teamcity/teamcity-documentation.html -# https://github.com/JetBrains/teamcity-docker-server -# compose: https://github.com/JetBrains/teamcity-docker-samples/blob/master/compose-ubuntu/docker-compose.yml -# volumes: https://github.com/JetBrains/teamcity-docker-images/blob/master/dockerhub/teamcity-agent/README.md - -version: '3.8' services: teamcity-server: - image: jetbrains/teamcity-server:${W9_VERSION} + image: ${W9_REPO}:${W9_VERSION} container_name: ${W9_ID} restart: unless-stopped hostname: ${W9_RCODE} @@ -15,7 +8,7 @@ services: - teamcity-mysql env_file: .env ports: - - "${W9_HTTP_PORT_SET}:8111" + - "${W9_HTTP_PORT_SET}:8111" # Web Console volumes: - teamcity_data:/data/teamcity_server/datadir - teamcity_logs:/opt/teamcity/logs @@ -27,7 +20,7 @@ services: restart: unless-stopped privileged: true environment: - - SERVER_URL=${W9_RCODE}:8111 # If use W9_ID which include _, agent can't connect Teamcity + - SERVER_URL=${W9_RCODE}:8111 # TeamCity hostname must not contain underscores - AGENT_NAME=${W9_ID}-agent - OWN_PORT=9090 - DOCKER_IN_DOCKER=start @@ -41,9 +34,9 @@ services: - agent_system:/opt/buildagent/system - agent_logs:/opt/buildagent/logs - agent_docker:/var/lib/docker - + teamcity-mysql: - image: mysql:$W9_DB_VERSION + image: mysql:${W9_DB_VERSION} container_name: ${W9_ID}-mysql environment: - MYSQL_ROOT_PASSWORD=${W9_POWER_PASSWORD} diff --git a/apps/teamcity/tests/cases.yml b/apps/teamcity/tests/cases.yml new file mode 100644 index 000000000..f2eb1661a --- /dev/null +++ b/apps/teamcity/tests/cases.yml @@ -0,0 +1,7 @@ +skip: + - id: web-access + +optional: + - id: server-http + type: script + script: check.sh diff --git a/apps/teamcity/tests/check.sh b/apps/teamcity/tests/check.sh new file mode 100644 index 000000000..403e2d2c5 --- /dev/null +++ b/apps/teamcity/tests/check.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +base="${BASE_URL:?BASE_URL is required}" +body="$(mktemp)" +trap 'rm -f "$body"' EXIT + +status="$(curl -sS -o "$body" -w '%{http_code}' "${base}/")" + +# TeamCity serves HTTP 503 from its maintenance page until the first start is +# confirmed and the database connection is configured; it serves 200 afterwards. +case "$status" in + 200 | 503) ;; + *) + echo "unexpected HTTP ${status} from TeamCity" >&2 + exit 1 + ;; +esac + +if ! grep -qi 'TeamCity' "$body"; then + echo "response does not look like a TeamCity page" >&2 + exit 1 +fi + +echo "TeamCity server responded with HTTP ${status}" diff --git a/apps/teamcity/variables.json b/apps/teamcity/variables.json index b79836878..a96b80c52 100644 --- a/apps/teamcity/variables.json +++ b/apps/teamcity/variables.json @@ -2,21 +2,34 @@ "name": "teamcity", "trademark": "TeamCity", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/jetbrains/teamcity-server", + "releases": "https://www.jetbrains.com/teamcity/download/", + "docs": [ + "https://www.jetbrains.com/help/teamcity/teamcity-documentation.html", + "https://github.com/JetBrains/teamcity-docker-server", + "https://github.com/JetBrains/teamcity-docker-samples" + ] + }, "edition": [ { "dist": "community", "version": [ - "2025.07", + "2026.2", "latest" ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8111, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", "disk": "1" - }, - "upstream": { - "image": "https://hub.docker.com/r/jetbrains/teamcity-server" } } diff --git a/apps/teleport/.env b/apps/teleport/.env index b2e4f8eb1..544c97899 100644 --- a/apps/teleport/.env +++ b/apps/teleport/.env @@ -1,18 +1,42 @@ -W9_REPO=public.ecr.aws/gravitational/teleport +W9_REPO=public.ecr.aws/gravitational/teleport-distroless W9_DIST=community -# get version from: https://gallery.ecr.aws/gravitational/teleport -W9_VERSION=14.0 +# get version from: https://gallery.ecr.aws/gravitational/teleport-distroless +W9_VERSION=18.10 + +# Optional password seed: enable only when the package actually controls a DB or built-in login. +# See docs/w9-env-spec.md for when W9_POWER_PASSWORD is appropriate. +W9_POWER_PASSWORD='ETlqpp1VnhwOOz' + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. #### -- Not allowed to edit below environments when recreate app based on existing data -- #### + W9_ID=teleport W9_HTTPS_PORT=3080 W9_HTTPS_PORT_SET=9001 -W9_LOGIN_GET_USER="Run command at Teleport container: [tctl users add admin --roles=editor,auditor,access --logins=root,ubuntu,ec2-user]" +# Built-in administrator, created on first start (see src/config/bootstrap.yaml). +W9_LOGIN_USER=admin +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} -# Must use Domain, IP can not use -W9_URL=example.domain.com +# Teleport requires a domain name; an IP address cannot be used. +W9_URL=example.yourdomain.com +W9_URL_REPLACE=true W9_NETWORK=websoft9 -#### --------------------------------------------------------------------------------------- #### -## Environment of Teleport, need research \ No newline at end of file +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Teleport image environment variables +# Docs: https://goteleport.com/docs/installation/single-machine/docker/ +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +# Teleport 18 refuses to disable the second factor unless this escape hatch is set. +TELEPORT_ALLOW_NO_SECOND_FACTOR=true + +# Not used by default; enable only when needed: diff --git a/apps/teleport/CHANGELOG.md b/apps/teleport/CHANGELOG.md index 582cf46c5..7165e0c74 100644 --- a/apps/teleport/CHANGELOG.md +++ b/apps/teleport/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-21 +- Bump Teleport from `14.0` to `18.10` and switch the image to the production `public.ecr.aws/gravitational/teleport-distroless`; the old `public.ecr.aws/gravitational/teleport` image stops at `14.4.x`. +- Regenerate `src/config/teleport.yaml` for the v18 schema. Teleport 18 rejects `second_factor: off` unless `TELEPORT_ALLOW_NO_SECOND_FACTOR=true`; the package sets that escape hatch and disables MFA for local users. +- Auto-create the first administrator and a full-access `admin` role on first start from `src/config/bootstrap.yaml` (`--bootstrap`). The admin password is `W9_LOGIN_PASSWORD` in `.env`, stored as a bcrypt hash in the bootstrap file. +- Add `restart: unless-stopped` and a `tctl status` healthcheck; remove the deprecated `version:` key and the unused `teleport_config` volume. +- Add `upstream` metadata and the web access entry to `variables.json`. +- Add `tests/cases.yml` with an HTTPS `/webapi/ping` and admin-login check. +- Note: existing 14.x cluster data cannot be upgraded directly to 18.x; upstream requires one major version at a time (14→15→16→17→18). Fresh deployments are unaffected. diff --git a/apps/teleport/Notes.md b/apps/teleport/Notes.md index 867cfb887..f70efcddd 100644 --- a/apps/teleport/Notes.md +++ b/apps/teleport/Notes.md @@ -1,5 +1,11 @@ # Teleport -- Need create teleport.yaml before create container: https://goteleport.com/docs/installation/#running-teleport-on-docker -- Need HTTPS access -- Need set configure item [proxy_service - public_addr:url:443], 443 is need otherwise url will add 3080 \ No newline at end of file +- Teleport Community Edition 18 runs from the production `public.ecr.aws/gravitational/teleport-distroless` image (no shell). Use `docker exec /usr/local/bin/tctl ...` for in-container commands. +- On first start the `teleport-init` one-shot service renders `src/config/bootstrap.yaml` from `src/config/bootstrap.yaml.tmpl`, hashing the current `W9_LOGIN_PASSWORD` value from `.env`, then Teleport creates the `admin` user and full-access `admin` role with `--bootstrap`. +- MFA is disabled for local users with `authentication.second_factor: off` plus `TELEPORT_ALLOW_NO_SECOND_FACTOR=true`. Remove both to require MFA. +- To change the password: change it in the Teleport Web UI (kept across restarts), or update `W9_LOGIN_PASSWORD` before the first start and redeploy so `teleport-init` regenerates the bootstrap hash. +- The config file `src/config/teleport.yaml` is rendered by `teleport-init` from the current `W9_URL`. `teleport.nodename`, `auth_service.cluster_name`, and `proxy_service.public_addr` should not be edited by hand in the generated file. +- The Web UI and API are HTTPS-only on port `3080` with a self-signed certificate. Check them with: + `curl -k https://:/webapi/ping` +- Regenerate a starter config with: + `docker run --rm --entrypoint /usr/local/bin/teleport public.ecr.aws/gravitational/teleport-distroless:18.10 configure --roles=proxy,auth,ssh` diff --git a/apps/teleport/README.md b/apps/teleport/README.md index 5ea84ea4f..92e784fd5 100644 --- a/apps/teleport/README.md +++ b/apps/teleport/README.md @@ -1,26 +1,81 @@ -# Teleport on Docker +# Teleport on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Teleport: +## Quick Start +### Deploy Verification - - community: 14.0, 13.0 +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Teleport**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Set `W9_URL` to the domain that resolves to this host, then rebuild or restart the app so the init service regenerates `src/config/teleport.yaml` before Teleport starts. +2. Open `https://:/` and sign in with the username and password from the **Access** tab (`W9_LOGIN_USER` / `W9_LOGIN_PASSWORD` in `.env`). MFA is disabled; enable it later from the Web UI if needed. -The following are the minimal [recommended requirements](https://gallery.ecr.aws/gravitational/teleport): +### Change Password -* **RAM**: 2 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +1. Change it in the Teleport Web UI; the new password is kept across restarts. +2. To change the initial password before first startup, update `W9_LOGIN_PASSWORD` in `.env` and redeploy. The init service regenerates `src/config/bootstrap.yaml` from the current value. + -## Install +## Configuration Reference -You can install this Teleport by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Teleport Docker image](https://gallery.ecr.aws/gravitational/teleport-distroless) and makes some improvements below. -If you want use Teleport with **Websoft9 Business Support** free, you can [subscribe Teleport](https://www.websoft9.com/apps) on Cloud platform + -## Documentation + -[Teleport Administrator Guide](https://support.websoft9.com/docs/teleport) powered by Websoft9 \ No newline at end of file +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 18.10, 18. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Teleport Proxy HTTPS (Web UI + API) | 3080 | + + +### Data Directory + + +Data is persisted in the `teleport_data` volume, mounted at `/var/lib/teleport`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration is overridden by mounting `./src/config` to `/etc/teleport`. + + +## References + +- [Teleport Administrator Guide](https://support.websoft9.com/docs/teleport) by Websoft9 + +- [Docker Hub image](https://gallery.ecr.aws/gravitational/teleport-distroless) + +- [Releases](https://github.com/gravitational/teleport/releases) + +- [Official docs](https://goteleport.com/docs/installation/single-machine/docker/) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/teleport/docker-compose.yml b/apps/teleport/docker-compose.yml index 80400213d..5c71b5ec9 100644 --- a/apps/teleport/docker-compose.yml +++ b/apps/teleport/docker-compose.yml @@ -1,23 +1,49 @@ -# image: https://gallery.ecr.aws/gravitational/teleport -# docs: https://goteleport.com/docs/management/guides/docker/ -# docs: https://goteleport.com/docs/try-out-teleport/docker-compose/ - +services: -version: '3.8' + teleport-init: + image: httpd:2.4-alpine + container_name: ${W9_ID}-init + restart: "no" + env_file: .env + entrypoint: + - /bin/sh + - -ec + - | + password_hash="$$(htpasswd -nbBC 10 "" "$${W9_LOGIN_PASSWORD}" | sed 's/^://')" + password_hash_b64="$$(printf '%s' "$${password_hash}" | base64 | tr -d '\n')" + sed -e "s|\$${W9_URL}|$${W9_URL}|g" /templates/teleport.yaml.tmpl > /work/teleport.yaml + sed \ + -e "s|\$${W9_LOGIN_USER}|$${W9_LOGIN_USER}|g" \ + -e "s|\$${W9_LOGIN_PASSWORD_HASH}|$${password_hash_b64}|g" \ + /templates/bootstrap.yaml.tmpl > /work/bootstrap.yaml + volumes: + - ./src/config:/work + - ./src/config/teleport.yaml.tmpl:/templates/teleport.yaml.tmpl:ro + - ./src/config/bootstrap.yaml.tmpl:/templates/bootstrap.yaml.tmpl:ro -services: teleport: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} hostname: localhost + restart: unless-stopped + depends_on: + teleport-init: + condition: service_completed_successfully + entrypoint: ["/usr/bin/dumb-init", "/usr/local/bin/teleport", "start", "-c", "/etc/teleport/teleport.yaml", "--bootstrap=/etc/teleport/bootstrap.yaml"] + env_file: .env ports: - - ${W9_HTTPS_PORT_SET}:3080 # HTTPS for API - #- 3025:3025 # SSH port - #- 3023:3023 # Node Tunneling + - "${W9_HTTPS_PORT_SET}:3080" # Teleport Proxy HTTPS (Web UI + API) + # - "3025:3025" # Auth Service (internal) + # - "3023:3023" # Node Tunneling (internal) volumes: - ./src/config:/etc/teleport - teleport_data:/var/lib/teleport - env_file: .env + healthcheck: + test: ["CMD", "/usr/local/bin/tctl", "status"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s networks: default: @@ -25,5 +51,4 @@ networks: external: true volumes: - teleport_config: - teleport_data: \ No newline at end of file + teleport_data: diff --git a/apps/teleport/src/config/bootstrap.yaml b/apps/teleport/src/config/bootstrap.yaml new file mode 100644 index 000000000..593b9c6bd --- /dev/null +++ b/apps/teleport/src/config/bootstrap.yaml @@ -0,0 +1,4 @@ +# Generated by teleport-init from bootstrap.yaml.tmpl. +# Keep this file present because docker-compose.yml mounts ./src/config into the +# container, but do not edit it directly. Any changes will be overwritten by the +# init service before Teleport starts. diff --git a/apps/teleport/src/config/bootstrap.yaml.tmpl b/apps/teleport/src/config/bootstrap.yaml.tmpl new file mode 100644 index 000000000..c9a9ada2c --- /dev/null +++ b/apps/teleport/src/config/bootstrap.yaml.tmpl @@ -0,0 +1,32 @@ +# +# Bootstrap resources, applied once on the cluster's first start. +# +# The init service renders this template into /etc/teleport/bootstrap.yaml using +# the current W9_LOGIN_PASSWORD value so App Store password rewrites take effect +# before Teleport initializes the cluster. +# +# Applied with --bootstrap, so it is ignored once the cluster is initialized. +# +kind: role +version: v7 +metadata: + name: admin +spec: + options: + max_session_ttl: 12h + allow: + logins: ["root", "ubuntu", "ec2-user"] + node_labels: + "*": "*" + rules: + - resources: ["*"] + verbs: ["*"] +--- +kind: user +version: v2 +metadata: + name: ${W9_LOGIN_USER} +spec: + roles: ["admin"] + local_auth: + password_hash: ${W9_LOGIN_PASSWORD_HASH} diff --git a/apps/teleport/src/config/teleport.yaml b/apps/teleport/src/config/teleport.yaml index 9c1a6333c..ff0a0ceae 100644 --- a/apps/teleport/src/config/teleport.yaml +++ b/apps/teleport/src/config/teleport.yaml @@ -1,38 +1,4 @@ -# -# A Sample Teleport configuration file. -# -# Things to update: -# 1. license.pem: Retrieve a license from your Teleport account https://teleport.sh -# if you are an Enterprise customer. -# -version: v3 -teleport: - nodename: localhost - data_dir: /var/lib/teleport - log: - output: stderr - severity: INFO - format: - output: text - ca_pin: "" - diag_addr: "" -auth_service: - enabled: "yes" - listen_addr: 0.0.0.0:3025 - proxy_listener_mode: multiplex - authentication: - type: local - second_factor: off -ssh_service: - enabled: "yes" - commands: - - name: hostname - command: [hostname] - period: 1m0s -proxy_service: - enabled: "yes" - https_keypairs: [] - https_keypairs_reload_interval: 0s - acme: {} - public_addr: - - 'example.yourdomain.com:443' \ No newline at end of file +# Generated by teleport-init from teleport.yaml.tmpl. +# Keep this file present because docker-compose.yml mounts ./src/config into the +# container, but do not edit it directly. Any changes will be overwritten by the +# init service before Teleport starts. diff --git a/apps/teleport/src/config/teleport.yaml.tmpl b/apps/teleport/src/config/teleport.yaml.tmpl new file mode 100644 index 000000000..552a8307f --- /dev/null +++ b/apps/teleport/src/config/teleport.yaml.tmpl @@ -0,0 +1,49 @@ +# +# Teleport configuration file (v18 schema). +# +# Things to update: +# 1. teleport.nodename / auth_service.cluster_name / proxy_service.public_addr: +# the init service rewrites them from ${W9_URL} before Teleport starts. +# 2. proxy_service.public_addr: keep the ":443" port so generated URLs omit the +# internal 3080 port. +# 3. license.pem: retrieve a license from https://teleport.sh only if you are an +# Enterprise customer. +# +# MFA is disabled for local users (second_factor: off). Teleport 18 refuses this +# unless the container runs with TELEPORT_ALLOW_NO_SECOND_FACTOR=true, which the +# package sets in .env. Remove both to require MFA. +# +# Regenerate a starter file with: +# docker run --rm --entrypoint /usr/local/bin/teleport \ +# public.ecr.aws/gravitational/teleport-distroless:18.10 \ +# configure --roles=proxy,auth,ssh +# +version: v3 +teleport: + nodename: ${W9_URL} + data_dir: /var/lib/teleport + log: + output: stderr + severity: INFO + format: + output: text + ca_pin: "" + diag_addr: "" +auth_service: + enabled: "yes" + listen_addr: 0.0.0.0:3025 + cluster_name: ${W9_URL} + proxy_listener_mode: multiplex + authentication: + type: local + second_factor: off +ssh_service: + enabled: "yes" +proxy_service: + enabled: "yes" + web_listen_addr: 0.0.0.0:3080 + https_keypairs: [] + https_keypairs_reload_interval: 0s + acme: {} + public_addr: + - '${W9_URL}:443' diff --git a/apps/teleport/tests/cases.yml b/apps/teleport/tests/cases.yml new file mode 100644 index 000000000..ae859c5d0 --- /dev/null +++ b/apps/teleport/tests/cases.yml @@ -0,0 +1,10 @@ +# Teleport serves its Web UI and API over HTTPS with a self-signed certificate, +# so the adaptive HTTP web-access check does not apply. The custom script waits +# for the web API over HTTPS instead. +skip: + - id: web-access + +optional: + - id: webapi-https + type: script + script: check.sh diff --git a/apps/teleport/tests/check.sh b/apps/teleport/tests/check.sh new file mode 100755 index 000000000..bca5d48d5 --- /dev/null +++ b/apps/teleport/tests/check.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -uo pipefail + +port="${W9_HTTPS_PORT_SET:-9001}" +base="${BASE_URL:-https://localhost:${port}}" +user="${W9_LOGIN_USER:-admin}" +password="${W9_LOGIN_PASSWORD:-}" +deadline=$((SECONDS + 300)) +code="000" + +while [ "$SECONDS" -lt "$deadline" ]; do + code=$(curl -k -s -o /dev/null -w "%{http_code}" --max-time 15 "${base}/webapi/ping" || true) + if [ "$code" = "200" ]; then + break + fi + sleep 5 +done + +if [ "$code" != "200" ]; then + echo "teleport webapi ${base}/webapi/ping -> ${code} (timeout)" + exit 1 +fi +echo "teleport webapi ${base}/webapi/ping -> ${code}" + +if [ -z "$password" ]; then + echo "W9_LOGIN_PASSWORD is empty; skipping admin login check" + exit 0 +fi + +login_code=$(curl -k -s -o /dev/null -w "%{http_code}" --max-time 15 \ + -X POST "${base}/webapi/sessions/web" \ + -H 'Content-Type: application/json' \ + --data "{\"user\":\"${user}\",\"pass\":\"${password}\",\"second_factor_token\":\"\"}" || true) + +if [ "$login_code" != "200" ]; then + echo "teleport admin login -> ${login_code}" + exit 1 +fi +echo "teleport admin login -> ${login_code}" diff --git a/apps/teleport/variables.json b/apps/teleport/variables.json index 403108a44..972cca9f5 100644 --- a/apps/teleport/variables.json +++ b/apps/teleport/variables.json @@ -2,15 +2,27 @@ "name": "teleport", "trademark": "Teleport", "release": true, + "upstream": { + "image": "https://gallery.ecr.aws/gravitational/teleport-distroless", + "releases": "https://github.com/gravitational/teleport/releases", + "docs": [ + "https://goteleport.com/docs/installation/single-machine/docker/" + ] + }, "edition": [ { "dist": "community", "version": [ - "14.0", - "13.0" + "18.10" ] } ], + "access": { + "web": { + "port": 3080, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", diff --git a/apps/tensorflow/.env b/apps/tensorflow/.env index 29e117fd1..43728fbe7 100644 --- a/apps/tensorflow/.env +++ b/apps/tensorflow/.env @@ -1,12 +1,42 @@ -W9_VERSION='2.19.0-jupyter' -W9_DIST='community' W9_REPO=tensorflow/tensorflow +W9_DIST=community +W9_VERSION=2.20.0-jupyter + +# Optional password seed: Tensorflow's Jupyter server can use a fixed token. +# The access UI shows it through W9_LOGIN_PASSWORD. +W9_POWER_PASSWORD='tf9K2mPq4vX1nR7s' + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='tensorflow' +W9_ID=tensorflow W9_HTTP_PORT=8888 -W9_HTTP_PORT_SET='8888' -W9_GUI_PORT_SET='6006' -W9_URL='appname.example.com' +W9_HTTP_PORT_SET=8888 +W9_GUI_PORT_SET=6006 +W9_LOGIN_USER=token +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} +W9_URL=appname.example.com +W9_ADMIN_PATH="/lab" W9_NETWORK=websoft9 -#### --------------------------------------------------------------------------------------- #### + +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Tensorflow image environment variables +# Docs: https://hub.docker.com/r/tensorflow/tensorflow +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +JUPYTER_TOKEN=${W9_LOGIN_PASSWORD} +TENSORBOARD_AUTOSTART=true +TENSORBOARD_LOGDIR=/tf/notebooks/logs +# Seed a small `demo` run so the TensorBoard dashboard is not empty on first start. +TENSORBOARD_DEMO=true + +# Not used by default; enable only when needed: +# JUPYTER_PORT=8888 +# JUPYTER_ENABLE_LAB=yes diff --git a/apps/tensorflow/CHANGELOG.md b/apps/tensorflow/CHANGELOG.md index 582cf46c5..7b0a1aa08 100644 --- a/apps/tensorflow/CHANGELOG.md +++ b/apps/tensorflow/CHANGELOG.md @@ -1,5 +1,17 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-21 +- Updated Tensorflow community package from `2.19.0-jupyter` to `2.20.0-jupyter`. +- Normalized `.env` and `docker-compose.yml` to current repository policy for variable formatting and port comments. +- Added app-specific functional test coverage metadata for the Jupyter entry path. + +## 2026-09-23 + +- Filled the missing `variables.json` fields: `upstream.docs`, `access`, `credentials`, `env`, and `help`. +- Rewrote `README.md` to the current repository structure and removed the stale `Notes.md`. +- Replace the random startup token with a fixed token wired from `W9_LOGIN_PASSWORD` so the Access tab can show a stable login secret. +- Start TensorBoard automatically at container launch and move the default Jupyter working directory to `/tf/notebooks` so notebooks and logs land on the persisted volume. +- Seed a small `demo` TensorBoard run on first start (disable with `TENSORBOARD_DEMO=false`) and ship `tensorboard_demo.py` so the dashboard shows charts immediately. +- Fix the `PS1: unbound variable` startup crash caused by sourcing `/etc/bash.bashrc` under `set -u`. +- Declare the `admin` access surface (`/lab` on 8888) alongside `web` and `metrics`. diff --git a/apps/tensorflow/Notes.md b/apps/tensorflow/Notes.md deleted file mode 100644 index 3f6d7b339..000000000 --- a/apps/tensorflow/Notes.md +++ /dev/null @@ -1,3 +0,0 @@ -# Tensorflow - -https://hub.docker.com/r/tensorflow/tensorflow diff --git a/apps/tensorflow/README.md b/apps/tensorflow/README.md index e6b01a697..2d5f04853 100644 --- a/apps/tensorflow/README.md +++ b/apps/tensorflow/README.md @@ -1,26 +1,100 @@ -# Tensorflow on Docker +# Tensorflow on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Tensorflow: +## Quick Start +### Deploy Verification - - community: 2.18.0-jupyter, latest-jupyter +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Tensorflow**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Open `http://:${W9_HTTP_PORT_SET}/lab` from the **Access** tab. +2. Paste the token from the **Access** tab (`W9_LOGIN_PASSWORD`) into the JupyterLab login page. `W9_LOGIN_USER` is a display label only. +3. Open TensorBoard at `http://:${W9_GUI_PORT_SET}`. It starts automatically and reads logs from `/tf/notebooks/logs`. +4. On first start the package seeds a small `demo` run, so TensorBoard shows an `accuracy`/`loss` chart immediately even before you run any training. +5. For real data, open the seeded `tensorboard_demo.py` in `/tf/notebooks`, run it, then refresh TensorBoard. To also embed it in a notebook, use `%load_ext tensorboard` and `%tensorboard --logdir /tf/notebooks/logs --bind_all`. -The following are the minimal [recommended requirements](https://github.com/onlyoffice/docker#recommended-system-requirements): +### Change Password -* **RAM**: 1 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +1. In the [Websoft9](https://www.websoft9.com) console, open the app's **Compose** tab. +2. Update `W9_LOGIN_PASSWORD` (or `W9_POWER_PASSWORD`) in `.env` and save. +3. Recreate the app so JupyterLab restarts with the new token. + -## Install +## Configuration Reference -You can install this Tensorflow by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Tensorflow Docker image](https://hub.docker.com/r/tensorflow/tensorflow) and makes some improvements below. -If you want use Tensorflow with **Websoft9 Business Support** free, you can [subscribe Tensorflow](https://www.websoft9.com/apps) on Cloud platform + +- The package uses the upstream Jupyter-enabled Tensorflow image and persists notebooks under `/tf/notebooks`. +- JupyterLab runs from `/tf/notebooks`, so new notebooks and TensorBoard logs stay on the persisted volume by default. +- The login token is fixed through `W9_LOGIN_PASSWORD`, which makes the Access tab usable without reading container logs. +- TensorBoard is exposed on `${W9_GUI_PORT_SET}` and starts automatically with logdir `/tf/notebooks/logs` unless you override `TENSORBOARD_LOGDIR`. +- On first start the package seeds a `demo` run (scalars, histogram, text) so TensorBoard is not empty. Set `TENSORBOARD_DEMO=false` in `.env` and recreate to skip it. +- A sample training script is copied to `/tf/notebooks/tensorboard_demo.py` so you can generate real charts with the TensorBoard Keras callback. + -## Documentation +Apps run as containers; rebuild after any configuration change. -[Tensorflow Administrator Guide](https://support.websoft9.com/docs/tensorflow) powered by Websoft9 \ No newline at end of file +### Version Support + +Supported versions: 2.20.0-jupyter, latest-jupyter. + + +### Ports + +| Purpose | Port | +| --- | --- | +| JupyterLab | 8888 | +| TensorBoard | 6006 | + + +### Data Directory + + +Data is persisted in the `tensorflow` volume, mounted at `/tf/notebooks`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +- `./src/entrypoint.sh` → `/usr/local/bin/w9-tensorflow-entrypoint.sh` +- `./src/seed_demo.py` → `/opt/w9/seed_demo.py` +- `./src/tensorboard_demo.py` → `/opt/w9/tensorboard_demo.py` + + + +## References + +- [Tensorflow Administrator Guide](https://support.websoft9.com/docs/tensorflow) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/tensorflow/tensorflow) + +- [Official docs](https://www.tensorflow.org/install/docker) + +- [GitHub docs](https://github.com/tensorflow/tensorflow) + + + +## Troubleshooting + +**Notebook page asks for a token?** +- Use the token shown in the **Access** tab (`W9_LOGIN_PASSWORD`). If you changed it in `.env`, recreate the app so JupyterLab picks up the new value. + +**TensorBoard is blank?** +- `No dashboards are active for the current data set` means TensorBoard is up but no event files exist under `/tf/notebooks/logs`. Seed data is written to `/tf/notebooks/logs/demo` unless `TENSORBOARD_DEMO=false`; run `tensorboard_demo.py` in `/tf/notebooks`, or update `TENSORBOARD_LOGDIR` and recreate the app. + +**How do I embed TensorBoard inside a notebook?** +- Load the extension with `%load_ext tensorboard`, then run `%tensorboard --logdir /tf/notebooks/logs --bind_all`. `--bind_all` is required inside Docker so the embedded view can reach the server. + +**Container exits unexpectedly?** +- Check `docker compose logs ${W9_ID}` for the Jupyter startup output. + diff --git a/apps/tensorflow/docker-compose.yml b/apps/tensorflow/docker-compose.yml index 70d4086bf..1eeb54f3e 100644 --- a/apps/tensorflow/docker-compose.yml +++ b/apps/tensorflow/docker-compose.yml @@ -1,20 +1,23 @@ -# image: https://hub.docker.com/r/tensorflow/tensorflow -# docs: https://github.com/tensorflow/tensorflow - -version: '3.8' - services: tensorflow: image: ${W9_REPO}:${W9_VERSION} container_name: ${W9_ID} restart: unless-stopped + command: ["bash", "/usr/local/bin/w9-tensorflow-entrypoint.sh"] ports: - # tensorboard port is 6006, but you need to start it on container by yourself - - ${W9_HTTP_PORT_SET}:8888 - - ${W9_GUI_PORT_SET}:6006 + - "${W9_HTTP_PORT_SET}:8888" # JupyterLab + - "${W9_GUI_PORT_SET}:6006" # TensorBoard env_file: .env + environment: + JUPYTER_TOKEN: ${JUPYTER_TOKEN} + TENSORBOARD_AUTOSTART: ${TENSORBOARD_AUTOSTART} + TENSORBOARD_LOGDIR: ${TENSORBOARD_LOGDIR} + TENSORBOARD_DEMO: ${TENSORBOARD_DEMO} volumes: - tensorflow:/tf/notebooks + - ./src/entrypoint.sh:/usr/local/bin/w9-tensorflow-entrypoint.sh:ro + - ./src/seed_demo.py:/opt/w9/seed_demo.py:ro + - ./src/tensorboard_demo.py:/opt/w9/tensorboard_demo.py:ro networks: default: diff --git a/apps/tensorflow/src/entrypoint.sh b/apps/tensorflow/src/entrypoint.sh new file mode 100644 index 000000000..40fb86609 --- /dev/null +++ b/apps/tensorflow/src/entrypoint.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +logdir="${TENSORBOARD_LOGDIR:-/tf/notebooks/logs}" +mkdir -p "${logdir}" + +if [[ "${TENSORBOARD_DEMO:-true}" == "true" && -f /opt/w9/seed_demo.py && ! -f "${logdir}/demo/.seeded" ]]; then + echo "Seeding TensorBoard demo data into ${logdir}/demo" + if TENSORBOARD_DEMO_LOGDIR="${logdir}/demo" python /opt/w9/seed_demo.py; then + touch "${logdir}/demo/.seeded" + else + echo "TensorBoard demo seeding failed; continuing without demo data" + fi +fi + +if [[ -f /opt/w9/tensorboard_demo.py && ! -f /tf/notebooks/tensorboard_demo.py ]]; then + cp /opt/w9/tensorboard_demo.py /tf/notebooks/tensorboard_demo.py +fi + +if [[ "${TENSORBOARD_AUTOSTART:-true}" == "true" ]]; then + echo "Starting TensorBoard on 0.0.0.0:6006 with logdir ${logdir}" + tensorboard --logdir "${logdir}" --bind_all --port 6006 & +fi + +set +u +source /etc/bash.bashrc +set -u + +cmd=( + jupyter notebook + --notebook-dir=/tf/notebooks + --ip 0.0.0.0 + --no-browser + --allow-root + --ServerApp.allow_password_change=False +) + +if [[ -n "${JUPYTER_TOKEN:-}" ]]; then + cmd+=(--ServerApp.token="${JUPYTER_TOKEN}") +fi + +exec "${cmd[@]}" diff --git a/apps/tensorflow/src/seed_demo.py b/apps/tensorflow/src/seed_demo.py new file mode 100644 index 000000000..0bdfc21c7 --- /dev/null +++ b/apps/tensorflow/src/seed_demo.py @@ -0,0 +1,34 @@ +"""Seed a small TensorBoard demo run so the dashboard is not empty on first start. + +This is onboarding data, not real training output. It is written under the +`demo` subdirectory of the TensorBoard logdir and can be disabled by setting +TENSORBOARD_DEMO=false. +""" + +import math +import os + +import tensorflow as tf + +logdir = os.environ.get("TENSORBOARD_DEMO_LOGDIR", "/tf/notebooks/logs/demo") +os.makedirs(logdir, exist_ok=True) + +writer = tf.summary.create_file_writer(logdir) +with writer.as_default(): + for step in range(1, 51): + accuracy = 0.50 + 0.009 * step + loss = 1.30 * math.exp(-0.05 * step) + tf.summary.scalar("demo/accuracy", accuracy, step=step) + tf.summary.scalar("demo/loss", loss, step=step) + tf.summary.scalar("demo/learning_rate", 0.01 * (0.95 ** step), step=step) + tf.summary.histogram("demo/weights", tf.random.normal([1000]), step=1) + tf.summary.text( + "demo/about", + "Demo data seeded by the Websoft9 TensorFlow package. " + "Run tensorboard_demo.py for a real training run, or set " + "TENSORBOARD_DEMO=false to disable this sample.", + step=1, + ) +writer.flush() + +print(f"Seeded TensorBoard demo data in {logdir}") diff --git a/apps/tensorflow/src/tensorboard_demo.py b/apps/tensorflow/src/tensorboard_demo.py new file mode 100644 index 000000000..c2be13eaa --- /dev/null +++ b/apps/tensorflow/src/tensorboard_demo.py @@ -0,0 +1,46 @@ +"""Minimal TensorBoard example for this package. + +Run this file inside JupyterLab (or `python /tf/notebooks/tensorboard_demo.py`), +then refresh TensorBoard at http://:6006. + +See https://tensorflow.google.cn/tensorboard/tensorboard_in_notebooks for the +notebook-based workflow, including the `%tensorboard --logdir logs --bind_all` +magic. +""" + +import datetime +import os + +import tensorflow as tf + +logdir = os.path.join( + "/tf/notebooks/logs", datetime.datetime.now().strftime("%Y%m%d-%H%M%S") +) +os.makedirs(logdir, exist_ok=True) + +(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() +x_train, x_test = x_train / 255.0, x_test / 255.0 + +model = tf.keras.models.Sequential( + [ + tf.keras.layers.Flatten(input_shape=(28, 28)), + tf.keras.layers.Dense(128, activation="relu"), + tf.keras.layers.Dropout(0.2), + tf.keras.layers.Dense(10, activation="softmax"), + ] +) +model.compile( + optimizer="adam", + loss="sparse_categorical_crossentropy", + metrics=["accuracy"], +) + +model.fit( + x_train, + y_train, + epochs=3, + validation_data=(x_test, y_test), + callbacks=[tf.keras.callbacks.TensorBoard(log_dir=logdir, histogram_freq=1)], +) + +print(f"Training logs written to {logdir}; refresh TensorBoard to view them.") diff --git a/apps/tensorflow/tests/cases.yml b/apps/tensorflow/tests/cases.yml new file mode 100644 index 000000000..624f8464a --- /dev/null +++ b/apps/tensorflow/tests/cases.yml @@ -0,0 +1,8 @@ +optional: + - id: notebook-ui + type: web-access + path: /lab + expect_status: 200 + - id: tensorboard-ui + type: script + script: check.sh diff --git a/apps/tensorflow/tests/check.sh b/apps/tensorflow/tests/check.sh new file mode 100644 index 000000000..c7eee93b0 --- /dev/null +++ b/apps/tensorflow/tests/check.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -uo pipefail + +port="${W9_GUI_PORT_SET:-6006}" +code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 "http://localhost:${port}/" || true) + +case "$code" in + 200|302) + echo "tensorboard ui http://localhost:${port}/ -> ${code}" + exit 0 + ;; +esac + +echo "tensorboard ui http://localhost:${port}/ -> ${code}" +exit 1 diff --git a/apps/tensorflow/variables.json b/apps/tensorflow/variables.json index 8d1fd4af3..c83e0863b 100644 --- a/apps/tensorflow/variables.json +++ b/apps/tensorflow/variables.json @@ -2,21 +2,47 @@ "name": "tensorflow", "trademark": "Tensorflow", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/tensorflow/tensorflow", + "docs": [ + "https://www.tensorflow.org/install/docker", + "https://github.com/tensorflow/tensorflow" + ] + }, "edition": [ { "dist": "community", "version": [ - "2.19.0-jupyter", + "2.20.0-jupyter", "latest-jupyter" ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8888, + "path": "/" + }, + "admin": { + "port": 8888, + "path": "/lab" + }, + "metrics": { + "port": 6006, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "1" }, - "upstream": { - "image": "https://hub.docker.com/r/tensorflow/tensorflow" + "credentials": {}, + "env": { + "first_startup_only": [] + }, + "help": { + "db": "No bundled database. JupyterLab uses the fixed token from W9_LOGIN_PASSWORD and TensorBoard starts automatically with logs under /tf/notebooks/logs." } } diff --git a/apps/theia/src/filelist b/apps/theia/src/filelist deleted file mode 100644 index 341240aed..000000000 --- a/apps/theia/src/filelist +++ /dev/null @@ -1,3 +0,0 @@ -docker-compose.yml -script/test.sh -docker \ No newline at end of file diff --git a/apps/thingsboard/.env b/apps/thingsboard/.env index 7736adf6d..290743e63 100644 --- a/apps/thingsboard/.env +++ b/apps/thingsboard/.env @@ -1,19 +1,39 @@ -W9_DIST='community' W9_REPO=thingsboard/tb-node -W9_VERSION='4.2.0' +W9_DIST=community +W9_VERSION=4.3.1 + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='thingsboard' + +W9_ID=thingsboard W9_HTTP_PORT=8080 -W9_HTTP_PORT_SET='9009' -W9_URL='appname.example.com' +W9_HTTP_PORT_SET=9009 + +W9_DB_EXPOSE=postgresql +W9_DB_VERSION=18 + W9_LOGIN_USER=sysadmin@thingsboard.org W9_LOGIN_PASSWORD=sysadmin + +W9_URL=appname.example.com + W9_NETWORK=websoft9 -W9_DB_EXPOSE='postgresql' -#### --------------------------------------------------------------------------------------- #### + +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# ThingsBoard image environment variables +# Docs: https://thingsboard.io/docs/installation/docker/ +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# ============================================================ + +# Used by docker-compose.yml: POSTGRESQL_USER=postgres POSTGRESQL_PASSWORD=postgres POSTGRESQL_DATABASE_NAME=thingsboard POSTGRESQL_HOST=${W9_ID}-postgres POSTGRESQL_PORT=5432 + +# Not used by default; enable only when needed: diff --git a/apps/thingsboard/CHANGELOG.md b/apps/thingsboard/CHANGELOG.md index 582cf46c5..2696e2f5f 100644 --- a/apps/thingsboard/CHANGELOG.md +++ b/apps/thingsboard/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG -## Release +## 2026-09-21 ### Fixes and Enhancements - +- Update ThingsBoard to 4.3.1 and the bundled PostgreSQL to 18. +- Correct `variables.json` upstream image from `thingsboard/tb-postgres` to `thingsboard/tb-node`; add releases and official docs references. +- Refresh `.env` to the current template layout and add `W9_DB_VERSION`. +- Normalize `docker-compose.yml` references to `${VAR}`, remove image source comments, and annotate the published port. +- Regenerate `README.md` so the advertised version matches `4.3.1`. +- Add `tests/cases.yml` and `tests/check.sh` covering the ThingsBoard REST login. diff --git a/apps/thingsboard/README.md b/apps/thingsboard/README.md index 78e343702..c64353322 100644 --- a/apps/thingsboard/README.md +++ b/apps/thingsboard/README.md @@ -1,26 +1,86 @@ -# ThingsBoard on Docker +# ThingsBoard on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for ThingsBoard: +## Quick Start +### Deploy Verification - - community: 3.9.1, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **ThingsBoard**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Open the app URL and sign in with the default System Administrator account `sysadmin@thingsboard.org` / `sysadmin`. +2. The bundled init service loads demo tenants, devices, and dashboards so you can explore the platform right away. -The following are the minimal [recommended requirements](https://thingsboard.io/docs/user-guide/install/docker): +### Change Password -* **RAM**: 2 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 2 GB of free space -* **bandwidth**: more fluent experience over 100M +1. Sign in to the ThingsBoard web UI. +2. Open the account menu in the top-right corner, choose **Account**, and change the password. +3. Administrators can reset other users' passwords under **Users**. + -## Install +## Configuration Reference -You can install this ThingsBoard by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [ThingsBoard Docker image](https://hub.docker.com/r/thingsboard/tb-node) and makes some improvements below. -If you want use ThingsBoard with **Websoft9 Business Support** free, you can [subscribe ThingsBoard](https://www.websoft9.com/apps) on Cloud platform + -## Documentation + -[ThingsBoard Administrator Guide](https://support.websoft9.com/docs/thingsboard) powered by Websoft9 \ No newline at end of file +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 4.3.1, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 8080 | + + +### Data Directory + + +Data is persisted in the `postgres-data` volume, mounted at `/var/lib/postgresql`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [ThingsBoard Administrator Guide](https://support.websoft9.com/docs/thingsboard) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/thingsboard/tb-node) + +- [Releases](https://github.com/thingsboard/thingsboard/releases) + +- [Official docs](https://thingsboard.io/docs/installation/docker/) + +- [GitHub docs](https://github.com/thingsboard/thingsboard) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/thingsboard/docker-compose.yml b/apps/thingsboard/docker-compose.yml index b06dc09fa..35955c3ad 100644 --- a/apps/thingsboard/docker-compose.yml +++ b/apps/thingsboard/docker-compose.yml @@ -1,7 +1,3 @@ -# image: https://hub.docker.com/r/thingsboard/tb-postgres/ -# docs: https://thingsboard.io/docs/user-guide/install/docker/ - - services: thingsboard-ce: restart: unless-stopped @@ -10,7 +6,7 @@ services: env_file: - .env ports: - - ${W9_HTTP_PORT_SET}:8080 + - "${W9_HTTP_PORT_SET}:8080" # Web Console logging: driver: "json-file" options: @@ -25,14 +21,14 @@ services: postgres: restart: unless-stopped - image: postgres:16 + image: postgres:${W9_DB_VERSION} container_name: ${W9_ID}-postgres environment: - POSTGRES_USER=${POSTGRESQL_USER} - POSTGRES_DB=${POSTGRESQL_DATABASE_NAME} - POSTGRES_PASSWORD=${POSTGRESQL_PASSWORD} volumes: - - postgres-data:/var/lib/postgresql/data + - postgres-data:/var/lib/postgresql healthcheck: test: ["CMD", "pg_isready", "-U", "${POSTGRESQL_USER}", "-d", "${POSTGRESQL_DATABASE_NAME}"] interval: 5s diff --git a/apps/thingsboard/tests/cases.yml b/apps/thingsboard/tests/cases.yml new file mode 100644 index 000000000..95b4ad144 --- /dev/null +++ b/apps/thingsboard/tests/cases.yml @@ -0,0 +1,4 @@ +optional: + - id: api-login + type: script + script: check.sh diff --git a/apps/thingsboard/tests/check.sh b/apps/thingsboard/tests/check.sh new file mode 100644 index 000000000..7d23329a2 --- /dev/null +++ b/apps/thingsboard/tests/check.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +base="${BASE_URL:?BASE_URL is required}" +user="${W9_LOGIN_USER:?W9_LOGIN_USER is required}" +password="${W9_LOGIN_PASSWORD:?W9_LOGIN_PASSWORD is required}" + +response_file="$(mktemp)" +trap 'rm -f "$response_file"' EXIT + +status="$(curl -sS -o "$response_file" -w '%{http_code}' \ + -X POST "${base}/api/auth/login" \ + -H 'Content-Type: application/json' \ + --data "{\"username\":\"${user}\",\"password\":\"${password}\"}")" + +if [ "$status" != "200" ]; then + echo "login failed with HTTP ${status}" >&2 + cat "$response_file" >&2 + exit 1 +fi + +if ! grep -q '"token"' "$response_file"; then + echo "login response has no token" >&2 + cat "$response_file" >&2 + exit 1 +fi + +echo "ThingsBoard API login succeeded" diff --git a/apps/thingsboard/variables.json b/apps/thingsboard/variables.json index 51c19764a..3dd7106e0 100644 --- a/apps/thingsboard/variables.json +++ b/apps/thingsboard/variables.json @@ -2,21 +2,33 @@ "name": "thingsboard", "trademark": "ThingsBoard", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/thingsboard/tb-node", + "releases": "https://github.com/thingsboard/thingsboard/releases", + "docs": [ + "https://thingsboard.io/docs/installation/docker/", + "https://github.com/thingsboard/thingsboard" + ] + }, "edition": [ { "dist": "community", "version": [ - "4.2.0", + "4.3.1", "latest" ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8080, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "2" - }, - "upstream": { - "image": "https://hub.docker.com/r/thingsboard/tb-postgres" } } diff --git a/apps/tomcat/.env b/apps/tomcat/.env index 4d3ecb7ce..f0d5c53f8 100644 --- a/apps/tomcat/.env +++ b/apps/tomcat/.env @@ -1,10 +1,36 @@ W9_REPO=tomcat W9_DIST=community -W9_VERSION=10 +W9_VERSION=11.0-jdk21-temurin + +#### -- Not allowed to edit below environments when recreate app based on existing data -- #### W9_ID=tomcat + +# Web/internal ports W9_HTTP_PORT=8080 W9_HTTP_PORT_SET=8080 + +# URL helper W9_URL=example.domain.com -W9_NETWORK=websoft9 \ No newline at end of file +W9_NETWORK=websoft9 + +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Tomcat image environment variables +# Docs: https://hub.docker.com/_/tomcat +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# The container runs the official Tomcat image with a mounted entrypoint +# (src/entrypoint.sh) that runs hooks before Tomcat starts. +# ============================================================ + +# Used by docker-compose.yml: + +# Not used by default; enable only when needed: +# CATALINA_OPTS= +# JAVA_OPTS= +# CATALINA_OUT= +# CATALINA_TMPDIR= diff --git a/apps/tomcat/CHANGELOG.md b/apps/tomcat/CHANGELOG.md index 582cf46c5..ba16b4ade 100644 --- a/apps/tomcat/CHANGELOG.md +++ b/apps/tomcat/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-21 +- Update Tomcat from the floating `10` major tag to `11.0-jdk21-temurin` (Tomcat 11 stable line with JDK21 LTS). +- Refresh the `variables.json` supported tags to the current upstream temurin variants (`11.0` / `10.1` / `9.0` with jdk25/21/17/11/8); drop the `corretto` tags that upstream no longer publishes. +- Align `.env` and `docker-compose.yml` with current repository policy: braced variable references, inline published-port comment, image-env section banner, removal of the `# image:` source comment and obsolete `version`, and a main-container healthcheck. +- Add `apps/tomcat/tests/cases.yml` with a welcome-page smoke test and a WAR auto-deploy test (`war-deploy.sh`) that builds a tiny WAR inside the container and verifies the context. +- Adopt the shared runtime startup mechanism: `src/entrypoint.sh` orchestrator + `src/entrypoint.d/10-webapps.sh` + `src/start.sh`, replacing `src/cmd.sh`. User hooks can be added under `/usr/local/tomcat/.w9/entrypoint.d` without rebuilding, and Tomcat now starts via `exec` as PID 1. +- Regenerate `README.md` from `variables.json` and `docker-compose.yml`. diff --git a/apps/tomcat/Notes.md b/apps/tomcat/Notes.md index 3e7bf9463..37abe7bdb 100644 --- a/apps/tomcat/Notes.md +++ b/apps/tomcat/Notes.md @@ -1,14 +1,51 @@ -## Tomcat +# Tomcat Notes -### 运行 war 包 +> 内部维护说明;面向客户的文档以 `README.md` 为准。 -进入 **tomcat** 容器,下载官方示例,会自动解压 +## 来源 +- 官方镜像:https://hub.docker.com/_/tomcat +- 镜像源码:https://github.com/docker-library/tomcat +- 版本列表:https://hub.docker.com/_/tomcat/tags + +## 版本 + +- `W9_VERSION=11.0-jdk21-temurin`:Tomcat 11 稳定线 + JDK21 LTS。 +- `variables.json` 只保留当前上游仍发布的 temurin 变体(`11.0` / `10.1` / `9.0`);官方已移除 `corretto` 变体,故不再列出。 +- 数据卷 `tomcat:/usr/local/tomcat` 持久化整个 Tomcat 目录(含 `webapps` 与 `conf`)。 + +## 启动机制(runtime-app 约定) + +参照 `docs/runtime-app-spec.md`,与 `springboot` 一致,但**不**引入非 root 用户/permissions 侧车(Tomcat 官方镜像以 root 运行,改动风险大)。 + +``` +src/entrypoint.sh # orchestrator,挂到 /opt/websoft9/entrypoint.sh +src/entrypoint.d/*.sh # 包内钩子,挂到 /opt/websoft9/entrypoint.d(只读) +src/start.sh # 默认启动,挂到 /opt/websoft9/start.sh(只读) ``` -cd /usr/local/tomcat/webapps && wget https://tomcat.apache.org/tomcat-10.0-doc/appdev/sample/sample.war -cd /usr/local/tomcat/webapps && wget https://tomcat.apache.org/tomcat-10.0-doc/appdev/sample/sample.war -O ROOT.war +- 钩子来源两处,同名用户钩子覆盖包内钩子,按文件名排序,每次启动都执行且必须幂等: + - 包内:`/opt/websoft9/entrypoint.d` + - 用户:`${APP_DIR}/.w9/entrypoint.d`,即 `/usr/local/tomcat/.w9/entrypoint.d` +- `APP_DIR=/usr/local/tomcat`(复用数据卷);用户可放 `${APP_DIR}/.w9/start.sh` 覆盖默认启动。 +- 默认钩子 `10-webapps.sh`:`cp -a webapps.dist/. webapps/`,恢复 ROOT/docs/examples 默认应用。 +- `start.sh` 用 `exec catalina.sh run`,保证 Tomcat 是 PID1、能收到 SIGTERM。 +- 与 runtime-app-spec 的差异:不使用 `DATABASE_URL` 覆盖,不使用非 root 用户。 +## 运行 war 包 + +进入 **tomcat** 容器,下载官方示例,会自动解压: + +``` +cd /usr/local/tomcat/webapps && wget https://tomcat.apache.org/tomcat-11.0-doc/appdev/sample/sample.war +cd /usr/local/tomcat/webapps && wget https://tomcat.apache.org/tomcat-11.0-doc/appdev/sample/sample.war -O ROOT.war ``` -ROOT.war 会自动解压到根目录,而不包含路径 \ No newline at end of file +`ROOT.war` 会自动解压到根目录(不包含路径)。 + +## 测试 + +- `tests/cases.yml`:默认自适应检查(compose-config / container-up / container-healthy / web-access `/`)之外,加两个 `script` 用例: + - `smoke.sh`:校验欢迎页内容,证明 `10-webapps.sh` 的默认应用已恢复。 + - `war-deploy.sh`:在容器内用 `jar` 造一个极小 WAR,放进 `webapps/`,等待自动解压并校验 context,验证真实 WAR 部署路径。 +- `script` 用例默认在**部署目标**执行(remote 时走 SSH,见 `docs/app-tests.md`),因此 `war-deploy.sh` 可以使用远端 `docker exec`;`BASE_URL` 在远端被改写为 `http://localhost:${W9_HTTP_PORT_SET}`。 diff --git a/apps/tomcat/README.md b/apps/tomcat/README.md index 659c6731d..d3cd0bffd 100644 --- a/apps/tomcat/README.md +++ b/apps/tomcat/README.md @@ -1,26 +1,94 @@ -# Tomcat on Docker +# Tomcat on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Tomcat : +## Quick Start +### Deploy Verification - - community: 11.0-jdk21-temurin, 10-jdk21-temurin, 10-jdk17-temurin, 10-jdk11-temurin, 9-jdk21-temurin, 9-jdk17-temurin, 9-jdk11-temurin, 9-jdk8-temurin, 9-jdk21-corretto, 9-jdk17-corretto, 9-jdk11-corretto, 9-jdk8-corretto +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Tomcat**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +Tomcat serves the default webapps (ROOT, docs, examples) on port `8080`; this package has no separate admin console. -The following are the minimal [recommended requirements](https://tomcat.apache.org/): +1. In the Websoft9 console, open **My Apps → Tomcat → Access** to get the URL (`http://:8080`). +2. Open it in a browser; you should see the Apache Tomcat welcome page. -* **RAM**: 4 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +### Deploy a WAR -## Install +1. Copy your WAR into the container's `webapps` directory: + `docker cp app.war ${W9_ID}:/usr/local/tomcat/webapps/` +2. Tomcat auto-deploys it; open `http://:8080/app/`. +3. To deploy at the root, name the file `ROOT.war` (it replaces the default welcome page). -You can install this Tomcat by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +### Customize Startup -If you want use Tomcat with **Websoft9 Business Support** free, you can [subscribe Tomcat ](https://www.websoft9.com/apps) on Cloud platform +Startup runs through `src/entrypoint.sh`, which executes hooks in filename order on every start. Package hooks live in `src/entrypoint.d/`. To add your own without rebuilding, put scripts in the `tomcat` volume at `/usr/local/tomcat/.w9/entrypoint.d/`; a `/usr/local/tomcat/.w9/start.sh` replaces the default start command. + -## Documentation +## Configuration Reference -[Tomcat Administrator Guide](https://support.websoft9.com/docs/tomcat) powered by Websoft9 \ No newline at end of file +Websoft9 packages this app from the official [Tomcat Docker image](https://hub.docker.com/_/tomcat) and makes some improvements below. + + +- Tomcat data (including `webapps` and `conf`) is persisted in the `tomcat` volume mounted at `/usr/local/tomcat`. +- Startup runs through `src/entrypoint.sh`, which executes hooks from `/opt/websoft9/entrypoint.d` (package) and `/usr/local/tomcat/.w9/entrypoint.d` (user) before starting Tomcat. Hooks run on every start and must be idempotent. +- The default `10-webapps.sh` hook restores the bundled default webapps (`webapps.dist/*` → `webapps`). +- The default `W9_VERSION=11.0-jdk21-temurin` pins Tomcat 11 on JDK 21 LTS; pick another supported tag to change the Tomcat/JDK combination. + + +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 11.0-jdk21-temurin, 11.0-jdk25-temurin, 11.0-jdk17-temurin, 10.1-jdk25-temurin, 10.1-jdk21-temurin, 10.1-jdk17-temurin, 10.1-jdk11-temurin, 9.0-jdk25-temurin, 9.0-jdk21-temurin, 9.0-jdk17-temurin, 9.0-jdk11-temurin, 9.0-jdk8-temurin. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Tomcat HTTP | 8080 | + + +### Data Directory + + +Data is persisted in the `tomcat` volume, mounted at `/usr/local/tomcat`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +- `./src/entrypoint.sh` → `/opt/websoft9/entrypoint.sh` +- `./src/entrypoint.d` → `/opt/websoft9/entrypoint.d` +- `./src/start.sh` → `/opt/websoft9/start.sh` + + + +## References + +- [Tomcat Administrator Guide](https://support.websoft9.com/docs/tomcat) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/_/tomcat) + + + +## Troubleshooting + +**Root URL returns 404?** +- The default webapps may not have been restored; confirm the `10-webapps.sh` hook ran and `/usr/local/tomcat/webapps/ROOT` exists in the container. + +**Container stays unhealthy?** +- Tomcat can take about 30 seconds to start; check `docker compose logs ${W9_ID}`. + +**Port not reachable?** +- Confirm `W9_HTTP_PORT_SET` is free and allowed by the firewall / security group. + diff --git a/apps/tomcat/docker-compose.yml b/apps/tomcat/docker-compose.yml index 737ec2823..ebbc45763 100644 --- a/apps/tomcat/docker-compose.yml +++ b/apps/tomcat/docker-compose.yml @@ -1,19 +1,27 @@ -# image: https://hub.docker.com/_/tomcat - -version: '3.8' services: tomcat: container_name: ${W9_ID} - image: $W9_REPO:$W9_VERSION + image: ${W9_REPO}:${W9_VERSION} restart: unless-stopped env_file: .env + working_dir: /usr/local/tomcat + entrypoint: ["/bin/bash", "/opt/websoft9/entrypoint.sh"] + environment: + - APP_DIR=/usr/local/tomcat ports: - - '${W9_HTTP_PORT_SET}:8080' + - "${W9_HTTP_PORT_SET}:8080" # Tomcat HTTP + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://localhost:8080/ >/dev/null"] + interval: 30s + timeout: 5s + retries: 5 + start_period: 30s volumes: - - tomcat:/usr/local/tomcat - - ./src/cmd.sh:/usr/src/app/cmd.sh - command: /bin/bash -c "bash /usr/src/app/cmd.sh && catalina.sh run" - + - tomcat:/usr/local/tomcat + - ./src/entrypoint.sh:/opt/websoft9/entrypoint.sh:ro + - ./src/entrypoint.d:/opt/websoft9/entrypoint.d:ro + - ./src/start.sh:/opt/websoft9/start.sh:ro + networks: default: name: ${W9_NETWORK} diff --git a/apps/tomcat/src/cmd.sh b/apps/tomcat/src/cmd.sh deleted file mode 100644 index 8704fcc98..000000000 --- a/apps/tomcat/src/cmd.sh +++ /dev/null @@ -1,11 +0,0 @@ -### This script is running before tomcat starting ############## -### You can add your CI code here, below is example - -cp -r webapps.dist/* webapps - -### Install os packages -# apt update -y && apt install unzip -y - -### Install java sample, access by: http://URL -# cd /usr/local/tomcat/webapps -# wget -O ROOT.war https://tomcat.apache.org/tomcat-10.0-doc/appdev/sample/sample.war diff --git a/apps/tomcat/src/entrypoint.d/10-webapps.sh b/apps/tomcat/src/entrypoint.d/10-webapps.sh new file mode 100644 index 000000000..f68a37927 --- /dev/null +++ b/apps/tomcat/src/entrypoint.d/10-webapps.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -euo pipefail + +APP_DIR="${APP_DIR:-/usr/local/tomcat}" +cd "${APP_DIR}" + +# Restore the bundled default webapps (ROOT, docs, examples) that the official +# image keeps in webapps.dist. Idempotent: safe to run on every start. +mkdir -p webapps + +if [ -d webapps.dist ]; then + echo "[tomcat-runtime] restoring default webapps into ${APP_DIR}/webapps" + cp -a webapps.dist/. webapps/ +fi diff --git a/apps/tomcat/src/entrypoint.sh b/apps/tomcat/src/entrypoint.sh new file mode 100644 index 000000000..88954b1c8 --- /dev/null +++ b/apps/tomcat/src/entrypoint.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -euo pipefail + +APP_DIR="${APP_DIR:-/usr/local/tomcat}" +PACKAGE_HOOKS_DIR="/opt/websoft9/entrypoint.d" +USER_HOOKS_DIR="${APP_DIR}/.w9/entrypoint.d" +PACKAGE_START="/opt/websoft9/start.sh" +USER_START="${APP_DIR}/.w9/start.sh" + +log() { + echo "[tomcat-runtime] $*" +} + +run_hooks() { + local -A hooks=() + local dir file name + + for dir in "${PACKAGE_HOOKS_DIR}" "${USER_HOOKS_DIR}"; do + [ -d "${dir}" ] || continue + for file in "${dir}"/*.sh; do + [ -e "${file}" ] || continue + name="$(basename "${file}")" + hooks["${name}"]="${file}" + done + done + + if [ "${#hooks[@]}" -eq 0 ]; then + return 0 + fi + + while IFS= read -r name; do + log "hook: ${name}" + bash "${hooks[${name}]}" + done < <(printf '%s\n' "${!hooks[@]}" | sort) +} + +mkdir -p "${APP_DIR}" +cd "${APP_DIR}" +export APP_DIR + +run_hooks + +# Start must be exec'd so Tomcat becomes PID 1 and receives signals. +if [ -f "${USER_START}" ]; then + log "starting with user start script: ${USER_START}" + exec bash "${USER_START}" +fi + +log "starting with default start script" +exec bash "${PACKAGE_START}" diff --git a/apps/tomcat/src/start.sh b/apps/tomcat/src/start.sh new file mode 100644 index 000000000..e6b2f7f06 --- /dev/null +++ b/apps/tomcat/src/start.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -euo pipefail + +APP_DIR="${APP_DIR:-/usr/local/tomcat}" +cd "${APP_DIR}" + +exec catalina.sh run diff --git a/apps/tomcat/tests/cases.yml b/apps/tomcat/tests/cases.yml new file mode 100644 index 000000000..6480b5ef9 --- /dev/null +++ b/apps/tomcat/tests/cases.yml @@ -0,0 +1,10 @@ +# The adaptive checks cover compose config, container health and the web root. +# Tomcat's core path is deploying a WAR, so war-deploy.sh builds a tiny WAR +# inside the container, waits for auto-deploy and verifies the context serves it. +optional: + - id: welcome-page + type: script + script: smoke.sh + - id: war-deploy + type: script + script: war-deploy.sh diff --git a/apps/tomcat/tests/smoke.sh b/apps/tomcat/tests/smoke.sh new file mode 100644 index 000000000..765b53f1d --- /dev/null +++ b/apps/tomcat/tests/smoke.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +# BASE_URL is provided by `libs app-tests`. +base="${BASE_URL:?BASE_URL is required}" +body="$(curl -fsS --max-time 15 "${base}/")" + +if printf '%s' "${body}" | grep -qi "tomcat"; then + echo "tomcat welcome page served at ${base}/" + exit 0 +fi + +echo "unexpected response from ${base}/" >&2 +exit 1 diff --git a/apps/tomcat/tests/war-deploy.sh b/apps/tomcat/tests/war-deploy.sh new file mode 100644 index 000000000..24310b851 --- /dev/null +++ b/apps/tomcat/tests/war-deploy.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Script cases run on the deployment target (see docs/app-tests.md). This builds +# a tiny WAR inside the Tomcat container, drops it into webapps/, waits for +# Tomcat to auto-deploy it, and verifies the context serves the expected page. + +container="${W9_ID:?W9_ID is required}" +base="${BASE_URL:?BASE_URL is required}" +context="w9smoke" + +cleanup() { + docker exec "${container}" rm -f "/usr/local/tomcat/webapps/${context}.war" >/dev/null 2>&1 || true + docker exec "${container}" rm -rf "/usr/local/tomcat/webapps/${context}" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +cleanup + +docker exec "${container}" bash -c ' + set -e + rm -rf /tmp/w9src /tmp/w9smoke.war + mkdir -p /tmp/w9src/WEB-INF + printf "%s\n" "

Websoft9 WAR smoke OK

" > /tmp/w9src/index.html + ( cd /tmp/w9src && jar cf /tmp/w9smoke.war . ) + cp /tmp/w9smoke.war /usr/local/tomcat/webapps/w9smoke.war +' + +deadline=$((SECONDS + 120)) +code="000" +while [ "${SECONDS}" -lt "${deadline}" ]; do + code="$(curl -s -o /tmp/w9war.html -w '%{http_code}' --max-time 10 "${base}/${context}/" || true)" + [ "${code}" = "200" ] && break + sleep 3 +done + +if [ "${code}" != "200" ]; then + echo "WAR context /${context}/ -> ${code} (timeout)" >&2 + exit 1 +fi + +if ! grep -q "Websoft9 WAR smoke OK" /tmp/w9war.html; then + echo "WAR context served unexpected body:" >&2 + cat /tmp/w9war.html >&2 + exit 1 +fi + +echo "WAR auto-deploy ok at ${base}/${context}/" diff --git a/apps/tomcat/variables.json b/apps/tomcat/variables.json index 2dad74c5c..a8094d465 100644 --- a/apps/tomcat/variables.json +++ b/apps/tomcat/variables.json @@ -1,23 +1,23 @@ { "name": "tomcat", - "trademark": "Tomcat ", + "trademark": "Tomcat", "release": true, "edition": [ { "dist": "community", "version": [ "11.0-jdk21-temurin", - "10-jdk21-temurin", - "10-jdk17-temurin", - "10-jdk11-temurin", - "9-jdk21-temurin", - "9-jdk17-temurin", - "9-jdk11-temurin", - "9-jdk8-temurin", - "9-jdk21-corretto", - "9-jdk17-corretto", - "9-jdk11-corretto", - "9-jdk8-corretto" + "11.0-jdk25-temurin", + "11.0-jdk17-temurin", + "10.1-jdk25-temurin", + "10.1-jdk21-temurin", + "10.1-jdk17-temurin", + "10.1-jdk11-temurin", + "9.0-jdk25-temurin", + "9.0-jdk21-temurin", + "9.0-jdk17-temurin", + "9.0-jdk11-temurin", + "9.0-jdk8-temurin" ] } ], diff --git a/apps/traefik/.env b/apps/traefik/.env index 17e4bcda6..04ce2a3a0 100644 --- a/apps/traefik/.env +++ b/apps/traefik/.env @@ -1,13 +1,33 @@ -W9_VERSION='v3.6' -W9_ID='traefik' - W9_REPO=traefik +W9_DIST=community +W9_VERSION=v3.7 + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -# 80 is external Traffic Port, 8080 is Dashboard and API port -W9_HTTP_PORT=80 -W9_HTTP_PORT_SET='9002' -W9_URL='' +W9_ID=traefik +W9_HTTP_PORT=8080 +W9_HTTP_PORT_SET=9002 +W9_API_PORT_SET=9003 +W9_URL=traefik.example.com +W9_ADMIN_PATH=/dashboard/ W9_NETWORK=websoft9 -#### --------------------------------------------------------------------------------------- #### -W9_DIST='community' + +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Traefik image environment variables +# Docs: https://doc.traefik.io/traefik/reference/install-configuration/providers/docker/ +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: + +# Not used by default; enable only when needed: +# TRAEFIK_LOG_LEVEL=INFO +# TRAEFIK_ACCESSLOG=true +# TRAEFIK_PROVIDERS_DOCKER_NETWORK=websoft9 diff --git a/apps/traefik/CHANGELOG.md b/apps/traefik/CHANGELOG.md index 582cf46c5..eb1825c61 100644 --- a/apps/traefik/CHANGELOG.md +++ b/apps/traefik/CHANGELOG.md @@ -1,5 +1,7 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-21 +- Update Traefik from `v3.6` to `v3.7`. +- Make the packaged Websoft9 entrypoint target the Traefik dashboard on port `8080` and expose the proxy HTTP entrypoint separately. +- Align app metadata and validation coverage with current repository rules. diff --git a/apps/traefik/Notes.md b/apps/traefik/Notes.md deleted file mode 100644 index e763c4eae..000000000 --- a/apps/traefik/Notes.md +++ /dev/null @@ -1,4 +0,0 @@ -# Traefik - -- 目前的配置文件默认支持 Docker 服务,k8s 下未研究 -- 8080 端口由于安全考虑,没有直接绑定到宿主机。Nginx proxy 的 location /dashboard {} 方案也无法达成目标 diff --git a/apps/traefik/README.md b/apps/traefik/README.md index 521597f90..b55910016 100644 --- a/apps/traefik/README.md +++ b/apps/traefik/README.md @@ -1,26 +1,87 @@ -# Traefik on Docker +# Traefik on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Traefik: +## Quick Start +### Deploy Verification - - community: 3.3, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Traefik**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Make sure you are signed in to the Traefik admin console. +2. Try a core feature. -The following are the minimal [recommended requirements](https://hub.docker.com/_/traefik): +### Change Password -* **RAM**: 1 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +1. In the [Websoft9](https://www.websoft9.com) console, open the app's **Compose** tab. +2. Update the password in `.env` and save. +3. Rebuild the app. + -## Install +## Configuration Reference -You can install this Traefik by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Traefik Docker image](https://hub.docker.com/_/traefik) and makes some improvements below. -If you want use Traefik with **Websoft9 Business Support** free, you can [subscribe Traefik](https://www.websoft9.com/apps) on Cloud platform + -## Documentation + -[Traefik Administrator Guide](https://support.websoft9.com/docs/traefik) powered by Websoft9 \ No newline at end of file +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: v3.7, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 8080 | +| HTTP Entrypoint | 80 | + + +### Data Directory + + +Data is persisted in the `/var/run/docker.sock` volume, mounted at `/var/run/docker.sock`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration is overridden by mounting `./src/traefik.yml` to `/etc/traefik/traefik.yml`. + + +## References + +- [Traefik Administrator Guide](https://support.websoft9.com/docs/traefik) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/_/traefik) + +- [Releases](https://github.com/traefik/traefik) + +- [Official docs](https://doc.traefik.io/traefik/reference/install-configuration/providers/docker/) + +- [Official docs](https://doc.traefik.io/traefik/migrate/v3/) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/traefik/docker-compose.yml b/apps/traefik/docker-compose.yml index a75407065..419b66527 100644 --- a/apps/traefik/docker-compose.yml +++ b/apps/traefik/docker-compose.yml @@ -1,8 +1,3 @@ -# image: https://hub.docker.com/_/traefik -# docs: https://doc.traefik.io/traefik/providers/docker/ - -version: '3.8' - services: traefik: image: ${W9_REPO}:${W9_VERSION} @@ -16,9 +11,8 @@ services: - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" ports: - - ${W9_HTTP_PORT_SET}:80 # For HTTP traffic - #- 4433:443 # For HTTPS traffic - - 8080:8080 # ← 已启用 Dashboard 端口 + - "${W9_HTTP_PORT_SET}:8080" # Web Console + - "${W9_API_PORT_SET}:80" # HTTP Entrypoint volumes: - /var/run/docker.sock:/var/run/docker.sock - ./src/traefik.yml:/etc/traefik/traefik.yml diff --git a/apps/traefik/tests/cases.yml b/apps/traefik/tests/cases.yml new file mode 100644 index 000000000..99774c8c2 --- /dev/null +++ b/apps/traefik/tests/cases.yml @@ -0,0 +1,8 @@ +skip: + - id: web-access + +optional: + - id: dashboard + type: web-access + path: /dashboard/ + expect_status: 200 diff --git a/apps/traefik/variables.json b/apps/traefik/variables.json index 6e1f9f484..64a03b040 100644 --- a/apps/traefik/variables.json +++ b/apps/traefik/variables.json @@ -2,21 +2,41 @@ "name": "traefik", "trademark": "Traefik", "release": true, + "upstream": { + "image": "https://hub.docker.com/_/traefik", + "releases": "https://github.com/traefik/traefik", + "docs": [ + "https://doc.traefik.io/traefik/reference/install-configuration/providers/docker/", + "https://doc.traefik.io/traefik/migrate/v3/" + ] + }, "edition": [ { "dist": "community", "version": [ - "v3.6", + "v3.7", "latest" ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8080, + "path": "/" + }, + "admin": { + "port": 8080, + "path": "/dashboard/" + }, + "api": { + "port": 8080, + "path": "/api/" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "1" - }, - "upstream": { - "image": "https://hub.docker.com/_/traefik" } } diff --git a/apps/trivy/.env b/apps/trivy/.env index 965339c24..ad11ea7e5 100644 --- a/apps/trivy/.env +++ b/apps/trivy/.env @@ -1,9 +1,44 @@ -W9_VERSION='0.68.1' -W9_DIST='community' W9_REPO=aquasec/trivy +W9_DIST=community +W9_VERSION=0.74.0 + +# Optional password seed: enable only when the package actually controls a DB or built-in login. +# See docs/w9-env-spec.md for when W9_POWER_PASSWORD is appropriate. +W9_POWER_PASSWORD='gyJ2wEL8smpUsA' + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='trivy' -W9_SCAN_PATH_SET='/docker/trivy' + +W9_ID=trivy +# Trivy server exposes a single HTTP API port (scan RPC + health endpoints). +W9_HTTP_PORT_SET=4954 + +# Trivy uses a single API token instead of a username/password login; the interface +# surfaces the token through the password field. The username is a display label only. +W9_LOGIN_USER=trivy +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} + W9_NETWORK=websoft9 -#### --------------------------------------------------------------------------------------- #### + +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Trivy image environment variables +# Docs: https://trivy.dev/latest/docs/references/modes/client-server/ +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +# Shared secret for client/server mode; clients must pass the same value via --token. +TRIVY_TOKEN=${W9_LOGIN_PASSWORD} + +# Not used by default; enable only when needed: +# TRIVY_LISTEN=0.0.0.0:4954 +# TRIVY_DEBUG=true +# TRIVY_SKIP_DB_UPDATE=false +# TRIVY_DB_REPOSITORY=ghcr.io/aquasecurity/trivy-db:2 +# TRIVY_INSECURE=false diff --git a/apps/trivy/CHANGELOG.md b/apps/trivy/CHANGELOG.md index 4591f22ad..b7beef716 100644 --- a/apps/trivy/CHANGELOG.md +++ b/apps/trivy/CHANGELOG.md @@ -1,2 +1,10 @@ # CHANGELOG +## 2026-09-21 + +- Switch Trivy from CLI shell mode to **server mode** (`trivy server --listen 0.0.0.0:4954`). +- Bump `aquasec/trivy` from `0.68.1` to `0.74.0`. +- Publish the server API on `${W9_HTTP_PORT_SET}` and persist the vulnerability database in the `trivy_cache` volume. +- Enable token authentication through `TRIVY_TOKEN`, carried by `W9_LOGIN_PASSWORD` so the interface can display it. +- Add a `/healthz` healthcheck and an app-specific `tests/cases.yml`. +- Remove the unused scan-path mount (`W9_SCAN_PATH_SET`) that only applied to the CLI shell mode. diff --git a/apps/trivy/Notes.md b/apps/trivy/Notes.md index 6a9d5f564..a9dcc5dad 100644 --- a/apps/trivy/Notes.md +++ b/apps/trivy/Notes.md @@ -1,14 +1,24 @@ # Trivy -#### how to scan +Trivy runs as a **server**: it keeps the vulnerability database up to date and lets remote clients scan without downloading the DB. -Access into container, run command as following: -``` -trivy fs /scandir -``` -#### quickly scan +#### Server endpoints + +- `http://:/healthz` — health check, returns `ok` +- `http://:/version` — server version information + +#### Scan from a client + +Install the [Trivy CLI](https://trivy.dev/latest/docs/getting-started/installation/) on the machine that runs the scan, then point it at this server: ``` -apk add --no-cache python3 && ln -sf python3 /usr/bin/python -trivy fs --scanners vuln /tmp/usr/share +trivy image --server http://: --token alpine:3.20 +trivy fs --server http://: --token /path/to/project +trivy repo --server http://: --token https://github.com/org/repo ``` + +The token is the `W9_LOGIN_PASSWORD` value in `.env` (also shown in the app's **Access** tab). + +#### Vulnerability database + +The DB is cached in the `trivy_cache` volume (`/root/.cache/trivy`) and refreshed automatically while the server runs. diff --git a/apps/trivy/README.md b/apps/trivy/README.md index 8a631c1f2..a845ad22e 100644 --- a/apps/trivy/README.md +++ b/apps/trivy/README.md @@ -1,26 +1,89 @@ -# Trivy on Docker +# Trivy on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Trivy: +## Quick Start +### Deploy Verification - - community: 0.61.0, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Trivy**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +Trivy is deployed in server mode and exposes an HTTP API on port 4954. -The following are the minimal [recommended requirements](https://aquasecurity.github.io/trivy/v0.53/docs/): +1. Open `http://:4954/healthz` and confirm it returns `ok`. +2. On a client machine, install the [Trivy CLI](https://trivy.dev/latest/docs/getting-started/installation/). +3. Run a scan against this server, for example `trivy image --server http://:4954 --token alpine:3.20`. -* **RAM**: 4 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 2 GB of free space -* **bandwidth**: more fluent experience over 100M +### Change Token -## Install +1. In the [Websoft9](https://www.websoft9.com) console, open the app's **Compose** tab. +2. Update `W9_LOGIN_PASSWORD` (or `W9_POWER_PASSWORD`) in `.env` and save. +3. Rebuild the app; clients must use the new token. + -You can install this Trivy by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +## Configuration Reference -If you want use Trivy with **Websoft9 Business Support** free, you can [subscribe Trivy](https://www.websoft9.com/apps) on Cloud platform +Websoft9 packages this app from the official [Trivy Docker image](https://hub.docker.com/r/aquasec/trivy) and makes some improvements below. -## Documentation + -[Trivy Administrator Guide](https://support.websoft9.com/docs/trivy) powered by Websoft9 \ No newline at end of file + + +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 0.74.0, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Trivy Server API | 4954 | + + +### Data Directory + + +Data is persisted in the `trivy_cache` volume, mounted at `/root/.cache/trivy`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [Trivy Administrator Guide](https://support.websoft9.com/docs/trivy) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/aquasec/trivy) + +- [Releases](https://github.com/aquasecurity/trivy/releases) + +- [Official docs](https://trivy.dev/latest/docs/references/modes/client-server/) + +- [Official docs](https://trivy.dev/latest/docs/references/configuration/cli/trivy_server/) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/trivy/docker-compose.yml b/apps/trivy/docker-compose.yml index bbd2bc3b9..f6f8b1232 100644 --- a/apps/trivy/docker-compose.yml +++ b/apps/trivy/docker-compose.yml @@ -1,22 +1,26 @@ -# image: https://hub.docker.com/r/aquasec/trivy -# docs: https://aquasecurity.github.io/trivy/v0.53/getting-started/installation/#docker - -version: '3.8' - services: trivy: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} restart: unless-stopped + command: server --listen 0.0.0.0:4954 env_file: .env + ports: + - "${W9_HTTP_PORT_SET}:4954" # Trivy Server API volumes: - # If you want to scan docker image, you can use the following mount - # - /var/run/docker.sock:/var/run/docker.sock - - $W9_SCAN_PATH_SET:/myproject - entrypoint: tail -f /dev/null - + - trivy_cache:/root/.cache/trivy + healthcheck: + test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1:4954/healthz | grep -q ok"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 120s + networks: default: - name: $W9_NETWORK + name: ${W9_NETWORK} external: true + +volumes: + trivy_cache: diff --git a/apps/trivy/tests/cases.yml b/apps/trivy/tests/cases.yml new file mode 100644 index 000000000..f524f3ae4 --- /dev/null +++ b/apps/trivy/tests/cases.yml @@ -0,0 +1,8 @@ +skip: + - id: web-access + +optional: + - id: server-health + type: web-access + path: /healthz + expect_status: 200 diff --git a/apps/trivy/variables.json b/apps/trivy/variables.json index 765ca652a..d8b9cd7ce 100644 --- a/apps/trivy/variables.json +++ b/apps/trivy/variables.json @@ -2,21 +2,32 @@ "name": "trivy", "trademark": "Trivy", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/aquasec/trivy", + "releases": "https://github.com/aquasecurity/trivy/releases", + "docs": [ + "https://trivy.dev/latest/docs/references/modes/client-server/", + "https://trivy.dev/latest/docs/references/configuration/cli/trivy_server/" + ] + }, "edition": [ { "dist": "community", "version": [ - "0.68.1", + "0.74.0", "latest" ] } ], + "access": { + "api": { + "port": 4954, + "path": "/healthz" + } + }, "requirements": { "cpu": "2", "memory": "4", - "disk": "2" - }, - "upstream": { - "image": "https://hub.docker.com/r/aquasec/trivy" + "disk": "4" } } diff --git a/apps/typesense/.env b/apps/typesense/.env index 13b9bf862..318aa4ad1 100644 --- a/apps/typesense/.env +++ b/apps/typesense/.env @@ -1,19 +1,43 @@ -W9_VERSION='29.0' -W9_DIST='community' W9_REPO=typesense/typesense -W9_POWER_PASSWORD='arwBeGlTzE758!DU' +W9_DIST=community +W9_VERSION=30.2 + #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='typesense' + +W9_ID=typesense + +# Web/internal ports W9_HTTP_PORT=8108 -W9_HTTP_PORT_SET='8109' -W9_URL='yourdomain.com' -W9_NETWORK=websoft9 +W9_HTTP_PORT_SET=8109 + +# URL helper +W9_URL=appname.example.com -# Don't use W9_POWER_PASSWORD for api_key -# Some special strings can not used for api, suggest user get key by command [openssl rand -base64 24] +# API key for the Typesense server and the bundled docsearch scraper. +# Do not reuse W9_POWER_PASSWORD here: some special characters are invalid in API keys. +# Generate with: openssl rand -base64 24 W9_LOGIN_API_KEY=cJ9XqddokC3OCRdx1SFQRv+uFj5QHYOT -#### --------------------------------------------------------------------------------------- #### +W9_NETWORK=websoft9 + +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Typesense image environment variables +# Docs: https://typesense.org/docs/guide/install-typesense.html +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: TYPESENSE_DATA_DIR=/data TYPESENSE_ENABLE_CORS=true + +# Not used by default; enable only when needed: +# TYPESENSE_LOG_DIR=/data/logs +# TYPESENSE_ENABLE_ACCESS_LOGGING=true +# TYPESENSE_THREAD_POOL_SIZE= +# TYPESENSE_MAX_INDEXING_CONCURRENCY= +# TYPESENSE_FILTER_BY_MAX_OPS= diff --git a/apps/typesense/CHANGELOG.md b/apps/typesense/CHANGELOG.md index 582cf46c5..1afd2a65f 100644 --- a/apps/typesense/CHANGELOG.md +++ b/apps/typesense/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-21 +- Update Typesense from `29.0` to `30.2` (latest stable upstream release). +- Note the v30 behavior changes: synonyms and overrides become top-level Synonym Sets / Curation Sets and analytics rules change shape; existing data is auto-migrated on upgrade, so take a snapshot before upgrading an existing instance. +- Align `.env` with the current repository policy: braced variable references, template layout, image-env section banner, and removal of the unused `W9_POWER_PASSWORD`. +- Add `apps/typesense/tests/cases.yml` with a `/health` reachability check, and drop the source-comment header from `docker-compose.yml` while adding an inline published-port comment. +- Regenerate `README.md` from `variables.json` and `docker-compose.yml`. diff --git a/apps/typesense/Notes.md b/apps/typesense/Notes.md deleted file mode 100644 index 92f212eb4..000000000 --- a/apps/typesense/Notes.md +++ /dev/null @@ -1,2 +0,0 @@ -## Typesense - diff --git a/apps/typesense/README.md b/apps/typesense/README.md index 8249b760b..76ae212f3 100644 --- a/apps/typesense/README.md +++ b/apps/typesense/README.md @@ -1,26 +1,96 @@ -# Typesense on Docker +# Typesense on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Typesense: +## Quick Start +### Deploy Verification - - community: 28.0 +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Typesense**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +Typesense is an HTTP search API server; it has no browser console. In the Websoft9 console, open **My Apps → Typesense → Access** to get the API URL (`http://:8109`). -The following are the minimal [recommended requirements](https://typesense.org/docs/guide/install-typesense.html): +1. Check the server: `curl http://:8109/health` → `{"ok":true}`. +2. Send the API key from `.env` (`W9_LOGIN_API_KEY`) as the `X-TYPESENSE-API-KEY` header on every other request. -* **RAM**: 4 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +Example: create a collection and search it: -## Install +```bash +export TYPESENSE_API_KEY= +curl -X POST "http://:8109/collections" \ + -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \ + -H "Content-Type: application/json" \ + -d '{"name":"books","fields":[{"name":"title","type":"string"}]}' +``` -You can install this Typesense by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +### Change API Key -If you want use Typesense with **Websoft9 Business Support** free, you can [subscribe Typesense](https://www.websoft9.com/apps) on Cloud platform +1. In the Websoft9 console, open the app's **Compose** tab. +2. Update `W9_LOGIN_API_KEY` in `.env` and save. +3. Rebuild the app; the bundled `docsearch-scraper` reads the same key. + -## Documentation +## Configuration Reference -[Typesense Administrator Guide](https://support.websoft9.com/docs/typesense) powered by Websoft9 \ No newline at end of file +Websoft9 packages this app from the official [Typesense Docker image](https://hub.docker.com/r/typesense/typesense) and makes some improvements below. + + +- Typesense is an HTTP API server; there is no browser admin UI. Authenticate API calls with the `X-TYPESENSE-API-KEY` header set to `W9_LOGIN_API_KEY`. +- `W9_LOGIN_API_KEY` is the admin API key and is also passed to the bundled `docsearch-scraper`. +- Data is persisted in the `typesense` volume (`/data`). +- The bundled `docsearch-scraper` indexes the Websoft9 documentation site by default; edit its `CONFIG` in `docker-compose.yml` to change the target. + + +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 30.2. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Typesense HTTP API | 8108 | + + +### Data Directory + + +Data is persisted in the `typesense` volume, mounted at `/data`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [Typesense Administrator Guide](https://support.websoft9.com/docs/typesense) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/typesense/typesense) + + + +## Troubleshooting + +**`/health` returns `{"ok":true}` but API calls return 401?** +- Send the `X-TYPESENSE-API-KEY` header with the value of `W9_LOGIN_API_KEY` from `.env`. + +**Port not reachable?** +- Confirm `W9_HTTP_PORT_SET` is free and allowed by the firewall / security group. + +**Upgrading an existing 29.x instance?** +- v30 auto-migrates synonyms, overrides and analytics rules; take a snapshot before upgrading. + diff --git a/apps/typesense/docker-compose.yml b/apps/typesense/docker-compose.yml index 1eeaa3d9b..c2758b4a1 100644 --- a/apps/typesense/docker-compose.yml +++ b/apps/typesense/docker-compose.yml @@ -1,10 +1,3 @@ -# image: https://hub.docker.com/r/typesense/typesense/tags -# docs: https://typesense.org/docs/guide/install-typesense.html -# https://typesense.org/docs/guide/docsearch.html#add-docsearch-meta-tags-optional -# https://typesense.org/docs/0.23.0/api/server-configuration.html#using-command-line-arguments - -version: "3.8" - services: typesense: image: ${W9_REPO}:${W9_VERSION} @@ -13,10 +6,10 @@ services: logging: driver: "json-file" options: - max-file: "5" - max-size: 10m + max-file: "5" + max-size: 10m ports: - - "${W9_HTTP_PORT_SET}:8108" + - "${W9_HTTP_PORT_SET}:8108" # Typesense HTTP API volumes: - typesense:/data env_file: .env @@ -30,8 +23,8 @@ services: logging: driver: "json-file" options: - max-file: "5" - max-size: 10m + max-file: "5" + max-size: 10m deploy: resources: limits: diff --git a/apps/typesense/tests/cases.yml b/apps/typesense/tests/cases.yml new file mode 100644 index 000000000..ac57ac3c4 --- /dev/null +++ b/apps/typesense/tests/cases.yml @@ -0,0 +1,8 @@ +skip: + - id: web-access + +optional: + - id: health + type: web-access + path: /health + expect_status: 200 diff --git a/apps/typesense/variables.json b/apps/typesense/variables.json index 7cae8c29f..7e4dfaf25 100644 --- a/apps/typesense/variables.json +++ b/apps/typesense/variables.json @@ -6,10 +6,17 @@ { "dist": "community", "version": [ - "29.0" + "30.2" ] } ], + "access": { + "defaultScheme": "http", + "api": { + "port": 8108, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", diff --git a/apps/typo3/.env b/apps/typo3/.env index cb9381b3d..fe14acd23 100644 --- a/apps/typo3/.env +++ b/apps/typo3/.env @@ -1,15 +1,53 @@ -W9_DIST='community' -W9_VERSION='13.4' W9_REPO=martinhelmich/typo3 +W9_DIST=community +W9_VERSION=13.4 + +# Optional password seed: enable only when the package actually controls a DB or built-in login. +# See docs/w9-env-spec.md for when W9_POWER_PASSWORD is appropriate. W9_POWER_PASSWORD='VO82vU2TIiI1uJ!L' + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='typo3' + +W9_ID=typo3 W9_HTTP_PORT=80 -W9_HTTP_PORT_SET='9001' +W9_HTTP_PORT_SET=9001 + +W9_DB_EXPOSE=mysql +W9_DB_VERSION=8.4 + +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} + +W9_URL=appname.example.com W9_URL_REPLACE=true -W9_URL='appname.example.com' -W9_ADMIN_PATH="/typo3" -W9_DB_EXPOSE="mysql" -W9_DB_VERSION="8" +W9_ADMIN_PATH=/typo3 + W9_NETWORK=websoft9 -#### --------------------------------------------------------------------------------------- #### + +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Typo3 image environment variables +# Docs: https://hub.docker.com/r/martinhelmich/typo3 +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# These variables drive the non-interactive first-run install performed +# by src/entrypoint.sh and take effect on first startup only. +# ============================================================ + +# Used by docker-compose.yml: +TYPO3_DB_DRIVER=mysqli +TYPO3_DB_HOST=${W9_ID}-mysql +TYPO3_DB_PORT=3306 +TYPO3_DB_DBNAME=${W9_ID} +TYPO3_DB_USERNAME=${W9_ID} +TYPO3_DB_PASSWORD=${W9_POWER_PASSWORD} +TYPO3_SETUP_ADMIN_USERNAME=${W9_LOGIN_USER} +TYPO3_SETUP_ADMIN_PASSWORD=${W9_LOGIN_PASSWORD} +TYPO3_SETUP_ADMIN_EMAIL=admin@example.com +TYPO3_PROJECT_NAME=Typo3 +TYPO3_SETUP_CREATE_SITE=http://${W9_URL} +TYPO3_SERVER_TYPE=apache + +# Not used by default; enable only when needed: diff --git a/apps/typo3/CHANGELOG.md b/apps/typo3/CHANGELOG.md index 582cf46c5..a91608e11 100644 --- a/apps/typo3/CHANGELOG.md +++ b/apps/typo3/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG -## Release +## 2026-09-21 ### Fixes and Enhancements - +- Refresh `.env` to the current template layout and add the `TYPO3_*` first-run install variables. +- Normalize `docker-compose.yml` references to `${VAR}`, remove the image source comment, and annotate the published port. +- Regenerate `README.md` so the advertised version matches `13.4`. +- Add `tests/cases.yml` covering the TYPO3 backend login and install tool. +- Add upstream releases and official GitHub/docs references to `variables.json`. +- Pin the bundled MySQL dependency to the `8.4` LTS tag. +- Add non-interactive first-run install via `src/entrypoint.sh`; declare `W9_LOGIN_*` and `W9_URL_REPLACE`. +- Add a healthcheck to the main Typo3 container. diff --git a/apps/typo3/Notes.md b/apps/typo3/Notes.md deleted file mode 100644 index 194870116..000000000 --- a/apps/typo3/Notes.md +++ /dev/null @@ -1,3 +0,0 @@ -# Typo3 - -安装参考:https://github.com/martin-helmich/docker-typo3 diff --git a/apps/typo3/README.md b/apps/typo3/README.md index 3ddba87fe..0496df7a8 100644 --- a/apps/typo3/README.md +++ b/apps/typo3/README.md @@ -1,26 +1,98 @@ -# Typo3 on Docker +# Typo3 on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Typo3: +## Quick Start +### Deploy Verification - - community: 12.4, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Typo3**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### First-run install -## System Requirements +The package runs TYPO3's non-interactive `setup` on first startup, so the database schema, a basic site, and the administrator account are created automatically: -The following are the minimal [recommended requirements](https://docs.typo3.org/m/typo3/tutorial-getting-started/11.5/en-us/Installation/Index.html): +1. Open the app URL and sign in to the backend at `/typo3/` with `W9_LOGIN_USER` / `W9_LOGIN_PASSWORD` from `.env`. +2. Start building content under the auto-generated site. -* **RAM**: 4 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +If `TYPO3_SETUP_ADMIN_PASSWORD` is left empty, first startup falls back to the browser install tool at `/typo3/install.php`; use the bundled MySQL service (host `typo3-mysql`, database/user `typo3`, password `W9_POWER_PASSWORD`). -## Install +### Change Password -You can install this Typo3 by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +`W9_LOGIN_PASSWORD` is applied only during the first-run install. To change it later, sign in to the TYPO3 backend at `/typo3/` and update the administrator password under **User Settings**, or reset it from the install tool. + -If you want use Typo3 with **Websoft9 Business Support** free, you can [subscribe Typo3](https://www.websoft9.com/apps) on Cloud platform +## Configuration Reference -## Documentation +Websoft9 packages this app from the official [Typo3 Docker image](https://hub.docker.com/r/martinhelmich/typo3) and makes some improvements below. -[Typo3 Administrator Guide](https://support.websoft9.com/docs/typo3) powered by Websoft9 \ No newline at end of file + + + + +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 13.4, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 80 | + + +### Data Directory + + +- `typo3fileadmin` → `/var/www/html/fileadmin` +- `typo3conf` → `/var/www/html/typo3conf` +- `typo3uploads` → `/var/www/html/uploads` +- `typo3temp` → `/var/www/html/typo3temp` +- `mysql_data` → `/var/lib/mysql` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +Note: `TYPO3_DB_DRIVER`, `TYPO3_DB_HOST`, `TYPO3_DB_PORT`, `TYPO3_DB_DBNAME`, `TYPO3_DB_USERNAME`, `TYPO3_DB_PASSWORD`, `TYPO3_SETUP_ADMIN_USERNAME`, `TYPO3_SETUP_ADMIN_PASSWORD`, `TYPO3_SETUP_ADMIN_EMAIL`, `TYPO3_PROJECT_NAME`, `TYPO3_SETUP_CREATE_SITE`, `TYPO3_SERVER_TYPE` take effect on first startup only; changing them after deployment may not take effect until the app is re-initialized. + + +### Configuration Files + + +Configuration is overridden by mounting `./src/entrypoint.sh` to `/usr/local/bin/websoft9-entrypoint.sh`. + + +## References + +- [Typo3 Administrator Guide](https://support.websoft9.com/docs/typo3) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/martinhelmich/typo3) + +- [Releases](https://github.com/TYPO3/typo3/releases) + +- [GitHub docs](https://github.com/martin-helmich/docker-typo3) + +- [GitHub docs](https://github.com/TYPO3/typo3) + +- [Official docs](https://docs.typo3.org/) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/typo3/docker-compose.yml b/apps/typo3/docker-compose.yml index 7f00a7643..518111f57 100644 --- a/apps/typo3/docker-compose.yml +++ b/apps/typo3/docker-compose.yml @@ -1,31 +1,38 @@ -# image and compose: https://hub.docker.com/r/martinhelmich/typo3 - -version: '3.8' - services: typo3: image: ${W9_REPO}:${W9_VERSION} container_name: ${W9_ID} + restart: unless-stopped + env_file: .env + entrypoint: ["/bin/bash", "/usr/local/bin/websoft9-entrypoint.sh"] + command: ["apache2-foreground"] ports: - - ${W9_HTTP_PORT_SET}:80 + - "${W9_HTTP_PORT_SET}:80" # Web Console volumes: + - ./src/entrypoint.sh:/usr/local/bin/websoft9-entrypoint.sh:ro - typo3fileadmin:/var/www/html/fileadmin - typo3conf:/var/www/html/typo3conf - typo3uploads:/var/www/html/uploads - typo3temp:/var/www/html/typo3temp - restart: unless-stopped - env_file: .env - + depends_on: + - mysql + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/typo3/install.php >/dev/null || exit 1"] + interval: 10s + timeout: 10s + retries: 12 + start_period: 120s + mysql: - image: mysql:$W9_DB_VERSION + image: mysql:${W9_DB_VERSION} container_name: ${W9_ID}-mysql restart: unless-stopped command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci] volumes: - mysql_data:/var/lib/mysql environment: - MYSQL_DATABASE: typo3 - MYSQL_USER: typo3 + MYSQL_DATABASE: ${W9_ID} + MYSQL_USER: ${W9_ID} MYSQL_PASSWORD: ${W9_POWER_PASSWORD} MYSQL_ROOT_PASSWORD: ${W9_POWER_PASSWORD} @@ -39,4 +46,4 @@ volumes: typo3conf: typo3uploads: typo3temp: - mysql_data: \ No newline at end of file + mysql_data: diff --git a/apps/typo3/src/entrypoint.sh b/apps/typo3/src/entrypoint.sh new file mode 100644 index 000000000..cb82726bb --- /dev/null +++ b/apps/typo3/src/entrypoint.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -e + +TYPO3_CLI="/var/www/html/typo3/sysext/core/bin/typo3" +SETTINGS_FILE="/var/www/html/typo3conf/system/settings.php" +FIRST_INSTALL_FILE="/var/www/html/FIRST_INSTALL" +DB_HOST="${TYPO3_DB_HOST:-localhost}" +DB_PORT="${TYPO3_DB_PORT:-3306}" +MAX_RETRIES="${TYPO3_SETUP_MAX_RETRIES:-60}" +RETRY_INTERVAL="${TYPO3_SETUP_RETRY_INTERVAL:-5}" + +if [ -n "${TYPO3_SETUP_ADMIN_PASSWORD:-}" ] && [ ! -f "$SETTINGS_FILE" ]; then + echo "websoft9: waiting for database at ${DB_HOST}:${DB_PORT} ..." + retries=0 + until php -r "@\$sock = fsockopen('${DB_HOST}', (int)'${DB_PORT}', \$errno, \$errstr, 1); if (!\$sock) { exit(1); } fclose(\$sock);" >/dev/null 2>&1; do + retries=$((retries + 1)) + if [ "$retries" -ge "$MAX_RETRIES" ]; then + echo "websoft9: database not reachable after $((MAX_RETRIES * RETRY_INTERVAL))s; leaving the browser install tool in place." + break + fi + sleep "$RETRY_INTERVAL" + done + + if [ "$retries" -lt "$MAX_RETRIES" ]; then + echo "websoft9: running non-interactive TYPO3 setup ..." + runuser -u www-data -- "$TYPO3_CLI" setup --force --no-interaction --server-type="${TYPO3_SERVER_TYPE:-apache}" + rm -f "$FIRST_INSTALL_FILE" + echo "websoft9: TYPO3 setup complete." + fi +fi + +if [ "$#" -eq 0 ]; then + set -- apache2-foreground +fi + +exec docker-php-entrypoint "$@" diff --git a/apps/typo3/tests/cases.yml b/apps/typo3/tests/cases.yml new file mode 100644 index 000000000..d8324d581 --- /dev/null +++ b/apps/typo3/tests/cases.yml @@ -0,0 +1,12 @@ +skip: + - id: web-access + +optional: + - id: backend-login + type: web-access + path: /typo3/ + expect_status: [200] + - id: install-tool + type: web-access + path: /typo3/install.php + expect_status: [200] diff --git a/apps/typo3/variables.json b/apps/typo3/variables.json index ebf72f2ad..a65eb7115 100644 --- a/apps/typo3/variables.json +++ b/apps/typo3/variables.json @@ -2,6 +2,15 @@ "name": "typo3", "trademark": "Typo3", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/martinhelmich/typo3", + "releases": "https://github.com/TYPO3/typo3/releases", + "docs": [ + "https://github.com/martin-helmich/docker-typo3", + "https://github.com/TYPO3/typo3", + "https://docs.typo3.org/" + ] + }, "edition": [ { "dist": "community", @@ -11,12 +20,36 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 80, + "path": "/" + }, + "admin": { + "port": 80, + "path": "/typo3" + } + }, "requirements": { "cpu": "2", "memory": "4", "disk": "1" }, - "upstream": { - "image": "https://hub.docker.com/r/martinhelmich/typo3" + "env": { + "first_startup_only": [ + "TYPO3_DB_DRIVER", + "TYPO3_DB_HOST", + "TYPO3_DB_PORT", + "TYPO3_DB_DBNAME", + "TYPO3_DB_USERNAME", + "TYPO3_DB_PASSWORD", + "TYPO3_SETUP_ADMIN_USERNAME", + "TYPO3_SETUP_ADMIN_PASSWORD", + "TYPO3_SETUP_ADMIN_EMAIL", + "TYPO3_PROJECT_NAME", + "TYPO3_SETUP_CREATE_SITE", + "TYPO3_SERVER_TYPE" + ] } } diff --git a/apps/umami/.env b/apps/umami/.env index eee18e82b..5b1ff7de6 100644 --- a/apps/umami/.env +++ b/apps/umami/.env @@ -1,19 +1,51 @@ -W9_DIST='community' -W9_REPO=umamisoftware/umami -W9_VERSION='3.0.3' -W9_POWER_PASSWORD='2F!XSIah4D5zhW5P' +W9_REPO=ghcr.io/umami-software/umami +W9_DIST=community +W9_VERSION=3.4 + +W9_POWER_PASSWORD="2F!XSIah4D5zhW5P" + #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='umami' + +W9_ID=umami + +# Web/internal ports W9_HTTP_PORT=3000 -W9_HTTP_PORT_SET='9001' -W9_URL='appname.example.com' +W9_HTTP_PORT_SET=9003 + +# Built-in login and URL helpers W9_LOGIN_USER=admin W9_LOGIN_PASSWORD=umami +W9_URL=appname.example.com + +# Bundled database W9_DB_EXPOSE="postgresql" -W9_RCODE='2GZ5ITB00lhq5' +W9_DB_VERSION=15 + +# Shared secondary secret used by the bundled database and the app +W9_RCODE="2GZ5ITB00lhq5" + W9_NETWORK=websoft9 -#### --------------------------------------------------------------------------------------- #### - -DATABASE_URL="postgresql://umami:$W9_RCODE@$W9_ID-postgresql:5432/umami" + +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Umami image environment variables +# Docs: https://umami.is/docs/environment-variables +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: + +DATABASE_URL=postgresql://umami:${W9_RCODE}@${W9_ID}-postgresql:5432/umami DATABASE_TYPE=postgresql -APP_SECRET=a1B2c3D4e5F6g-$W9_POWER_PASSWORD +APP_SECRET=a1B2c3D4e5F6g-${W9_POWER_PASSWORD} +# Required for two-factor authentication; 64 hex characters (openssl rand -hex 32) +TWO_FACTOR_ENCRYPTION_KEY=8355138b8f5ee8f53d96fe785db6c384cf9b27373747aa9aa5d70c7769f960e9 + +# Not used by default; enable only when needed: +# BASE_PATH=/analytics +# REDIS_URL=redis://${W9_ID}-redis:6379 +# SKIP_DB_MIGRATION=1 +# MCP_ENABLED=1 diff --git a/apps/umami/CHANGELOG.md b/apps/umami/CHANGELOG.md index 582cf46c5..709537bd3 100644 --- a/apps/umami/CHANGELOG.md +++ b/apps/umami/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-20 +- Update Umami to 3.4 and switch the image to the official `ghcr.io/umami-software/umami`. +- Add `TWO_FACTOR_ENCRYPTION_KEY` required by two-factor authentication. +- Model the bundled PostgreSQL version with `W9_DB_VERSION` and wait for a healthy database before starting the app. +- Default the web port to 9003; refresh `variables.json`, README, Notes and add a login/health test. diff --git a/apps/umami/Notes.md b/apps/umami/Notes.md deleted file mode 100644 index 713652830..000000000 --- a/apps/umami/Notes.md +++ /dev/null @@ -1,8 +0,0 @@ -# Plausible - -## to do - -* 密码随机化 -* 数据库规则化表达 - -## FAQ diff --git a/apps/umami/README.md b/apps/umami/README.md index 0f23889fb..23b5866ed 100644 --- a/apps/umami/README.md +++ b/apps/umami/README.md @@ -1,26 +1,94 @@ -# Umami on Docker +# Umami on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Umami: +## Quick Start +### Deploy Verification - - community: 2.17.0, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Umami**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Open the Umami console and sign in with `admin` / `umami`. +2. Add a website, then copy the tracking script into your site. +3. Open the website report to confirm data is being collected. -The following are the minimal [recommended requirements](https://umami.is/docs/install): +### Change Password -* **RAM**: 1 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +1. Sign in to Umami and open **Settings → Profile**. +2. Change the password there; the default `admin` / `umami` account is not controlled by `.env`. + -## Install +## Configuration Reference -You can install this Umami by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Umami Docker image](https://ghcr.io/umami-software/umami) and makes some improvements below. -If you want use Umami with **Websoft9 Business Support** free, you can [subscribe Umami](https://www.websoft9.com/apps) on Cloud platform + +- The admin account is seeded on first start as `admin` / `umami`; change it after the first login. +- `DATABASE_URL` points at the bundled PostgreSQL service `${W9_ID}-postgresql`. +- `TWO_FACTOR_ENCRYPTION_KEY` (64 hex characters) must be set to use two-factor authentication. +- Database migrations run automatically on container start. + -## Documentation +Apps run as containers; rebuild after any configuration change. -[Umami Administrator Guide](https://support.websoft9.com/docs/umami) powered by Websoft9 \ No newline at end of file +### Version Support + +Supported versions: 3.4, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 3000 | + + +### Data Directory + + +Data is persisted in the `postgresql` volume, mounted at `/var/lib/postgresql/data`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration is overridden by mounting `./src/postgresql_init.sh` to `/docker-entrypoint-initdb.d/postgresql_init.sh`. + + +## References + +- [Umami Administrator Guide](https://support.websoft9.com/docs/umami) by Websoft9 + +- [GHCR image](https://ghcr.io/umami-software/umami) + +- [Releases](https://github.com/umami-software/umami/releases) + +- [Official compose](https://raw.githubusercontent.com/umami-software/umami/v3.4.0/docker-compose.yml) + +- [Official docs](https://umami.is/docs/install) + +- [Official docs](https://umami.is/docs/environment-variables) + + + +## Troubleshooting + +**Container stays unhealthy?** +- The first start runs database migrations; wait a minute and check `docker compose logs ${W9_ID}`. + +**Login fails with the default credentials?** +- The `admin` / `umami` account exists only on a fresh database. If it was changed, reset it from the database or recreate the stack. + +**Database connection error?** +- Confirm `${W9_ID}-postgresql` is healthy and that `DATABASE_URL` matches the bundled database. + diff --git a/apps/umami/docker-compose.yml b/apps/umami/docker-compose.yml index 97f1ca4b1..c00aaaa51 100644 --- a/apps/umami/docker-compose.yml +++ b/apps/umami/docker-compose.yml @@ -1,32 +1,31 @@ -# image: https://github.com/plausible/hosting -# docs: https://umami.is/docs/install -# compose: https://github.com/umami-software/umami/blob/master/docker-compose.yml - -version: '3.8' services: umami: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} + restart: unless-stopped + init: true env_file: - .env ports: - - $W9_HTTP_PORT_SET:3000 + - "${W9_HTTP_PORT_SET}:3000" # Web Console depends_on: - - postgresql - restart: unless-stopped + postgresql: + condition: service_healthy healthcheck: - test: ["CMD-SHELL", "curl http://localhost:3000/api/heartbeat"] + test: ["CMD-SHELL", "curl -fsS http://localhost:3000/api/heartbeat"] interval: 5s timeout: 5s retries: 5 + start_period: 30s postgresql: - image: postgres:15-alpine - container_name: $W9_ID-postgresql + image: postgres:${W9_DB_VERSION}-alpine + container_name: ${W9_ID}-postgresql + restart: unless-stopped environment: POSTGRES_DB: umami POSTGRES_USER: postgres - POSTGRES_PASSWORD: $W9_POWER_PASSWORD + POSTGRES_PASSWORD: ${W9_POWER_PASSWORD} POSTGRES_UMAMI_PASSWORD: ${W9_RCODE} volumes: - postgresql:/var/lib/postgresql/data @@ -36,7 +35,8 @@ services: interval: 5s timeout: 5s retries: 5 - + start_period: 20s + networks: default: name: ${W9_NETWORK} diff --git a/apps/umami/tests/cases.yml b/apps/umami/tests/cases.yml new file mode 100644 index 000000000..6475a9b85 --- /dev/null +++ b/apps/umami/tests/cases.yml @@ -0,0 +1,7 @@ +# The adaptive checks cover the compose config, container state, the compose +# healthcheck (/api/heartbeat) and the web root. The authenticated path is the +# core of Umami, so it is exercised explicitly. +custom: + - id: login-api + type: script + script: check.sh diff --git a/apps/umami/tests/check.sh b/apps/umami/tests/check.sh new file mode 100644 index 000000000..0861c0b44 --- /dev/null +++ b/apps/umami/tests/check.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -uo pipefail + +# BASE_URL is provided by `libs app-tests`; no package-specific variables needed. +base="${BASE_URL:?BASE_URL is required}" +# Umami seeds this admin account in the database migration; it is not controlled +# by the package .env, so the smoke test uses the documented default. +user="admin" +password="umami" +deadline=$((SECONDS + 240)) + +code="000" +while [ "$SECONDS" -lt "$deadline" ]; do + code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "${base}/api/heartbeat" || true) + [ "$code" = "200" ] && break + sleep 5 +done + +if [ "$code" != "200" ]; then + echo "umami heartbeat -> ${code} (timeout)" + exit 1 +fi +echo "umami heartbeat -> 200" + +body=$(curl -s --max-time 15 -X POST "${base}/api/auth/login" \ + -H 'Content-Type: application/json' \ + -d "{\"username\":\"${user}\",\"password\":\"${password}\"}" || true) + +if printf '%s' "$body" | grep -q '"token"'; then + echo "umami login ok" + exit 0 +fi + +echo "umami login failed: ${body}" +exit 1 diff --git a/apps/umami/variables.json b/apps/umami/variables.json index 81f2e6121..eec318510 100644 --- a/apps/umami/variables.json +++ b/apps/umami/variables.json @@ -2,21 +2,35 @@ "name": "umami", "trademark": "Umami", "release": true, + "upstream": { + "image": "https://ghcr.io/umami-software/umami", + "releases": "https://github.com/umami-software/umami/releases", + "compose": { + "compose": "https://raw.githubusercontent.com/umami-software/umami/v3.4.0/docker-compose.yml" + }, + "docs": [ + "https://umami.is/docs/install", + "https://umami.is/docs/environment-variables" + ] + }, "edition": [ { "dist": "community", "version": [ - "3.0.3", + "3.4", "latest" ] } ], + "access": { + "web": { + "port": 3000, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "1" - }, - "upstream": { - "image": "https://hub.docker.com/r/umamisoftware/umami" } } diff --git a/apps/umbraco/.env b/apps/umbraco/.env index 05b66c29d..39cbe8914 100644 --- a/apps/umbraco/.env +++ b/apps/umbraco/.env @@ -1,3 +1,46 @@ -W9_POWER_PASSWORD=spJNF09yzwWJaG! +W9_REPO=websoft9dev/umbraco +W9_DIST=community +W9_VERSION=18.2.0 + +# Optional password seed: enable only when the package actually controls a DB or built-in login. +# See docs/w9-env-spec.md for when W9_POWER_PASSWORD is appropriate. +W9_POWER_PASSWORD='spJNF09yzwWJaG!' + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + +#### -- Not allowed to edit below environments when recreate app based on existing data -- #### + +W9_ID=umbraco +W9_HTTPS_PORT=8443 +W9_HTTPS_PORT_SET=9001 + +# Built-in login and URL helpers: keep these in the protected block because the appstore parser +# reads them from the app package metadata surface. +W9_LOGIN_USER=admin@example.com +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} +W9_URL=umbraco.example.com +W9_URL_REPLACE=true +W9_ADMIN_PATH="/umbraco" + W9_NETWORK=websoft9 -W9_NAME=umbraco + +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Umbraco image environment variables +# Docs: https://docs.umbraco.com/umbraco-cms/get-started/installation/running-umbraco-on-docker-locally +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +UMBRACO_DB_DSN=Data Source=/app/umbraco/Data/Umbraco.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True +UMBRACO_HTTPS_CERT_PASSWORD=umbraco-selfsigned + +# Not used by default; enable only when needed: +# Umbraco__CMS__Runtime__Mode=Production +# Umbraco__CMS__Hosting__Debug=false +# Umbraco__CMS__Unattended__UnattendedTelemetryLevel=Basic +# Umbraco__CMS__HealthChecks__DisabledChecks__0__Id=E2048C48-21C5-4BE1-A80B-8062162DF124 diff --git a/apps/umbraco/CHANGELOG.md b/apps/umbraco/CHANGELOG.md index 582cf46c5..5cb76a03e 100644 --- a/apps/umbraco/CHANGELOG.md +++ b/apps/umbraco/CHANGELOG.md @@ -1,5 +1,22 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-20 +- Rebuild the Umbraco package from the official Umbraco Docker guidance as a self-contained app. +- Build the Umbraco CMS 18.2.0 image from `Umbraco.Templates` on `mcr.microsoft.com/dotnet/aspnet:10.0` via `Dockerfile`. +- Use SQLite for storage (`/app/umbraco/Data/Umbraco.sqlite.db`) with a persisted data volume; no external database service. +- Create the administrator account on first start through Umbraco unattended install using `W9_LOGIN_USER` / `W9_LOGIN_PASSWORD`. +- Add `tests/cases.yml` with a backoffice reachability check. +## 2026-09-22 +- Disable OpenIddict's HTTPS transport requirement in the generated Umbraco app so backoffice login can work on HTTP-only deployments. +- Keep ASP.NET Core forwarded headers enabled for reverse-proxy deployments while restoring the package application URL to `http://${W9_URL}`. +- Remove local image build instructions from `docker-compose.yml` so runtime deploys use the published `${W9_REPO}:${W9_VERSION}` image directly. +- Bump the package/image version to `18.2.0-hotfix.1` and strip the hotfix suffix inside `Dockerfile` so image tags can change without breaking the upstream Umbraco template install version. + +## 2026-09-23 +- Switch the package to HTTPS-by-default using a self-signed certificate generated at container start and persisted under `/app/umbraco/certs`. +- Restore Umbraco's HTTPS enforcement for backoffice authentication while keeping the stable package/image tag at `18.2.0`. +- Add Serilog console output so application logs appear in `docker logs` while preserving the existing Umbraco file sink. +- Simplify the Dockerfile by removing the temporary hotfix tag parsing and the unnecessary `apt-get install openssl` layer. +- Switch `dotnet new install` to the modern `@` version syntax and add standard OCI image labels. +- Declare `access.defaultScheme` as `https` for machine-readable HTTPS metadata. diff --git a/apps/umbraco/Dockerfile b/apps/umbraco/Dockerfile new file mode 100644 index 000000000..718441e35 --- /dev/null +++ b/apps/umbraco/Dockerfile @@ -0,0 +1,35 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build + +ARG UMBRACO_VERSION=18.2.0 + +RUN dotnet new install Umbraco.Templates@${UMBRACO_VERSION} + +WORKDIR /src +RUN dotnet new umbraco -n UmbracoApp -o UmbracoApp --no-restore +COPY src/appsettings.Production.json /src/UmbracoApp/appsettings.Production.json +RUN dotnet publish UmbracoApp/UmbracoApp.csproj -c Release -o /app/publish /p:UseAppHost=false + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final + +ARG UMBRACO_VERSION=18.2.0 + +LABEL org.opencontainers.image.authors="https://www.websoft9.com" \ + org.opencontainers.image.description="Umbraco packaged by Websoft9" \ + org.opencontainers.image.source="https://github.com/Websoft9/docker-library/tree/main/apps/umbraco" \ + org.opencontainers.image.title="umbraco" \ + org.opencontainers.image.vendor="Websoft9" \ + org.opencontainers.image.version="${UMBRACO_VERSION}" + +WORKDIR /app +COPY --from=build /app/publish . +COPY src/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh + +RUN chmod +x /usr/local/bin/docker-entrypoint.sh \ + && mkdir -p /app/umbraco/Data /app/umbraco/Logs /app/umbraco/certs /app/wwwroot/media + +ENV ASPNETCORE_URLS="https://+:8443;http://+:8080" \ + ASPNETCORE_ENVIRONMENT=Production + +EXPOSE 8080 8443 + +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] diff --git a/apps/umbraco/Notes.md b/apps/umbraco/Notes.md deleted file mode 100644 index a6cc33a98..000000000 --- a/apps/umbraco/Notes.md +++ /dev/null @@ -1,4 +0,0 @@ -# Umbraco - -https://our.umbraco.com/documentation/Fundamentals/Setup/Install/ - diff --git a/apps/umbraco/README.md b/apps/umbraco/README.md index 12dca39bd..6f0ec1c1f 100644 --- a/apps/umbraco/README.md +++ b/apps/umbraco/README.md @@ -1,26 +1,97 @@ -# Umbraco on Docker +# Umbraco on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Umbraco: +## Quick Start +### Deploy Verification - - community: latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Umbraco**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Open `https://:${W9_HTTPS_PORT_SET}` from the **Access** tab and accept the self-signed certificate warning. +2. Sign in to the backoffice at `/umbraco` with the credentials from the **Access** tab. +3. Create your first content node to confirm the publishing flow. -The following are the minimal [recommended requirements](https://umbraco.com/): +### Change Password -* **RAM**: 4 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +1. Change the administrator password from the user profile inside the Umbraco backoffice. +2. `W9_LOGIN_USER` and `W9_LOGIN_PASSWORD` create the administrator on first start only; changing them afterwards requires updating the user in the backoffice or removing the `umbraco_data` volume and rebuilding. + -## Install +## Configuration Reference -You can install this Umbraco by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Umbraco Docker image](https://hub.docker.com/r/websoft9dev/umbraco) and makes some improvements below. -If you want use Umbraco with **Websoft9 Business Support** free, you can [subscribe Umbraco](https://www.websoft9.com/apps) on Cloud platform + -## Documentation +- Umbraco runs HTTPS by default with a self-signed certificate generated on first start and stored in the `umbraco_data` volume. +- Application logs are written to both `docker logs` and the persisted Umbraco log files under `/app/umbraco/Logs`. -[Umbraco Administrator Guide](https://support.websoft9.com/docs/umbraco) powered by Websoft9 \ No newline at end of file + + +`docker compose up` uses the prebuilt image `${W9_REPO}:${W9_VERSION}`. The local `Dockerfile` is kept for separate image build/publish workflows and is not invoked by the runtime compose file. + +Apps run as containers; recreate after any configuration change. + +### Version Support + +Supported versions: 18.2.0. + + +### Ports + +| Purpose | Port | +| --- | --- | +| HTTPS | 8443 | + + +### Data Directory + + +- `umbraco_data` → `/app/umbraco` +- `umbraco_media` → `/app/wwwroot/media` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +Note: `W9_LOGIN_USER`, `W9_LOGIN_PASSWORD` take effect on first startup only; changing them after deployment may not take effect until the app is re-initialized. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [Umbraco Administrator Guide](https://support.websoft9.com/docs/umbraco) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/websoft9dev/umbraco) + +- [Releases](https://github.com/umbraco/Umbraco-CMS/releases) + +- [Official docs](https://docs.umbraco.com/umbraco-cms/get-started/installation/running-umbraco-on-docker-locally) + +- [Official docs](https://docs.umbraco.com/umbraco-cms/run-in-production/infrastructure-and-ops/server-setup/running-umbraco-in-docker) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Certificate warning in the browser?** +- The package serves a self-signed certificate by default. Open `https://:${W9_HTTPS_PORT_SET}` and accept the warning, or replace it with your own certificate at the reverse proxy layer. + +**Port not reachable?** +- Ensure the firewall / security group allows the HTTPS port. + diff --git a/apps/umbraco/docker-compose.yml b/apps/umbraco/docker-compose.yml index 60c04ffe1..959970ad3 100644 --- a/apps/umbraco/docker-compose.yml +++ b/apps/umbraco/docker-compose.yml @@ -1,11 +1,34 @@ -version: '3.8' - services: - -networks: - default: - name: ${W9_NAME} + + umbraco: + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} + restart: unless-stopped + env_file: .env + ports: + - "${W9_HTTPS_PORT_SET}:8443" # HTTPS + environment: + ASPNETCORE_URLS: https://+:8443;http://+:8080 + ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" + ASPNETCORE_Kestrel__Certificates__Default__Path: /app/umbraco/certs/umbraco-selfsigned.pfx + ASPNETCORE_Kestrel__Certificates__Default__Password: ${UMBRACO_HTTPS_CERT_PASSWORD} + ConnectionStrings__umbracoDbDSN: "${UMBRACO_DB_DSN}" + ConnectionStrings__umbracoDbDSN_ProviderName: Microsoft.Data.Sqlite + Umbraco__CMS__Global__UseHttps: "true" + Umbraco__CMS__WebRouting__UmbracoApplicationUrl: https://${W9_URL} + Umbraco__CMS__Unattended__InstallUnattended: "true" + Umbraco__CMS__Unattended__UnattendedUserName: Administrator + Umbraco__CMS__Unattended__UnattendedUserEmail: ${W9_LOGIN_USER} + Umbraco__CMS__Unattended__UnattendedUserPassword: ${W9_LOGIN_PASSWORD} + volumes: + - umbraco_data:/app/umbraco + - umbraco_media:/app/wwwroot/media volumes: - mysql: - suitecrm: + umbraco_data: + umbraco_media: + +networks: + default: + name: ${W9_NETWORK} + external: true diff --git a/apps/umbraco/src/appsettings.Production.json b/apps/umbraco/src/appsettings.Production.json new file mode 100644 index 000000000..7ead111bd --- /dev/null +++ b/apps/umbraco/src/appsettings.Production.json @@ -0,0 +1,22 @@ +{ + "Serilog": { + "WriteTo": [ + { + "Name": "Async", + "Args": { + "configure": [ + { + "Name": "Console" + } + ] + } + }, + { + "Name": "UmbracoFile", + "Args": { + "Enabled": "True" + } + } + ] + } +} diff --git a/apps/umbraco/src/docker-entrypoint.sh b/apps/umbraco/src/docker-entrypoint.sh new file mode 100644 index 000000000..6bc285ab9 --- /dev/null +++ b/apps/umbraco/src/docker-entrypoint.sh @@ -0,0 +1,64 @@ +#!/bin/sh +set -eu + +cert_dir="${UMBRACO_CERT_DIR:-/app/umbraco/certs}" +cert_path="${UMBRACO_CERT_PATH:-${cert_dir}/umbraco-selfsigned.pfx}" +host_file="${cert_dir}/.hostname" +host_raw="${W9_URL:-localhost}" +host_name="${host_raw%%:*}" +cert_password="${ASPNETCORE_Kestrel__Certificates__Default__Password:-${UMBRACO_HTTPS_CERT_PASSWORD:-umbraco-selfsigned}}" + +mkdir -p "${cert_dir}" + +needs_regen=0 +if [ ! -f "${cert_path}" ]; then + needs_regen=1 +fi +if [ ! -f "${host_file}" ] || [ "$(cat "${host_file}" 2>/dev/null || true)" != "${host_name}" ]; then + needs_regen=1 +fi + +if [ "${needs_regen}" -eq 1 ]; then + tmpdir="$(mktemp -d)" + trap 'rm -rf "${tmpdir}"' EXIT + + san_entries="DNS:localhost,IP:127.0.0.1" + if printf '%s' "${host_name}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then + san_entries="${san_entries},IP:${host_name}" + else + san_entries="${san_entries},DNS:${host_name}" + fi + + cat > "${tmpdir}/openssl.cnf" </dev/null 2>&1 + + openssl pkcs12 -export \ + -out "${cert_path}" \ + -inkey "${tmpdir}/key.pem" \ + -in "${tmpdir}/cert.pem" \ + -passout pass:"${cert_password}" >/dev/null 2>&1 + + printf '%s' "${host_name}" > "${host_file}" + trap - EXIT + rm -rf "${tmpdir}" +fi + +exec dotnet UmbracoApp.dll diff --git a/apps/umbraco/tests/cases.yml b/apps/umbraco/tests/cases.yml new file mode 100644 index 000000000..25ecd97c0 --- /dev/null +++ b/apps/umbraco/tests/cases.yml @@ -0,0 +1,9 @@ +# Umbraco serves the backoffice over HTTPS with a self-signed certificate, so +# the adaptive HTTP web-access check does not apply. +skip: + - id: web-access + +optional: + - id: backoffice-https + type: script + script: check.sh diff --git a/apps/umbraco/tests/check.sh b/apps/umbraco/tests/check.sh new file mode 100644 index 000000000..bb58bc994 --- /dev/null +++ b/apps/umbraco/tests/check.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -uo pipefail + +port="${W9_HTTPS_PORT_SET:-9001}" +base="${BASE_URL:-https://localhost:${port}}" +user="${W9_LOGIN_USER:-admin@example.com}" +password="${W9_LOGIN_PASSWORD:-}" +deadline=$((SECONDS + 300)) +code="000" + +while [ "$SECONDS" -lt "$deadline" ]; do + code=$(curl -k -s -o /dev/null -w "%{http_code}" --max-time 15 "${base}/umbraco/" || true) + case "$code" in + 200|301|302) + break + ;; + esac + sleep 5 +done + +if [ "$code" != "200" ] && [ "$code" != "301" ] && [ "$code" != "302" ]; then + echo "umbraco backoffice ${base}/umbraco/ -> ${code} (timeout)" + exit 1 +fi +echo "umbraco backoffice ${base}/umbraco/ -> ${code}" + +if [ -z "$password" ]; then + echo "W9_LOGIN_PASSWORD is empty; skipping admin login check" + exit 0 +fi + +headers_file="$(mktemp)" +body_file="$(mktemp)" +trap 'rm -f "$headers_file" "$body_file"' EXIT + +login_code=$(curl -k -s -o "$body_file" -D "$headers_file" -w "%{http_code}" --max-time 15 \ + -X POST "${base}/umbraco/management/api/v1/security/back-office/login" \ + -H 'Content-Type: application/json' \ + --data "{\"username\":\"${user}\",\"password\":\"${password}\"}" || true) + +if [ "$login_code" != "200" ]; then + echo "umbraco admin login -> ${login_code}" + sed -n '1,80p' "$body_file" + exit 1 +fi + +if ! grep -qi '^Set-Cookie: UMB_UCONTEXT=' "$headers_file"; then + echo "umbraco admin login -> 200 but UMB_UCONTEXT cookie missing" + sed -n '1,80p' "$headers_file" + exit 1 +fi + +echo "umbraco admin login -> ${login_code}" diff --git a/apps/umbraco/variables.json b/apps/umbraco/variables.json index b78c650ad..8644af23e 100644 --- a/apps/umbraco/variables.json +++ b/apps/umbraco/variables.json @@ -1,18 +1,47 @@ { "name": "umbraco", "trademark": "Umbraco", - "release": false, + "release": true, + "upstream": { + "image": "https://hub.docker.com/r/websoft9dev/umbraco", + "releases": "https://github.com/umbraco/Umbraco-CMS/releases", + "docs": [ + "https://docs.umbraco.com/umbraco-cms/get-started/installation/running-umbraco-on-docker-locally", + "https://docs.umbraco.com/umbraco-cms/run-in-production/infrastructure-and-ops/server-setup/running-umbraco-in-docker" + ] + }, "edition": [ { "dist": "community", "version": [ - "latest" + "18.2.0" ] } ], + "access": { + "defaultScheme": "https", + "web": { + "port": 8443, + "path": "/" + }, + "admin": { + "port": 8443, + "path": "/umbraco" + } + }, "requirements": { "cpu": "2", "memory": "4", - "disk": "1" + "disk": "4" + }, + "credentials": {}, + "env": { + "first_startup_only": [ + "W9_LOGIN_USER", + "W9_LOGIN_PASSWORD" + ] + }, + "help": { + "db": "Bundled SQLite database; the administrator account is created on first start." } } diff --git a/apps/uptimekuma/.env b/apps/uptimekuma/.env index 389194099..cf6bd0758 100644 --- a/apps/uptimekuma/.env +++ b/apps/uptimekuma/.env @@ -1,19 +1,40 @@ - - W9_REPO=louislam/uptime-kuma -W9_DIST='community' +W9_DIST=community +W9_VERSION=2.5.5 -# latest version is 1 -W9_VERSION='2.0.2' +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='uptimekuma' -W9_HTTP_PORT_SET='9001' + +W9_ID=uptimekuma + +# Web/internal ports: uncomment the ones the package actually uses. W9_HTTP_PORT=3001 -W9_URL='example.yourdomain.com' +W9_HTTP_PORT_SET=9001 + +# Built-in login and URL helpers: keep these in the protected block because the appstore parser +# reads them from the app package metadata surface. +# Uptime Kuma creates the first admin account interactively in the browser, so no login pair is seeded. +W9_URL=example.yourdomain.com + W9_NETWORK=websoft9 #### ----------------------------------------------------------------------------------------- #### +# ============================================================ +# Uptime Kuma image environment variables +# Docs: https://github.com/louislam/uptime-kuma/wiki/Environment-Variables +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: -# Below environment is created by uptime-kuma: https://github.com/louislam/uptime-kuma/wiki/Environment-Variables +# Not used by default; enable only when needed: +# UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN=false +# UPTIME_KUMA_WS_ORIGIN_CHECK=cors-like +# UPTIME_KUMA_SQLITE_SINGLE_CONNECTION=true +# NOTIFICATION_PROXY= +# NODE_TLS_REJECT_UNAUTHORIZED=0 diff --git a/apps/uptimekuma/CHANGELOG.md b/apps/uptimekuma/CHANGELOG.md index 582cf46c5..62be72ed9 100644 --- a/apps/uptimekuma/CHANGELOG.md +++ b/apps/uptimekuma/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-20 +- Updated `W9_VERSION` from `2.0.2` to `2.5.5` and aligned `variables.json.edition` with the current upstream tag (upstream publishes no `x.x` tag, so the patch pin is intentional). +- Normalized `.env` and `docker-compose.yml` to current repository policy: braced `${VAR}` references, a port purpose comment, no image/docs source comments, and no obsolete compose `version` key. +- Added `upstream.releases`, `upstream.docs`, and `variables.json.access`. +- Added `tests/cases.yml` with an entry-page API check. +- Regenerated `README.md` from `variables.json` and `docker-compose.yml`. diff --git a/apps/uptimekuma/Notes.md b/apps/uptimekuma/Notes.md deleted file mode 100644 index 1df64c597..000000000 --- a/apps/uptimekuma/Notes.md +++ /dev/null @@ -1 +0,0 @@ -# Uptime Kuma diff --git a/apps/uptimekuma/README.md b/apps/uptimekuma/README.md index 397424a00..e74213e5a 100644 --- a/apps/uptimekuma/README.md +++ b/apps/uptimekuma/README.md @@ -1,26 +1,83 @@ -# Uptime Kuma on Docker +# Uptime Kuma on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Uptime Kuma: +## Quick Start +### Deploy Verification - - community: 1.23.15, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Uptime Kuma**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Open the app URL and create the first admin account (Uptime Kuma creates it interactively in the browser). +2. Add a monitor to confirm monitoring works. -The following are the minimal [recommended requirements](https://github.com/louislam/uptime-kuma/wiki): +### Change Password -* **RAM**: 1 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +1. Sign in to the Uptime Kuma console. +2. Open **Settings** → **Security** and update the password. + -## Install +## Configuration Reference -You can install this Uptime Kuma by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Uptime Kuma Docker image](https://hub.docker.com/r/louislam/uptime-kuma) and makes some improvements below. -If you want use Uptime Kuma with **Websoft9 Business Support** free, you can [subscribe Uptime Kuma](https://www.websoft9.com/apps) on Cloud platform + -## Documentation + -[Uptime Kuma Administrator Guide](https://support.websoft9.com/docs/uptimekuma) powered by Websoft9 \ No newline at end of file +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 2.5.5, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 3001 | + + +### Data Directory + + +Data is persisted in the `uptime-kuma` volume, mounted at `/app/data`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [Uptime Kuma Administrator Guide](https://support.websoft9.com/docs/uptimekuma) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/louislam/uptime-kuma) + +- [Releases](https://github.com/louislam/uptime-kuma/releases) + +- [GitHub docs](https://github.com/louislam/uptime-kuma/wiki/Environment-Variables) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/uptimekuma/docker-compose.yml b/apps/uptimekuma/docker-compose.yml index 1da7d29fe..0305d971c 100644 --- a/apps/uptimekuma/docker-compose.yml +++ b/apps/uptimekuma/docker-compose.yml @@ -1,24 +1,18 @@ -# docs: https://github.com/louislam/uptime-kuma -# image: https://hub.docker.com/r/louislam/uptime-kuma -# compose: https://github.com/louislam/uptime-kuma/blob/master/compose.yaml - -version: '3.8' - services: uptime-kuma: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} restart: unless-stopped ports: - - $W9_HTTP_PORT_SET:3001 + - "${W9_HTTP_PORT_SET}:3001" # Web Console env_file: .env volumes: - uptime-kuma:/app/data -volumes: - uptime-kuma: - networks: default: - name: $W9_NETWORK + name: ${W9_NETWORK} external: true + +volumes: + uptime-kuma: diff --git a/apps/uptimekuma/tests/cases.yml b/apps/uptimekuma/tests/cases.yml new file mode 100644 index 000000000..01c9e884d --- /dev/null +++ b/apps/uptimekuma/tests/cases.yml @@ -0,0 +1,5 @@ +optional: + - id: entry-page-api + type: web-access + path: /api/entry-page + expect_status: 200 diff --git a/apps/uptimekuma/variables.json b/apps/uptimekuma/variables.json index 17be8c7e9..84aedd625 100644 --- a/apps/uptimekuma/variables.json +++ b/apps/uptimekuma/variables.json @@ -2,21 +2,31 @@ "name": "uptimekuma", "trademark": "Uptime Kuma", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/louislam/uptime-kuma", + "releases": "https://github.com/louislam/uptime-kuma/releases", + "docs": [ + "https://github.com/louislam/uptime-kuma/wiki/Environment-Variables" + ] + }, "edition": [ { "dist": "community", "version": [ - "2.0.2", - "1" + "2.5.5", + "latest" ] } ], + "access": { + "web": { + "port": 3001, + "path": "/" + } + }, "requirements": { "cpu": "1", "memory": "1", "disk": "1" - }, - "upstream": { - "image": "https://hub.docker.com/r/louislam/uptime-kuma" } } diff --git a/apps/varnish/.env b/apps/varnish/.env index 1f65ac8b8..5b25eca65 100644 --- a/apps/varnish/.env +++ b/apps/varnish/.env @@ -1,15 +1,32 @@ -W9_REPO="varnish" -W9_DIST='community' -W9_VERSION='8.0' -W9_HTTP_PORT_SET='9001' +W9_REPO=varnish +W9_DIST=community +W9_VERSION=9.0 + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='varnish' + +W9_ID=varnish W9_HTTP_PORT=80 -W9_URL='example.youdomain.com' +W9_HTTP_PORT_SET=9001 +W9_URL=example.youdomain.com W9_NETWORK=websoft9 #### ----------------------------------------------------------------------------------------- #### -# Below environment is created by this app +# ============================================================ +# Varnish image environment variables +# Docs: https://hub.docker.com/_/varnish +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: VARNISH_SIZE=2G + +# Not used by default; enable only when needed: +# VARNISH_BACKEND_HOST=https://appname.example.com/ +# VARNISH_FILESERVER=true +# VARNISH_VCL_FILE=/etc/varnish/default.vcl diff --git a/apps/varnish/CHANGELOG.md b/apps/varnish/CHANGELOG.md index 582cf46c5..3786628b8 100644 --- a/apps/varnish/CHANGELOG.md +++ b/apps/varnish/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG -## Release -### Fixes and Enhancements - +## 2026-09-20 +- Updated Varnish to `9.0` (official `varnish` image, currently 9.0.4); the version list is now `9.0` and `latest` (the `stable` tag tracks the unrelated 6.0 LTS line). +- Replaced the stale `src/default.vcl` (VCL 4.0 with an unresolvable backend that prevented the container from starting) with the upstream 9.0 default VCL; the backend is now configured through `VARNISH_BACKEND_HOST`. +- Exposed the Varnish image variables in `.env` (`VARNISH_SIZE` plus optional `VARNISH_BACKEND_HOST`, `VARNISH_FILESERVER`, `VARNISH_VCL_FILE`). +- Normalized `.env` and `docker-compose.yml` to current repository policy rules (braced `${VAR}` references, port purpose comment, removal of the source comment). +- Added `tests/cases.yml` with a Varnish smoke check. +- Expanded the `upstream` metadata with the GitHub releases and official documentation sources. +- Regenerated the README. diff --git a/apps/varnish/Notes.md b/apps/varnish/Notes.md deleted file mode 100644 index afa7a55db..000000000 --- a/apps/varnish/Notes.md +++ /dev/null @@ -1,25 +0,0 @@ -# Varnish - -## WordPress 设置 Varnish 教程 - -1. 分别在 Websoft9 控制台安装 WordPress 和 Varnish 两个应用 - > 确保 Varnish 配置的域名是最终提供给用户访问的域名 - -2. 编辑 Varnish 应用的 `./src/default.vcl` 文件中相关参数,将 WordPress 容器名和容器端口作为连接点 - ``` - backend default { - .host = "wordpress_shlez"; - .port = "80"; - } - ``` - -3. 重建 Varnish 应用后,Varnish 已经将 WordPress 缓存 - -4. 访问 Varnish 所绑定的域名,便发现访问速度大大提升 - -## 配置选项 - -- 缓存大小:通过 VARNISH_SIZE 环境变量设置 -- 配置文件:`./src/default.vcl` - -## FAQ diff --git a/apps/varnish/README.md b/apps/varnish/README.md index 71f1ac600..d74e26d54 100644 --- a/apps/varnish/README.md +++ b/apps/varnish/README.md @@ -1,26 +1,90 @@ -# Varnish on Docker +# Varnish on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Varnish: +## Quick Start +### Deploy Verification - - community: 7.7, stable, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Varnish**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +Varnish is an HTTP cache and reverse proxy; it has no built-in login. Until a backend is configured it serves a local placeholder page. -The following are the minimal [recommended requirements](https://varnish-cache.org): +1. Set `VARNISH_BACKEND_HOST` in `.env` to your origin, for example `http://wordpress_shlez:80/`. +2. Rebuild the app. Requests to the Varnish URL are now cached and served from your origin. -* **RAM**: 1 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 8 GB of free space -* **bandwidth**: more fluent experience over 100M +To customize caching rules, edit `./src/default.vcl` and rebuild. -## Install +### Configuration -You can install this Varnish by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +- Cache size: `VARNISH_SIZE` in `.env`. +- Backend origin: `VARNISH_BACKEND_HOST` in `.env`. +- Static file mode: `VARNISH_FILESERVER=true` in `.env`. + -If you want use Varnish with **Websoft9 Business Support** free, you can [subscribe Varnish](https://www.websoft9.com/apps) on Cloud platform +## Configuration Reference -## Documentation +Websoft9 packages this app from the official [Varnish Docker image](https://hub.docker.com/_/varnish) and makes some improvements below. -[Varnish Administrator Guide](https://support.websoft9.com/docs/varnish) powered by Websoft9 \ No newline at end of file + + + + +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 9.0, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web | 80 | + + +### Data Directory + + +Data is kept inside the container; a named volume is recommended for persistence. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration is overridden by mounting `./src/default.vcl` to `/etc/varnish/default.vcl`. + + +## References + +- [Varnish Administrator Guide](https://support.websoft9.com/docs/varnish) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/_/varnish) + +- [Releases](https://github.com/varnish/varnish/releases) + +- [Official docs](https://varnish-cache.org/docs/) + +- [GitHub docs](https://github.com/varnish/docker-varnish) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/varnish/docker-compose.yml b/apps/varnish/docker-compose.yml index 956613380..516d5ba13 100644 --- a/apps/varnish/docker-compose.yml +++ b/apps/varnish/docker-compose.yml @@ -1,19 +1,17 @@ -# image,docs: https://hub.docker.com/_/varnish - services: varnish: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} restart: unless-stopped ports: - - $W9_HTTP_PORT_SET:80 + - "${W9_HTTP_PORT_SET}:80" # Web volumes: - ./src/default.vcl:/etc/varnish/default.vcl:ro env_file: .env tmpfs: - /var/lib/varnish/varnishd:exec - + networks: default: - name: $W9_NETWORK + name: ${W9_NETWORK} external: true diff --git a/apps/varnish/src/default.vcl b/apps/varnish/src/default.vcl index bbdef9acb..ae2c67628 100644 --- a/apps/varnish/src/default.vcl +++ b/apps/varnish/src/default.vcl @@ -1,40 +1,58 @@ -# -# This is an example VCL file for Varnish. -# -# It does not do anything by default, delegating control to the -# builtin VCL. The builtin VCL is called when there is no explicit -# return statement. -# -# See the VCL chapters in the Users Guide at https://www.varnish-cache.org/docs/ -# and https://www.varnish-cache.org/trac/wiki/VCLExamples for more examples. - -# Marker to tell the VCL compiler that this VCL has been adapted to the -# new 4.0 format. -vcl 4.0; - -# Default backend definition. Set this to point to your content server. -backend default { - .host = "wordpress_zm4pm"; - .port = "80"; +# Important documentation links: +# - general entry point: https://www.varnish-cache.org/docs/ +# - VCL primer: https://varnish-cache.org/docs/2.1/tutorial/vcl.html +# - more VCL information: https://www.varnish-software.com/developers/tutorials/varnish-configuration-language-vcl/ +# - logging: https://docs.varnish-software.com/tutorials/vsl-query/ + +vcl 4.1; + +import fileserver; +import reqwest; +import std; + +# https://github.com/varnish/toolbox/tree/master/vcls/hit-miss +include "hit-miss.vcl"; + +backend default none; + +sub vcl_init { + # sanity check to fail the VCL loading if VARNISH_BACKEND_HOST + # doesn't look right + if (std.getenv("VARNISH_BACKEND_HOST") && + std.getenv("VARNISH_BACKEND_HOST") !~ "^https?://") { + return(fail("VARNISH_BACKEND_HOST is set but doesn't start with http:// or https://")); + } + new http_backend = reqwest.client(base_url = std.getenv("VARNISH_BACKEND_HOST")); + new file_backend = fileserver.root("/var/www/html"); } sub vcl_recv { - # Happens before we check if we have this in cache already. - # - # Typically you clean up the request here, removing cookies you don't need, - # rewriting the request, etc. + if (std.getenv("VARNISH_BACKEND_HOST")) { + # if VARNISH_BACKEND_HOST is set, use the HTTP backend + set req.backend_hint = http_backend.backend(); + } else if (std.getenv("VARNISH_FILESERVER")) { + # if VARNISH_FILESERVER, act as a fileserver + set req.backend_hint = file_backend.backend(); + } else { + # otherwise, force the path to our default page and serve it + # from disk + set req.backend_hint = file_backend.backend(); + set req.url = "/index.html"; + } +} + +# if the request goes to the backend, unset the host header and let +# vmod-reqwest set it, according to VARNISH_BACKEND_HOST (and it doesn't +# matter for file_backend) +sub vcl_backend_fetch { + unset bereq.http.host; } +# vcl_backend_response is the opportunity to set/unset backend response headers +# (beresp.http.*) before they enter the cache sub vcl_backend_response { - # Happens after we have read the response headers from the backend. - # - # Here you clean the response headers, removing silly Set-Cookie headers - # and other mistakes your backend does. + set beresp.http.varnish-default-vcl = "true"; } -sub vcl_deliver { - # Happens when we have all the pieces we need, and are about to send the - # response to the client. - # - # You can do accounting or modifying the final object here. -} \ No newline at end of file +# https://github.com/varnish/toolbox/tree/master/vcls/verbose_builtin +include "verbose_builtin.vcl"; diff --git a/apps/varnish/tests/cases.yml b/apps/varnish/tests/cases.yml new file mode 100644 index 000000000..4556ebf3d --- /dev/null +++ b/apps/varnish/tests/cases.yml @@ -0,0 +1,4 @@ +optional: + - id: varnish-smoke + type: script + script: smoke.sh diff --git a/apps/varnish/tests/smoke.sh b/apps/varnish/tests/smoke.sh new file mode 100644 index 000000000..db814d4a7 --- /dev/null +++ b/apps/varnish/tests/smoke.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +status="$(curl -s -o /dev/null -w '%{http_code}' "${BASE_URL}/")" +if [ "${status}" != "200" ]; then + echo "unexpected status from ${BASE_URL}/: ${status}" >&2 + exit 1 +fi + +headers="$(curl -sI "${BASE_URL}/")" +if ! grep -qi '^varnish-default-vcl: true' <<<"${headers}"; then + echo "missing varnish-default-vcl header" >&2 + exit 1 +fi +if ! grep -qi '^via: .*Varnish/' <<<"${headers}"; then + echo "missing Via: ... Varnish header" >&2 + exit 1 +fi + +echo "varnish serving ${BASE_URL}/ (status=${status})" diff --git a/apps/varnish/variables.json b/apps/varnish/variables.json index dbfe7b578..76ad9422d 100644 --- a/apps/varnish/variables.json +++ b/apps/varnish/variables.json @@ -6,8 +6,7 @@ { "dist": "community", "version": [ - "8.0", - "stable", + "9.0", "latest" ] } @@ -18,6 +17,11 @@ "disk": "8" }, "upstream": { - "image": "https://hub.docker.com/_/varnish" + "image": "https://hub.docker.com/_/varnish", + "releases": "https://github.com/varnish/varnish/releases", + "docs": [ + "https://varnish-cache.org/docs/", + "https://github.com/varnish/docker-varnish" + ] } } diff --git a/apps/vault/.env b/apps/vault/.env index d24393273..24d3038da 100644 --- a/apps/vault/.env +++ b/apps/vault/.env @@ -1,13 +1,36 @@ -# This image have no latest -W9_VERSION='1.21' -W9_DIST='community' W9_REPO=hashicorp/vault +W9_DIST=community +W9_VERSION=2.1 + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='vault' + +W9_ID=vault + +# Vault serves its web UI and HTTP API on 8200. +W9_HTTP_PORT_SET=9001 W9_HTTP_PORT=8200 -W9_HTTP_PORT_SET='9001' -W9_URL='appname.example.com' +W9_URL=appname.example.com W9_NETWORK=websoft9 -W9_LOGIN_GET_TOKEN="Get Token from your Vault Container logs" -#### --------------------------------------------------------------------------------------- #### +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Vault image environment variables +# Docs: https://hub.docker.com/r/hashicorp/vault +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +VAULT_LOCAL_CONFIG={} + +# Not used by default; enable only when needed: +# VAULT_DEV_ROOT_TOKEN_ID=root +# VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200 +# VAULT_LOG_LEVEL=info +# VAULT_DISABLE_MLOCK=true +# VAULT_ADDR=http://127.0.0.1:8200 diff --git a/apps/vault/CHANGELOG.md b/apps/vault/CHANGELOG.md index 582cf46c5..6a6d3e6b5 100644 --- a/apps/vault/CHANGELOG.md +++ b/apps/vault/CHANGELOG.md @@ -1,5 +1,14 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-20 +- Updated Vault from `1.21` to `2.1`, the latest stable upstream minor (upstream `2.1.1`). +- Pinned `W9_VERSION` to `2.1` and declared it in `variables.json`. +- Kept the package in Vault dev mode (`server -dev`), matching the current image default. +- Replaced the legacy `W9_LOGIN_GET_TOKEN` hint with a declarative `variables.json.credentials.token` source (`container-log`, pattern `Root Token:`). +- Aligned `.env` and `docker-compose.yml` with current repository policy: braced variable references, inline published-port comment, and the `.env` section banner with a Docs URL. +- Removed the obsolete `version:` key and the `# image:` / `# docs:` source comments; moved `VAULT_LOCAL_CONFIG` into `.env`. +- Added a healthcheck against `/v1/sys/health`. +- Added `tests/cases.yml` with a Vault health check. +- Added upstream releases and documentation references to `variables.json`. +- Regenerated `README.md`. diff --git a/apps/vault/README.md b/apps/vault/README.md index 4dc1adb55..d1f610f85 100644 --- a/apps/vault/README.md +++ b/apps/vault/README.md @@ -1,26 +1,87 @@ -# Vault on Docker +# Vault on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Vault: +## Quick Start +### Deploy Verification - - community: 1.19, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Vault**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Open the Vault URL from the Websoft9 **Access** tab; the web UI and HTTP API are served on port 8200. +2. Get the root token from the container logs: run `docker logs vault` and look for the `Root Token:` line. +3. Paste the token into the Vault sign-in page. -The following are the minimal [recommended requirements](https://learn.hashicorp.com/tutorials/vault): +### Change Token -* **RAM**: 2 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +This package runs Vault in dev mode, so all data is held in memory and a new root token is generated on every restart. To use a fixed token, set `VAULT_DEV_ROOT_TOKEN_ID` in `.env` and rebuild. + -## Install +## Configuration Reference -You can install this Vault by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Vault Docker image](https://hub.docker.com/r/hashicorp/vault) and makes some improvements below. -If you want use Vault with **Websoft9 Business Support** free, you can [subscribe Vault](https://www.websoft9.com/apps) on Cloud platform + +This package runs Vault in dev mode (`server -dev`): storage is in memory and the root token is printed in the container logs. Do not use it for production data. + -## Documentation +Apps run as containers; rebuild after any configuration change. -[Vault Administrator Guide](https://support.websoft9.com/docs/vault) powered by Websoft9 \ No newline at end of file +### Version Support + +Supported versions: 2.1, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Vault UI and HTTP API | 8200 | + + +### Data Directory + + +- `vault-logs` → `/vault/logs` +- `vault-file` → `/vault/file` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [Vault Administrator Guide](https://support.websoft9.com/docs/vault) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/hashicorp/vault) + +- [Releases](https://github.com/hashicorp/vault/releases) + +- [Official docs](https://developer.hashicorp.com/vault/docs) + +- [Official docs](https://hub.docker.com/r/hashicorp/vault) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/vault/docker-compose.yml b/apps/vault/docker-compose.yml index ea61f23e1..312778fab 100644 --- a/apps/vault/docker-compose.yml +++ b/apps/vault/docker-compose.yml @@ -1,23 +1,22 @@ -# image: https://hub.docker.com/r/hashicorp/vault -# docs: https://www.vaultproject.io/ - -version: '3.8' - services: vault: image: ${W9_REPO}:${W9_VERSION} container_name: ${W9_ID} - cap_add: + cap_add: - IPC_LOCK ports: - - '${W9_HTTP_PORT_SET}:8200' + - "${W9_HTTP_PORT_SET}:8200" # Vault UI and HTTP API and CLI env_file: - .env - environment: - - VAULT_LOCAL_CONFIG={} volumes: - - 'vault-logs:/vault/logs' - - 'vault-file:/vault/file' + - vault-logs:/vault/logs + - vault-file:/vault/file + healthcheck: + test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8200/v1/sys/health || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s networks: default: diff --git a/apps/vault/tests/cases.yml b/apps/vault/tests/cases.yml new file mode 100644 index 000000000..ad82476e4 --- /dev/null +++ b/apps/vault/tests/cases.yml @@ -0,0 +1,5 @@ +optional: + - id: vault-health + type: web-access + path: /v1/sys/health + expect_status: 200 diff --git a/apps/vault/variables.json b/apps/vault/variables.json index 8014b1044..b307d0c72 100644 --- a/apps/vault/variables.json +++ b/apps/vault/variables.json @@ -2,21 +2,45 @@ "name": "vault", "trademark": "Vault", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/hashicorp/vault", + "releases": "https://github.com/hashicorp/vault/releases", + "docs": [ + "https://developer.hashicorp.com/vault/docs", + "https://hub.docker.com/r/hashicorp/vault" + ] + }, "edition": [ { "dist": "community", "version": [ - "1.21", + "2.1", "latest" ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8200, + "path": "/" + }, + "api": { + "port": 8200, + "path": "/v1/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "1" }, - "upstream": { - "image": "https://hub.docker.com/r/hashicorp/vault" + "credentials": { + "token": { + "source": "container-log", + "match": "regex", + "pattern": "Root Token:\\s*(.+)", + "group": 1 + } } } diff --git a/apps/vaultwarden/.env b/apps/vaultwarden/.env index f82e5541b..9c4f4eaaf 100644 --- a/apps/vaultwarden/.env +++ b/apps/vaultwarden/.env @@ -1,24 +1,51 @@ W9_REPO=vaultwarden/server -W9_DIST='community' -W9_VERSION='1.34.3' -W9_DB_VERSION=10.4 +W9_DIST=community +W9_VERSION=1.37.3 + +# Optional password seed: drives the bundled MariaDB password and the admin token. W9_POWER_PASSWORD='fPgk6t8tPVHH!jyh' W9_RCODE='pH3A0atXKks3V' -W9_HTTP_PORT_SET='9001' -W9_ID='vaultwarden' + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + +#### -- Not allowed to edit below environments when recreate app based on existing data -- #### + +W9_ID=vaultwarden + +# Web/internal ports and bundled dependency shape. +W9_HTTP_PORT_SET=9001 W9_HTTP_PORT=80 +W9_DB_VERSION=11.4 W9_DB_EXPOSE=mariadb -W9_URL='example.youdomain.com' + +# Public URL helpers. +W9_URL=example.youdomain.com W9_URL_REPLACE=true W9_URL_WITH_PORT=false + W9_NETWORK=websoft9 -#you can find more environment variables information in https://github.com/dani-garcia/vaultwarden/wiki +#### ----------------------------------------------------------------------------------------- #### -SIGNUPS_ALLOWED=true # Deactivate this with "false" after you have created your account so that no strangers can register +# ============================================================ +# Vaultwarden image environment variables +# Docs: https://github.com/dani-garcia/vaultwarden/wiki +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +SIGNUPS_ALLOWED=true DATABASE_URL=mysql://vaultwarden:${W9_RCODE}@${W9_ID}-mariadb:3306/vaultwarden -# It is used in W9_URL/admin to access the admin page -# How to set it? refer to https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#using-argon2 -# You should redeploy the app -ADMIN_TOKEN=$W9_POWER_PASSWORD +ADMIN_TOKEN=${W9_POWER_PASSWORD} +DOMAIN=https://${W9_URL} RUST_BACKTRACE=1 + +# Not used by default; enable only when needed: +# SIGNUPS_DOMAINS_WHITELIST=example.com,example.net +# INVITATIONS_ALLOWED=true +# DISABLE_ADMIN_TOKEN=false +# SHOW_PASSWORD_HINT=false +# SSO_ENABLED=false diff --git a/apps/vaultwarden/CHANGELOG.md b/apps/vaultwarden/CHANGELOG.md index 582cf46c5..2bf17faf6 100644 --- a/apps/vaultwarden/CHANGELOG.md +++ b/apps/vaultwarden/CHANGELOG.md @@ -1,5 +1,14 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-20 +- Updated Vaultwarden from `1.34.3` to `1.37.3`, the latest stable upstream release (includes the 1.35.5 / 1.36.0 / 1.37.0 security fixes). +- Pinned `W9_VERSION` to `1.37.3` and declared it in `variables.json`. +- Bumped the bundled MariaDB dependency from `10.4` (EOL) to `11.4` (LTS). +- Fixed the URL contract by wiring `DOMAIN=https://${W9_URL}`, which the previous `W9_URL_REPLACE=true` declaration lacked (policy gate failure). +- Aligned `.env` and `docker-compose.yml` with current repository policy: braced variable references, inline published-port comment, and the `.env` section banner with a Docs URL. +- Removed the obsolete `version:` key and the `# image:` / `# docs:` source comments. +- Added a healthcheck against `/alive`. +- Added `tests/cases.yml` with an `/alive` check. +- Added upstream releases and documentation references to `variables.json`. +- Regenerated `README.md`. diff --git a/apps/vaultwarden/README.md b/apps/vaultwarden/README.md index f46dee7f5..fdfd992e9 100644 --- a/apps/vaultwarden/README.md +++ b/apps/vaultwarden/README.md @@ -1,26 +1,88 @@ -# Vaultwarden on Docker +# Vaultwarden on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Vaultwarden: +## Quick Start +### Deploy Verification - - community: 1.33.2, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Vaultwarden**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Open the Vaultwarden URL from the Websoft9 **Access** tab. +2. Create the first account in the web vault, then set `SIGNUPS_ALLOWED=false` in `.env` and rebuild to stop open registration. +3. The admin page is at `/admin`; sign in with the `ADMIN_TOKEN` value from `.env`. -The following are the minimal [recommended requirements](): +### Change Password -* **RAM**: 4 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 1 GB of free space -* **bandwidth**: more fluent experience over 100M +The admin token is `W9_POWER_PASSWORD` in `.env`; update it and rebuild to rotate it. User account passwords are managed inside the web vault. + -## Install +## Configuration Reference -You can install this Vaultwarden by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Vaultwarden Docker image](https://hub.docker.com/r/vaultwarden/server) and makes some improvements below. -If you want use Vaultwarden with **Websoft9 Business Support** free, you can [subscribe Vaultwarden](https://www.websoft9.com/apps) on Cloud platform + +Vaultwarden needs HTTPS for the browser Web Crypto API (WebAuthn), so publish it behind a TLS-enabled domain. This package bundles MariaDB 11.4; deployments created on the previous MariaDB 10.4 volume must follow the MariaDB stepwise upgrade path before starting this version. + -## Documentation +Apps run as containers; rebuild after any configuration change. -[Vaultwarden Administrator Guide](https://support.websoft9.com/docs/vaultwarden) powered by Websoft9 \ No newline at end of file +### Version Support + +Supported versions: 1.37.3, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web vault and API | 80 | + + +### Data Directory + + +- `vaultwarden_vol` → `/data/` +- `mariadb_vol` → `/var/lib/mysql` +- `/etc/localtime` → `/etc/localtime` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [Vaultwarden Administrator Guide](https://support.websoft9.com/docs/vaultwarden) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/vaultwarden/server) + +- [Releases](https://github.com/dani-garcia/vaultwarden/releases) + +- [GitHub docs](https://github.com/dani-garcia/vaultwarden/wiki) + +- [GitHub docs](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/vaultwarden/docker-compose.yml b/apps/vaultwarden/docker-compose.yml index f4c00f955..56b50d25b 100644 --- a/apps/vaultwarden/docker-compose.yml +++ b/apps/vaultwarden/docker-compose.yml @@ -1,34 +1,38 @@ -# image: https://hub.docker.com/r/vaultwarden/server -# docs: https://github.com/dani-garcia/vaultwarden/wiki - -version: '3.8' services: vaultwarden: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} restart: unless-stopped ports: - - $W9_HTTP_PORT_SET:80 - env_file: .env + - "${W9_HTTP_PORT_SET}:80" # Web vault and API + env_file: + - .env volumes: - - "vaultwarden_vol:/data/" + - vaultwarden_vol:/data/ + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/alive || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s mariadb: - image: mariadb:$W9_DB_VERSION - container_name: "$W9_ID-mariadb" + image: mariadb:${W9_DB_VERSION} + container_name: ${W9_ID}-mariadb restart: unless-stopped volumes: - - "mariadb_vol:/var/lib/mysql" - - "/etc/localtime:/etc/localtime:ro" + - mariadb_vol:/var/lib/mysql + - /etc/localtime:/etc/localtime:ro environment: - - "MYSQL_ROOT_PASSWORD=$W9_RCODE" - - "MYSQL_PASSWORD=$W9_RCODE" - - "MYSQL_DATABASE=vaultwarden" - - "MYSQL_USER=vaultwarden" + - MYSQL_ROOT_PASSWORD=${W9_RCODE} + - MYSQL_PASSWORD=${W9_RCODE} + - MYSQL_DATABASE=vaultwarden + - MYSQL_USER=vaultwarden + volumes: vaultwarden_vol: mariadb_vol: - + networks: default: - name: $W9_NETWORK + name: ${W9_NETWORK} external: true diff --git a/apps/vaultwarden/tests/cases.yml b/apps/vaultwarden/tests/cases.yml new file mode 100644 index 000000000..7ddb8df96 --- /dev/null +++ b/apps/vaultwarden/tests/cases.yml @@ -0,0 +1,5 @@ +optional: + - id: alive + type: web-access + path: /alive + expect_status: 200 diff --git a/apps/vaultwarden/variables.json b/apps/vaultwarden/variables.json index 1827828cc..3a70c2364 100644 --- a/apps/vaultwarden/variables.json +++ b/apps/vaultwarden/variables.json @@ -6,7 +6,7 @@ { "dist": "community", "version": [ - "1.34.3", + "1.37.3", "latest" ] } @@ -17,6 +17,11 @@ "disk": "1" }, "upstream": { - "image": "https://hub.docker.com/r/vaultwarden/server" + "image": "https://hub.docker.com/r/vaultwarden/server", + "releases": "https://github.com/dani-garcia/vaultwarden/releases", + "docs": [ + "https://github.com/dani-garcia/vaultwarden/wiki", + "https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page" + ] } } diff --git a/apps/vespa/.env b/apps/vespa/.env index 9b2b7ff8f..3c4c2b905 100644 --- a/apps/vespa/.env +++ b/apps/vespa/.env @@ -1,9 +1,33 @@ -W9_VERSION=latest -W9_ID=vespa W9_REPO=vespaengine/vespa +W9_DIST=community +W9_VERSION=8.751.13 + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + +#### -- Not allowed to edit below environments when recreate app based on existing data -- #### + +W9_ID=vespa + +# Vespa serves the query/document HTTP API on 8080, and the config server / deployment API on 19071. +# The HTTP API on 8080 only starts after an application package is deployed. +W9_HTTP_PORT=8080 +W9_HTTP_PORT_SET=9001 +W9_API_PORT_SET=9002 + W9_NETWORK=websoft9 -# Port configurations -W9_HTTP_PORT=19071 -W9_HTTP_PORT_SET=19071 +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Vespa image environment variables +# Docs: https://docs.vespa.ai/en/operations/self-managed/docker-containers.html +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +# (none) +# Not used by default; enable only when needed: diff --git a/apps/vespa/CHANGELOG.md b/apps/vespa/CHANGELOG.md index 09c4e7bd0..2f9e568df 100644 --- a/apps/vespa/CHANGELOG.md +++ b/apps/vespa/CHANGELOG.md @@ -1,5 +1,13 @@ -# CHANGELOG +# Changelog -## Release +## 2026-09-20 -### Fixes and Enhancements +- Pin the Vespa image from floating `latest` to `8.751.13`, the newest release with a published image. +- Point `upstream.image` at the Docker Hub registry and add upstream releases and docs references so version scanning works again. +- Rework the compose topology to the official single-container shape running `configserver,services`. +- Map `W9_HTTP_PORT_SET` to the query/document HTTP API (`8080`) and expose the config server / deployment API (`19071`) on `W9_API_PORT_SET`, the endpoint needed for `vespa deploy`. +- Fix data persistence paths to the official `/opt/vespa/var` and `/opt/vespa/logs`; the previous `/var/lib/vespa` and `/etc/vespa` paths do not exist in the image. +- Add a healthcheck against the config server `/state/v1/health`, which is up before any application package is deployed. +- Skip the default web check in `tests/cases.yml`, since the HTTP API on `8080` is not available until an application is deployed. +- Document in the README that Vespa has no web UI or built-in authentication, and how the ports are used. +- Normalize `.env` and `docker-compose.yml` to current repository policy and regenerate `README.md`. diff --git a/apps/vespa/Notes.md b/apps/vespa/Notes.md index 20c52464b..2853e964d 100644 --- a/apps/vespa/Notes.md +++ b/apps/vespa/Notes.md @@ -1 +1,8 @@ -# vespa +# Vespa + +- 自托管 Vespa 没有 Web UI,也没有内置用户名/密码认证。 +- `8080`:应用 HTTP API(查询 / 文档)。**必须先在 `19071` 部署应用包后才会监听**。 +- `19071`:config server / 部署端点。全新安装即可用,但**未认证**,暴露到公网需自行加访问控制。 +- 部署应用包:`vespa deploy --target http://:19071 ./app` +- 查询示例:`curl 'http://:8080/search/?yql=select * from sources * where true'` +- 启用认证:在应用包 `services.xml` 配置 TLS + 客户端证书(mTLS)或自定义 filter chain;Vespa 内部通信可用 `VESPA_TLS_CONFIG_FILE` 走 mTLS。 diff --git a/apps/vespa/README.md b/apps/vespa/README.md index f246ba2c0..d7687cdff 100644 --- a/apps/vespa/README.md +++ b/apps/vespa/README.md @@ -1,26 +1,84 @@ -# Vespa on Docker +# Vespa on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Vespa: +## Quick Start +### Deploy Verification - - community: latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Vespa**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +Vespa has no web UI or built-in login; use the Vespa CLI or the REST API. -The following are the minimal [recommended requirements](https://github.com/vespa-engine/docker-image-dev#vespa-development-on-almalinux-8): +1. Deploy an application package to the config server on port 19071, for example `vespa deploy --target http://:19071 ./app`. +2. After the application is deployed, the query and document API becomes available on port 8080. +3. Query it with `curl 'http://:8080/search/?yql=select * from sources * where true'`. + -* **RAM**: 8 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 128 GB of free space -* **bandwidth**: more fluent experience over 100M +## Configuration Reference -## Install +Websoft9 packages this app from the official [Vespa Docker image](https://hub.docker.com/r/vespaengine/vespa) and makes some improvements below. -You can install this Vespa by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). + +Vespa has no built-in authentication. The HTTP API on port 8080 is open by default and only starts after an application package is deployed. The config server on port 19071 is an unauthenticated deployment endpoint; expose it only to trusted networks. Secure access with TLS, client certificates (mTLS), or HTTP filter chains in the application package. + -If you want use Vespa with **Websoft9 Business Support** free, you can [subscribe Vespa](https://www.websoft9.com/apps) on Cloud platform +Apps run as containers; rebuild after any configuration change. -## Documentation +### Version Support -[Vespa Administrator Guide](https://support.websoft9.com/docs/vespa) powered by Websoft9 \ No newline at end of file +Supported versions: 8.751.13, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| HTTP API (query/document) | 8080 | +| Config server (deployment API) | 19071 | + + +### Data Directory + + +- `vespa-var` → `/opt/vespa/var` +- `vespa-logs` → `/opt/vespa/logs` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [Vespa Administrator Guide](https://support.websoft9.com/docs/vespa) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/vespaengine/vespa) + +- [Releases](https://github.com/vespa-engine/vespa/releases) + +- [Official docs](https://docs.vespa.ai/en/operations/self-managed/docker-containers.html) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/vespa/docker-compose.yml b/apps/vespa/docker-compose.yml index 830dc0df9..c8295b132 100644 --- a/apps/vespa/docker-compose.yml +++ b/apps/vespa/docker-compose.yml @@ -1,34 +1,28 @@ -# image: https://hub.docker.com/r/vespaengine/vespa -# doc: https://github.com/vespa-engine/vespa - version: '3.8' services: vespa: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID - hostname: vespa-app - command: services - depends_on: - - configserver - volumes: - - vespa-data:/var/lib/vespa - environment: - VESPA_CONFIGSERVERS: vespa-config-server - + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} + hostname: vespa-container + command: ["configserver,services"] restart: unless-stopped - - configserver: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID-config-server - hostname: vespa-config-server - command: configserver ports: - - $W9_HTTP_PORT_SET:19071 + - "${W9_HTTP_PORT_SET}:8080" # HTTP API (query/document) + - "${W9_API_PORT_SET}:19071" # Config server (deployment API) volumes: - - vespa-config:/etc/vespa + - vespa-var:/opt/vespa/var + - vespa-logs:/opt/vespa/logs environment: - VESPA_CONFIGSERVERS: vespa-config-server + VESPA_CONFIGSERVERS: vespa-container + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:19071/state/v1/health || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 120s + env_file: + - .env networks: default: @@ -36,5 +30,5 @@ networks: external: true volumes: - vespa-config: - vespa-data: + vespa-var: + vespa-logs: diff --git a/apps/vespa/tests/cases.yml b/apps/vespa/tests/cases.yml new file mode 100644 index 000000000..e24b793ac --- /dev/null +++ b/apps/vespa/tests/cases.yml @@ -0,0 +1,2 @@ +skip: + - id: web-access diff --git a/apps/vespa/variables.json b/apps/vespa/variables.json index 8edc5e8a8..91b8d095e 100644 --- a/apps/vespa/variables.json +++ b/apps/vespa/variables.json @@ -1,11 +1,12 @@ { "name": "vespa", "trademark": "Vespa", - "release": false, + "release": true, "edition": [ { "dist": "community", "version": [ + "8.751.13", "latest" ] } @@ -13,9 +14,13 @@ "requirements": { "cpu": "2", "memory": "8", - "disk": "128" + "disk": "5" }, "upstream": { - "image": "https://github.com/vespa-engine/vespa" + "image": "https://hub.docker.com/r/vespaengine/vespa", + "releases": "https://github.com/vespa-engine/vespa/releases", + "docs": [ + "https://docs.vespa.ai/en/operations/self-managed/docker-containers.html" + ] } } diff --git a/apps/wazuh/.env b/apps/wazuh/.env index 770cbd6c1..998f53b4a 100644 --- a/apps/wazuh/.env +++ b/apps/wazuh/.env @@ -1,53 +1,62 @@ -W9_REPO="wordpress" +W9_REPO=wazuh/wazuh-dashboard W9_DIST=community -W9_VERSION="latest" - -W9_POWER_PASSWORD="1PrMxExC45LsCT" - -# Environments which for user settings when create application -# Named expression: W9_xxx_xxx_SET, xxx refer to file fields -W9_HTTP_PORT_SET=9001 -# W9_HTTPS_PORT_SET=9002 -# W9_DB_PORT_SET=3306 -# W9_SSH_PORT_SET=23 -W9_KEY_SET="dfsjdkjf77xjxcjcj" +W9_VERSION=4.14.7 #### -- Not allowed to edit below environments when recreate app based on existing data -- #### W9_ID=wazuh -# W9_HTTP_PORT or W9_HTTPS_PORT is need at leaset and used for proxy for web application -# Some container (e.g teleport) need HTTPS access, then need to set this pra -W9_HTTP_PORT=80 -W9_HTTPS_PORT=81 +# Web/internal ports +W9_HTTPS_PORT=5601 +W9_HTTPS_PORT_SET=9443 +W9_AGENT_PORT_SET=1514 +W9_ENROLLMENT_PORT_SET=1515 +# Optional; enable together with the matching port in docker-compose.yml: +# W9_SYSLOG_UDP_PORT_SET=514 +# W9_API_PORT_SET=55000 +# Built-in login and URL helpers W9_LOGIN_USER=admin -# use https://1password.com/zh-cn/password-generator/ to genarate 14 bit password -# this password can also use password file -W9_LOGIN_PASSWORD=$W9_POWER_PASSWORD -W9_ADMIN_PATH="/wp-login" - -# Container name's suffix must use one of the value -W9_DB_EXPOSE="mysql,postgresql,mariadb,mongodb,redis" - -# It is used when the application APP needs to set an external URL, which can be IP(or domain), IP:PORT -# If have protocols, should be set it in the APP's ENV -W9_URL=example.youdomain.com -# modifies W9_URL on init when it is true -W9_URL_REPLACE=true +W9_LOGIN_PASSWORD=SecretPassword +W9_URL=appname.example.com W9_NETWORK=websoft9 #### ----------------------------------------------------------------------------------------- #### - -# Below environment is created by this app - -WORDPRESS_DB_HOST=$W9_ID-mariadb -WORDPRESS_DB_USER=wordpress #if use postgresql, it need set to postgres -WORDPRESS_DB_PASSWORD=$W9_POWER_PASSWORD -WORDPRESS_DB_NAME=wordpress - - -#W9_NAME="" -#W9_RCODE="" \ No newline at end of file +# ============================================================ +# Wazuh image environment variables +# Docs: https://documentation.wazuh.com/current/deployment-options/docker/index.html +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: + +# Wazuh indexer +OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g + +# Wazuh manager +INDEXER_URL=https://wazuh.indexer:9200 +INDEXER_USERNAME=admin +INDEXER_PASSWORD=SecretPassword +FILEBEAT_SSL_VERIFICATION_MODE=full +SSL_CERTIFICATE_AUTHORITIES=/etc/ssl/wazuh/root-ca-manager.pem +SSL_CERTIFICATE=/etc/ssl/wazuh/wazuh.manager.pem +SSL_KEY=/etc/ssl/wazuh/wazuh.manager-key.pem + +# Wazuh API user, shared by the manager and the dashboard +API_USERNAME=wazuh-wui +API_PASSWORD=MyS3cr37P450r.*- + +# Wazuh dashboard +WAZUH_API_URL=https://wazuh.manager +DASHBOARD_USERNAME=kibanaserver +DASHBOARD_PASSWORD=kibanaserver + +# Not used by default; enable only when needed: +# CERT_TOOL_VERSION=4.14 +# WAZUH_CLUSTER_KEY= +# WAZUH_REGISTRATION_PASSWORD= +# WAZUH_API_PORT=55000 diff --git a/apps/wazuh/CHANGELOG.md b/apps/wazuh/CHANGELOG.md index 582cf46c5..32404a131 100644 --- a/apps/wazuh/CHANGELOG.md +++ b/apps/wazuh/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-20 +- Rebuild the package as the official Wazuh single-node stack (manager, indexer and dashboard at 4.14.7). +- Replace the placeholder WordPress package with Wazuh configs, ports, environment variables and tests. +- Generate the indexer TLS certificates at deploy time with `wazuh/wazuh-certs-generator`. +- Raise `vm.max_map_count` with a one-shot privileged `busybox` helper before the indexer starts. +- Publish only the required ports (dashboard 5601, agent 1514/1515); keep syslog 514/udp and manager API 55000 commented for optional use. +- Isolate the stack on a per-instance `${W9_ID}-internal` network so multiple Wazuh instances can share the host without hostname/TLS collisions. diff --git a/apps/wazuh/Dockerfile b/apps/wazuh/Dockerfile deleted file mode 100644 index 4364b46fb..000000000 --- a/apps/wazuh/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -# image: https://hub.docker.com/r/websoft9dev/discuzq - -FROM ccr.ccs.tencentyun.com/discuzq/dzq:latest - -LABEL org.opencontainers.image.authors="https://www.websoft9.com" \ - org.opencontainers.image.description="Application packaged by Websoft9" \ - org.opencontainers.image.source="https://github.com/Websoft9/docker-library/tree/main/apps/opencart" \ - org.opencontainers.image.title="OpenCart" \ - org.opencontainers.image.vendor="Websoft9 Inc." \ - org.opencontainers.image.version="4.0.1.1" - -ENV DISCUZQ_MYSQL_HOST=mysql -ENV DISCUZQ_MYSQL_USER=discuzq -ENV DISCUZQ_MYSQL_PASSWORD=discuzq -ENV DISCUZQ_MYSQL_DATABASE=discuzq -ENV DISCUZQ_SITENAME=DiscuzQ - -COPY cmd.sh /tmp -RUN chmod +x /tmp/cmd.sh - -CMD ["/tmp/cmd.sh"] diff --git a/apps/wazuh/Notes.md b/apps/wazuh/Notes.md index 1fdbbb29d..5d55b7e2e 100644 --- a/apps/wazuh/Notes.md +++ b/apps/wazuh/Notes.md @@ -1,2 +1,109 @@ -# Appname -## FAQ +# Wazuh Notes + +> Internal maintenance notes. Customer-facing documentation lives in `README.md`. + +## Sources + +- Official images: `wazuh/wazuh-manager`, `wazuh/wazuh-indexer`, `wazuh/wazuh-dashboard` +- Official deployment: https://github.com/wazuh/wazuh-docker (single-node, tag `v4.14.7`) +- Docs: https://documentation.wazuh.com/current/deployment-options/docker/wazuh-container.html + +## Version coupling + +The manager, indexer and dashboard images must always share the same version. `W9_VERSION` drives +all three images and the `CERT_TOOL_VERSION` used by the certificate generator (`4.14` for the +`4.14.x` line). Update them together. + +Wazuh publishes full patch tags only (`4.14.7`), so the package intentionally pins `x.x.x` instead +of an `x.x` tag. + +## Host prerequisite: vm.max_map_count + +The indexer (OpenSearch based) needs `vm.max_map_count >= 262144`. The `busybox` service runs a +privileged one-shot `sysctl -w vm.max_map_count=262144` before the indexer starts. If the host +already reports a higher value, the command is a no-op. On hosts that block this, set it manually: + +```bash +echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf && sudo sysctl -p +``` + +## TLS certificates + +The `wazuh-certs` service runs `wazuh/wazuh-certs-generator:0.0.4`, which downloads +`wazuh-certs-tool.sh` from `packages.wazuh.com` and writes the certificates into the bind-mounted +`src/wazuh_indexer_ssl_certs/` directory. Generation runs only when `wazuh.indexer.pem` is absent, +and the stale `/wazuh-certificates` work dir is removed first, so repeated `docker compose up` does +not rotate the certificates. + +Do not switch this back to a named volume: `wazuh/wazuh-indexer` ships stock demo certificates at +`/usr/share/wazuh-indexer/config/certs/`, and Docker would copy them into a fresh named volume +before the generator runs, making the guard skip generation and leaving the indexer without the +`wazuh.indexer.pem` it expects. + +- The generated directory is forced to `755` after generation so the `wazuh` (999) and + `wazuh-indexer`/`wazuh-dashboard` (1000) users can traverse it. +- To rotate the certificates, delete `src/wazuh_indexer_ssl_certs/*` and recreate the stack: + `docker compose down && docker compose up -d`. +- The container paths differ from upstream because the whole volume is mounted as a directory: + the manager reads from `/etc/ssl/wazuh/`, the indexer from + `/usr/share/wazuh-indexer/config/certs/`, the dashboard from + `/usr/share/wazuh-dashboard/certs/`. + +## Startup ordering + +The dashboard must not start before the indexer can serve requests, otherwise its first saved +objects migration (`.kibana_1`) times out and the dashboard waits forever. The indexer therefore +defines a healthcheck (`_cluster/health` with the admin credentials) and the manager and dashboard +depend on `service_healthy`. + +If a deployment is interrupted and the dashboard reports +`Another OpenSearch Dashboards instance appears to be migrating the index`, delete the broken index +and restart the dashboard: + +```bash +docker exec ${W9_ID}-indexer curl -k -s -u admin:${W9_LOGIN_PASSWORD} -X DELETE https://localhost:9200/.kibana_1 +docker restart ${W9_ID} +``` + +## Multi-instance isolation + +The upstream single-node stack uses fixed hostnames (`wazuh.indexer`, `wazuh.manager`, +`wazuh.dashboard`) that are baked into the certificates and configs. On the shared `websoft9` +network these names collide with any other Wazuh instance, causing clients to reach the wrong +indexer/manager and fail with `SSLHandshakeException (unknown_ca)`. + +To keep instances isolated, the manager, indexer and dashboard join a per-instance private bridge +network `${W9_ID}-internal`; only the dashboard is also attached to `websoft9` for platform access. +Keep it this way: dropping the private network reintroduces the collision. `hostname` alone cannot +fix it because Compose always registers the service name as a network alias. + +## Credentials + +- Dashboard login: `admin` / `SecretPassword` (`W9_LOGIN_USER` / `W9_LOGIN_PASSWORD`). +- The password is defined by the bcrypt hash in `src/internal_users.yml`, which the indexer loads + only when it initializes a fresh security index. Changing `W9_LOGIN_PASSWORD` in `.env` does not + rewrite an existing deployment. +- Internal service accounts keep the upstream defaults: `kibanaserver`/`kibanaserver` and + `wazuh-wui`/`MyS3cr37P450r.*-`. +- Rotate credentials with the Wazuh `wazuh-passwords-tool.sh` inside the manager, or by recreating + the stack with an updated `src/internal_users.yml` hash. + +## Ports + +| Purpose | Host variable | Container | +| --- | --- | --- | +| Web Console (HTTPS) | `W9_HTTPS_PORT_SET` | 5601 | +| Agent Connection | `W9_AGENT_PORT_SET` | 1514 | +| Agent Enrollment | `W9_ENROLLMENT_PORT_SET` | 1515 | +| Syslog Collection (optional) | `W9_SYSLOG_UDP_PORT_SET` | 514/udp | +| Manager API (optional) | `W9_API_PORT_SET` | 55000 | + +The syslog and API ports are commented out in `docker-compose.yml` (and their `_SET` vars are commented +in `.env`). The dashboard reaches the manager API over the Docker network, so 55000 only needs to be +published for external automation; 514/udp only for external syslog sources. + +## Known limits + +- The dashboard is HTTPS-only, so the package has no `W9_HTTP_PORT_SET`; the deploy test uses the + `tests/check.sh` HTTPS probe instead of the adaptive HTTP web-access check. +- First startup takes a few minutes while the indexer builds its security index. diff --git a/apps/wazuh/README.md b/apps/wazuh/README.md index f75ff7075..d6a7c9411 100644 --- a/apps/wazuh/README.md +++ b/apps/wazuh/README.md @@ -1,26 +1,119 @@ -# Wazuh on Docker +# Wazuh on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Wazuh: +## Quick Start +### Deploy Verification - - community: latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Wazuh**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Open the dashboard at `https://:${W9_HTTPS_PORT_SET}` and accept the self-signed certificate. +2. Sign in with `admin` / `SecretPassword`. +3. Deploy agents and point them at the manager: agent connection on port `1514`, enrollment on `1515`, syslog on `514/udp`. -The following are the minimal [recommended requirements](https://wazuh.com): +### Change Password -* **RAM**: 4 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 10 GB of free space -* **bandwidth**: more fluent experience over 100M +1. In the [Websoft9](https://www.websoft9.com) console, open the app's **Compose** tab. +2. Update `W9_LOGIN_PASSWORD` in `.env` and the matching bcrypt hash in `src/internal_users.yml`. +3. Recreate the stack so the indexer rebuilds its security index (`docker compose down -v && docker compose up -d`). + -## Install +## Configuration Reference -You can install this Wazuh by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Wazuh Docker image](https://hub.docker.com/r/wazuh/wazuh-dashboard) and makes some improvements below. -If you want use Wazuh with **Websoft9 Business Support** free, you can [subscribe Wazuh](https://www.websoft9.com/apps) on Cloud platform + +- The manager, indexer and dashboard must run the same version; `W9_VERSION` drives all three images. +- TLS certificates are generated on first start by `wazuh/wazuh-certs-generator` and stored in the `wazuh-certs` volume. +- The indexer requires `vm.max_map_count=262144`; the `busybox` helper service sets it before startup. +- `W9_LOGIN_PASSWORD` takes effect only when the indexer initializes a fresh security index. + -## Documentation +Apps run as containers; rebuild after any configuration change. -[Wazuh Administrator Guide](https://support.websoft9.com/docs/wazuh) powered by Websoft9 \ No newline at end of file +### Version Support + +Supported versions: 4.14.7. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Agent Connection | 1514 | +| Agent Enrollment | 1515 | +| Web Console | 5601 | + + +### Data Directory + + +- `wazuh-indexer-data` → `/var/lib/wazuh-indexer` +- `wazuh_api_configuration` → `/var/ossec/api/configuration` +- `wazuh_etc` → `/var/ossec/etc` +- `wazuh_logs` → `/var/ossec/logs` +- `wazuh_queue` → `/var/ossec/queue` +- `wazuh_var_multigroups` → `/var/ossec/var/multigroups` +- `wazuh_integrations` → `/var/ossec/integrations` +- `wazuh_active_response` → `/var/ossec/active-response/bin` +- `wazuh_agentless` → `/var/ossec/agentless` +- `wazuh_wodles` → `/var/ossec/wodles` +- `filebeat_etc` → `/etc/filebeat` +- `filebeat_var` → `/var/lib/filebeat` +- `wazuh-dashboard-config` → `/usr/share/wazuh-dashboard/data/wazuh/config` +- `wazuh-dashboard-custom` → `/usr/share/wazuh-dashboard/plugins/wazuh/public/assets/custom` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +- `./src/wazuh_indexer_ssl_certs` → `/certificates` +- `./src/certs.yml` → `/config/certs.yml` +- `./src/wazuh_indexer_ssl_certs` → `/usr/share/wazuh-indexer/config/certs` +- `./src/wazuh.indexer.yml` → `/usr/share/wazuh-indexer/config/opensearch.yml` +- `./src/internal_users.yml` → `/usr/share/wazuh-indexer/config/opensearch-security/internal_users.yml` +- `./src/wazuh_indexer_ssl_certs` → `/etc/ssl/wazuh` +- `./src/wazuh_manager.conf` → `/wazuh-config-mount/etc/ossec.conf` +- `./src/wazuh_indexer_ssl_certs` → `/usr/share/wazuh-dashboard/certs` +- `./src/opensearch_dashboards.yml` → `/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml` +- `./src/wazuh.yml` → `/usr/share/wazuh-dashboard/data/wazuh/config/wazuh.yml` + + + +## References + +- [Wazuh Administrator Guide](https://support.websoft9.com/docs/wazuh) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/wazuh/wazuh-dashboard) + +- [Releases](https://github.com/wazuh/wazuh/releases) + +- [Official compose](https://raw.githubusercontent.com/wazuh/wazuh-docker/v4.14.7/single-node/docker-compose.yml) + +- [Official docs](https://documentation.wazuh.com/current/deployment-options/docker/wazuh-container.html) + +- [GitHub docs](https://github.com/wazuh/wazuh-docker) + + + +## Troubleshooting + +**Indexer exits because `vm.max_map_count` is too low?** +- Run `sudo sysctl -w vm.max_map_count=262144` on the host, then restart the stack. + +**Dashboard unreachable or certificate error?** +- The dashboard is HTTPS-only. Use `https://:${W9_HTTPS_PORT_SET}` and accept the self-signed certificate. + +**App fails to start?** +- Check `docker compose logs wazuh.indexer wazuh.manager wazuh.dashboard`. + diff --git a/apps/wazuh/docker-compose.yml b/apps/wazuh/docker-compose.yml index 1c1948688..bacf6b3d5 100644 --- a/apps/wazuh/docker-compose.yml +++ b/apps/wazuh/docker-compose.yml @@ -1,50 +1,148 @@ -# image,docs: https://hub.docker.com/_/wordpress/ - services: + wazuh-certs: + image: wazuh/wazuh-certs-generator:0.0.4 + container_name: ${W9_ID}-certs + restart: "no" + environment: + CERT_TOOL_VERSION: "4.14" + entrypoint: ["/bin/bash", "-c"] + command: + - | + if [ ! -f /certificates/wazuh.indexer.pem ]; then + rm -rf /wazuh-certificates + /entrypoint.sh + fi + chmod 755 /certificates + volumes: + - ./src/wazuh_indexer_ssl_certs:/certificates + - ./src/certs.yml:/config/certs.yml + networks: + - wazuh-internal - wordpress: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID - restart: unless-stopped - #This is for access host from container - # extra_hosts: ["host.docker.internal:host-gateway"] - # command: | - # /bin/bash -c "ping -c 3 host.docker.internal" - logging: - driver: "json-file" - options: - max-file: "5" - max-size: 10m - deploy: - resources: - limits: - memory: 5g - cpus: '0.7' - ports: - - $W9_HTTP_PORT_SET:80 + busybox: + image: busybox + container_name: ${W9_ID}-sysctl + restart: "no" + command: /bin/sh -c "sysctl -w vm.max_map_count=262144 || true" + privileged: true + networks: + - wazuh-internal + + wazuh.indexer: + image: wazuh/wazuh-indexer:${W9_VERSION} + container_name: ${W9_ID}-indexer + hostname: wazuh.indexer + restart: always env_file: .env + depends_on: + wazuh-certs: + condition: service_completed_successfully + busybox: + condition: service_completed_successfully + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 + hard: 65536 + healthcheck: + test: ["CMD-SHELL", "curl -k -s -u admin:$${INDEXER_PASSWORD} https://localhost:9200/_cluster/health | grep -q '\"status\"'"] + interval: 10s + timeout: 10s + retries: 30 + start_period: 60s volumes: - - wordpress:/var/www/html - - ./src/php_exra.ini:/usr/local/etc/php/conf.d/php_exra.ini + - wazuh-indexer-data:/var/lib/wazuh-indexer + - ./src/wazuh_indexer_ssl_certs:/usr/share/wazuh-indexer/config/certs + - ./src/wazuh.indexer.yml:/usr/share/wazuh-indexer/config/opensearch.yml + - ./src/internal_users.yml:/usr/share/wazuh-indexer/config/opensearch-security/internal_users.yml + networks: + - wazuh-internal - mariadb: - image: mariadb:10.4 - container_name: $W9_ID-mariadb - restart: unless-stopped - command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --innodb_log_buffer_size=30M + wazuh.manager: + image: wazuh/wazuh-manager:${W9_VERSION} + container_name: ${W9_ID}-manager + hostname: wazuh.manager + restart: always + env_file: .env + depends_on: + wazuh.indexer: + condition: service_healthy + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 655360 + hard: 655360 + ports: + - "${W9_AGENT_PORT_SET}:1514" # Agent Connection + - "${W9_ENROLLMENT_PORT_SET}:1515" # Agent Enrollment + # Uncomment to collect syslog from external devices: + # - "${W9_SYSLOG_UDP_PORT_SET}:514/udp" # Syslog Collection + # Uncomment to expose the Wazuh REST API to external automation: + # - "${W9_API_PORT_SET}:55000" # Manager API volumes: - - mysql_data:/var/lib/mysql - environment: - MYSQL_DATABASE: $WORDPRESS_DB_NAME - MYSQL_USER: $WORDPRESS_DB_USER - MYSQL_PASSWORD: $W9_POWER_PASSWORD - MYSQL_ROOT_PASSWORD: $W9_POWER_PASSWORD + - wazuh_api_configuration:/var/ossec/api/configuration + - wazuh_etc:/var/ossec/etc + - wazuh_logs:/var/ossec/logs + - wazuh_queue:/var/ossec/queue + - wazuh_var_multigroups:/var/ossec/var/multigroups + - wazuh_integrations:/var/ossec/integrations + - wazuh_active_response:/var/ossec/active-response/bin + - wazuh_agentless:/var/ossec/agentless + - wazuh_wodles:/var/ossec/wodles + - filebeat_etc:/etc/filebeat + - filebeat_var:/var/lib/filebeat + - ./src/wazuh_indexer_ssl_certs:/etc/ssl/wazuh + - ./src/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf + networks: + - wazuh-internal + + wazuh.dashboard: + image: wazuh/wazuh-dashboard:${W9_VERSION} + container_name: ${W9_ID} + hostname: wazuh.dashboard + restart: always + env_file: .env + depends_on: + wazuh.indexer: + condition: service_healthy + wazuh.manager: + condition: service_started + ports: + - "${W9_HTTPS_PORT_SET}:5601" # Web Console + volumes: + - ./src/wazuh_indexer_ssl_certs:/usr/share/wazuh-dashboard/certs + - ./src/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml + - ./src/wazuh.yml:/usr/share/wazuh-dashboard/data/wazuh/config/wazuh.yml + - wazuh-dashboard-config:/usr/share/wazuh-dashboard/data/wazuh/config + - wazuh-dashboard-custom:/usr/share/wazuh-dashboard/plugins/wazuh/public/assets/custom + networks: + - wazuh-internal + - default volumes: - wordpress: - mysql_data: - + wazuh-indexer-data: + wazuh_api_configuration: + wazuh_etc: + wazuh_logs: + wazuh_queue: + wazuh_var_multigroups: + wazuh_integrations: + wazuh_active_response: + wazuh_agentless: + wazuh_wodles: + filebeat_etc: + filebeat_var: + wazuh-dashboard-config: + wazuh-dashboard-custom: + networks: + wazuh-internal: + name: ${W9_ID}-internal + driver: bridge default: - name: $W9_NETWORK + name: ${W9_NETWORK} external: true diff --git a/apps/wazuh/src/certs.yml b/apps/wazuh/src/certs.yml new file mode 100755 index 000000000..c3e017be1 --- /dev/null +++ b/apps/wazuh/src/certs.yml @@ -0,0 +1,16 @@ +nodes: + # Wazuh indexer server nodes + indexer: + - name: wazuh.indexer + ip: wazuh.indexer + + # Wazuh server nodes + # Use node_type only with more than one Wazuh manager + server: + - name: wazuh.manager + ip: wazuh.manager + + # Wazuh dashboard node + dashboard: + - name: wazuh.dashboard + ip: wazuh.dashboard diff --git a/apps/wazuh/src/internal_users.yml b/apps/wazuh/src/internal_users.yml new file mode 100644 index 000000000..d9f05b343 --- /dev/null +++ b/apps/wazuh/src/internal_users.yml @@ -0,0 +1,56 @@ +--- +# This is the internal user database +# The hash value is a bcrypt hash and can be generated with plugin/tools/hash.sh + +_meta: + type: "internalusers" + config_version: 2 + +# Define your internal users here + +## Demo users + +admin: + hash: "$2y$12$K/SpwjtB.wOHJ/Nc6GVRDuc1h0rM1DfvziFRNPtk27P.c4yDr9njO" + reserved: true + backend_roles: + - "admin" + description: "Demo admin user" + +kibanaserver: + hash: "$2a$12$4AcgAt3xwOWadA5s5blL6ev39OXDNhmOesEoo33eZtrq2N0YrU3H." + reserved: true + description: "Demo kibanaserver user" + +kibanaro: + hash: "$2a$12$JJSXNfTowz7Uu5ttXfeYpeYE0arACvcwlPBStB1F.MI7f0U9Z4DGC" + reserved: false + backend_roles: + - "kibanauser" + - "readall" + attributes: + attribute1: "value1" + attribute2: "value2" + attribute3: "value3" + description: "Demo kibanaro user" + +logstash: + hash: "$2a$12$u1ShR4l4uBS3Uv59Pa2y5.1uQuZBrZtmNfqB3iM/.jL0XoV9sghS2" + reserved: false + backend_roles: + - "logstash" + description: "Demo logstash user" + +readall: + hash: "$2a$12$ae4ycwzwvLtZxwZ82RmiEunBbIPiAmGZduBAjKN0TXdwQFtCwARz2" + reserved: false + backend_roles: + - "readall" + description: "Demo readall user" + +snapshotrestore: + hash: "$2y$12$DpwmetHKwgYnorbgdvORCenv4NAK8cPUg8AI6pxLCuWf/ALc0.v7W" + reserved: false + backend_roles: + - "snapshotrestore" + description: "Demo snapshotrestore user" diff --git a/apps/wazuh/src/opensearch_dashboards.yml b/apps/wazuh/src/opensearch_dashboards.yml new file mode 100644 index 000000000..903045eb1 --- /dev/null +++ b/apps/wazuh/src/opensearch_dashboards.yml @@ -0,0 +1,16 @@ +server.host: 0.0.0.0 +server.port: 5601 +opensearch.hosts: https://wazuh.indexer:9200 +opensearch.ssl.verificationMode: certificate +opensearch.requestHeadersWhitelist: ["securitytenant","Authorization"] +opensearch_security.multitenancy.enabled: false +opensearch_security.readonly_mode.roles: ["kibana_read_only"] +server.ssl.enabled: true +server.ssl.key: "/usr/share/wazuh-dashboard/certs/wazuh.dashboard-key.pem" +server.ssl.certificate: "/usr/share/wazuh-dashboard/certs/wazuh.dashboard.pem" +opensearch.ssl.certificateAuthorities: ["/usr/share/wazuh-dashboard/certs/root-ca.pem"] +uiSettings.overrides.defaultRoute: /app/wz-home +# Session expiration settings +opensearch_security.cookie.ttl: 900000 +opensearch_security.session.ttl: 900000 +opensearch_security.session.keepalive: true diff --git a/apps/wazuh/src/wazuh.indexer.yml b/apps/wazuh/src/wazuh.indexer.yml new file mode 100644 index 000000000..21b8e978c --- /dev/null +++ b/apps/wazuh/src/wazuh.indexer.yml @@ -0,0 +1,36 @@ +network.host: "0.0.0.0" +node.name: "wazuh.indexer" +cluster.name: "wazuh-cluster" +path.data: /var/lib/wazuh-indexer +path.logs: /var/log/wazuh-indexer +discovery.type: single-node +compatibility.override_main_response_version: true +plugins.security.ssl.http.pemcert_filepath: /usr/share/wazuh-indexer/config/certs/wazuh.indexer.pem +plugins.security.ssl.http.pemkey_filepath: /usr/share/wazuh-indexer/config/certs/wazuh.indexer-key.pem +plugins.security.ssl.http.pemtrustedcas_filepath: /usr/share/wazuh-indexer/config/certs/root-ca.pem +plugins.security.ssl.transport.pemcert_filepath: /usr/share/wazuh-indexer/config/certs/wazuh.indexer.pem +plugins.security.ssl.transport.pemkey_filepath: /usr/share/wazuh-indexer/config/certs/wazuh.indexer-key.pem +plugins.security.ssl.transport.pemtrustedcas_filepath: /usr/share/wazuh-indexer/config/certs/root-ca.pem +plugins.security.ssl.http.enabled: true +plugins.security.ssl.transport.enforce_hostname_verification: false +plugins.security.ssl.transport.resolve_hostname: false +plugins.security.ssl.http.enabled_ciphers: + - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" + - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" + - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" +plugins.security.ssl.http.enabled_protocols: + - "TLSv1.2" +plugins.security.authcz.admin_dn: +- "CN=admin,OU=Wazuh,O=Wazuh,L=California,C=US" +plugins.security.check_snapshot_restore_write_privileges: true +plugins.security.enable_snapshot_restore_privilege: true +plugins.security.nodes_dn: +- "CN=wazuh.indexer,OU=Wazuh,O=Wazuh,L=California,C=US" +plugins.security.restapi.roles_enabled: +- "all_access" +- "security_rest_api_access" +plugins.security.system_indices.enabled: true +plugins.security.system_indices.indices: [".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opendistro-notifications-*", ".opendistro-notebooks", ".opensearch-observability", ".opendistro-asynchronous-search-response*", ".replication-metadata-store"] +plugins.security.allow_default_init_securityindex: true +cluster.routing.allocation.disk.threshold_enabled: false \ No newline at end of file diff --git a/apps/wazuh/src/wazuh.yml b/apps/wazuh/src/wazuh.yml new file mode 100644 index 000000000..5ff4e2bef --- /dev/null +++ b/apps/wazuh/src/wazuh.yml @@ -0,0 +1,7 @@ +hosts: + - 1513629884013: + url: "https://wazuh.manager" + port: 55000 + username: wazuh-wui + password: "MyS3cr37P450r.*-" + run_as: true diff --git a/apps/runtime/src/9panel/docs/developer-guide.md b/apps/wazuh/src/wazuh_indexer_ssl_certs/.gitkeep similarity index 100% rename from apps/runtime/src/9panel/docs/developer-guide.md rename to apps/wazuh/src/wazuh_indexer_ssl_certs/.gitkeep diff --git a/apps/wazuh/src/wazuh_manager.conf b/apps/wazuh/src/wazuh_manager.conf new file mode 100644 index 000000000..5ff2c0762 --- /dev/null +++ b/apps/wazuh/src/wazuh_manager.conf @@ -0,0 +1,311 @@ + + + yes + yes + no + no + no + smtp.example.wazuh.com + wazuh@example.wazuh.com + recipient@example.wazuh.com + 12 + alerts.log + 10m + 0 + + + + 3 + 12 + + + + + plain + + + + secure + 1514 + tcp + 131072 + + + + + no + yes + yes + yes + yes + yes + yes + yes + + + 43200 + + etc/rootcheck/rootkit_files.txt + etc/rootcheck/rootkit_trojans.txt + + yes + + + + yes + 1800 + 1d + yes + + wodles/java + wodles/ciscat + + + + + yes + yes + /var/log/osquery/osqueryd.results.log + /etc/osquery/osquery.conf + yes + + + + + no + 1h + yes + yes + yes + yes + yes + yes + yes + + + + 10 + + + + + yes + yes + 12h + yes + + + + yes + yes + 60m + + + + yes + + https://wazuh.indexer:9200 + + + + /etc/ssl/wazuh/root-ca-manager.pem + + /etc/ssl/wazuh/wazuh.manager.pem + /etc/ssl/wazuh/wazuh.manager-key.pem + + + + + + no + + + 43200 + + yes + + + yes + + + no + + + /etc,/usr/bin,/usr/sbin + /bin,/sbin,/boot + + + /etc/mtab + /etc/hosts.deny + /etc/mail/statistics + /etc/random-seed + /etc/random.seed + /etc/adjtime + /etc/httpd/logs + /etc/utmpx + /etc/wtmpx + /etc/cups/certs + /etc/dumpdates + /etc/svc/volatile + + + .log$|.swp$ + + + /etc/ssl/private.key + + yes + yes + yes + yes + + + 10 + + + 100 + + + + yes + 5m + 1h + 10 + + + + + + 127.0.0.1 + ^localhost.localdomain$ + + + + disable-account + disable-account + yes + + + + restart-wazuh + restart-wazuh + + + + firewall-drop + firewall-drop + yes + + + + host-deny + host-deny + yes + + + + route-null + route-null + yes + + + + win_route-null + route-null.exe + yes + + + + netsh + netsh.exe + yes + + + + + + + command + df -P + 360 + + + + full_command + netstat -tulpn | sed 's/\([[:alnum:]]\+\)\ \+[[:digit:]]\+\ \+[[:digit:]]\+\ \+\(.*\):\([[:digit:]]*\)\ \+\([0-9\.\:\*]\+\).\+\ \([[:digit:]]*\/[[:alnum:]\-]*\).*/\1 \2 == \3 == \4 \5/' | sort -k 4 -g | sed 's/ == \(.*\) ==/:\1/' | sed 1,2d + netstat listening ports + 360 + + + + full_command + last -n 20 + 360 + + + + + ruleset/decoders + ruleset/rules + 0215-policy_rules.xml + etc/lists/audit-keys + etc/lists/amazon/aws-eventnames + etc/lists/security-eventchannel + etc/lists/malicious-ioc/malicious-ip + etc/lists/malicious-ioc/malicious-domains + etc/lists/malicious-ioc/malware-hashes + + + etc/decoders + etc/rules + + + + yes + 1 + 64 + 15m + + + + + no + 1515 + no + yes + no + HIGH:!ADH:!EXP:!MD5:!RC4:!3DES:!CAMELLIA:@STRENGTH + + no + etc/sslmanager.cert + etc/sslmanager.key + no + + + + wazuh + node01 + master + aa093264ef885029653eea20dfcf51ae + 1516 + 0.0.0.0 + + wazuh.manager + + no + yes + + + + + + + syslog + /var/ossec/logs/active-responses.log + + + diff --git a/apps/wazuh/tests/cases.yml b/apps/wazuh/tests/cases.yml new file mode 100644 index 000000000..f16490388 --- /dev/null +++ b/apps/wazuh/tests/cases.yml @@ -0,0 +1,9 @@ +# The Wazuh dashboard is HTTPS-only, so the adaptive HTTP web-access check does +# not apply. The custom script waits for the dashboard over HTTPS instead. +skip: + - id: web-access + +optional: + - id: dashboard-https + type: script + script: check.sh diff --git a/apps/wazuh/tests/check.sh b/apps/wazuh/tests/check.sh new file mode 100755 index 000000000..25d4dd76d --- /dev/null +++ b/apps/wazuh/tests/check.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -uo pipefail + +port="${W9_HTTPS_PORT_SET:-9443}" +base="${BASE_URL:-https://localhost:${port}}" +deadline=$((SECONDS + 300)) +code="000" + +while [ "$SECONDS" -lt "$deadline" ]; do + code=$(curl -k -s -o /dev/null -w "%{http_code}" --max-time 15 "${base}/" || true) + case "$code" in + 200|301|302) + echo "wazuh dashboard ${base}/ -> ${code}" + exit 0 + ;; + esac + sleep 5 +done + +echo "wazuh dashboard ${base}/ -> ${code} (timeout)" +exit 1 diff --git a/apps/wazuh/variables.json b/apps/wazuh/variables.json index eadd7bf01..71218f435 100644 --- a/apps/wazuh/variables.json +++ b/apps/wazuh/variables.json @@ -1,21 +1,35 @@ { "name": "wazuh", "trademark": "Wazuh", - "release": false, + "release": true, + "upstream": { + "image": "https://hub.docker.com/r/wazuh/wazuh-dashboard", + "releases": "https://github.com/wazuh/wazuh/releases", + "compose": { + "compose": "https://raw.githubusercontent.com/wazuh/wazuh-docker/v4.14.7/single-node/docker-compose.yml" + }, + "docs": [ + "https://documentation.wazuh.com/current/deployment-options/docker/wazuh-container.html", + "https://github.com/wazuh/wazuh-docker" + ] + }, "edition": [ { "dist": "community", "version": [ - "latest" + "4.14.7" ] } ], + "access": { + "web": { + "port": 5601, + "path": "/" + } + }, "requirements": { "cpu": "2", "memory": "4", "disk": "10" - }, - "upstream": { - "image": "https://hub.docker.com/r/wazuh/wazuh-manager" } } diff --git a/apps/weaviate/.env b/apps/weaviate/.env index f0be13916..cbdfb1888 100644 --- a/apps/weaviate/.env +++ b/apps/weaviate/.env @@ -1,18 +1,41 @@ W9_REPO=semitechnologies/weaviate W9_DIST=community -W9_VERSION=latest +W9_VERSION=1.39.5 + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. #### -- Not allowed to edit below environments when recreate app based on existing data -- #### + W9_ID=weaviate -# Environments which for user settings when create applications + +# Weaviate exposes an HTTP API (8080) and a gRPC API (50051) used by clients. W9_HTTP_PORT_SET=8080 W9_HTTP_PORT=8080 +W9_GRPC_PORT_SET=50051 W9_URL=example.youdomain.com W9_NETWORK=websoft9 +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Weaviate image environment variables +# Docs: https://weaviate.io/developers/weaviate/installation/docker-compose +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: QUERY_DEFAULTS_LIMIT=25 AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED='true' PERSISTENCE_DATA_PATH='/var/lib/weaviate' DEFAULT_VECTORIZER_MODULE='none' -ENABLE_API_BASED_MODULES='true' CLUSTER_HOSTNAME='node1' + +# Not used by default; enable only when needed: +# AUTHENTICATION_APIKEY_ENABLED='false' +# AUTHENTICATION_APIKEY_ALLOWED_KEYS='user-a-key,user-b-key' +# AUTHENTICATION_APIKEY_USERS='user-a,user-b' +# AUTHORIZATION_ENABLE_RBAC='true' +# AUTHORIZATION_RBAC_ROOT_USERS='user-a' diff --git a/apps/weaviate/CHANGELOG.md b/apps/weaviate/CHANGELOG.md index 09c4e7bd0..4fd05e6a0 100644 --- a/apps/weaviate/CHANGELOG.md +++ b/apps/weaviate/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG -## Release +## 2026-09-20 -### Fixes and Enhancements +- Updated Weaviate from `1.26.6` to `1.39.5`, the latest stable upstream release. +- Pinned `W9_VERSION` to `1.39.5` and declared it in `variables.json`. +- Added the gRPC API port (`W9_GRPC_PORT_SET`, 50051) required by Weaviate clients. +- Removed `ENABLE_API_BASED_MODULES`, which upstream removed in v1.33. +- Aligned `.env` and `docker-compose.yml` with current repository policy: braced variable references, inline published-port comments, and the `.env` section banner with a Docs URL. +- Added a readiness healthcheck against `/v1/.well-known/ready`. +- Added `tests/cases.yml` with an app-specific readiness check. +- Added upstream releases and documentation references to `variables.json`. +- Regenerated `README.md`. diff --git a/apps/weaviate/Notes.md b/apps/weaviate/Notes.md deleted file mode 100644 index 122ecbbb8..000000000 --- a/apps/weaviate/Notes.md +++ /dev/null @@ -1 +0,0 @@ -# Weaviate diff --git a/apps/weaviate/README.md b/apps/weaviate/README.md index 6947c4e14..0d3a7948e 100644 --- a/apps/weaviate/README.md +++ b/apps/weaviate/README.md @@ -1,26 +1,87 @@ -# Weaviate on Docker +# Weaviate on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Weaviate: +## Quick Start +### Deploy Verification - - community: 1.26.6, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Weaviate**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Make sure you are signed in to the Weaviate admin console. +2. Try a core feature. -The following are the minimal [recommended requirements](https://weaviate.io/developers/weaviate/current/): +### Change Password -* **RAM**: 4 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 10 GB of free space -* **bandwidth**: more fluent experience over 100M +1. In the [Websoft9](https://www.websoft9.com) console, open the app's **Compose** tab. +2. Update the password in `.env` and save. +3. Rebuild the app. + -## Install +## Configuration Reference -You can install this Weaviate by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Weaviate Docker image](https://hub.docker.com/r/semitechnologies/weaviate) and makes some improvements below. -If you want use Weaviate with **Websoft9 Business Support** free, you can [subscribe Weaviate](https://www.websoft9.com/apps) on Cloud platform + -## Documentation + -[Weaviate Administrator Guide](https://support.websoft9.com/docs/weaviate) powered by Websoft9 \ No newline at end of file +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 1.39.5, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| HTTP API | 8080 | +| gRPC API | 50051 | + + +### Data Directory + + +Data is persisted in the `weaviate_data` volume, mounted at `/var/lib/weaviate`. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [Weaviate Administrator Guide](https://support.websoft9.com/docs/weaviate) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/semitechnologies/weaviate) + +- [Releases](https://github.com/weaviate/weaviate/releases) + +- [Official docs](https://weaviate.io/developers/weaviate/installation/docker-compose) + +- [Official docs](https://weaviate.io/developers/weaviate/config-refs/env-vars) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/weaviate/docker-compose.yml b/apps/weaviate/docker-compose.yml index 66db30a71..ab9c2b4f5 100644 --- a/apps/weaviate/docker-compose.yml +++ b/apps/weaviate/docker-compose.yml @@ -1,27 +1,33 @@ -services: - weaviate: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID - command: - - --host - - 0.0.0.0 - - --port - - '8080' - - --scheme - - http - ports: - - $W9_HTTP_PORT_SET:8080 - - volumes: - - weaviate_data:/var/lib/weaviate - restart: unless-stopped - env_file: - - .env - -networks: - default: - name: $W9_NETWORK - external: true - -volumes: - weaviate_data: +services: + weaviate: + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} + command: + - --host + - 0.0.0.0 + - --port + - '8080' + - --scheme + - http + ports: + - "${W9_HTTP_PORT_SET}:8080" # HTTP API + - "${W9_GRPC_PORT_SET}:50051" # gRPC API + volumes: + - weaviate_data:/var/lib/weaviate + restart: unless-stopped + env_file: + - .env + healthcheck: + test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8080/v1/.well-known/ready || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + +networks: + default: + name: ${W9_NETWORK} + external: true + +volumes: + weaviate_data: diff --git a/apps/weaviate/tests/cases.yml b/apps/weaviate/tests/cases.yml new file mode 100644 index 000000000..3d7522aaa --- /dev/null +++ b/apps/weaviate/tests/cases.yml @@ -0,0 +1,8 @@ +skip: + - id: web-access + +optional: + - id: readiness + type: web-access + path: /v1/.well-known/ready + expect_status: 200 diff --git a/apps/weaviate/variables.json b/apps/weaviate/variables.json index 3fbf8ad51..e4228dc18 100644 --- a/apps/weaviate/variables.json +++ b/apps/weaviate/variables.json @@ -6,17 +6,29 @@ { "dist": "community", "version": [ - "1.26.6", + "1.39.5", "latest" ] } ], + "access": { + "defaultScheme": "http", + "api": { + "port": 8080, + "path": "/v1/" + } + }, "requirements": { "cpu": "2", "memory": "4", "disk": "10" }, "upstream": { - "image": "https://hub.docker.com/r/semitechnologies/weaviate" + "image": "https://hub.docker.com/r/semitechnologies/weaviate", + "releases": "https://github.com/weaviate/weaviate/releases", + "docs": [ + "https://weaviate.io/developers/weaviate/installation/docker-compose", + "https://weaviate.io/developers/weaviate/config-refs/env-vars" + ] } } diff --git a/apps/webcheck/src/nginx-proxy.conf.template b/apps/webcheck/src/nginx-proxy.conf.template deleted file mode 100644 index 951364ffe..000000000 --- a/apps/webcheck/src/nginx-proxy.conf.template +++ /dev/null @@ -1,57 +0,0 @@ -proxy_busy_buffers_size 512k; -proxy_buffers 4 512k; -proxy_buffer_size 256k; -client_max_body_size 50m; -# override default location / -location / { - add_header X-Served-By $host; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-Scheme $scheme; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Real-IP $remote_addr; - proxy_pass $forward_scheme://$server:$port$request_uri; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection upgrade; - } - -location /console { - proxy_pass http://$server:8080; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; -} - -# for echo some useful information -location / { - default_type text/plain; - return 200 'Hello World'; -} - -location /oida/ { -# this is the address and port of the ORDS installation -proxy_pass http://127.0.0.1:8080/ords/; - -# set Origin to blank to avoid Chrome problems with CORS -proxy_set_header Origin "" ; - -# pass along some header variables with the public host name/port/and so on -proxy_set_header Host $host; -proxy_set_header X-Forwarded-Host $host:$server_port; -proxy_set_header X-Real-IP $remote_addr; -proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -proxy_set_header X-Forwarded-Proto $scheme; - -# this reverse proxies any "location" headers being passed in the response -proxy_redirect http://$host/ords/ https://$host/oida/; - -# also tell cookies their public path -proxy_cookie_path /ords/ /oida/; - -# reverse proxy links included in response (ie from ORDS webservice) -sub_filter_types application/json ; -sub_filter http://$host/ords/ https://$host/oida/; -sub_filter_once off; -} diff --git a/apps/webcheck/src/php_exra.ini b/apps/webcheck/src/php_exra.ini deleted file mode 100644 index b253d5718..000000000 --- a/apps/webcheck/src/php_exra.ini +++ /dev/null @@ -1,8 +0,0 @@ -file_uploads = On -max_input_time = 800 -max_execution_time = 300 -memory_limit = 600M -upload_max_filesize = 900M -post_max_size = 900M -max_file_uploads = 200 -error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT \ No newline at end of file diff --git a/apps/wordpress/variables.json b/apps/wordpress/variables.json index 790af82bf..4e8611eb0 100644 --- a/apps/wordpress/variables.json +++ b/apps/wordpress/variables.json @@ -21,6 +21,17 @@ ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 80, + "path": "/" + }, + "admin": { + "port": 80, + "path": "/wp-admin" + } + }, "requirements": { "cpu": "1", "memory": "1", diff --git a/apps/xwiki/.env b/apps/xwiki/.env index d385357bf..17e5951d6 100644 --- a/apps/xwiki/.env +++ b/apps/xwiki/.env @@ -1,31 +1,45 @@ W9_REPO=xwiki -W9_DIST='community' +W9_DIST=community +W9_VERSION=18.7 -# This tag is tomcat+mysql runtime, not xwiki version -# xwiki version should set XWIKI_VERSION -W9_VERSION='17.10' -W9_POWER_PASSWORD='fNoyaf5!dgkPBx0E' +W9_POWER_PASSWORD="fNoyaf5!dgkPBx0E" #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='xwiki' -# W9_HTTP_PORT or W9_HTTPS_PORT is need at leaset and used for proxy for web application -# Some container (e.g teleport) need HTTPS access, then need to set this pra +W9_ID=xwiki + +# Web/internal ports W9_HTTP_PORT=8080 -W9_HTTP_PORT_SET='9001' +W9_HTTP_PORT_SET=9004 + +# URL helper +W9_URL=appname.example.com +# Bundled database W9_DB_EXPOSE="mysql" -W9_DB_VERSION="8.3" -W9_URL='example.youdomain.com' +W9_DB_VERSION=8.4 + W9_NETWORK=websoft9 #### ----------------------------------------------------------------------------------------- #### +# ============================================================ +# XWiki image environment variables +# Docs: https://github.com/xwiki/xwiki-docker +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ -# Below environment is created by xwiki -# default database is xwiki, so it need to create it before install -# XWIKI_VERSION=15.10.4 is for docker build: https://github.com/xwiki/xwiki-docker/blob/master/15/mysql-tomcat/Dockerfile, don't need for docker run -DB_USER=root +# Used by docker-compose.yml: + +DB_USER=xwiki DB_DATABASE=xwiki DB_PASSWORD=${W9_POWER_PASSWORD} DB_HOST=${W9_ID}-mysql + +# Not used by default; enable only when needed: +# XWIKI_VERSION=18.7.0 +# DB_USE_SSL=true +# JDBC_PARAMS=useSSL=false +# XWIKI_MEMORY=2048 diff --git a/apps/xwiki/CHANGELOG.md b/apps/xwiki/CHANGELOG.md index 582cf46c5..f95c42ed9 100644 --- a/apps/xwiki/CHANGELOG.md +++ b/apps/xwiki/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-20 +- Update XWiki to 18.7 (latest stable) and the bundled MySQL to 8.4 LTS. +- Connect as the dedicated `xwiki` database user instead of `root`. +- Add `init: true` and a main-container healthcheck; default the web port to 9004. +- Refresh `variables.json`, README and Notes, and add a webapp smoke test. diff --git a/apps/xwiki/Notes.md b/apps/xwiki/Notes.md index 02755fcf1..531e41e8d 100644 --- a/apps/xwiki/Notes.md +++ b/apps/xwiki/Notes.md @@ -1,29 +1,30 @@ -## XWiki +# XWiki Notes -### 安装 +> 内部维护说明;面向客户的文档以 `README.md` 为准。 -1. MySQL 初始化问题 +## 来源 -官方文档要求初始化时运行 `grant all privileges on *.* to xwiki@'%'`。但即使不运行这段脚本,查询 xwiki 权限发现也具有 ALL PRIVILEGES +- 官方镜像:https://hub.docker.com/_/xwiki +- 镜像源码:https://github.com/xwiki/xwiki-docker +- 版本列表:https://github.com/xwiki/xwiki-platform/releases -``` -mysql> show grants for xwiki@'%'; -+--------------------------------------------------+ -| Grants for xwiki@% | -+--------------------------------------------------+ -| GRANT USAGE ON *.* TO 'xwiki'@'%' | -| GRANT ALL PRIVILEGES ON `xwiki`.* TO 'xwiki'@'%' | -+--------------------------------------------------+ -2 rows in set (0.00 sec) -``` +## 版本与镜像标签 -所以暂时不做权限处理。 +- `W9_VERSION=18.7` 对应官方 `18.7` 标签(即 `18/mysql-tomcat` 变体,`latest`/`stable` 也指向它)。 +- `xwiki` 默认标签就是 mysql-tomcat 变体,无需再加 `-mysql-tomcat` 后缀。 +- 官方 compose 通过 `XWIKI_VERSION` 指定具体 XWiki 版本;本包依赖 `18.7` 浮动标签,因此不设置 `XWIKI_VERSION`,随 18.7.x 自动更新。 +- 18.7 之前的大版本(LTS `17.10`、中间 LTS `18.4`)仍由官方发布,但本包跟随最新稳定线。 -2. Solr service +## 数据库 -By default XWiki ships with an embedded Solr. 但推荐使用外部 solr。官方方案配置外部 solr 还需要挂载一个配置文件,并更改权限,考虑复杂性,暂时不做 +- 内置 MySQL `${W9_ID}-mysql`,镜像 `mysql:${W9_DB_VERSION}`(当前 8.4 LTS)。 +- 连接参数:`DB_USER=xwiki`、`DB_DATABASE=xwiki`、`DB_PASSWORD=${W9_POWER_PASSWORD}`、`DB_HOST=${W9_ID}-mysql`。 +- `src/mysql_init.sql`(官方 `init.sql`)执行 `grant all privileges on *.* to xwiki@'%'`。这一步是**必需**的:XWiki 迁移要读取 `information_schema` 元数据,需要全局 `PROCESS` 权限,仅 `GRANT ALL ON xwiki.*` 会报 `Access denied; you need (at least one of) the PROCESS privilege(s)`,导致数据库迁移失败(`Database is currently in version [0]`)。 +- 启动参数保持官方推荐:`utf8mb4` / `utf8mb4_bin` / `explicit-defaults-for-timestamp=1`(MySQL 8.4 仍接受)。 -3. 安装向导 - -安装向导会在线拉去资源,故时间比较长 +## 首次安装与升级 +- 首次访问会进入 **Distribution Wizard**,会在线下载 Standard Flavor,需要外网,耗时数分钟。 +- 管理员账号在向导中创建,不受 `.env` 控制。 +- 17.x → 18.x 为大版本升级:先备份 MySQL 数据卷与 `xwiki` 数据卷,升级后按向导完成数据库 schema 迁移。 +- 默认使用内嵌 Solr;官方推荐外部 Solr,但需额外挂载配置并处理权限,本包暂不启用。 diff --git a/apps/xwiki/README.md b/apps/xwiki/README.md index 9df37186d..a66528ad7 100644 --- a/apps/xwiki/README.md +++ b/apps/xwiki/README.md @@ -1,26 +1,98 @@ -# XWiki on Docker +# XWiki on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for XWiki: +## Quick Start +### Deploy Verification - - community: 17.4, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **XWiki**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Open the XWiki console; the first visit shows the **Distribution Wizard**. +2. Complete the wizard to create the wiki and the first administrator account. +3. After installation, sign in and start creating pages. -The following are the minimal [recommended requirements](https://github.com/xwiki-contrib/docker-xwiki/blob/master/README.md): +### Change Password -* **RAM**: 2 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 8 GB of free space -* **bandwidth**: more fluent experience over 100M +1. Sign in to XWiki and open your user profile. +2. Change your own password there, or use **Administration → Users** to reset other accounts. + -## Install +## Configuration Reference -You can install this XWiki by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [XWiki Docker image](https://hub.docker.com/_/xwiki) and makes some improvements below. -If you want use XWiki with **Websoft9 Business Support** free, you can [subscribe XWiki](https://www.websoft9.com/apps) on Cloud platform + +- The first visit runs the XWiki Distribution Wizard; it downloads the standard flavor, so the first install takes several minutes and needs outbound network access. +- `DB_USER` / `DB_PASSWORD` / `DB_DATABASE` / `DB_HOST` configure the bundled MySQL service `${W9_ID}-mysql`. +- XWiki data is persisted in the `xwiki` volume (`/usr/local/xwiki`). +- The administrator account is created interactively in the wizard and is not controlled by `.env`. + -## Documentation +Apps run as containers; rebuild after any configuration change. -[XWiki Administrator Guide](https://support.websoft9.com/docs/xwiki) powered by Websoft9 \ No newline at end of file +### Version Support + +Supported versions: 18.7, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 8080 | + + +### Data Directory + + +- `xwiki` → `/usr/local/xwiki` +- `mysql` → `/var/lib/mysql` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration is overridden by mounting `./src/mysql_init.sql` to `/docker-entrypoint-initdb.d/init.sql`. + + +## References + +- [XWiki Administrator Guide](https://support.websoft9.com/docs/xwiki) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/_/xwiki) + +- [Releases](https://github.com/xwiki/xwiki-platform/releases) + +- [Official compose](https://raw.githubusercontent.com/xwiki/xwiki-docker/master/18/mysql-tomcat/docker-compose.yml) + +- [Official env example](https://raw.githubusercontent.com/xwiki/xwiki-docker/master/18/mysql-tomcat/.env) + +- [Official docs](https://www.xwiki.org/xwiki/bin/view/Documentation/AdminGuide/Installation/) + +- [GitHub docs](https://github.com/xwiki/xwiki-docker) + + + +## Troubleshooting + +**The first page keeps showing the Distribution Wizard?** +- Complete the wizard and let it download the standard flavor; it needs outbound network access. + +**Container stays unhealthy?** +- XWiki can take a couple of minutes to start; check `docker compose logs ${W9_ID}`. + +**Database connection error?** +- Confirm `${W9_ID}-mysql` is running and that `DB_USER` / `DB_PASSWORD` / `DB_DATABASE` match the MySQL service. + diff --git a/apps/xwiki/docker-compose.yml b/apps/xwiki/docker-compose.yml index b6f3b36a5..27b4026a7 100644 --- a/apps/xwiki/docker-compose.yml +++ b/apps/xwiki/docker-compose.yml @@ -1,21 +1,23 @@ -# image: https://hub.docker.com/_/xwiki -# docs: https://github.com/xwiki/xwiki-docker/blob/master/README.md - -version: '3.8' - services: xwiki: - image: $W9_REPO:$W9_VERSION + image: ${W9_REPO}:${W9_VERSION} container_name: ${W9_ID} + restart: unless-stopped + init: true + env_file: + - .env ports: - - $W9_HTTP_PORT_SET:8080 + - "${W9_HTTP_PORT_SET}:8080" # Web Console volumes: - xwiki:/usr/local/xwiki - env_file: - - .env depends_on: - db - restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://localhost:8080/bin/view/Main/"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 120s db: image: mysql:${W9_DB_VERSION} @@ -27,6 +29,7 @@ services: - "--explicit-defaults-for-timestamp=1" volumes: - mysql:/var/lib/mysql + - ./src/mysql_init.sql:/docker-entrypoint-initdb.d/init.sql environment: MYSQL_DATABASE: xwiki MYSQL_USER: xwiki @@ -35,9 +38,9 @@ services: networks: default: - name: $W9_NETWORK + name: ${W9_NETWORK} external: true volumes: xwiki: - mysql: \ No newline at end of file + mysql: diff --git a/apps/xwiki/src/mysql_init.sql b/apps/xwiki/src/mysql_init.sql new file mode 100644 index 000000000..76dfd8c76 --- /dev/null +++ b/apps/xwiki/src/mysql_init.sql @@ -0,0 +1 @@ +grant all privileges on *.* to xwiki@'%' diff --git a/apps/xwiki/tests/cases.yml b/apps/xwiki/tests/cases.yml new file mode 100644 index 000000000..5a2eabae1 --- /dev/null +++ b/apps/xwiki/tests/cases.yml @@ -0,0 +1,7 @@ +# The adaptive checks cover compose config, container state, the healthcheck and +# the web root. XWiki is served from the ROOT context, so the app-specific check +# follows the redirect chain and asserts the XWiki webapp is actually rendered. +custom: + - id: xwiki-webapp + type: script + script: check.sh diff --git a/apps/xwiki/tests/check.sh b/apps/xwiki/tests/check.sh new file mode 100644 index 000000000..2c713ccfd --- /dev/null +++ b/apps/xwiki/tests/check.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -uo pipefail + +# BASE_URL is provided by `libs app-tests`; no package-specific variables needed. +base="${BASE_URL:?BASE_URL is required}" +body_file="$(mktemp)" +trap 'rm -f "$body_file"' EXIT +deadline=$((SECONDS + 300)) +code="000" + +while [ "$SECONDS" -lt "$deadline" ]; do + code=$(curl -s -L -o "$body_file" -w "%{http_code}" --max-time 20 "${base}/" || true) + if [ "$code" = "200" ] && grep -qi "xwiki" "$body_file"; then + echo "xwiki webapp served at ${base}/ (200)" + exit 0 + fi + sleep 5 +done + +echo "xwiki webapp -> ${code} (timeout)" +exit 1 diff --git a/apps/xwiki/variables.json b/apps/xwiki/variables.json index 313c892cc..4598af7cf 100644 --- a/apps/xwiki/variables.json +++ b/apps/xwiki/variables.json @@ -2,21 +2,36 @@ "name": "xwiki", "trademark": "XWiki", "release": true, + "upstream": { + "image": "https://hub.docker.com/_/xwiki", + "releases": "https://github.com/xwiki/xwiki-platform/releases", + "compose": { + "compose": "https://raw.githubusercontent.com/xwiki/xwiki-docker/master/18/mysql-tomcat/docker-compose.yml", + "env": "https://raw.githubusercontent.com/xwiki/xwiki-docker/master/18/mysql-tomcat/.env" + }, + "docs": [ + "https://www.xwiki.org/xwiki/bin/view/Documentation/AdminGuide/Installation/", + "https://github.com/xwiki/xwiki-docker" + ] + }, "edition": [ { "dist": "community", "version": [ - "17.10", + "18.7", "latest" ] } ], + "access": { + "web": { + "port": 8080, + "path": "/xwiki/" + } + }, "requirements": { "cpu": "1", "memory": "2", "disk": "8" - }, - "upstream": { - "image": "https://hub.docker.com/_/xwiki" } } diff --git a/apps/youtrack/.env b/apps/youtrack/.env index f4423def6..0f1106a22 100644 --- a/apps/youtrack/.env +++ b/apps/youtrack/.env @@ -1,13 +1,32 @@ W9_REPO=jetbrains/youtrack -W9_DIST='community' -W9_VERSION='2025.2.89748' -W9_ID='youtrack' -W9_HTTP_PORT_SET='9001' +W9_DIST=community +W9_VERSION=2026.2.18991 + +#### -- Not allowed to edit below environments when recreate app based on existing data -- #### + +W9_ID=youtrack + +# Web/internal ports: uncomment the ones the package actually uses. W9_HTTP_PORT=8080 -W9_URL='example.youdomain.com' -W9_URL_REPLACE=false -W9_URL_WITH_PORT=false +W9_HTTP_PORT_SET=9001 + +# Built-in login and URL helpers: keep these in the protected block because the appstore parser +# reads them from the app package metadata surface. +W9_URL=example.youdomain.com + W9_NETWORK=websoft9 -# config and Envrioment -# https://www.jetbrains.com/help/youtrack/server/youtrack-java-start-parameters.html +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# YouTrack image environment variables +# Docs: https://www.jetbrains.com/help/youtrack/server/youtrack-docker-installation.html +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +# (none) + +# Not used by default; enable only when needed: diff --git a/apps/youtrack/CHANGELOG.md b/apps/youtrack/CHANGELOG.md index 582cf46c5..f9ba63698 100644 --- a/apps/youtrack/CHANGELOG.md +++ b/apps/youtrack/CHANGELOG.md @@ -1,5 +1,10 @@ -# CHANGELOG +# Changelog -## Release -### Fixes and Enhancements +## 2026-09-20 +- Update YouTrack community image from `2025.2.89748` to `2026.2.18991`. +- Remove dead `W9_URL_REPLACE` and `W9_URL_WITH_PORT` helpers that failed the policy gate. +- Normalize `.env` and `docker-compose.yml` to current repository policy, including braced variable references and port purpose comments. +- Raise the documented memory requirement to 1.5 GB to match upstream. +- Declare the first-run Configuration Wizard token as a `container-file` credential source. +- Regenerate README. diff --git a/apps/youtrack/Notes.md b/apps/youtrack/Notes.md deleted file mode 100644 index f1d50c332..000000000 --- a/apps/youtrack/Notes.md +++ /dev/null @@ -1,7 +0,0 @@ -# YouTrack - -# Installation - -you can followed the url https://www.jetbrains.com/help/youtrack/server/youtrack-docker-installation.html. - -## FAQ diff --git a/apps/youtrack/README.md b/apps/youtrack/README.md index 68ea12b43..32d047152 100644 --- a/apps/youtrack/README.md +++ b/apps/youtrack/README.md @@ -1,26 +1,84 @@ -# YouTrack on Docker +# YouTrack on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for YouTrack: +## Quick Start +### Deploy Verification - - community: 2025.1.76253 +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **YouTrack**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Make sure you are signed in to the YouTrack admin console. +2. Try a core feature. -The following are the minimal [recommended requirements](https://www.jetbrains.com/help/youtrack/server/youtrack-supported-environments.html#hardware-requirements): +### Change Password -* **RAM**: 1 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 1.5 GB of free space -* **bandwidth**: more fluent experience over 100M +1. In the [Websoft9](https://www.websoft9.com) console, open the app's **Compose** tab. +2. Update the password in `.env` and save. +3. Rebuild the app. + -## Install +## Configuration Reference -You can install this YouTrack by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [YouTrack Docker image](https://hub.docker.com/r/jetbrains/youtrack) and makes some improvements below. -If you want use YouTrack with **Websoft9 Business Support** free, you can [subscribe YouTrack](https://www.websoft9.com/apps) on Cloud platform + -## Documentation + -[YouTrack Administrator Guide](https://support.websoft9.com/docs/youtrack) powered by Websoft9 \ No newline at end of file +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 2026.2.18991. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 8080 | + + +### Data Directory + + +- `conf` → `/opt/youtrack/conf` +- `data` → `/opt/youtrack/data` +- `logs` → `/opt/youtrack/logs` +- `backups` → `/opt/youtrack/backups` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [YouTrack Administrator Guide](https://support.websoft9.com/docs/youtrack) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/jetbrains/youtrack) + +- [Official docs](https://www.jetbrains.com/help/youtrack/server/youtrack-docker-installation.html) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/youtrack/docker-compose.yml b/apps/youtrack/docker-compose.yml index f6dc97947..3761b42bc 100644 --- a/apps/youtrack/docker-compose.yml +++ b/apps/youtrack/docker-compose.yml @@ -1,12 +1,9 @@ -#image: https://hub.docker.com/r/jetbrains/youtrack -#docs: https://www.jetbrains.com/help/youtrack/server/youtrack-docker-installation.html - version: '3.8' services: youtrack: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} restart: unless-stopped volumes: - conf:/opt/youtrack/conf @@ -14,16 +11,17 @@ services: - logs:/opt/youtrack/logs - backups:/opt/youtrack/backups ports: - - $W9_HTTP_PORT_SET:8080 - env_file: .env - + - "${W9_HTTP_PORT_SET}:8080" # Web Console + env_file: + - .env + networks: default: - name: $W9_NETWORK + name: ${W9_NETWORK} external: true volumes: conf: data: logs: - backups: \ No newline at end of file + backups: diff --git a/apps/youtrack/variables.json b/apps/youtrack/variables.json index 6aade8086..df78b69ae 100644 --- a/apps/youtrack/variables.json +++ b/apps/youtrack/variables.json @@ -2,20 +2,40 @@ "name": "youtrack", "trademark": "YouTrack", "release": true, + "upstream": { + "image": "https://hub.docker.com/r/jetbrains/youtrack", + "docs": [ + "https://www.jetbrains.com/help/youtrack/server/youtrack-docker-installation.html" + ] + }, "edition": [ { "dist": "community", "version": [ - "2025.2.89748" + "2026.2.18991" ] } ], + "access": { + "defaultScheme": "http", + "web": { + "port": 8080, + "path": "/" + } + }, "requirements": { "cpu": "2", - "memory": "1", - "disk": "1.5" + "memory": "1.5", + "disk": "10" }, - "upstream": { - "image": "https://hub.docker.com/r/jetbrains/youtrack" + "credentials": { + "token": { + "source": "container-file", + "path": "/opt/youtrack/conf/internal/services/configurationWizard/wizard_token.txt", + "format": "text" + } + }, + "env": { + "first_startup_only": [] } } diff --git a/apps/zammad/.env b/apps/zammad/.env index 674b79665..cce3fd078 100644 --- a/apps/zammad/.env +++ b/apps/zammad/.env @@ -1,39 +1,63 @@ -W9_DIST='community' -# don't forget to add the minus before the version -W9_VERSION='6.5' -W9_REPO=zammad/zammad-docker-compose +W9_REPO=ghcr.io/zammad/zammad +W9_DIST=community +W9_VERSION=7.1 + +# Optional password seed: enable only when the package actually controls a DB or built-in login. +# See docs/w9-env-spec.md for when W9_POWER_PASSWORD is appropriate. W9_POWER_PASSWORD='Yy6!CK!BebD1dzKn' +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + #### -- Not allowed to edit below environments when recreate app based on existing data -- #### + W9_ID='zammad' -W9_HTTP_PORT_SET='9001' W9_HTTP_PORT=8080 +W9_HTTP_PORT_SET='9001' + +# Dependency helpers: bundled PostgreSQL stores Zammad data. +W9_DB_EXPOSE='postgresql' +W9_DB_VERSION='17.11-alpine' + +W9_URL='zammad.example.com' -W9_DB_EXPOSE="postgresql" -W9_URL='' W9_NETWORK=websoft9 # It need to modify for every creating application W9_RCODE='ZqTXurwg4e9zD' #### --------------------------------------------------------------------------------------- #### -# zammad environments: https://docs.zammad.org/en/latest/install/docker-compose/environment.html - -MEMCACHE_SERVERS=$W9_ID-memcached:11211 -MEMCACHE_VERSION=1.6.20-alpine - -ELASTICSEARCH_ENABLED=false - -# This is for init container, not for postgresql container -# postgresql connection have some trouble: host and password -POSTGRESQL_DB=zammad -POSTGRESQL_HOST=$W9_RCODE-postgresql +# ============================================================ +# Zammad image environment variables +# Docs: https://docs.zammad.org/en/latest/install/docker-compose/environment.html +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +MEMCACHE_SERVERS=${W9_ID}-memcached:11211 +MEMCACHE_VERSION='1.6-alpine' +REDIS_URL=redis://${W9_ID}-redis:6379 +REDIS_VERSION='8.10-alpine' +POSTGRESQL_DB=zammad_production +# This environment variable is not allowed to use _ in the name, so we use W9_RCODE +POSTGRESQL_HOST=${W9_RCODE}-postgresql POSTGRESQL_USER=zammad -POSTGRESQL_PASS=$W9_RCODE +POSTGRESQL_PASS=${W9_RCODE} POSTGRESQL_PORT=5432 -POSTGRESQL_VERSION=15.3-alpine +POSTGRESQL_OPTIONS=?pool=50 POSTGRESQL_DB_CREATE=false - - -REDIS_URL=redis://$W9_ID-redis:6379 -REDIS_VERSION=7.0.5-alpine +ELASTICSEARCH_ENABLED=true +ELASTICSEARCH_HOST=${W9_ID}-elasticsearch +ELASTICSEARCH_PORT=9200 +ELASTICSEARCH_SCHEMA=http +ELASTICSEARCH_NAMESPACE=${W9_ID} +ELASTICSEARCH_REINDEX=true +ELASTICSEARCH_VERSION='9.5.3' + +# Not used by default; enable only when needed: +# NGINX_SERVER_NAME= +# NGINX_SERVER_SCHEME=https +# ZAMMAD_FQDN= +# ZAMMAD_HTTP_TYPE=https diff --git a/apps/zammad/CHANGELOG.md b/apps/zammad/CHANGELOG.md index 582cf46c5..c450092ee 100644 --- a/apps/zammad/CHANGELOG.md +++ b/apps/zammad/CHANGELOG.md @@ -1,5 +1,7 @@ # CHANGELOG -## Release -### Fixes and Enhancements - +## 2026-09-20 +- Update Zammad from 6.5 to 7.1 and switch the image to the official `ghcr.io/zammad/zammad`. +- Rework `docker-compose.yml` and `.env` to the current upstream stack: PostgreSQL 17, Redis 8.10, Memcached 1.6, and a dedicated non-superuser `zammad` role/database provisioned on first init. +- Bundle Elasticsearch 9 and enable it (`ELASTICSEARCH_ENABLED=true`) so search, reports, and attachment indexing work out of the box; scope the ES index namespace to the instance (`ELASTICSEARCH_NAMESPACE=${W9_ID}`) so multiple deployments can safely share one external ES; administrator account is created through the first-run setup wizard. +- Add `tests/cases.yml` with a guided-setup API smoke check. diff --git a/apps/zammad/README.md b/apps/zammad/README.md index ae01d6fd8..ebf62f60c 100644 --- a/apps/zammad/README.md +++ b/apps/zammad/README.md @@ -1,26 +1,97 @@ -# Zammad on Docker +# Zammad on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Zammad: +## Quick Start +### Deploy Verification - - community: 6.4, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Zammad**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Open the Zammad web console from the **Access** tab. +2. Complete the first-run setup wizard to create the administrator account. +3. Configure an email channel and create your first ticket to confirm the flow. -The following are the minimal [recommended requirements](https://github.com/zammad-contrib/docker-zammad/blob/master/README.md): +### Change Password -* **RAM**: 2 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 2 GB of free space -* **bandwidth**: more fluent experience over 100M +1. Change the administrator password from the user profile inside Zammad. +2. `W9_POWER_PASSWORD` and `W9_RCODE` seed the bundled PostgreSQL credentials and only take effect on first startup; to rotate them, update the roles in PostgreSQL (or recreate the `postgresql-data` volume) and then rebuild the app. + -## Install +## Configuration Reference -You can install this Zammad by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Zammad Docker image](https://ghcr.io/zammad/zammad) and makes some improvements below. -If you want use Zammad with **Websoft9 Business Support** free, you can [subscribe Zammad](https://www.websoft9.com/apps) on Cloud platform + -## Documentation + -[Zammad Administrator Guide](https://support.websoft9.com/docs/zammad) powered by Websoft9 \ No newline at end of file +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 7.1, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 8080 | + + +### Data Directory + + +- `zammad-storage` → `/opt/zammad/storage` +- `elasticsearch-data` → `/usr/share/elasticsearch/data` +- `redis-data` → `/data` +- `postgresql-data` → `/var/lib/postgresql/data` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +Note: `W9_POWER_PASSWORD`, `W9_RCODE` take effect on first startup only; changing them after deployment may not take effect until the app is re-initialized. + + +### Configuration Files + + +Configuration is overridden by mounting `./src/postgresql_init.sh` to `/docker-entrypoint-initdb.d/postgresql_init.sh`. + + +## References + +- [Zammad Administrator Guide](https://support.websoft9.com/docs/zammad) by Websoft9 + +- [GHCR image](https://ghcr.io/zammad/zammad) + +- [Releases](https://github.com/zammad/zammad-docker-compose/releases) + +- [Official compose](https://github.com/zammad/zammad-docker-compose/blob/v17.2.0/docker-compose.yml) + +- [Official env example](https://github.com/zammad/zammad-docker-compose/blob/v17.2.0/.env.dist) + +- [Official docs](https://docs.zammad.org/en/latest/install/docker-compose.html) + +- [Official docs](https://docs.zammad.org/en/latest/install/docker-compose/environment.html) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/zammad/docker-compose.yml b/apps/zammad/docker-compose.yml index 3c4ad42f5..92666d022 100644 --- a/apps/zammad/docker-compose.yml +++ b/apps/zammad/docker-compose.yml @@ -1,92 +1,124 @@ -# image: https://hub.docker.com/r/zammad/zammad-docker-compose -# docs: https://docs.zammad.org/en/latest/install/docker-compose.html#getting-started-with-zammad-docker-compose -# github: https://github.com/zammad/zammad-docker-compose - -version: '3.8' - x-shared: zammad-service: &zammad-service image: ${W9_REPO}:${W9_VERSION} + init: true restart: unless-stopped env_file: .env volumes: - zammad-storage:/opt/zammad/storage - - zammad-var:/opt/zammad/var depends_on: - - zammad-postgresql - - zammad-redis + zammad-memcached: + condition: service_healthy + zammad-postgresql: + condition: service_healthy + zammad-redis: + condition: service_healthy services: + zammad-nginx: <<: *zammad-service - container_name: $W9_ID + container_name: ${W9_ID} command: ["zammad-nginx"] ports: - - $W9_HTTP_PORT_SET:8080 - expose: - - "8080" + - "${W9_HTTP_PORT_SET}:8080" # Web Console depends_on: - - zammad-railsserver - volumes: - - zammad-var:/opt/zammad/var:ro # required for the zammad-ready check file + zammad-railsserver: + condition: service_healthy zammad-init: <<: *zammad-service + container_name: ${W9_ID}-init command: ["zammad-init"] - container_name: $W9_ID-init - depends_on: - - zammad-postgresql restart: on-failure user: 0:0 + zammad-elasticsearch: + image: elasticsearch:${ELASTICSEARCH_VERSION} + container_name: ${W9_ID}-elasticsearch + restart: unless-stopped + environment: + discovery.type: single-node + xpack.security.enabled: "false" + ES_JAVA_OPTS: "-Xms1g -Xmx1g" + volumes: + - elasticsearch-data:/usr/share/elasticsearch/data + zammad-railsserver: - container_name: $W9_ID-railsserver <<: *zammad-service + container_name: ${W9_ID}-railsserver command: ["zammad-railsserver"] + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:3000"] + interval: 30s + timeout: 5s + start_period: 120s + retries: 3 zammad-scheduler: <<: *zammad-service - container_name: $W9_ID-scheduler + container_name: ${W9_ID}-scheduler command: ["zammad-scheduler"] zammad-websocket: <<: *zammad-service - container_name: $W9_ID-websocket + container_name: ${W9_ID}-websocket command: ["zammad-websocket"] zammad-memcached: - command: memcached -m 256M - container_name: $W9_ID-memcached image: memcached:${MEMCACHE_VERSION} + container_name: ${W9_ID}-memcached + restart: unless-stopped + command: memcached -m 256M + healthcheck: + test: ["CMD", "nc", "-z", "127.0.0.1", "11211"] + interval: 10s + timeout: 5s + start_period: 10s + retries: 5 zammad-redis: - container_name: $W9_ID-redis image: redis:${REDIS_VERSION} + container_name: ${W9_ID}-redis + restart: unless-stopped volumes: - redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + start_period: 10s + retries: 5 zammad-postgresql: - container_name: $W9_ID-postgresql - hostname: $W9_RCODE-postgresql + image: postgres:${W9_DB_VERSION} + container_name: ${W9_ID}-postgresql + hostname: ${W9_RCODE}-postgresql + restart: unless-stopped environment: - POSTGRES_DB: zammad POSTGRES_USER: postgres POSTGRES_PASSWORD: ${W9_POWER_PASSWORD} - POSTGRES_ZAMMAD_PASSWORD: ${W9_RCODE} - image: postgres:${POSTGRESQL_VERSION} + ZAMMAD_DB: zammad_production + ZAMMAD_DB_USER: zammad + ZAMMAD_DB_PASS: ${W9_RCODE} volumes: - postgresql-data:/var/lib/postgresql/data - - ./src/postgresql_init.sh:/docker-entrypoint-initdb.d/postgresql_init.sh + - ./src/postgresql_init.sh:/docker-entrypoint-initdb.d/postgresql_init.sh:ro + healthcheck: + test: + - CMD-SHELL + - |- + PGPASSWORD="$${ZAMMAD_DB_PASS}" psql --no-password --quiet --output /dev/null --variable ON_ERROR_STOP=1 --host 127.0.0.1 --username "$${ZAMMAD_DB_USER}" --dbname "$${ZAMMAD_DB}" --command "SELECT 1" + interval: 10s + timeout: 5s + start_period: 60s + retries: 5 volumes: + elasticsearch-data: postgresql-data: - driver: local redis-data: - driver: local zammad-storage: - driver: local - zammad-var: - driver: local networks: default: diff --git a/apps/zammad/src/postgresql_init.sh b/apps/zammad/src/postgresql_init.sh old mode 100644 new mode 100755 index b90b2af73..dc9f4576b --- a/apps/zammad/src/postgresql_init.sh +++ b/apps/zammad/src/postgresql_init.sh @@ -1,10 +1,35 @@ #!/bin/bash -set -a - -echo "POSTGRES_USER is set to: '${POSTGRES_USER}'" -echo "POSTGRES_ZAMMAD_PASSWORD is set to: '${POSTGRES_ZAMMAD_PASSWORD}'" -# create zammad user and database -psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL - CREATE USER zammad WITH PASSWORD '${POSTGRES_ZAMMAD_PASSWORD}' SUPERUSER; -EOSQL \ No newline at end of file +set -o errexit +set -o pipefail + +zammad_abort() { + echo "$1" >&2 + echo "Correct this, then remove the postgresql-data volume and start again. The" >&2 + echo " database directory is already initialised, so this will not run twice." >&2 + exit 1 +} + +# Zammad must not reuse the bootstrap role, which is always a superuser. +if [ "${ZAMMAD_DB_USER}" = "${POSTGRES_USER}" ]; then + zammad_abort "ZAMMAD_DB_USER and POSTGRES_USER must differ, the latter is a superuser." +fi + +# The system databases exist already, so they would keep the bootstrap role as owner. +case "${ZAMMAD_DB}" in + postgres | template0 | template1) + zammad_abort "ZAMMAD_DB must not be one of PostgreSQL's system databases." + ;; +esac + +echo "Creating the '${ZAMMAD_DB_USER}' role and the '${ZAMMAD_DB}' database..." + +psql --variable ON_ERROR_STOP=1 \ + --username "${POSTGRES_USER}" \ + --dbname "${POSTGRES_DB:-postgres}" \ + --variable role="${ZAMMAD_DB_USER}" \ + --variable pass="${ZAMMAD_DB_PASS}" \ + --variable db="${ZAMMAD_DB}" <<'EOSQL' +CREATE ROLE :"role" LOGIN PASSWORD :'pass'; +CREATE DATABASE :"db" OWNER :"role"; +EOSQL diff --git a/apps/zammad/tests/cases.yml b/apps/zammad/tests/cases.yml new file mode 100644 index 000000000..9aa4cb34c --- /dev/null +++ b/apps/zammad/tests/cases.yml @@ -0,0 +1,5 @@ +optional: + - id: guided-setup-api + type: web-access + path: /api/v1/getting_started + expect_status: 200 diff --git a/apps/zammad/variables.json b/apps/zammad/variables.json index f0abc837e..b6767e36a 100644 --- a/apps/zammad/variables.json +++ b/apps/zammad/variables.json @@ -2,21 +2,46 @@ "name": "zammad", "trademark": "Zammad", "release": true, + "upstream": { + "image": "ghcr.io/zammad/zammad", + "releases": "https://github.com/zammad/zammad-docker-compose/releases", + "compose": { + "compose": "https://github.com/zammad/zammad-docker-compose/blob/v17.2.0/docker-compose.yml", + "env": "https://github.com/zammad/zammad-docker-compose/blob/v17.2.0/.env.dist" + }, + "docs": [ + "https://docs.zammad.org/en/latest/install/docker-compose.html", + "https://docs.zammad.org/en/latest/install/docker-compose/environment.html" + ] + }, "edition": [ { "dist": "community", "version": [ - "6.5", + "7.1", "latest" ] } ], + "access": { + "web": { + "port": 8080, + "path": "/" + } + }, "requirements": { - "cpu": "1", - "memory": "2", - "disk": "2" + "cpu": "2", + "memory": "4", + "disk": "8" }, - "upstream": { - "image": "https://hub.docker.com/r/zammad/zammad-docker-compose" + "credentials": {}, + "env": { + "first_startup_only": [ + "W9_POWER_PASSWORD", + "W9_RCODE" + ] + }, + "help": { + "db": "Bundled PostgreSQL 17 stores Zammad data; bundled Elasticsearch 9 powers search and reports." } } diff --git a/apps/zentao/.env b/apps/zentao/.env index 215dedce4..e946b0748 100644 --- a/apps/zentao/.env +++ b/apps/zentao/.env @@ -1,24 +1,51 @@ W9_REPO=easysoft/zentao -W9_DIST='community' -W9_VERSION='21.7' -W9_POWER_PASSWORD='78VCi6!ZrZ8T46xM' +W9_DIST=community +W9_VERSION=22.6 + +# Optional password seed: enable only when the package actually controls a DB or built-in login. +# See docs/w9-env-spec.md for when W9_POWER_PASSWORD is appropriate. +W9_POWER_PASSWORD="78VCi6!ZrZ8T46xM" #### -- Not allowed to edit below environments when recreate app based on existing data -- #### -W9_ID='zentao' -# Environments which for user settings when create applications -W9_HTTP_PORT_SET='9001' + +W9_ID=zentao + +# Web/internal ports: uncomment the ones the package actually uses. W9_HTTP_PORT=80 -W9_URL='example.youdomain.com' -W9_DB_EXPOSE="mysql" +W9_HTTP_PORT_SET=9001 + +# Dependency helpers: uncomment when the package bundles a dependency service. +W9_DB_EXPOSE=mysql +W9_DB_VERSION=5.7 + +# Built-in login and URL helpers: keep these in the protected block because the appstore parser +# reads them from the app package metadata surface. +W9_URL=example.youdomain.com + W9_NETWORK=websoft9 + #### ------------------------------------------------------------------------------------ #### +# ============================================================ +# ZenTao image environment variables +# Docs: https://github.com/easysoft/zentaopms +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ -##--------------- Zentao environments for user ---------------------------------------------- ## -ZT_MYSQL_HOST=$W9_ID-mysql +# Used by docker-compose.yml: +ZT_MYSQL_HOST=${W9_ID}-mysql ZT_MYSQL_PORT=3306 -# it need root for privilege + +# It needs root for privilege during the install wizard. ZT_MYSQL_USER=root -ZT_MYSQL_PASSWORD=$W9_POWER_PASSWORD -# Not suggest use W9_ID which will can not connect when wizard +ZT_MYSQL_PASSWORD=${W9_POWER_PASSWORD} ZT_MYSQL_DB=zentao + +# Not used by default; enable only when needed: +# PHP_MAX_EXECUTION_TIME= +# PHP_MAX_INPUT_VARS= +# PHP_MEMORY_LIMIT= +# PHP_POST_MAX_SIZE= +# PHP_UPLOAD_MAX_FILESIZE= diff --git a/apps/zentao/CHANGELOG.md b/apps/zentao/CHANGELOG.md index 582cf46c5..e4df29653 100644 --- a/apps/zentao/CHANGELOG.md +++ b/apps/zentao/CHANGELOG.md @@ -1,5 +1,7 @@ -# CHANGELOG +# Changelog -## Release -### Fixes and Enhancements +## 2026-09-20 +- Update ZenTao community image from `21.7` to `22.6`. +- Normalize `.env` and `docker-compose.yml` to current repository policy, including braced variable references and dependency tag management. +- Add app-local test coverage metadata for the install-wizard flow. diff --git a/apps/zentao/Notes.md b/apps/zentao/Notes.md deleted file mode 100644 index 989d330fe..000000000 --- a/apps/zentao/Notes.md +++ /dev/null @@ -1,11 +0,0 @@ -## ZenTao - -- ZenTao 需要用户自助完成安装向导流程 -- 18.8 以后无需 cmd.sh 修改密码配置 - -## FAQ - - -#### 如何修改 php 配置文件? - -目前没有环境变量方案,官方提供的路径: /etc/php/7.0/apache2 \ No newline at end of file diff --git a/apps/zentao/README.md b/apps/zentao/README.md index 876de0da0..834bd3c36 100644 --- a/apps/zentao/README.md +++ b/apps/zentao/README.md @@ -1,26 +1,84 @@ -# ZenTao on Docker +# ZenTao on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for ZenTao: +## Quick Start +### Deploy Verification - - community: 21.7, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **ZenTao**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Make sure you are signed in to the ZenTao admin console. +2. Try a core feature. -The following are the minimal [recommended requirements](https://hub.docker.com/r/easysoft/zentao): +### Change Password -* **RAM**: 1 GB or more -* **CPU**: 1 cores or higher -* **Disk**: at least 4 GB of free space -* **bandwidth**: more fluent experience over 100M +1. In the [Websoft9](https://www.websoft9.com) console, open the app's **Compose** tab. +2. Update the password in `.env` and save. +3. Rebuild the app. + -## Install +## Configuration Reference -You can install this ZenTao by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [ZenTao Docker image](https://hub.docker.com/r/easysoft/zentao) and makes some improvements below. -If you want use ZenTao with **Websoft9 Business Support** free, you can [subscribe ZenTao](https://www.websoft9.com/apps) on Cloud platform + -## Documentation + -[ZenTao Administrator Guide](https://support.websoft9.com/docs/zentao) powered by Websoft9 \ No newline at end of file +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 22.6, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 80 | + + +### Data Directory + + +- `zentao` → `/data` +- `mysql_data` → `/var/lib/mysql` + + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +### Configuration Files + + +Configuration files live inside the image; mount a single file read-only to override, and never replace the whole directory. + + +## References + +- [ZenTao Administrator Guide](https://support.websoft9.com/docs/zentao) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/easysoft/zentao) + +- [GitHub docs](https://github.com/easysoft/zentaopms) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/zentao/docker-compose.yml b/apps/zentao/docker-compose.yml index 9e409716f..355de0599 100644 --- a/apps/zentao/docker-compose.yml +++ b/apps/zentao/docker-compose.yml @@ -1,23 +1,20 @@ -# image: https://hub.docker.com/r/easysoft/zentao -# docs: https://github.com/easysoft/zentaopms - version: '3.8' services: zentao: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} ports: - - $W9_HTTP_PORT_SET:80 + - "${W9_HTTP_PORT_SET}:80" # Web Console volumes: - zentao:/data env_file: - .env restart: unless-stopped - + mysql: - image: mysql:5.7 - container_name: $W9_ID-mysql + image: mysql:${W9_DB_VERSION} + container_name: ${W9_ID}-mysql restart: unless-stopped command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci env_file: @@ -26,13 +23,13 @@ services: - mysql_data:/var/lib/mysql environment: MYSQL_DATABASE: zentao - MYSQL_ROOT_PASSWORD: $W9_POWER_PASSWORD - + MYSQL_ROOT_PASSWORD: ${W9_POWER_PASSWORD} + networks: default: - name: $W9_NETWORK + name: ${W9_NETWORK} external: true volumes: zentao: - mysql_data: \ No newline at end of file + mysql_data: diff --git a/apps/zentao/tests/cases.yml b/apps/zentao/tests/cases.yml new file mode 100644 index 000000000..3d69311c0 --- /dev/null +++ b/apps/zentao/tests/cases.yml @@ -0,0 +1,5 @@ +optional: + - id: install-wizard + type: web-access + path: /install.php + expect_status: 200 diff --git a/apps/zentao/variables.json b/apps/zentao/variables.json index 2304bba6e..6ed0f020c 100644 --- a/apps/zentao/variables.json +++ b/apps/zentao/variables.json @@ -6,17 +6,24 @@ { "dist": "community", "version": [ - "21.7", + "22.6", "latest" ] } ], + "credentials": {}, "requirements": { "cpu": "1", "memory": "1", "disk": "4" }, + "env": { + "first_startup_only": [] + }, "upstream": { - "image": "https://hub.docker.com/r/easysoft/zentao" + "image": "https://hub.docker.com/r/easysoft/zentao", + "docs": [ + "https://github.com/easysoft/zentaopms" + ] } } diff --git a/apps/zookeeper/CHANGELOG.md b/apps/zookeeper/CHANGELOG.md deleted file mode 100644 index 582cf46c5..000000000 --- a/apps/zookeeper/CHANGELOG.md +++ /dev/null @@ -1,5 +0,0 @@ -# CHANGELOG - -## Release -### Fixes and Enhancements - diff --git a/apps/zulip/.env b/apps/zulip/.env index 6c0a6e491..f1f6d368a 100644 --- a/apps/zulip/.env +++ b/apps/zulip/.env @@ -1,6 +1,6 @@ -W9_REPO=zulip/docker-zulip -W9_DIST='community' -W9_VERSION=latest +W9_REPO=ghcr.io/zulip/zulip-server +W9_DIST=community +W9_VERSION=12.2-0 W9_POWER_PASSWORD=1PrMxExC45LsCT @@ -8,25 +8,49 @@ W9_HTTPS_PORT_SET=9443 W9_HTTPS_PORT=443 #### -- Not allowed to edit below environments when recreate app based on existing data -- #### + W9_ID=zulip W9_URL=example.youdomain.com W9_URL_REPLACE=true +W9_ADMIN_PATH=/login/ +W9_LOGIN_USER=admin@example.com +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} W9_NETWORK=websoft9 -W9_RCODE="UGz0IARz117ssO" +W9_RCODE=UGz0IARz117ssO #### ----------------------------------------------------------------------------------------- #### -DB_HOST=$W9_ID-postgresql -DB_HOST_PORT="5432" -DB_USER="zulip" -SSL_CERTIFICATE_GENERATION="self-signed" -SETTING_ZULIP_ADMINISTRATOR="admin@example.com" -SETTING_MEMCACHED_LOCATION=$W9_ID-memcached:11211 -SETTING_RABBITMQ_HOST=$W9_ID-rabbitmq -SETTING_REDIS_HOST=$W9_ID-redis -SECRETS_rabbitmq_password=$W9_RCODE -SECRETS_postgres_password=$W9_POWER_PASSWORD -SECRETS_memcached_password=$W9_POWER_PASSWORD -SECRETS_redis_password=$W9_POWER_PASSWORD -SECRETS_secret_key="Fz7!dJ3q@vP#2Lk5^Wn8*Rm6Tp4&Yb9^Xc1$Hj0%Ql7!Gz8@Vr2" -SETTING_EXTERNAL_HOST=$W9_URL + +# ============================================================ +# Zulip image environment variables +# Docs: https://zulip.readthedocs.io/projects/docker/en/latest/reference/environment-vars.html +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +SETTING_REMOTE_POSTGRES_HOST=${W9_ID}-postgresql +SETTING_MEMCACHED_LOCATION=${W9_ID}-memcached:11211 +SETTING_RABBITMQ_HOST=${W9_ID}-rabbitmq +SETTING_REDIS_HOST=${W9_ID}-redis +SETTING_ZULIP_ADMINISTRATOR=${W9_LOGIN_USER} +CERTIFICATES="self-signed" +SECRETS_rabbitmq_password=${W9_RCODE} +SECRETS_postgres_password=${W9_POWER_PASSWORD} +SECRETS_memcached_password=${W9_POWER_PASSWORD} +SECRETS_redis_password=${W9_POWER_PASSWORD} +SECRETS_secret_key="Fz7!dJ3q@vP#2Lk5^Wn8*Rm6Tp4&Hb9^Xc1Qj0%Ql7!Gz8@Vr2" +SETTING_EXTERNAL_HOST=${W9_URL} +SETTING_FAKE_EMAIL_DOMAIN="zulip.example.com" ZULIP_AUTH_BACKENDS="EmailAuthBackend" +W9_ZULIP_REALM_NAME="Default Organization" +W9_ZULIP_REALM_STRING_ID="" +W9_ZULIP_ADMIN_FULL_NAME="Administrator" + +# Not used by default; enable only when needed: +# CONFIG_application_server__queue_workers_multiprocess=False +# LOADBALANCER_IPS= +# TRUST_GATEWAY_IP=True +# SETTING_EMAIL_HOST= +# SETTING_EMAIL_HOST_USER= +# SECRETS_email_password= diff --git a/apps/zulip/CHANGELOG.md b/apps/zulip/CHANGELOG.md index 582cf46c5..70147d9b6 100644 --- a/apps/zulip/CHANGELOG.md +++ b/apps/zulip/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG -## Release -### Fixes and Enhancements +## 2026-09-18 +- Fixed the first-start organization bootstrap on the 12.x image: the password file is now written as root before being chowned to `zulip`, so the post-setup script no longer fails with `Permission denied` when the container root lacks DAC override. +- Fixed the `create_realm` call to pass all positional arguments together, so Zulip 12.2 no longer rejects the owner email and full name as unrecognized arguments. +- Declared `SETTING_FAKE_EMAIL_DOMAIN=zulip.example.com` so first-start realm creation succeeds when `W9_URL` is an IP address rather than a domain. +- Declared `W9_ADMIN_PATH=/login/` in `.env` and the matching `access` block (web `/`, admin `/login/` on port 443) in `variables.json`. +- Migrated the package from the legacy Docker Hub image line to `ghcr.io/zulip/zulip-server:12.2-0`, updating the compose wiring and 12.x environment variable names so the main container can start again. +- Switched the first-start organization bootstrap from a custom container entrypoint to an official `post-setup.d` script that creates a default organization plus owner account after Zulip finishes initialization. +- Declared first-start organization and login variables in `.env`, documented them in metadata, and normalized compose/env variable references to the braced repository style. diff --git a/apps/zulip/Notes.md b/apps/zulip/Notes.md index 82450d00d..920a46ae4 100644 --- a/apps/zulip/Notes.md +++ b/apps/zulip/Notes.md @@ -2,7 +2,18 @@ ## When installing, it is necessary to bind the domain name, otherwise the installation will fail ## After installation, it is necessary to apply for SSL certificate from nginx -## Create a Zulip organization +## Zulip 12.x packaging +- The package now uses `ghcr.io/zulip/zulip-server:12.2-0`. +- 11.x legacy env names like `DB_HOST` and `SSL_CERTIFICATE_GENERATION` are no longer valid in Zulip 12.x; use `SETTING_*`, `CONFIG_*`, and `CERTIFICATES`. + +## First startup organization bootstrap +- The package now creates a default organization and owner account automatically on the first successful startup. +- The organization name, optional subdomain, owner full name, and owner email/password come from `W9_ZULIP_REALM_NAME`, `W9_ZULIP_REALM_STRING_ID`, `W9_ZULIP_ADMIN_FULL_NAME`, `W9_LOGIN_USER`, and `W9_LOGIN_PASSWORD`. +- These values are first-start only; changing them later does not modify an existing Zulip database. +- `SETTING_FAKE_EMAIL_DOMAIN=zulip.example.com` is required because `W9_URL` may resolve to an IP address; Zulip rejects an IP as the fake email domain during owner creation. +- Admin entry is declared as `W9_ADMIN_PATH=/login/`; the app store `access` block mirrors it (web `/`, admin `/login/` on port 443). + +## Manual organization creation ``` docker exec -it container_name bash su zulip -c /home/zulip/deployments/current/manage.py generate_realm_creation_link diff --git a/apps/zulip/README.md b/apps/zulip/README.md index c065272ff..f52109908 100644 --- a/apps/zulip/README.md +++ b/apps/zulip/README.md @@ -1,26 +1,91 @@ -# Zulip on Docker +# Zulip on Docker -This is an **[Docker Compose template](https://github.com/Websoft9/docker-library)** powered by [Websoft9](https://www.websoft9.com) based on Docker for Zulip: +## Quick Start +### Deploy Verification - - community: 9.1-3, latest +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Zulip**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + +### Usage -## System Requirements +1. Make sure you are signed in to the Zulip admin console. +2. Try a core feature. -The following are the minimal [recommended requirements](https://zulip.readthedocs.io/en/latest/production/requirements.html): +### Change Password -* **RAM**: 2 GB or more -* **CPU**: 2 cores or higher -* **Disk**: at least 10 GB of free space -* **bandwidth**: more fluent experience over 100M +1. In the [Websoft9](https://www.websoft9.com) console, open the app's **Compose** tab. +2. Update the password in `.env` and save. +3. Rebuild the app. + -## Install +## Configuration Reference -You can install this Zulip by [How to use it?](https://github.com/Websoft9/docker-library#how-to-use-it). +Websoft9 packages this app from the official [Zulip Docker image](https://ghcr.io/zulip/zulip-server) and makes some improvements below. -If you want use Zulip with **Websoft9 Business Support** free, you can [subscribe Zulip](https://www.websoft9.com/apps) on Cloud platform + -## Documentation + -[Zulip Administrator Guide](https://support.websoft9.com/docs/zulip) powered by Websoft9 \ No newline at end of file +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 12.2-0. + + +### Ports + +| Purpose | Port | +| --- | --- | +| HTTPS | 443 | + + +### Data Directory + + +Data is kept inside the container; a named volume is recommended for persistence. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +Note: `W9_LOGIN_USER`, `W9_LOGIN_PASSWORD`, `W9_ZULIP_REALM_NAME`, `W9_ZULIP_REALM_STRING_ID`, `W9_ZULIP_ADMIN_FULL_NAME` take effect on first startup only; changing them after deployment may not take effect until the app is re-initialized. + + +### Configuration Files + + +Configuration is overridden by mounting `./src/create-default-realm.sh` to `/data/post-setup.d/10-create-default-realm.sh`. + + +## References + +- [Zulip Administrator Guide](https://support.websoft9.com/docs/zulip) by Websoft9 + +- [GHCR image](https://ghcr.io/zulip/zulip-server) + +- [Releases](https://github.com/zulip/docker-zulip/releases) + +- [GitHub docs](https://github.com/zulip/docker-zulip/blob/main/README.md) + +- [Official docs](https://zulip.readthedocs.io/projects/docker/en/latest/how-to/compose-upgrading-from-legacy.html) + +- [Official docs](https://zulip.readthedocs.io/projects/docker/en/latest/reference/environment-vars.html) + +- [Official docs](https://zulip.readthedocs.io/en/latest/production/management-commands.html) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/apps/zulip/docker-compose.yml b/apps/zulip/docker-compose.yml index 67990e4a0..bb59c8fbd 100644 --- a/apps/zulip/docker-compose.yml +++ b/apps/zulip/docker-compose.yml @@ -1,23 +1,18 @@ -# docker:https://hub.docker.com/r/zulip/docker-zulip -# docs: https://github.com/zulip/docker-zulip/blob/main/README.md - -version: "3.8" - services: database: image: "zulip/zulip-postgresql:14" - container_name: $W9_ID-postgresql + container_name: ${W9_ID}-postgresql restart: unless-stopped environment: POSTGRES_DB: "zulip" POSTGRES_USER: "zulip" - POSTGRES_PASSWORD: $W9_POWER_PASSWORD + POSTGRES_PASSWORD: ${W9_POWER_PASSWORD} volumes: - "postgresql-14:/var/lib/postgresql/data:rw" memcached: image: "memcached:alpine" - container_name: $W9_ID-memcached + container_name: ${W9_ID}-memcached restart: unless-stopped command: - "sh" @@ -30,21 +25,25 @@ services: environment: SASL_CONF_PATH: "/home/memcache/memcached.conf" MEMCACHED_SASL_PWDB: "/home/memcache/memcached-sasl-db" - MEMCACHED_PASSWORD: $W9_POWER_PASSWORD + MEMCACHED_PASSWORD: ${W9_POWER_PASSWORD} rabbitmq: - image: "rabbitmq:3.7.7" - container_name: $W9_ID-rabbitmq + image: "rabbitmq:4.2" + container_name: ${W9_ID}-rabbitmq restart: unless-stopped - environment: - RABBITMQ_DEFAULT_USER: "zulip" - RABBITMQ_DEFAULT_PASS: $W9_RCODE + command: + - "sh" + - "-euc" + - | + echo 'default_user = zulip' >> /etc/rabbitmq/rabbitmq.conf + echo 'default_pass = ${W9_RCODE}' >> /etc/rabbitmq/rabbitmq.conf + exec docker-entrypoint.sh rabbitmq-server volumes: - "rabbitmq:/var/lib/rabbitmq:rw" redis: image: "redis:alpine" - container_name: $W9_ID-redis + container_name: ${W9_ID}-redis restart: unless-stopped command: - "sh" @@ -53,23 +52,29 @@ services: echo "requirepass '$$REDIS_PASSWORD'" > /etc/redis.conf exec redis-server /etc/redis.conf environment: - REDIS_PASSWORD: $W9_POWER_PASSWORD + REDIS_PASSWORD: ${W9_POWER_PASSWORD} volumes: - "redis:/data:rw" zulip: - image: $W9_REPO:$W9_VERSION - container_name: $W9_ID + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} restart: unless-stopped ports: - - $W9_HTTPS_PORT_SET:443 + - "${W9_HTTPS_PORT_SET}:443" # HTTPS env_file: .env volumes: - "zulip:/data:rw" + - ./src/create-default-realm.sh:/data/post-setup.d/10-create-default-realm.sh:ro ulimits: nofile: soft: 1000000 hard: 1048576 + depends_on: + - database + - memcached + - rabbitmq + - redis volumes: zulip: @@ -80,5 +85,5 @@ volumes: networks: default: - name: $W9_NETWORK + name: ${W9_NETWORK} external: true diff --git a/apps/zulip/src/README.md b/apps/zulip/src/README.md index cdbd7a0b9..49c0c4ff5 100644 --- a/apps/zulip/src/README.md +++ b/apps/zulip/src/README.md @@ -1,3 +1,3 @@ -# About +# Local overrides -This folder includes files mount to container and used by Websoft9 +- `create-default-realm.sh` is mounted into `/data/post-setup.d/` so the official Zulip 12.x entrypoint runs it after configuration and database migrations. It creates a default organization plus owner account on first startup. diff --git a/apps/zulip/src/create-default-realm.sh b/apps/zulip/src/create-default-realm.sh new file mode 100755 index 000000000..404a7255b --- /dev/null +++ b/apps/zulip/src/create-default-realm.sh @@ -0,0 +1,32 @@ +#!/bin/sh +set -eu + +REALM_MARKER="/data/.realm-created" +ZULIP_MANAGE="/home/zulip/deployments/current/manage.py" + +if [ -f "${REALM_MARKER}" ]; then + exit 0 +fi + +if [ -z "${W9_ZULIP_REALM_NAME:-}" ] || [ -z "${W9_LOGIN_USER:-}" ] || [ -z "${W9_LOGIN_PASSWORD:-}" ]; then + echo "Skipping default Zulip organization creation because required W9_ZULIP_REALM_* or W9_LOGIN_* settings are missing." + exit 0 +fi + +if su zulip -c "${ZULIP_MANAGE} list_realms | awk 'NR > 2 && \$2 != \"zulipinternal\" { found = 1 } END { exit found ? 0 : 1 }'"; then + touch "${REALM_MARKER}" + exit 0 +fi + +password_file="$(mktemp)" +cleanup() { + rm -f "${password_file}" +} +trap cleanup EXIT INT TERM +printf '%s' "${W9_LOGIN_PASSWORD}" > "${password_file}" +chown zulip:zulip "${password_file}" +chmod 600 "${password_file}" + +echo "Creating default Zulip organization \"${W9_ZULIP_REALM_NAME}\" and owner ${W9_LOGIN_USER} ..." +su zulip -c "${ZULIP_MANAGE} create_realm \"${W9_ZULIP_REALM_NAME}\" \"${W9_LOGIN_USER}\" \"${W9_ZULIP_ADMIN_FULL_NAME:-Administrator}\" --string-id \"${W9_ZULIP_REALM_STRING_ID:-}\" --password-file \"${password_file}\"" +touch "${REALM_MARKER}" diff --git a/apps/zulip/tests/cases.yml b/apps/zulip/tests/cases.yml new file mode 100644 index 000000000..9e10fd4d4 --- /dev/null +++ b/apps/zulip/tests/cases.yml @@ -0,0 +1,7 @@ +skip: + - id: web-access + +custom: + - id: login-page + type: script + script: login-page.sh diff --git a/apps/zulip/tests/login-page.sh b/apps/zulip/tests/login-page.sh new file mode 100755 index 000000000..382f1e431 --- /dev/null +++ b/apps/zulip/tests/login-page.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu + +base_url="${BASE_URL:-}" +if [ -z "${base_url}" ]; then + base_url="https://127.0.0.1:${W9_HTTPS_PORT_SET}" +fi + +curl -kfsSL "${base_url}/login/" | grep -qi "zulip" diff --git a/apps/zulip/variables.json b/apps/zulip/variables.json index ba40935bf..5c52a4669 100644 --- a/apps/zulip/variables.json +++ b/apps/zulip/variables.json @@ -6,17 +6,47 @@ { "dist": "community", "version": [ - "9.1-3", - "latest" + "12.2-0" ] } ], + "access": { + "web": { + "port": 443, + "path": "/" + }, + "admin": { + "port": 443, + "path": "/login/" + } + }, "requirements": { "cpu": "2", "memory": "2", "disk": "10" }, + "credentials": {}, + "env": { + "first_startup_only": [ + "W9_LOGIN_USER", + "W9_LOGIN_PASSWORD", + "W9_ZULIP_REALM_NAME", + "W9_ZULIP_REALM_STRING_ID", + "W9_ZULIP_ADMIN_FULL_NAME" + ] + }, + "help": { + "db": "Bundled PostgreSQL, RabbitMQ, Redis, and Memcached credentials are initialized from the first-start settings on the initial deployment.", + "login": "On first startup the package creates a default Zulip organization and owner account from W9_ZULIP_REALM_* and W9_LOGIN_*; later edits do not backfill existing data." + }, "upstream": { - "image": "https://hub.docker.com/r/zulip/docker-zulip" + "image": "https://ghcr.io/zulip/zulip-server", + "releases": "https://github.com/zulip/docker-zulip/releases", + "docs": [ + "https://github.com/zulip/docker-zulip/blob/main/README.md", + "https://zulip.readthedocs.io/projects/docker/en/latest/how-to/compose-upgrading-from-legacy.html", + "https://zulip.readthedocs.io/projects/docker/en/latest/reference/environment-vars.html", + "https://zulip.readthedocs.io/en/latest/production/management-commands.html" + ] } } diff --git a/apps/rclone/.env b/archive/apps/rclone/.env similarity index 100% rename from apps/rclone/.env rename to archive/apps/rclone/.env diff --git a/apps/rclone/CHANGELOG.md b/archive/apps/rclone/CHANGELOG.md similarity index 100% rename from apps/rclone/CHANGELOG.md rename to archive/apps/rclone/CHANGELOG.md diff --git a/apps/rclone/Notes.md b/archive/apps/rclone/Notes.md similarity index 100% rename from apps/rclone/Notes.md rename to archive/apps/rclone/Notes.md diff --git a/apps/rclone/README.md b/archive/apps/rclone/README.md similarity index 100% rename from apps/rclone/README.md rename to archive/apps/rclone/README.md diff --git a/apps/rclone/docker-compose.yml b/archive/apps/rclone/docker-compose.yml similarity index 100% rename from apps/rclone/docker-compose.yml rename to archive/apps/rclone/docker-compose.yml diff --git a/apps/rclone/src/README.md b/archive/apps/rclone/src/README.md similarity index 100% rename from apps/rclone/src/README.md rename to archive/apps/rclone/src/README.md diff --git a/apps/rclone/variables.json b/archive/apps/rclone/variables.json similarity index 100% rename from apps/rclone/variables.json rename to archive/apps/rclone/variables.json diff --git a/apps/redash/.env b/archive/apps/redash/.env similarity index 100% rename from apps/redash/.env rename to archive/apps/redash/.env diff --git a/apps/redash/CHANGELOG.md b/archive/apps/redash/CHANGELOG.md similarity index 100% rename from apps/redash/CHANGELOG.md rename to archive/apps/redash/CHANGELOG.md diff --git a/apps/redash/Notes.md b/archive/apps/redash/Notes.md similarity index 100% rename from apps/redash/Notes.md rename to archive/apps/redash/Notes.md diff --git a/apps/redash/README.md b/archive/apps/redash/README.md similarity index 100% rename from apps/redash/README.md rename to archive/apps/redash/README.md diff --git a/apps/redash/docker-compose.yml b/archive/apps/redash/docker-compose.yml similarity index 100% rename from apps/redash/docker-compose.yml rename to archive/apps/redash/docker-compose.yml diff --git a/apps/redash/src/filelist b/archive/apps/redash/src/filelist similarity index 100% rename from apps/redash/src/filelist rename to archive/apps/redash/src/filelist diff --git a/apps/redash/variables.json b/archive/apps/redash/variables.json similarity index 100% rename from apps/redash/variables.json rename to archive/apps/redash/variables.json diff --git a/apps/rocketmq/.env b/archive/apps/rocketmq/.env similarity index 100% rename from apps/rocketmq/.env rename to archive/apps/rocketmq/.env diff --git a/apps/rocketmq/CHANGELOG.md b/archive/apps/rocketmq/CHANGELOG.md similarity index 100% rename from apps/rocketmq/CHANGELOG.md rename to archive/apps/rocketmq/CHANGELOG.md diff --git a/apps/runtime/src/9panel/docs/user-guide.md b/archive/apps/rocketmq/Notes.md similarity index 100% rename from apps/runtime/src/9panel/docs/user-guide.md rename to archive/apps/rocketmq/Notes.md diff --git a/apps/rocketmq/README.md b/archive/apps/rocketmq/README.md similarity index 100% rename from apps/rocketmq/README.md rename to archive/apps/rocketmq/README.md diff --git a/apps/rocketmq/docker-compose.yml b/archive/apps/rocketmq/docker-compose.yml similarity index 100% rename from apps/rocketmq/docker-compose.yml rename to archive/apps/rocketmq/docker-compose.yml diff --git a/apps/rocketmq/src/README.md b/archive/apps/rocketmq/src/README.md similarity index 100% rename from apps/rocketmq/src/README.md rename to archive/apps/rocketmq/src/README.md diff --git a/apps/rocketmq/variables.json b/archive/apps/rocketmq/variables.json similarity index 100% rename from apps/rocketmq/variables.json rename to archive/apps/rocketmq/variables.json diff --git a/apps/rowy/.env b/archive/apps/rowy/.env similarity index 100% rename from apps/rowy/.env rename to archive/apps/rowy/.env diff --git a/apps/rowy/CHANGELOG.md b/archive/apps/rowy/CHANGELOG.md similarity index 100% rename from apps/rowy/CHANGELOG.md rename to archive/apps/rowy/CHANGELOG.md diff --git a/apps/rowy/Notes.md b/archive/apps/rowy/Notes.md similarity index 100% rename from apps/rowy/Notes.md rename to archive/apps/rowy/Notes.md diff --git a/apps/rowy/README.md b/archive/apps/rowy/README.md similarity index 100% rename from apps/rowy/README.md rename to archive/apps/rowy/README.md diff --git a/apps/rowy/docker-compose.yml b/archive/apps/rowy/docker-compose.yml similarity index 100% rename from apps/rowy/docker-compose.yml rename to archive/apps/rowy/docker-compose.yml diff --git a/apps/rowy/variables.json b/archive/apps/rowy/variables.json similarity index 100% rename from apps/rowy/variables.json rename to archive/apps/rowy/variables.json diff --git a/apps/rudderstack/.env b/archive/apps/rudderstack/.env similarity index 100% rename from apps/rudderstack/.env rename to archive/apps/rudderstack/.env diff --git a/apps/rudderstack/CHANGELOG.md b/archive/apps/rudderstack/CHANGELOG.md similarity index 100% rename from apps/rudderstack/CHANGELOG.md rename to archive/apps/rudderstack/CHANGELOG.md diff --git a/apps/rudderstack/Notes.md b/archive/apps/rudderstack/Notes.md similarity index 100% rename from apps/rudderstack/Notes.md rename to archive/apps/rudderstack/Notes.md diff --git a/apps/rudderstack/README.md b/archive/apps/rudderstack/README.md similarity index 100% rename from apps/rudderstack/README.md rename to archive/apps/rudderstack/README.md diff --git a/apps/rudderstack/docker-compose.yml b/archive/apps/rudderstack/docker-compose.yml similarity index 100% rename from apps/rudderstack/docker-compose.yml rename to archive/apps/rudderstack/docker-compose.yml diff --git a/apps/rudderstack/src/README.md b/archive/apps/rudderstack/src/README.md similarity index 100% rename from apps/rudderstack/src/README.md rename to archive/apps/rudderstack/src/README.md diff --git a/apps/rudderstack/variables.json b/archive/apps/rudderstack/variables.json similarity index 100% rename from apps/rudderstack/variables.json rename to archive/apps/rudderstack/variables.json diff --git a/apps/runtime/.env b/archive/apps/runtime/.env similarity index 100% rename from apps/runtime/.env rename to archive/apps/runtime/.env diff --git a/apps/runtime/CHANGELOG.md b/archive/apps/runtime/CHANGELOG.md similarity index 100% rename from apps/runtime/CHANGELOG.md rename to archive/apps/runtime/CHANGELOG.md diff --git a/apps/runtime/Notes.md b/archive/apps/runtime/Notes.md similarity index 100% rename from apps/runtime/Notes.md rename to archive/apps/runtime/Notes.md diff --git a/apps/runtime/README.md b/archive/apps/runtime/README.md similarity index 100% rename from apps/runtime/README.md rename to archive/apps/runtime/README.md diff --git a/apps/runtime/compose.db.mysql.yml b/archive/apps/runtime/compose.db.mysql.yml similarity index 100% rename from apps/runtime/compose.db.mysql.yml rename to archive/apps/runtime/compose.db.mysql.yml diff --git a/apps/runtime/compose.lang.dotnet.yml b/archive/apps/runtime/compose.lang.dotnet.yml similarity index 100% rename from apps/runtime/compose.lang.dotnet.yml rename to archive/apps/runtime/compose.lang.dotnet.yml diff --git a/apps/runtime/compose.lang.golang.yml b/archive/apps/runtime/compose.lang.golang.yml similarity index 100% rename from apps/runtime/compose.lang.golang.yml rename to archive/apps/runtime/compose.lang.golang.yml diff --git a/apps/runtime/compose.lang.java-corretto.yml b/archive/apps/runtime/compose.lang.java-corretto.yml similarity index 100% rename from apps/runtime/compose.lang.java-corretto.yml rename to archive/apps/runtime/compose.lang.java-corretto.yml diff --git a/apps/runtime/compose.lang.java-openjdk.yml b/archive/apps/runtime/compose.lang.java-openjdk.yml similarity index 100% rename from apps/runtime/compose.lang.java-openjdk.yml rename to archive/apps/runtime/compose.lang.java-openjdk.yml diff --git a/apps/runtime/compose.lang.java-zulu.yml b/archive/apps/runtime/compose.lang.java-zulu.yml similarity index 100% rename from apps/runtime/compose.lang.java-zulu.yml rename to archive/apps/runtime/compose.lang.java-zulu.yml diff --git a/apps/runtime/compose.lang.node.yml b/archive/apps/runtime/compose.lang.node.yml similarity index 100% rename from apps/runtime/compose.lang.node.yml rename to archive/apps/runtime/compose.lang.node.yml diff --git a/apps/runtime/compose.lang.php.yml b/archive/apps/runtime/compose.lang.php.yml similarity index 100% rename from apps/runtime/compose.lang.php.yml rename to archive/apps/runtime/compose.lang.php.yml diff --git a/apps/runtime/compose.lang.python.yml b/archive/apps/runtime/compose.lang.python.yml similarity index 100% rename from apps/runtime/compose.lang.python.yml rename to archive/apps/runtime/compose.lang.python.yml diff --git a/apps/runtime/compose.lang.ruby.yml b/archive/apps/runtime/compose.lang.ruby.yml similarity index 100% rename from apps/runtime/compose.lang.ruby.yml rename to archive/apps/runtime/compose.lang.ruby.yml diff --git a/apps/runtime/compose.lang.swift.yml b/archive/apps/runtime/compose.lang.swift.yml similarity index 100% rename from apps/runtime/compose.lang.swift.yml rename to archive/apps/runtime/compose.lang.swift.yml diff --git a/apps/runtime/compose.proxy.nginx.yml b/archive/apps/runtime/compose.proxy.nginx.yml similarity index 100% rename from apps/runtime/compose.proxy.nginx.yml rename to archive/apps/runtime/compose.proxy.nginx.yml diff --git a/apps/runtime/config/dotnet/service.sh b/archive/apps/runtime/config/dotnet/service.sh similarity index 100% rename from apps/runtime/config/dotnet/service.sh rename to archive/apps/runtime/config/dotnet/service.sh diff --git a/apps/runtime/config/golang/service.sh b/archive/apps/runtime/config/golang/service.sh similarity index 100% rename from apps/runtime/config/golang/service.sh rename to archive/apps/runtime/config/golang/service.sh diff --git a/apps/runtime/config/java/service.sh b/archive/apps/runtime/config/java/service.sh similarity index 100% rename from apps/runtime/config/java/service.sh rename to archive/apps/runtime/config/java/service.sh diff --git a/apps/runtime/config/java/supervisord.conf b/archive/apps/runtime/config/java/supervisord.conf similarity index 100% rename from apps/runtime/config/java/supervisord.conf rename to archive/apps/runtime/config/java/supervisord.conf diff --git a/apps/runtime/config/node/sample.js b/archive/apps/runtime/config/node/sample.js similarity index 100% rename from apps/runtime/config/node/sample.js rename to archive/apps/runtime/config/node/sample.js diff --git a/apps/runtime/config/node/service.sh b/archive/apps/runtime/config/node/service.sh similarity index 100% rename from apps/runtime/config/node/service.sh rename to archive/apps/runtime/config/node/service.sh diff --git a/apps/runtime/config/node/service1.sh b/archive/apps/runtime/config/node/service1.sh similarity index 100% rename from apps/runtime/config/node/service1.sh rename to archive/apps/runtime/config/node/service1.sh diff --git a/apps/runtime/config/php/service.sh b/archive/apps/runtime/config/php/service.sh similarity index 100% rename from apps/runtime/config/php/service.sh rename to archive/apps/runtime/config/php/service.sh diff --git a/apps/runtime/config/php/supervisord.conf b/archive/apps/runtime/config/php/supervisord.conf similarity index 100% rename from apps/runtime/config/php/supervisord.conf rename to archive/apps/runtime/config/php/supervisord.conf diff --git a/apps/runtime/config/python/service.sh b/archive/apps/runtime/config/python/service.sh similarity index 100% rename from apps/runtime/config/python/service.sh rename to archive/apps/runtime/config/python/service.sh diff --git a/apps/runtime/config/ruby/service.sh b/archive/apps/runtime/config/ruby/service.sh similarity index 100% rename from apps/runtime/config/ruby/service.sh rename to archive/apps/runtime/config/ruby/service.sh diff --git a/apps/runtime/config/runtime.conf b/archive/apps/runtime/config/runtime.conf similarity index 100% rename from apps/runtime/config/runtime.conf rename to archive/apps/runtime/config/runtime.conf diff --git a/apps/runtime/config/swift/service.sh b/archive/apps/runtime/config/swift/service.sh similarity index 100% rename from apps/runtime/config/swift/service.sh rename to archive/apps/runtime/config/swift/service.sh diff --git a/apps/runtime/docs/PRD.md b/archive/apps/runtime/docs/PRD.md similarity index 100% rename from apps/runtime/docs/PRD.md rename to archive/apps/runtime/docs/PRD.md diff --git a/apps/runtime/src/9panel/CHANGELOG.md b/archive/apps/runtime/src/9panel/CHANGELOG.md similarity index 100% rename from apps/runtime/src/9panel/CHANGELOG.md rename to archive/apps/runtime/src/9panel/CHANGELOG.md diff --git a/apps/runtime/src/9panel/Notes.md b/archive/apps/runtime/src/9panel/Notes.md similarity index 100% rename from apps/runtime/src/9panel/Notes.md rename to archive/apps/runtime/src/9panel/Notes.md diff --git a/apps/runtime/src/9panel/css/font-face.css b/archive/apps/runtime/src/9panel/css/font-face.css similarity index 100% rename from apps/runtime/src/9panel/css/font-face.css rename to archive/apps/runtime/src/9panel/css/font-face.css diff --git a/apps/runtime/src/9panel/css/theme.css b/archive/apps/runtime/src/9panel/css/theme.css similarity index 100% rename from apps/runtime/src/9panel/css/theme.css rename to archive/apps/runtime/src/9panel/css/theme.css diff --git a/apps/runtime/src/9panel/db.html b/archive/apps/runtime/src/9panel/db.html similarity index 100% rename from apps/runtime/src/9panel/db.html rename to archive/apps/runtime/src/9panel/db.html diff --git a/apps/runtime/src/9panel/lang/en.js b/archive/apps/runtime/src/9panel/docs/PRD.md similarity index 100% rename from apps/runtime/src/9panel/lang/en.js rename to archive/apps/runtime/src/9panel/docs/PRD.md diff --git a/apps/runtime/src/9panel/lang/zh.js b/archive/apps/runtime/src/9panel/docs/developer-guide.md similarity index 100% rename from apps/runtime/src/9panel/lang/zh.js rename to archive/apps/runtime/src/9panel/docs/developer-guide.md diff --git a/apps/syncthing/src/get_version.sh b/archive/apps/runtime/src/9panel/docs/user-guide.md similarity index 100% rename from apps/syncthing/src/get_version.sh rename to archive/apps/runtime/src/9panel/docs/user-guide.md diff --git a/apps/runtime/src/9panel/favicon.ico b/archive/apps/runtime/src/9panel/favicon.ico similarity index 100% rename from apps/runtime/src/9panel/favicon.ico rename to archive/apps/runtime/src/9panel/favicon.ico diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-100italic.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-200italic.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-300italic.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-500italic.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-600italic.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-700italic.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-800italic.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-900italic.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-italic.woff2 diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.eot b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.eot similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.eot rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.eot diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.svg b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.svg similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.svg rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.svg diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.ttf b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.ttf similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.ttf rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.ttf diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.woff b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.woff similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.woff rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.woff diff --git a/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.woff2 b/archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.woff2 similarity index 100% rename from apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.woff2 rename to archive/apps/runtime/src/9panel/fonts/poppins/poppins-v5-latin-regular.woff2 diff --git a/apps/runtime/src/9panel/ftp.html b/archive/apps/runtime/src/9panel/ftp.html similarity index 100% rename from apps/runtime/src/9panel/ftp.html rename to archive/apps/runtime/src/9panel/ftp.html diff --git a/apps/runtime/src/9panel/images/catpassword.png b/archive/apps/runtime/src/9panel/images/catpassword.png similarity index 100% rename from apps/runtime/src/9panel/images/catpassword.png rename to archive/apps/runtime/src/9panel/images/catpassword.png diff --git a/apps/runtime/src/9panel/images/checkit.png b/archive/apps/runtime/src/9panel/images/checkit.png similarity index 100% rename from apps/runtime/src/9panel/images/checkit.png rename to archive/apps/runtime/src/9panel/images/checkit.png diff --git a/apps/runtime/src/9panel/images/databasesecurity.png b/archive/apps/runtime/src/9panel/images/databasesecurity.png similarity index 100% rename from apps/runtime/src/9panel/images/databasesecurity.png rename to archive/apps/runtime/src/9panel/images/databasesecurity.png diff --git a/apps/runtime/src/9panel/images/domain.png b/archive/apps/runtime/src/9panel/images/domain.png similarity index 100% rename from apps/runtime/src/9panel/images/domain.png rename to archive/apps/runtime/src/9panel/images/domain.png diff --git a/apps/runtime/src/9panel/images/example-configit-websoft9.png b/archive/apps/runtime/src/9panel/images/example-configit-websoft9.png similarity index 100% rename from apps/runtime/src/9panel/images/example-configit-websoft9.png rename to archive/apps/runtime/src/9panel/images/example-configit-websoft9.png diff --git a/apps/runtime/src/9panel/images/example-helpdesk-websoft9.png b/archive/apps/runtime/src/9panel/images/example-helpdesk-websoft9.png similarity index 100% rename from apps/runtime/src/9panel/images/example-helpdesk-websoft9.png rename to archive/apps/runtime/src/9panel/images/example-helpdesk-websoft9.png diff --git a/apps/runtime/src/9panel/images/icon/AI_websoft9-w60.png b/archive/apps/runtime/src/9panel/images/icon/AI_websoft9-w60.png similarity index 100% rename from apps/runtime/src/9panel/images/icon/AI_websoft9-w60.png rename to archive/apps/runtime/src/9panel/images/icon/AI_websoft9-w60.png diff --git a/apps/runtime/src/9panel/images/icon/websoft9-demologo.png b/archive/apps/runtime/src/9panel/images/icon/websoft9-demologo.png similarity index 100% rename from apps/runtime/src/9panel/images/icon/websoft9-demologo.png rename to archive/apps/runtime/src/9panel/images/icon/websoft9-demologo.png diff --git a/apps/runtime/src/9panel/images/icon/websoft9-imagepanel.png b/archive/apps/runtime/src/9panel/images/icon/websoft9-imagepanel.png similarity index 100% rename from apps/runtime/src/9panel/images/icon/websoft9-imagepanel.png rename to archive/apps/runtime/src/9panel/images/icon/websoft9-imagepanel.png diff --git a/apps/runtime/src/9panel/images/install003.png b/archive/apps/runtime/src/9panel/images/install003.png similarity index 100% rename from apps/runtime/src/9panel/images/install003.png rename to archive/apps/runtime/src/9panel/images/install003.png diff --git a/apps/runtime/src/9panel/images/windows-remote_en.png b/archive/apps/runtime/src/9panel/images/windows-remote_en.png similarity index 100% rename from apps/runtime/src/9panel/images/windows-remote_en.png rename to archive/apps/runtime/src/9panel/images/windows-remote_en.png diff --git a/apps/runtime/src/9panel/images/windows-remote_zh.png b/archive/apps/runtime/src/9panel/images/windows-remote_zh.png similarity index 100% rename from apps/runtime/src/9panel/images/windows-remote_zh.png rename to archive/apps/runtime/src/9panel/images/windows-remote_zh.png diff --git a/apps/runtime/src/9panel/images/winscp-newsite_en.png b/archive/apps/runtime/src/9panel/images/winscp-newsite_en.png similarity index 100% rename from apps/runtime/src/9panel/images/winscp-newsite_en.png rename to archive/apps/runtime/src/9panel/images/winscp-newsite_en.png diff --git a/apps/runtime/src/9panel/images/winscp-newsite_zh.png b/archive/apps/runtime/src/9panel/images/winscp-newsite_zh.png similarity index 100% rename from apps/runtime/src/9panel/images/winscp-newsite_zh.png rename to archive/apps/runtime/src/9panel/images/winscp-newsite_zh.png diff --git a/apps/runtime/src/9panel/index.html b/archive/apps/runtime/src/9panel/index.html similarity index 100% rename from apps/runtime/src/9panel/index.html rename to archive/apps/runtime/src/9panel/index.html diff --git a/apps/runtime/src/9panel/js/copyright.js b/archive/apps/runtime/src/9panel/js/copyright.js similarity index 100% rename from apps/runtime/src/9panel/js/copyright.js rename to archive/apps/runtime/src/9panel/js/copyright.js diff --git a/apps/runtime/src/9panel/js/header-desktop.js b/archive/apps/runtime/src/9panel/js/header-desktop.js similarity index 100% rename from apps/runtime/src/9panel/js/header-desktop.js rename to archive/apps/runtime/src/9panel/js/header-desktop.js diff --git a/apps/runtime/src/9panel/js/main.js b/archive/apps/runtime/src/9panel/js/main.js similarity index 100% rename from apps/runtime/src/9panel/js/main.js rename to archive/apps/runtime/src/9panel/js/main.js diff --git a/apps/runtime/src/9panel/js/websoft9.js b/archive/apps/runtime/src/9panel/js/websoft9.js similarity index 100% rename from apps/runtime/src/9panel/js/websoft9.js rename to archive/apps/runtime/src/9panel/js/websoft9.js diff --git a/archive/apps/runtime/src/9panel/lang/en.js b/archive/apps/runtime/src/9panel/lang/en.js new file mode 100644 index 000000000..e69de29bb diff --git a/archive/apps/runtime/src/9panel/lang/zh.js b/archive/apps/runtime/src/9panel/lang/zh.js new file mode 100644 index 000000000..e69de29bb diff --git a/apps/runtime/src/9panel/readme.md b/archive/apps/runtime/src/9panel/readme.md similarity index 100% rename from apps/runtime/src/9panel/readme.md rename to archive/apps/runtime/src/9panel/readme.md diff --git a/apps/runtime/src/9panel/tools.html b/archive/apps/runtime/src/9panel/tools.html similarity index 100% rename from apps/runtime/src/9panel/tools.html rename to archive/apps/runtime/src/9panel/tools.html diff --git a/apps/runtime/src/9panel/vendor/animsition/animsition.min.css b/archive/apps/runtime/src/9panel/vendor/animsition/animsition.min.css similarity index 100% rename from apps/runtime/src/9panel/vendor/animsition/animsition.min.css rename to archive/apps/runtime/src/9panel/vendor/animsition/animsition.min.css diff --git a/apps/runtime/src/9panel/vendor/animsition/animsition.min.js b/archive/apps/runtime/src/9panel/vendor/animsition/animsition.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/animsition/animsition.min.js rename to archive/apps/runtime/src/9panel/vendor/animsition/animsition.min.js diff --git a/apps/runtime/src/9panel/vendor/bootstrap-4.1/bootstrap.min.css b/archive/apps/runtime/src/9panel/vendor/bootstrap-4.1/bootstrap.min.css similarity index 100% rename from apps/runtime/src/9panel/vendor/bootstrap-4.1/bootstrap.min.css rename to archive/apps/runtime/src/9panel/vendor/bootstrap-4.1/bootstrap.min.css diff --git a/apps/runtime/src/9panel/vendor/bootstrap-4.1/bootstrap.min.js b/archive/apps/runtime/src/9panel/vendor/bootstrap-4.1/bootstrap.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/bootstrap-4.1/bootstrap.min.js rename to archive/apps/runtime/src/9panel/vendor/bootstrap-4.1/bootstrap.min.js diff --git a/apps/runtime/src/9panel/vendor/bootstrap-4.1/popper.min.js b/archive/apps/runtime/src/9panel/vendor/bootstrap-4.1/popper.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/bootstrap-4.1/popper.min.js rename to archive/apps/runtime/src/9panel/vendor/bootstrap-4.1/popper.min.js diff --git a/apps/runtime/src/9panel/vendor/bootstrap-progressbar/bootstrap-progressbar-3.3.4.min.css b/archive/apps/runtime/src/9panel/vendor/bootstrap-progressbar/bootstrap-progressbar-3.3.4.min.css similarity index 100% rename from apps/runtime/src/9panel/vendor/bootstrap-progressbar/bootstrap-progressbar-3.3.4.min.css rename to archive/apps/runtime/src/9panel/vendor/bootstrap-progressbar/bootstrap-progressbar-3.3.4.min.css diff --git a/apps/runtime/src/9panel/vendor/bootstrap-progressbar/bootstrap-progressbar.min.js b/archive/apps/runtime/src/9panel/vendor/bootstrap-progressbar/bootstrap-progressbar.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/bootstrap-progressbar/bootstrap-progressbar.min.js rename to archive/apps/runtime/src/9panel/vendor/bootstrap-progressbar/bootstrap-progressbar.min.js diff --git a/apps/runtime/src/9panel/vendor/css-hamburgers/hamburgers.css b/archive/apps/runtime/src/9panel/vendor/css-hamburgers/hamburgers.css similarity index 100% rename from apps/runtime/src/9panel/vendor/css-hamburgers/hamburgers.css rename to archive/apps/runtime/src/9panel/vendor/css-hamburgers/hamburgers.css diff --git a/apps/runtime/src/9panel/vendor/css-hamburgers/hamburgers.min.css b/archive/apps/runtime/src/9panel/vendor/css-hamburgers/hamburgers.min.css similarity index 100% rename from apps/runtime/src/9panel/vendor/css-hamburgers/hamburgers.min.css rename to archive/apps/runtime/src/9panel/vendor/css-hamburgers/hamburgers.min.css diff --git a/apps/runtime/src/9panel/vendor/font-awesome-4.7/HELP-US-OUT.txt b/archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/HELP-US-OUT.txt similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-4.7/HELP-US-OUT.txt rename to archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/HELP-US-OUT.txt diff --git a/apps/runtime/src/9panel/vendor/font-awesome-4.7/css/font-awesome.css b/archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/css/font-awesome.css similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-4.7/css/font-awesome.css rename to archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/css/font-awesome.css diff --git a/apps/runtime/src/9panel/vendor/font-awesome-4.7/css/font-awesome.min.css b/archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/css/font-awesome.min.css similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-4.7/css/font-awesome.min.css rename to archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/css/font-awesome.min.css diff --git a/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/FontAwesome.otf b/archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/FontAwesome.otf similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/FontAwesome.otf rename to archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/FontAwesome.otf diff --git a/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.eot b/archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.eot similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.eot rename to archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.eot diff --git a/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.svg b/archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.svg similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.svg rename to archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.svg diff --git a/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.ttf b/archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.ttf similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.ttf rename to archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.ttf diff --git a/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.woff b/archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.woff similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.woff rename to archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.woff diff --git a/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.woff2 b/archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.woff2 similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.woff2 rename to archive/apps/runtime/src/9panel/vendor/font-awesome-4.7/fonts/fontawesome-webfont.woff2 diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/css/fontawesome-all.min.css b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/css/fontawesome-all.min.css similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/css/fontawesome-all.min.css rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/css/fontawesome-all.min.css diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.eot b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.eot similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.eot rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.eot diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.svg b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.svg similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.svg rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.svg diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.ttf b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.ttf similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.ttf rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.ttf diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.woff b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.woff similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.woff rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.woff diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.woff2 b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.woff2 similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.woff2 rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-brands-400.woff2 diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.eot b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.eot similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.eot rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.eot diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.svg b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.svg similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.svg rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.svg diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.ttf b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.ttf similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.ttf rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.ttf diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.woff b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.woff similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.woff rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.woff diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.woff2 b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.woff2 similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.woff2 rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-regular-400.woff2 diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.eot b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.eot similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.eot rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.eot diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.svg b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.svg similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.svg rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.svg diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.ttf b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.ttf similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.ttf rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.ttf diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.woff b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.woff similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.woff rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.woff diff --git a/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.woff2 b/archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.woff2 similarity index 100% rename from apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.woff2 rename to archive/apps/runtime/src/9panel/vendor/font-awesome-5/webfonts/fa-solid-900.woff2 diff --git a/apps/runtime/src/9panel/vendor/jquery-3.2.1.min.js b/archive/apps/runtime/src/9panel/vendor/jquery-3.2.1.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/jquery-3.2.1.min.js rename to archive/apps/runtime/src/9panel/vendor/jquery-3.2.1.min.js diff --git a/apps/runtime/src/9panel/vendor/jquery-ui.min.js b/archive/apps/runtime/src/9panel/vendor/jquery-ui.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/jquery-ui.min.js rename to archive/apps/runtime/src/9panel/vendor/jquery-ui.min.js diff --git a/apps/runtime/src/9panel/vendor/mdi-font/css/material-design-iconic-font.css b/archive/apps/runtime/src/9panel/vendor/mdi-font/css/material-design-iconic-font.css similarity index 100% rename from apps/runtime/src/9panel/vendor/mdi-font/css/material-design-iconic-font.css rename to archive/apps/runtime/src/9panel/vendor/mdi-font/css/material-design-iconic-font.css diff --git a/apps/runtime/src/9panel/vendor/mdi-font/css/material-design-iconic-font.min.css b/archive/apps/runtime/src/9panel/vendor/mdi-font/css/material-design-iconic-font.min.css similarity index 100% rename from apps/runtime/src/9panel/vendor/mdi-font/css/material-design-iconic-font.min.css rename to archive/apps/runtime/src/9panel/vendor/mdi-font/css/material-design-iconic-font.min.css diff --git a/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.eot b/archive/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.eot similarity index 100% rename from apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.eot rename to archive/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.eot diff --git a/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.svg b/archive/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.svg similarity index 100% rename from apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.svg rename to archive/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.svg diff --git a/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.ttf b/archive/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.ttf similarity index 100% rename from apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.ttf rename to archive/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.ttf diff --git a/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.woff b/archive/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.woff similarity index 100% rename from apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.woff rename to archive/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.woff diff --git a/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.woff2 b/archive/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.woff2 similarity index 100% rename from apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.woff2 rename to archive/apps/runtime/src/9panel/vendor/mdi-font/fonts/Material-Design-Iconic-Font.woff2 diff --git a/apps/runtime/src/9panel/vendor/perfect-scrollbar/perfect-scrollbar.css b/archive/apps/runtime/src/9panel/vendor/perfect-scrollbar/perfect-scrollbar.css similarity index 100% rename from apps/runtime/src/9panel/vendor/perfect-scrollbar/perfect-scrollbar.css rename to archive/apps/runtime/src/9panel/vendor/perfect-scrollbar/perfect-scrollbar.css diff --git a/apps/runtime/src/9panel/vendor/perfect-scrollbar/perfect-scrollbar.js b/archive/apps/runtime/src/9panel/vendor/perfect-scrollbar/perfect-scrollbar.js similarity index 100% rename from apps/runtime/src/9panel/vendor/perfect-scrollbar/perfect-scrollbar.js rename to archive/apps/runtime/src/9panel/vendor/perfect-scrollbar/perfect-scrollbar.js diff --git a/apps/runtime/src/9panel/vendor/perfect-scrollbar/perfect-scrollbar.min.js b/archive/apps/runtime/src/9panel/vendor/perfect-scrollbar/perfect-scrollbar.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/perfect-scrollbar/perfect-scrollbar.min.js rename to archive/apps/runtime/src/9panel/vendor/perfect-scrollbar/perfect-scrollbar.min.js diff --git a/apps/runtime/src/9panel/vendor/progressbar/progressbar.js b/archive/apps/runtime/src/9panel/vendor/progressbar/progressbar.js similarity index 100% rename from apps/runtime/src/9panel/vendor/progressbar/progressbar.js rename to archive/apps/runtime/src/9panel/vendor/progressbar/progressbar.js diff --git a/apps/runtime/src/9panel/vendor/progressbar/progressbar.min.js b/archive/apps/runtime/src/9panel/vendor/progressbar/progressbar.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/progressbar/progressbar.min.js rename to archive/apps/runtime/src/9panel/vendor/progressbar/progressbar.min.js diff --git a/apps/runtime/src/9panel/vendor/select2/select2.min.css b/archive/apps/runtime/src/9panel/vendor/select2/select2.min.css similarity index 100% rename from apps/runtime/src/9panel/vendor/select2/select2.min.css rename to archive/apps/runtime/src/9panel/vendor/select2/select2.min.css diff --git a/apps/runtime/src/9panel/vendor/select2/select2.min.js b/archive/apps/runtime/src/9panel/vendor/select2/select2.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/select2/select2.min.js rename to archive/apps/runtime/src/9panel/vendor/select2/select2.min.js diff --git a/apps/runtime/src/9panel/vendor/slick/ajax-loader.gif b/archive/apps/runtime/src/9panel/vendor/slick/ajax-loader.gif similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/ajax-loader.gif rename to archive/apps/runtime/src/9panel/vendor/slick/ajax-loader.gif diff --git a/apps/runtime/src/9panel/vendor/slick/config.rb b/archive/apps/runtime/src/9panel/vendor/slick/config.rb similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/config.rb rename to archive/apps/runtime/src/9panel/vendor/slick/config.rb diff --git a/apps/runtime/src/9panel/vendor/slick/fonts/slick.eot b/archive/apps/runtime/src/9panel/vendor/slick/fonts/slick.eot similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/fonts/slick.eot rename to archive/apps/runtime/src/9panel/vendor/slick/fonts/slick.eot diff --git a/apps/runtime/src/9panel/vendor/slick/fonts/slick.svg b/archive/apps/runtime/src/9panel/vendor/slick/fonts/slick.svg similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/fonts/slick.svg rename to archive/apps/runtime/src/9panel/vendor/slick/fonts/slick.svg diff --git a/apps/runtime/src/9panel/vendor/slick/fonts/slick.ttf b/archive/apps/runtime/src/9panel/vendor/slick/fonts/slick.ttf similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/fonts/slick.ttf rename to archive/apps/runtime/src/9panel/vendor/slick/fonts/slick.ttf diff --git a/apps/runtime/src/9panel/vendor/slick/fonts/slick.woff b/archive/apps/runtime/src/9panel/vendor/slick/fonts/slick.woff similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/fonts/slick.woff rename to archive/apps/runtime/src/9panel/vendor/slick/fonts/slick.woff diff --git a/apps/runtime/src/9panel/vendor/slick/slick-theme.css b/archive/apps/runtime/src/9panel/vendor/slick/slick-theme.css similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/slick-theme.css rename to archive/apps/runtime/src/9panel/vendor/slick/slick-theme.css diff --git a/apps/runtime/src/9panel/vendor/slick/slick-theme.less b/archive/apps/runtime/src/9panel/vendor/slick/slick-theme.less similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/slick-theme.less rename to archive/apps/runtime/src/9panel/vendor/slick/slick-theme.less diff --git a/apps/runtime/src/9panel/vendor/slick/slick-theme.scss b/archive/apps/runtime/src/9panel/vendor/slick/slick-theme.scss similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/slick-theme.scss rename to archive/apps/runtime/src/9panel/vendor/slick/slick-theme.scss diff --git a/apps/runtime/src/9panel/vendor/slick/slick.css b/archive/apps/runtime/src/9panel/vendor/slick/slick.css similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/slick.css rename to archive/apps/runtime/src/9panel/vendor/slick/slick.css diff --git a/apps/runtime/src/9panel/vendor/slick/slick.js b/archive/apps/runtime/src/9panel/vendor/slick/slick.js similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/slick.js rename to archive/apps/runtime/src/9panel/vendor/slick/slick.js diff --git a/apps/runtime/src/9panel/vendor/slick/slick.less b/archive/apps/runtime/src/9panel/vendor/slick/slick.less similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/slick.less rename to archive/apps/runtime/src/9panel/vendor/slick/slick.less diff --git a/apps/runtime/src/9panel/vendor/slick/slick.min.js b/archive/apps/runtime/src/9panel/vendor/slick/slick.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/slick.min.js rename to archive/apps/runtime/src/9panel/vendor/slick/slick.min.js diff --git a/apps/runtime/src/9panel/vendor/slick/slick.scss b/archive/apps/runtime/src/9panel/vendor/slick/slick.scss similarity index 100% rename from apps/runtime/src/9panel/vendor/slick/slick.scss rename to archive/apps/runtime/src/9panel/vendor/slick/slick.scss diff --git a/apps/runtime/src/9panel/vendor/smk-accordion/accordion.css b/archive/apps/runtime/src/9panel/vendor/smk-accordion/accordion.css similarity index 100% rename from apps/runtime/src/9panel/vendor/smk-accordion/accordion.css rename to archive/apps/runtime/src/9panel/vendor/smk-accordion/accordion.css diff --git a/apps/runtime/src/9panel/vendor/smk-accordion/accordion.js b/archive/apps/runtime/src/9panel/vendor/smk-accordion/accordion.js similarity index 100% rename from apps/runtime/src/9panel/vendor/smk-accordion/accordion.js rename to archive/apps/runtime/src/9panel/vendor/smk-accordion/accordion.js diff --git a/apps/runtime/src/9panel/vendor/smk-accordion/accordion.min.js b/archive/apps/runtime/src/9panel/vendor/smk-accordion/accordion.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/smk-accordion/accordion.min.js rename to archive/apps/runtime/src/9panel/vendor/smk-accordion/accordion.min.js diff --git a/apps/runtime/src/9panel/vendor/sweetalert/sweetalert.min.js b/archive/apps/runtime/src/9panel/vendor/sweetalert/sweetalert.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/sweetalert/sweetalert.min.js rename to archive/apps/runtime/src/9panel/vendor/sweetalert/sweetalert.min.js diff --git a/apps/runtime/src/9panel/vendor/vue/vue-i18n.js b/archive/apps/runtime/src/9panel/vendor/vue/vue-i18n.js similarity index 100% rename from apps/runtime/src/9panel/vendor/vue/vue-i18n.js rename to archive/apps/runtime/src/9panel/vendor/vue/vue-i18n.js diff --git a/apps/runtime/src/9panel/vendor/vue/vue.min.js b/archive/apps/runtime/src/9panel/vendor/vue/vue.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/vue/vue.min.js rename to archive/apps/runtime/src/9panel/vendor/vue/vue.min.js diff --git a/apps/runtime/src/9panel/vendor/wow/animate.css b/archive/apps/runtime/src/9panel/vendor/wow/animate.css similarity index 100% rename from apps/runtime/src/9panel/vendor/wow/animate.css rename to archive/apps/runtime/src/9panel/vendor/wow/animate.css diff --git a/apps/runtime/src/9panel/vendor/wow/wow.min.js b/archive/apps/runtime/src/9panel/vendor/wow/wow.min.js similarity index 100% rename from apps/runtime/src/9panel/vendor/wow/wow.min.js rename to archive/apps/runtime/src/9panel/vendor/wow/wow.min.js diff --git a/apps/runtime/src/9panel/wizard.html b/archive/apps/runtime/src/9panel/wizard.html similarity index 100% rename from apps/runtime/src/9panel/wizard.html rename to archive/apps/runtime/src/9panel/wizard.html diff --git a/apps/runtime/src/Dockerfile.9panel b/archive/apps/runtime/src/Dockerfile.9panel similarity index 100% rename from apps/runtime/src/Dockerfile.9panel rename to archive/apps/runtime/src/Dockerfile.9panel diff --git a/apps/runtime/src/Dockerfile.java b/archive/apps/runtime/src/Dockerfile.java similarity index 100% rename from apps/runtime/src/Dockerfile.java rename to archive/apps/runtime/src/Dockerfile.java diff --git a/apps/runtime/src/Dockerfile.php b/archive/apps/runtime/src/Dockerfile.php similarity index 100% rename from apps/runtime/src/Dockerfile.php rename to archive/apps/runtime/src/Dockerfile.php diff --git a/apps/runtime/src/entrypoint-9panel.sh b/archive/apps/runtime/src/entrypoint-9panel.sh similarity index 100% rename from apps/runtime/src/entrypoint-9panel.sh rename to archive/apps/runtime/src/entrypoint-9panel.sh diff --git a/apps/runtime/src/nginx/cockpit-proxy.conf b/archive/apps/runtime/src/nginx/cockpit-proxy.conf similarity index 100% rename from apps/runtime/src/nginx/cockpit-proxy.conf rename to archive/apps/runtime/src/nginx/cockpit-proxy.conf diff --git a/apps/runtime/src/nginx/database.sqlite b/archive/apps/runtime/src/nginx/database.sqlite similarity index 100% rename from apps/runtime/src/nginx/database.sqlite rename to archive/apps/runtime/src/nginx/database.sqlite diff --git a/apps/runtime/src/nginx/nginx_init.sh b/archive/apps/runtime/src/nginx/nginx_init.sh similarity index 100% rename from apps/runtime/src/nginx/nginx_init.sh rename to archive/apps/runtime/src/nginx/nginx_init.sh diff --git a/apps/runtime/src/nginx/sqlite3 b/archive/apps/runtime/src/nginx/sqlite3 similarity index 100% rename from apps/runtime/src/nginx/sqlite3 rename to archive/apps/runtime/src/nginx/sqlite3 diff --git a/apps/runtime/variables.json b/archive/apps/runtime/variables.json similarity index 100% rename from apps/runtime/variables.json rename to archive/apps/runtime/variables.json diff --git a/apps/rustdesk/.env b/archive/apps/rustdesk/.env similarity index 100% rename from apps/rustdesk/.env rename to archive/apps/rustdesk/.env diff --git a/apps/rustdesk/CHANGELOG.md b/archive/apps/rustdesk/CHANGELOG.md similarity index 100% rename from apps/rustdesk/CHANGELOG.md rename to archive/apps/rustdesk/CHANGELOG.md diff --git a/apps/rustdesk/Notes.md b/archive/apps/rustdesk/Notes.md similarity index 100% rename from apps/rustdesk/Notes.md rename to archive/apps/rustdesk/Notes.md diff --git a/apps/rustdesk/README.md b/archive/apps/rustdesk/README.md similarity index 100% rename from apps/rustdesk/README.md rename to archive/apps/rustdesk/README.md diff --git a/apps/rustdesk/docker-compose.yml b/archive/apps/rustdesk/docker-compose.yml similarity index 100% rename from apps/rustdesk/docker-compose.yml rename to archive/apps/rustdesk/docker-compose.yml diff --git a/apps/rustdesk/src/README.md b/archive/apps/rustdesk/src/README.md similarity index 100% rename from apps/rustdesk/src/README.md rename to archive/apps/rustdesk/src/README.md diff --git a/apps/rustdesk/variables.json b/archive/apps/rustdesk/variables.json similarity index 100% rename from apps/rustdesk/variables.json rename to archive/apps/rustdesk/variables.json diff --git a/apps/safeline/.env b/archive/apps/safeline/.env similarity index 100% rename from apps/safeline/.env rename to archive/apps/safeline/.env diff --git a/apps/safeline/CHANGELOG.md b/archive/apps/safeline/CHANGELOG.md similarity index 100% rename from apps/safeline/CHANGELOG.md rename to archive/apps/safeline/CHANGELOG.md diff --git a/apps/safeline/Notes.md b/archive/apps/safeline/Notes.md similarity index 100% rename from apps/safeline/Notes.md rename to archive/apps/safeline/Notes.md diff --git a/apps/safeline/docker-compose.yml b/archive/apps/safeline/docker-compose.yml similarity index 100% rename from apps/safeline/docker-compose.yml rename to archive/apps/safeline/docker-compose.yml diff --git a/apps/safeline/src/README.md b/archive/apps/safeline/src/README.md similarity index 100% rename from apps/safeline/src/README.md rename to archive/apps/safeline/src/README.md diff --git a/apps/safeline/variables.json b/archive/apps/safeline/variables.json similarity index 100% rename from apps/safeline/variables.json rename to archive/apps/safeline/variables.json diff --git a/apps/sakai/.env b/archive/apps/sakai/.env similarity index 100% rename from apps/sakai/.env rename to archive/apps/sakai/.env diff --git a/apps/sakai/CHANGELOG.md b/archive/apps/sakai/CHANGELOG.md similarity index 100% rename from apps/sakai/CHANGELOG.md rename to archive/apps/sakai/CHANGELOG.md diff --git a/apps/sakai/Dockerfile b/archive/apps/sakai/Dockerfile similarity index 100% rename from apps/sakai/Dockerfile rename to archive/apps/sakai/Dockerfile diff --git a/apps/sakai/Notes.md b/archive/apps/sakai/Notes.md similarity index 100% rename from apps/sakai/Notes.md rename to archive/apps/sakai/Notes.md diff --git a/apps/sakai/README.md b/archive/apps/sakai/README.md similarity index 100% rename from apps/sakai/README.md rename to archive/apps/sakai/README.md diff --git a/apps/sakai/docker-compose.yml b/archive/apps/sakai/docker-compose.yml similarity index 100% rename from apps/sakai/docker-compose.yml rename to archive/apps/sakai/docker-compose.yml diff --git a/apps/sakai/src/filelist b/archive/apps/sakai/src/filelist similarity index 100% rename from apps/sakai/src/filelist rename to archive/apps/sakai/src/filelist diff --git a/apps/sakai/variables.json b/archive/apps/sakai/variables.json similarity index 100% rename from apps/sakai/variables.json rename to archive/apps/sakai/variables.json diff --git a/apps/saleor/.env b/archive/apps/saleor/.env similarity index 100% rename from apps/saleor/.env rename to archive/apps/saleor/.env diff --git a/apps/saleor/CHANGELOG.md b/archive/apps/saleor/CHANGELOG.md similarity index 100% rename from apps/saleor/CHANGELOG.md rename to archive/apps/saleor/CHANGELOG.md diff --git a/apps/saleor/Notes.md b/archive/apps/saleor/Notes.md similarity index 100% rename from apps/saleor/Notes.md rename to archive/apps/saleor/Notes.md diff --git a/apps/saleor/README.md b/archive/apps/saleor/README.md similarity index 100% rename from apps/saleor/README.md rename to archive/apps/saleor/README.md diff --git a/apps/saleor/docker-compose.yml b/archive/apps/saleor/docker-compose.yml similarity index 100% rename from apps/saleor/docker-compose.yml rename to archive/apps/saleor/docker-compose.yml diff --git a/apps/saleor/src/README.md b/archive/apps/saleor/src/README.md similarity index 100% rename from apps/saleor/src/README.md rename to archive/apps/saleor/src/README.md diff --git a/apps/saleor/src/replica_user.sql b/archive/apps/saleor/src/replica_user.sql similarity index 100% rename from apps/saleor/src/replica_user.sql rename to archive/apps/saleor/src/replica_user.sql diff --git a/apps/saleor/variables.json b/archive/apps/saleor/variables.json similarity index 100% rename from apps/saleor/variables.json rename to archive/apps/saleor/variables.json diff --git a/apps/scmmanager/.env b/archive/apps/scmmanager/.env similarity index 100% rename from apps/scmmanager/.env rename to archive/apps/scmmanager/.env diff --git a/apps/scmmanager/CHANGELOG.md b/archive/apps/scmmanager/CHANGELOG.md similarity index 100% rename from apps/scmmanager/CHANGELOG.md rename to archive/apps/scmmanager/CHANGELOG.md diff --git a/apps/scmmanager/Notes.md b/archive/apps/scmmanager/Notes.md similarity index 100% rename from apps/scmmanager/Notes.md rename to archive/apps/scmmanager/Notes.md diff --git a/apps/scmmanager/README.md b/archive/apps/scmmanager/README.md similarity index 100% rename from apps/scmmanager/README.md rename to archive/apps/scmmanager/README.md diff --git a/apps/scmmanager/docker-compose.yml b/archive/apps/scmmanager/docker-compose.yml similarity index 100% rename from apps/scmmanager/docker-compose.yml rename to archive/apps/scmmanager/docker-compose.yml diff --git a/apps/scmmanager/src/README.md b/archive/apps/scmmanager/src/README.md similarity index 100% rename from apps/scmmanager/src/README.md rename to archive/apps/scmmanager/src/README.md diff --git a/apps/scmmanager/variables.json b/archive/apps/scmmanager/variables.json similarity index 100% rename from apps/scmmanager/variables.json rename to archive/apps/scmmanager/variables.json diff --git a/apps/snapdrop/.env b/archive/apps/snapdrop/.env similarity index 100% rename from apps/snapdrop/.env rename to archive/apps/snapdrop/.env diff --git a/apps/snapdrop/CHANGELOG.md b/archive/apps/snapdrop/CHANGELOG.md similarity index 100% rename from apps/snapdrop/CHANGELOG.md rename to archive/apps/snapdrop/CHANGELOG.md diff --git a/apps/snapdrop/Notes.md b/archive/apps/snapdrop/Notes.md similarity index 100% rename from apps/snapdrop/Notes.md rename to archive/apps/snapdrop/Notes.md diff --git a/apps/snapdrop/README.md b/archive/apps/snapdrop/README.md similarity index 100% rename from apps/snapdrop/README.md rename to archive/apps/snapdrop/README.md diff --git a/apps/snapdrop/docker-compose.yml b/archive/apps/snapdrop/docker-compose.yml similarity index 100% rename from apps/snapdrop/docker-compose.yml rename to archive/apps/snapdrop/docker-compose.yml diff --git a/apps/snapdrop/src/README.md b/archive/apps/snapdrop/src/README.md similarity index 100% rename from apps/snapdrop/src/README.md rename to archive/apps/snapdrop/src/README.md diff --git a/apps/snapdrop/variables.json b/archive/apps/snapdrop/variables.json similarity index 100% rename from apps/snapdrop/variables.json rename to archive/apps/snapdrop/variables.json diff --git a/apps/snipeit/.env b/archive/apps/snipeit/.env similarity index 100% rename from apps/snipeit/.env rename to archive/apps/snipeit/.env diff --git a/apps/snipeit/CHANGELOG.md b/archive/apps/snipeit/CHANGELOG.md similarity index 100% rename from apps/snipeit/CHANGELOG.md rename to archive/apps/snipeit/CHANGELOG.md diff --git a/apps/snipeit/Notes.md b/archive/apps/snipeit/Notes.md similarity index 100% rename from apps/snipeit/Notes.md rename to archive/apps/snipeit/Notes.md diff --git a/apps/snipeit/README.md b/archive/apps/snipeit/README.md similarity index 100% rename from apps/snipeit/README.md rename to archive/apps/snipeit/README.md diff --git a/apps/snipeit/docker-compose.yml b/archive/apps/snipeit/docker-compose.yml similarity index 100% rename from apps/snipeit/docker-compose.yml rename to archive/apps/snipeit/docker-compose.yml diff --git a/apps/snipeit/src/README.md b/archive/apps/snipeit/src/README.md similarity index 100% rename from apps/snipeit/src/README.md rename to archive/apps/snipeit/src/README.md diff --git a/apps/snipeit/variables.json b/archive/apps/snipeit/variables.json similarity index 100% rename from apps/snipeit/variables.json rename to archive/apps/snipeit/variables.json diff --git a/apps/squid/.env b/archive/apps/squid/.env similarity index 100% rename from apps/squid/.env rename to archive/apps/squid/.env diff --git a/apps/squid/CHANGELOG.md b/archive/apps/squid/CHANGELOG.md similarity index 100% rename from apps/squid/CHANGELOG.md rename to archive/apps/squid/CHANGELOG.md diff --git a/apps/squid/Dockerfile b/archive/apps/squid/Dockerfile similarity index 100% rename from apps/squid/Dockerfile rename to archive/apps/squid/Dockerfile diff --git a/apps/squid/Notes.md b/archive/apps/squid/Notes.md similarity index 100% rename from apps/squid/Notes.md rename to archive/apps/squid/Notes.md diff --git a/apps/squid/README.jinja2 b/archive/apps/squid/README.jinja2 similarity index 100% rename from apps/squid/README.jinja2 rename to archive/apps/squid/README.jinja2 diff --git a/apps/squid/README.md b/archive/apps/squid/README.md similarity index 100% rename from apps/squid/README.md rename to archive/apps/squid/README.md diff --git a/apps/squid/docker-compose.yml b/archive/apps/squid/docker-compose.yml similarity index 100% rename from apps/squid/docker-compose.yml rename to archive/apps/squid/docker-compose.yml diff --git a/apps/squid/src/README.md b/archive/apps/squid/src/README.md similarity index 100% rename from apps/squid/src/README.md rename to archive/apps/squid/src/README.md diff --git a/apps/squid/src/nginx-proxy.conf.template b/archive/apps/squid/src/nginx-proxy.conf.template similarity index 100% rename from apps/squid/src/nginx-proxy.conf.template rename to archive/apps/squid/src/nginx-proxy.conf.template diff --git a/apps/squid/src/php_exra.ini b/archive/apps/squid/src/php_exra.ini similarity index 100% rename from apps/squid/src/php_exra.ini rename to archive/apps/squid/src/php_exra.ini diff --git a/apps/squid/variables.json b/archive/apps/squid/variables.json similarity index 100% rename from apps/squid/variables.json rename to archive/apps/squid/variables.json diff --git a/apps/srs/.env b/archive/apps/srs/.env similarity index 100% rename from apps/srs/.env rename to archive/apps/srs/.env diff --git a/apps/srs/CHANGELOG.md b/archive/apps/srs/CHANGELOG.md similarity index 100% rename from apps/srs/CHANGELOG.md rename to archive/apps/srs/CHANGELOG.md diff --git a/apps/srs/Notes.md b/archive/apps/srs/Notes.md similarity index 100% rename from apps/srs/Notes.md rename to archive/apps/srs/Notes.md diff --git a/apps/srs/README.md b/archive/apps/srs/README.md similarity index 100% rename from apps/srs/README.md rename to archive/apps/srs/README.md diff --git a/apps/srs/docker-compose.yml b/archive/apps/srs/docker-compose.yml similarity index 100% rename from apps/srs/docker-compose.yml rename to archive/apps/srs/docker-compose.yml diff --git a/apps/srs/src/get_version.sh b/archive/apps/srs/src/get_version.sh similarity index 100% rename from apps/srs/src/get_version.sh rename to archive/apps/srs/src/get_version.sh diff --git a/apps/srs/variables.json b/archive/apps/srs/variables.json similarity index 100% rename from apps/srs/variables.json rename to archive/apps/srs/variables.json diff --git a/apps/stirlingpdf/.env b/archive/apps/stirlingpdf/.env similarity index 100% rename from apps/stirlingpdf/.env rename to archive/apps/stirlingpdf/.env diff --git a/apps/stirlingpdf/CHANGELOG.md b/archive/apps/stirlingpdf/CHANGELOG.md similarity index 100% rename from apps/stirlingpdf/CHANGELOG.md rename to archive/apps/stirlingpdf/CHANGELOG.md diff --git a/apps/stirlingpdf/Notes.md b/archive/apps/stirlingpdf/Notes.md similarity index 100% rename from apps/stirlingpdf/Notes.md rename to archive/apps/stirlingpdf/Notes.md diff --git a/apps/stirlingpdf/README.md b/archive/apps/stirlingpdf/README.md similarity index 100% rename from apps/stirlingpdf/README.md rename to archive/apps/stirlingpdf/README.md diff --git a/apps/stirlingpdf/docker-compose.yml b/archive/apps/stirlingpdf/docker-compose.yml similarity index 100% rename from apps/stirlingpdf/docker-compose.yml rename to archive/apps/stirlingpdf/docker-compose.yml diff --git a/apps/stirlingpdf/src/README.md b/archive/apps/stirlingpdf/src/README.md similarity index 100% rename from apps/stirlingpdf/src/README.md rename to archive/apps/stirlingpdf/src/README.md diff --git a/apps/stirlingpdf/variables.json b/archive/apps/stirlingpdf/variables.json similarity index 100% rename from apps/stirlingpdf/variables.json rename to archive/apps/stirlingpdf/variables.json diff --git a/apps/streampipes/.env b/archive/apps/streampipes/.env similarity index 100% rename from apps/streampipes/.env rename to archive/apps/streampipes/.env diff --git a/apps/streampipes/CHANGELOG.md b/archive/apps/streampipes/CHANGELOG.md similarity index 100% rename from apps/streampipes/CHANGELOG.md rename to archive/apps/streampipes/CHANGELOG.md diff --git a/apps/streampipes/Notes.md b/archive/apps/streampipes/Notes.md similarity index 100% rename from apps/streampipes/Notes.md rename to archive/apps/streampipes/Notes.md diff --git a/apps/streampipes/README.md b/archive/apps/streampipes/README.md similarity index 100% rename from apps/streampipes/README.md rename to archive/apps/streampipes/README.md diff --git a/apps/streampipes/docker-compose.yml b/archive/apps/streampipes/docker-compose.yml similarity index 100% rename from apps/streampipes/docker-compose.yml rename to archive/apps/streampipes/docker-compose.yml diff --git a/apps/streampipes/src/README.md b/archive/apps/streampipes/src/README.md similarity index 100% rename from apps/streampipes/src/README.md rename to archive/apps/streampipes/src/README.md diff --git a/apps/streampipes/variables.json b/archive/apps/streampipes/variables.json similarity index 100% rename from apps/streampipes/variables.json rename to archive/apps/streampipes/variables.json diff --git a/apps/suitecrm/.env b/archive/apps/suitecrm/.env similarity index 100% rename from apps/suitecrm/.env rename to archive/apps/suitecrm/.env diff --git a/apps/suitecrm/CHANGELOG.md b/archive/apps/suitecrm/CHANGELOG.md similarity index 100% rename from apps/suitecrm/CHANGELOG.md rename to archive/apps/suitecrm/CHANGELOG.md diff --git a/apps/suitecrm/Notes.md b/archive/apps/suitecrm/Notes.md similarity index 100% rename from apps/suitecrm/Notes.md rename to archive/apps/suitecrm/Notes.md diff --git a/apps/suitecrm/README.md b/archive/apps/suitecrm/README.md similarity index 100% rename from apps/suitecrm/README.md rename to archive/apps/suitecrm/README.md diff --git a/apps/suitecrm/docker-compose.yml b/archive/apps/suitecrm/docker-compose.yml similarity index 100% rename from apps/suitecrm/docker-compose.yml rename to archive/apps/suitecrm/docker-compose.yml diff --git a/apps/suitecrm/src/README.md b/archive/apps/suitecrm/src/README.md similarity index 100% rename from apps/suitecrm/src/README.md rename to archive/apps/suitecrm/src/README.md diff --git a/apps/suitecrm/src/nginx-proxy.conf b/archive/apps/suitecrm/src/nginx-proxy.conf similarity index 100% rename from apps/suitecrm/src/nginx-proxy.conf rename to archive/apps/suitecrm/src/nginx-proxy.conf diff --git a/apps/suitecrm/variables.json b/archive/apps/suitecrm/variables.json similarity index 100% rename from apps/suitecrm/variables.json rename to archive/apps/suitecrm/variables.json diff --git a/apps/supertokens/.env b/archive/apps/supertokens/.env similarity index 100% rename from apps/supertokens/.env rename to archive/apps/supertokens/.env diff --git a/apps/supertokens/CHANGELOG.md b/archive/apps/supertokens/CHANGELOG.md similarity index 100% rename from apps/supertokens/CHANGELOG.md rename to archive/apps/supertokens/CHANGELOG.md diff --git a/apps/supertokens/Notes.md b/archive/apps/supertokens/Notes.md similarity index 100% rename from apps/supertokens/Notes.md rename to archive/apps/supertokens/Notes.md diff --git a/apps/supertokens/README.md b/archive/apps/supertokens/README.md similarity index 100% rename from apps/supertokens/README.md rename to archive/apps/supertokens/README.md diff --git a/apps/supertokens/docker-compose.yml b/archive/apps/supertokens/docker-compose.yml similarity index 100% rename from apps/supertokens/docker-compose.yml rename to archive/apps/supertokens/docker-compose.yml diff --git a/apps/supertokens/src/README.md b/archive/apps/supertokens/src/README.md similarity index 100% rename from apps/supertokens/src/README.md rename to archive/apps/supertokens/src/README.md diff --git a/apps/supertokens/variables.json b/archive/apps/supertokens/variables.json similarity index 100% rename from apps/supertokens/variables.json rename to archive/apps/supertokens/variables.json diff --git a/archive/apps/syncthing/.env b/archive/apps/syncthing/.env new file mode 100644 index 000000000..6d6d95bcf --- /dev/null +++ b/archive/apps/syncthing/.env @@ -0,0 +1,36 @@ +W9_REPO=syncthing/syncthing +W9_DIST=community +W9_VERSION=2.1 +W9_POWER_PASSWORD='nM7xQ2pL8vR4sK6d' + +# Canonical semantics live in docs/w9-env-spec.md. +# This template controls layout; the spec controls meaning and decision rules. + +#### -- Not allowed to edit below environments when recreate app based on existing data -- #### + +W9_ID=syncthing +W9_HTTP_PORT=8384 +W9_HTTP_PORT_SET=8384 +W9_LOGIN_USER=admin +W9_LOGIN_PASSWORD=${W9_POWER_PASSWORD} +W9_URL=appname.example.com +W9_NETWORK=websoft9 + +#### ----------------------------------------------------------------------------------------- #### + +# ============================================================ +# Syncthing image environment variables +# Docs: https://docs.syncthing.net/intro/getting-started.html +# Follow docs/w9-env-spec.md when deciding which vars belong here. +# Enable the vars actually used in docker-compose.yml below; list up +# to 5 unused vars commented out. Full reference lives in the Docs URL. +# ============================================================ + +# Used by docker-compose.yml: +PUID=1000 +PGID=1000 +SYNC_PATH=/data/websoft9/syncthing +STGUIADDRESS=0.0.0.0:8384 + +# Not used by default; enable only when needed: +# STNOUPGRADE=true diff --git a/archive/apps/syncthing/CHANGELOG.md b/archive/apps/syncthing/CHANGELOG.md new file mode 100644 index 000000000..9e71fce4a --- /dev/null +++ b/archive/apps/syncthing/CHANGELOG.md @@ -0,0 +1,8 @@ +# CHANGELOG + +## 2026-09-21 + +- Update Syncthing to 2.1 +- Align `.env` and `docker-compose.yml` with current repository env and variable-reference rules +- Refresh metadata links and add app-specific test coverage +- Add first-start GUI username/password initialization from `W9_LOGIN_USER` and `W9_LOGIN_PASSWORD` diff --git a/apps/syncthing/Notes.md b/archive/apps/syncthing/Notes.md similarity index 100% rename from apps/syncthing/Notes.md rename to archive/apps/syncthing/Notes.md diff --git a/archive/apps/syncthing/README.md b/archive/apps/syncthing/README.md new file mode 100644 index 000000000..1643af5e8 --- /dev/null +++ b/archive/apps/syncthing/README.md @@ -0,0 +1,85 @@ +# Syncthing on Docker + +## Quick Start + +### Deploy Verification + +1. In the [Websoft9](https://www.websoft9.com) console, open **My Apps** and select **Syncthing**. +2. In the **Access** tab, get the login URL and credentials. +3. Open the login URL in a browser and sign in to confirm the app works. + + +### Usage + +1. Make sure you are signed in to the Syncthing admin console. +2. Try a core feature. + +### Change Password + +1. In the [Websoft9](https://www.websoft9.com) console, open the app's **Compose** tab. +2. Update the password in `.env` and save. +3. Rebuild the app. + + +## Configuration Reference + +Websoft9 packages this app from the official [Syncthing Docker image](https://hub.docker.com/r/syncthing/syncthing) and makes some improvements below. + + + + + +Apps run as containers; rebuild after any configuration change. + +### Version Support + +Supported versions: 2.1, latest. + +The `latest` tag is not guaranteed to remain valid; pin a specific version for production. + + +### Ports + +| Purpose | Port | +| --- | --- | +| Web Console | 8384 | + + +### Data Directory + + +Data is kept inside the container; a named volume is recommended for persistence. + + +### Environment Variables + +Environment variables are defined in the app's `.env` file; see the reference section at the end of `.env` for supported variables. + + +Note: `W9_LOGIN_USER`, `W9_LOGIN_PASSWORD` take effect on first startup only; changing them after deployment may not take effect until the app is re-initialized. + + +### Configuration Files + + +Configuration is overridden by mounting `./src/init-syncthing.sh` to `/websoft9/init-syncthing.sh`. + + +## References + +- [Syncthing Administrator Guide](https://support.websoft9.com/docs/syncthing) by Websoft9 + +- [Docker Hub image](https://hub.docker.com/r/syncthing/syncthing) + +- [Official docs](https://docs.syncthing.net/intro/getting-started.html) + + + +## Troubleshooting + +**App fails to start?** +- Check `docker compose logs`. + +**Port not reachable?** +- Ensure the firewall / security group allows the port. + diff --git a/archive/apps/syncthing/docker-compose.yml b/archive/apps/syncthing/docker-compose.yml new file mode 100644 index 000000000..ebba6223e --- /dev/null +++ b/archive/apps/syncthing/docker-compose.yml @@ -0,0 +1,23 @@ +services: + syncthing: + image: ${W9_REPO}:${W9_VERSION} + container_name: ${W9_ID} + hostname: syncthing + restart: unless-stopped + entrypoint: + - /bin/sh + - /websoft9/init-syncthing.sh + env_file: .env + volumes: + - ${SYNC_PATH}/${W9_ID}:/var/syncthing + - ./src/init-syncthing.sh:/websoft9/init-syncthing.sh:ro + ports: + - "${W9_HTTP_PORT_SET}:8384" # Web Console + # - 22000:22000/tcp # TCP file transfers + # - 22000:22000/udp # QUIC file transfers + # - 21027:21027/udp # Receive local discovery broadcasts + +networks: + default: + name: ${W9_NETWORK} + external: true diff --git a/archive/apps/syncthing/src/get_version.sh b/archive/apps/syncthing/src/get_version.sh new file mode 100644 index 000000000..e69de29bb diff --git a/archive/apps/syncthing/src/init-syncthing.sh b/archive/apps/syncthing/src/init-syncthing.sh new file mode 100755 index 000000000..c3e040ba0 --- /dev/null +++ b/archive/apps/syncthing/src/init-syncthing.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +set -eu + +CONFIG_DIR="${STHOMEDIR:-/var/syncthing/config}" +DATA_DIR="${HOME:-/var/syncthing}" +CONFIG_FILE="${CONFIG_DIR}/config.xml" + +fix_ownership() { + if [ "$(id -u)" = "0" ]; then + chown -R "${PUID:-1000}:${PGID:-1000}" "${DATA_DIR}" || true + fi +} + +if [ ! -f "${CONFIG_FILE}" ] && [ -n "${W9_LOGIN_USER:-}" ] && [ -n "${W9_LOGIN_PASSWORD:-}" ]; then + mkdir -p "${CONFIG_DIR}" + syncthing generate \ + --home="${CONFIG_DIR}" \ + --gui-user="${W9_LOGIN_USER}" \ + --gui-password="${W9_LOGIN_PASSWORD}" \ + --no-port-probing >/dev/null +fi + +fix_ownership + +exec /bin/entrypoint.sh /bin/syncthing diff --git a/archive/apps/syncthing/tests/cases.yml b/archive/apps/syncthing/tests/cases.yml new file mode 100644 index 000000000..a24753cc3 --- /dev/null +++ b/archive/apps/syncthing/tests/cases.yml @@ -0,0 +1,7 @@ +skip: + - id: web-access + +custom: + - id: gui-auth + type: script + script: check.sh diff --git a/archive/apps/syncthing/tests/check.sh b/archive/apps/syncthing/tests/check.sh new file mode 100755 index 000000000..f248ee749 --- /dev/null +++ b/archive/apps/syncthing/tests/check.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +deadline=$((SECONDS + 120)) +config_file="/var/syncthing/config/config.xml" +status="pending" + +while [ "$SECONDS" -lt "$deadline" ]; do + if docker exec syncthing sh -c "grep -q '${W9_LOGIN_USER}' ${config_file} && grep -q '\\$2' ${config_file}" >/dev/null 2>&1; then + status="ok" + break + fi + sleep 3 +done + +if [ "${status}" != "ok" ]; then + echo "Syncthing GUI credentials were not written to ${config_file} during first startup" >&2 + exit 1 +fi + +echo "Syncthing GUI credentials were written to ${config_file} on first startup" diff --git a/archive/apps/syncthing/variables.json b/archive/apps/syncthing/variables.json new file mode 100644 index 000000000..cf846abe1 --- /dev/null +++ b/archive/apps/syncthing/variables.json @@ -0,0 +1,32 @@ +{ + "name": "syncthing", + "trademark": "Syncthing", + "release": true, + "edition": [ + { + "dist": "community", + "version": [ + "2.1", + "latest" + ] + } + ], + "requirements": { + "cpu": "1", + "memory": "2", + "disk": "2" + }, + "env": { + "first_startup_only": [ + "W9_LOGIN_USER", + "W9_LOGIN_PASSWORD" + ] + }, + "upstream": { + "image": "https://hub.docker.com/r/syncthing/syncthing", + "docs": [ + "https://docs.syncthing.net/intro/getting-started.html" + ], + "version_notes": "https://github.com/syncthing/syncthing/releases/tag/v2.1.5" + } +} diff --git a/apps/taskingai/.env b/archive/apps/taskingai/.env similarity index 100% rename from apps/taskingai/.env rename to archive/apps/taskingai/.env diff --git a/apps/syncthing/CHANGELOG.md b/archive/apps/taskingai/CHANGELOG.md similarity index 100% rename from apps/syncthing/CHANGELOG.md rename to archive/apps/taskingai/CHANGELOG.md diff --git a/apps/taskingai/Notes.md b/archive/apps/taskingai/Notes.md similarity index 100% rename from apps/taskingai/Notes.md rename to archive/apps/taskingai/Notes.md diff --git a/apps/taskingai/README.md b/archive/apps/taskingai/README.md similarity index 100% rename from apps/taskingai/README.md rename to archive/apps/taskingai/README.md diff --git a/apps/taskingai/docker-compose.yml b/archive/apps/taskingai/docker-compose.yml similarity index 100% rename from apps/taskingai/docker-compose.yml rename to archive/apps/taskingai/docker-compose.yml diff --git a/apps/taskingai/src/README.md b/archive/apps/taskingai/src/README.md similarity index 100% rename from apps/taskingai/src/README.md rename to archive/apps/taskingai/src/README.md diff --git a/apps/taskingai/src/proxy.conf b/archive/apps/taskingai/src/proxy.conf similarity index 100% rename from apps/taskingai/src/proxy.conf rename to archive/apps/taskingai/src/proxy.conf diff --git a/apps/taskingai/variables.json b/archive/apps/taskingai/variables.json similarity index 100% rename from apps/taskingai/variables.json rename to archive/apps/taskingai/variables.json diff --git a/apps/theia/.env b/archive/apps/theia/.env similarity index 100% rename from apps/theia/.env rename to archive/apps/theia/.env diff --git a/apps/taskingai/CHANGELOG.md b/archive/apps/theia/CHANGELOG.md similarity index 100% rename from apps/taskingai/CHANGELOG.md rename to archive/apps/theia/CHANGELOG.md diff --git a/apps/theia/Notes.md b/archive/apps/theia/Notes.md similarity index 100% rename from apps/theia/Notes.md rename to archive/apps/theia/Notes.md diff --git a/apps/theia/README.md b/archive/apps/theia/README.md similarity index 100% rename from apps/theia/README.md rename to archive/apps/theia/README.md diff --git a/apps/theia/docker-compose.yml b/archive/apps/theia/docker-compose.yml similarity index 100% rename from apps/theia/docker-compose.yml rename to archive/apps/theia/docker-compose.yml diff --git a/apps/seafile/src/filelist b/archive/apps/theia/src/filelist similarity index 100% rename from apps/seafile/src/filelist rename to archive/apps/theia/src/filelist diff --git a/apps/theia/variables.json b/archive/apps/theia/variables.json similarity index 100% rename from apps/theia/variables.json rename to archive/apps/theia/variables.json diff --git a/apps/tinyproxy/.env b/archive/apps/tinyproxy/.env similarity index 100% rename from apps/tinyproxy/.env rename to archive/apps/tinyproxy/.env diff --git a/apps/theia/CHANGELOG.md b/archive/apps/tinyproxy/CHANGELOG.md similarity index 100% rename from apps/theia/CHANGELOG.md rename to archive/apps/tinyproxy/CHANGELOG.md diff --git a/apps/tinyproxy/Notes.md b/archive/apps/tinyproxy/Notes.md similarity index 100% rename from apps/tinyproxy/Notes.md rename to archive/apps/tinyproxy/Notes.md diff --git a/apps/tinyproxy/README.md b/archive/apps/tinyproxy/README.md similarity index 100% rename from apps/tinyproxy/README.md rename to archive/apps/tinyproxy/README.md diff --git a/apps/tinyproxy/docker-compose.yml b/archive/apps/tinyproxy/docker-compose.yml similarity index 100% rename from apps/tinyproxy/docker-compose.yml rename to archive/apps/tinyproxy/docker-compose.yml diff --git a/apps/tinyproxy/src/README.md b/archive/apps/tinyproxy/src/README.md similarity index 100% rename from apps/tinyproxy/src/README.md rename to archive/apps/tinyproxy/src/README.md diff --git a/apps/tinyproxy/src/tinyproxy.conf b/archive/apps/tinyproxy/src/tinyproxy.conf similarity index 100% rename from apps/tinyproxy/src/tinyproxy.conf rename to archive/apps/tinyproxy/src/tinyproxy.conf diff --git a/apps/tinyproxy/variables.json b/archive/apps/tinyproxy/variables.json similarity index 100% rename from apps/tinyproxy/variables.json rename to archive/apps/tinyproxy/variables.json diff --git a/apps/tomee/.env b/archive/apps/tomee/.env similarity index 100% rename from apps/tomee/.env rename to archive/apps/tomee/.env diff --git a/apps/tinyproxy/CHANGELOG.md b/archive/apps/tomee/CHANGELOG.md similarity index 100% rename from apps/tinyproxy/CHANGELOG.md rename to archive/apps/tomee/CHANGELOG.md diff --git a/apps/tomee/Notes.md b/archive/apps/tomee/Notes.md similarity index 100% rename from apps/tomee/Notes.md rename to archive/apps/tomee/Notes.md diff --git a/apps/tomee/README.md b/archive/apps/tomee/README.md similarity index 100% rename from apps/tomee/README.md rename to archive/apps/tomee/README.md diff --git a/apps/tomee/docker-compose.yml b/archive/apps/tomee/docker-compose.yml similarity index 100% rename from apps/tomee/docker-compose.yml rename to archive/apps/tomee/docker-compose.yml diff --git a/apps/tomee/src/README.md b/archive/apps/tomee/src/README.md similarity index 100% rename from apps/tomee/src/README.md rename to archive/apps/tomee/src/README.md diff --git a/apps/tomee/src/cmd.sh b/archive/apps/tomee/src/cmd.sh similarity index 100% rename from apps/tomee/src/cmd.sh rename to archive/apps/tomee/src/cmd.sh diff --git a/apps/tomee/variables.json b/archive/apps/tomee/variables.json similarity index 100% rename from apps/tomee/variables.json rename to archive/apps/tomee/variables.json diff --git a/apps/tooljet/.env b/archive/apps/tooljet/.env similarity index 100% rename from apps/tooljet/.env rename to archive/apps/tooljet/.env diff --git a/apps/tooljet/CHANGELOG.md b/archive/apps/tooljet/CHANGELOG.md similarity index 100% rename from apps/tooljet/CHANGELOG.md rename to archive/apps/tooljet/CHANGELOG.md diff --git a/apps/tooljet/README.md b/archive/apps/tooljet/README.md similarity index 100% rename from apps/tooljet/README.md rename to archive/apps/tooljet/README.md diff --git a/apps/tooljet/docker-compose.yml b/archive/apps/tooljet/docker-compose.yml similarity index 100% rename from apps/tooljet/docker-compose.yml rename to archive/apps/tooljet/docker-compose.yml diff --git a/apps/tooljet/src/README.md b/archive/apps/tooljet/src/README.md similarity index 100% rename from apps/tooljet/src/README.md rename to archive/apps/tooljet/src/README.md diff --git a/apps/tooljet/src/nginx-proxy.conf b/archive/apps/tooljet/src/nginx-proxy.conf similarity index 100% rename from apps/tooljet/src/nginx-proxy.conf rename to archive/apps/tooljet/src/nginx-proxy.conf diff --git a/apps/tooljet/tests/cases.yml b/archive/apps/tooljet/tests/cases.yml similarity index 100% rename from apps/tooljet/tests/cases.yml rename to archive/apps/tooljet/tests/cases.yml diff --git a/apps/tooljet/variables.json b/archive/apps/tooljet/variables.json similarity index 100% rename from apps/tooljet/variables.json rename to archive/apps/tooljet/variables.json diff --git a/apps/traccar/.env b/archive/apps/traccar/.env similarity index 100% rename from apps/traccar/.env rename to archive/apps/traccar/.env diff --git a/apps/tomee/CHANGELOG.md b/archive/apps/traccar/CHANGELOG.md similarity index 100% rename from apps/tomee/CHANGELOG.md rename to archive/apps/traccar/CHANGELOG.md diff --git a/apps/traccar/Notes.md b/archive/apps/traccar/Notes.md similarity index 100% rename from apps/traccar/Notes.md rename to archive/apps/traccar/Notes.md diff --git a/apps/traccar/README.md b/archive/apps/traccar/README.md similarity index 100% rename from apps/traccar/README.md rename to archive/apps/traccar/README.md diff --git a/apps/traccar/docker-compose.yml b/archive/apps/traccar/docker-compose.yml similarity index 100% rename from apps/traccar/docker-compose.yml rename to archive/apps/traccar/docker-compose.yml diff --git a/apps/traccar/src/README.md b/archive/apps/traccar/src/README.md similarity index 100% rename from apps/traccar/src/README.md rename to archive/apps/traccar/src/README.md diff --git a/apps/traccar/variables.json b/archive/apps/traccar/variables.json similarity index 100% rename from apps/traccar/variables.json rename to archive/apps/traccar/variables.json diff --git a/apps/trafficserver/.env b/archive/apps/trafficserver/.env similarity index 100% rename from apps/trafficserver/.env rename to archive/apps/trafficserver/.env diff --git a/apps/traccar/CHANGELOG.md b/archive/apps/trafficserver/CHANGELOG.md similarity index 100% rename from apps/traccar/CHANGELOG.md rename to archive/apps/trafficserver/CHANGELOG.md diff --git a/apps/trafficserver/Notes.md b/archive/apps/trafficserver/Notes.md similarity index 100% rename from apps/trafficserver/Notes.md rename to archive/apps/trafficserver/Notes.md diff --git a/apps/trafficserver/README.md b/archive/apps/trafficserver/README.md similarity index 100% rename from apps/trafficserver/README.md rename to archive/apps/trafficserver/README.md diff --git a/apps/trafficserver/docker-compose.yml b/archive/apps/trafficserver/docker-compose.yml similarity index 100% rename from apps/trafficserver/docker-compose.yml rename to archive/apps/trafficserver/docker-compose.yml diff --git a/apps/trafficserver/src/README.md b/archive/apps/trafficserver/src/README.md similarity index 100% rename from apps/trafficserver/src/README.md rename to archive/apps/trafficserver/src/README.md diff --git a/apps/trafficserver/src/records.yaml b/archive/apps/trafficserver/src/records.yaml similarity index 100% rename from apps/trafficserver/src/records.yaml rename to archive/apps/trafficserver/src/records.yaml diff --git a/apps/trafficserver/src/remap.config b/archive/apps/trafficserver/src/remap.config similarity index 100% rename from apps/trafficserver/src/remap.config rename to archive/apps/trafficserver/src/remap.config diff --git a/apps/trafficserver/src/storage.config b/archive/apps/trafficserver/src/storage.config similarity index 100% rename from apps/trafficserver/src/storage.config rename to archive/apps/trafficserver/src/storage.config diff --git a/apps/trafficserver/variables.json b/archive/apps/trafficserver/variables.json similarity index 100% rename from apps/trafficserver/variables.json rename to archive/apps/trafficserver/variables.json diff --git a/apps/triggerdev/.env b/archive/apps/triggerdev/.env similarity index 100% rename from apps/triggerdev/.env rename to archive/apps/triggerdev/.env diff --git a/apps/trafficserver/CHANGELOG.md b/archive/apps/triggerdev/CHANGELOG.md similarity index 100% rename from apps/trafficserver/CHANGELOG.md rename to archive/apps/triggerdev/CHANGELOG.md diff --git a/apps/triggerdev/Notes.md b/archive/apps/triggerdev/Notes.md similarity index 100% rename from apps/triggerdev/Notes.md rename to archive/apps/triggerdev/Notes.md diff --git a/apps/triggerdev/docker-compose.yml b/archive/apps/triggerdev/docker-compose.yml similarity index 100% rename from apps/triggerdev/docker-compose.yml rename to archive/apps/triggerdev/docker-compose.yml diff --git a/apps/triggerdev/src/README.md b/archive/apps/triggerdev/src/README.md similarity index 100% rename from apps/triggerdev/src/README.md rename to archive/apps/triggerdev/src/README.md diff --git a/apps/triggerdev/variables.json b/archive/apps/triggerdev/variables.json similarity index 100% rename from apps/triggerdev/variables.json rename to archive/apps/triggerdev/variables.json diff --git a/apps/ttrss/.env b/archive/apps/ttrss/.env similarity index 100% rename from apps/ttrss/.env rename to archive/apps/ttrss/.env diff --git a/apps/ttrss/CHANGELOG.md b/archive/apps/ttrss/CHANGELOG.md similarity index 100% rename from apps/ttrss/CHANGELOG.md rename to archive/apps/ttrss/CHANGELOG.md diff --git a/apps/ttrss/Notes.md b/archive/apps/ttrss/Notes.md similarity index 100% rename from apps/ttrss/Notes.md rename to archive/apps/ttrss/Notes.md diff --git a/apps/ttrss/README.md b/archive/apps/ttrss/README.md similarity index 100% rename from apps/ttrss/README.md rename to archive/apps/ttrss/README.md diff --git a/apps/ttrss/docker-compose.yml b/archive/apps/ttrss/docker-compose.yml similarity index 100% rename from apps/ttrss/docker-compose.yml rename to archive/apps/ttrss/docker-compose.yml diff --git a/apps/ttrss/src/README.md b/archive/apps/ttrss/src/README.md similarity index 100% rename from apps/ttrss/src/README.md rename to archive/apps/ttrss/src/README.md diff --git a/apps/ttrss/variables.json b/archive/apps/ttrss/variables.json similarity index 100% rename from apps/ttrss/variables.json rename to archive/apps/ttrss/variables.json diff --git a/apps/twenty/.env b/archive/apps/twenty/.env similarity index 100% rename from apps/twenty/.env rename to archive/apps/twenty/.env diff --git a/apps/triggerdev/CHANGELOG.md b/archive/apps/twenty/CHANGELOG.md similarity index 100% rename from apps/triggerdev/CHANGELOG.md rename to archive/apps/twenty/CHANGELOG.md diff --git a/apps/twenty/Notes.md b/archive/apps/twenty/Notes.md similarity index 100% rename from apps/twenty/Notes.md rename to archive/apps/twenty/Notes.md diff --git a/apps/twenty/README.md b/archive/apps/twenty/README.md similarity index 100% rename from apps/twenty/README.md rename to archive/apps/twenty/README.md diff --git a/apps/twenty/docker-compose.yml b/archive/apps/twenty/docker-compose.yml similarity index 100% rename from apps/twenty/docker-compose.yml rename to archive/apps/twenty/docker-compose.yml diff --git a/apps/twenty/src/README.md b/archive/apps/twenty/src/README.md similarity index 100% rename from apps/twenty/src/README.md rename to archive/apps/twenty/src/README.md diff --git a/apps/twenty/variables.json b/archive/apps/twenty/variables.json similarity index 100% rename from apps/twenty/variables.json rename to archive/apps/twenty/variables.json diff --git a/apps/tyk/.env b/archive/apps/tyk/.env similarity index 100% rename from apps/tyk/.env rename to archive/apps/tyk/.env diff --git a/apps/twenty/CHANGELOG.md b/archive/apps/tyk/CHANGELOG.md similarity index 100% rename from apps/twenty/CHANGELOG.md rename to archive/apps/tyk/CHANGELOG.md diff --git a/apps/tyk/Notes.md b/archive/apps/tyk/Notes.md similarity index 100% rename from apps/tyk/Notes.md rename to archive/apps/tyk/Notes.md diff --git a/apps/tyk/README.md b/archive/apps/tyk/README.md similarity index 100% rename from apps/tyk/README.md rename to archive/apps/tyk/README.md diff --git a/apps/tyk/docker-compose.yml b/archive/apps/tyk/docker-compose.yml similarity index 100% rename from apps/tyk/docker-compose.yml rename to archive/apps/tyk/docker-compose.yml diff --git a/apps/tyk/src/README.md b/archive/apps/tyk/src/README.md similarity index 100% rename from apps/tyk/src/README.md rename to archive/apps/tyk/src/README.md diff --git a/apps/tyk/src/tyk.conf b/archive/apps/tyk/src/tyk.conf similarity index 100% rename from apps/tyk/src/tyk.conf rename to archive/apps/tyk/src/tyk.conf diff --git a/apps/tyk/variables.json b/archive/apps/tyk/variables.json similarity index 100% rename from apps/tyk/variables.json rename to archive/apps/tyk/variables.json diff --git a/apps/unleash/.env b/archive/apps/unleash/.env similarity index 100% rename from apps/unleash/.env rename to archive/apps/unleash/.env diff --git a/apps/tyk/CHANGELOG.md b/archive/apps/unleash/CHANGELOG.md similarity index 100% rename from apps/tyk/CHANGELOG.md rename to archive/apps/unleash/CHANGELOG.md diff --git a/apps/unleash/Notes.md b/archive/apps/unleash/Notes.md similarity index 100% rename from apps/unleash/Notes.md rename to archive/apps/unleash/Notes.md diff --git a/apps/unleash/README.md b/archive/apps/unleash/README.md similarity index 100% rename from apps/unleash/README.md rename to archive/apps/unleash/README.md diff --git a/apps/unleash/docker-compose.yml b/archive/apps/unleash/docker-compose.yml similarity index 100% rename from apps/unleash/docker-compose.yml rename to archive/apps/unleash/docker-compose.yml diff --git a/apps/unleash/src/README.md b/archive/apps/unleash/src/README.md similarity index 100% rename from apps/unleash/src/README.md rename to archive/apps/unleash/src/README.md diff --git a/apps/unleash/variables.json b/archive/apps/unleash/variables.json similarity index 100% rename from apps/unleash/variables.json rename to archive/apps/unleash/variables.json diff --git a/apps/v2ray/.env b/archive/apps/v2ray/.env similarity index 100% rename from apps/v2ray/.env rename to archive/apps/v2ray/.env diff --git a/apps/unleash/CHANGELOG.md b/archive/apps/v2ray/CHANGELOG.md similarity index 100% rename from apps/unleash/CHANGELOG.md rename to archive/apps/v2ray/CHANGELOG.md diff --git a/apps/v2ray/Notes.md b/archive/apps/v2ray/Notes.md similarity index 100% rename from apps/v2ray/Notes.md rename to archive/apps/v2ray/Notes.md diff --git a/apps/v2ray/README.md b/archive/apps/v2ray/README.md similarity index 100% rename from apps/v2ray/README.md rename to archive/apps/v2ray/README.md diff --git a/apps/v2ray/docker-compose.yml b/archive/apps/v2ray/docker-compose.yml similarity index 100% rename from apps/v2ray/docker-compose.yml rename to archive/apps/v2ray/docker-compose.yml diff --git a/apps/v2ray/src/README.md b/archive/apps/v2ray/src/README.md similarity index 100% rename from apps/v2ray/src/README.md rename to archive/apps/v2ray/src/README.md diff --git a/apps/v2ray/src/config.json b/archive/apps/v2ray/src/config.json similarity index 100% rename from apps/v2ray/src/config.json rename to archive/apps/v2ray/src/config.json diff --git a/apps/v2ray/variables.json b/archive/apps/v2ray/variables.json similarity index 100% rename from apps/v2ray/variables.json rename to archive/apps/v2ray/variables.json diff --git a/apps/webcheck/.env b/archive/apps/webcheck/.env similarity index 100% rename from apps/webcheck/.env rename to archive/apps/webcheck/.env diff --git a/apps/v2ray/CHANGELOG.md b/archive/apps/webcheck/CHANGELOG.md similarity index 100% rename from apps/v2ray/CHANGELOG.md rename to archive/apps/webcheck/CHANGELOG.md diff --git a/apps/webcheck/Notes.md b/archive/apps/webcheck/Notes.md similarity index 100% rename from apps/webcheck/Notes.md rename to archive/apps/webcheck/Notes.md diff --git a/apps/webcheck/README.md b/archive/apps/webcheck/README.md similarity index 100% rename from apps/webcheck/README.md rename to archive/apps/webcheck/README.md diff --git a/apps/webcheck/docker-compose.yml b/archive/apps/webcheck/docker-compose.yml similarity index 100% rename from apps/webcheck/docker-compose.yml rename to archive/apps/webcheck/docker-compose.yml diff --git a/apps/webcheck/src/README.md b/archive/apps/webcheck/src/README.md similarity index 100% rename from apps/webcheck/src/README.md rename to archive/apps/webcheck/src/README.md diff --git a/apps/wazuh/src/nginx-proxy.conf.template b/archive/apps/webcheck/src/nginx-proxy.conf.template similarity index 100% rename from apps/wazuh/src/nginx-proxy.conf.template rename to archive/apps/webcheck/src/nginx-proxy.conf.template diff --git a/apps/wazuh/src/php_exra.ini b/archive/apps/webcheck/src/php_exra.ini similarity index 100% rename from apps/wazuh/src/php_exra.ini rename to archive/apps/webcheck/src/php_exra.ini diff --git a/apps/webcheck/variables.json b/archive/apps/webcheck/variables.json similarity index 100% rename from apps/webcheck/variables.json rename to archive/apps/webcheck/variables.json diff --git a/apps/wikijs/.env b/archive/apps/wikijs/.env similarity index 100% rename from apps/wikijs/.env rename to archive/apps/wikijs/.env diff --git a/apps/webcheck/CHANGELOG.md b/archive/apps/wikijs/CHANGELOG.md similarity index 100% rename from apps/webcheck/CHANGELOG.md rename to archive/apps/wikijs/CHANGELOG.md diff --git a/apps/wikijs/Notes.md b/archive/apps/wikijs/Notes.md similarity index 100% rename from apps/wikijs/Notes.md rename to archive/apps/wikijs/Notes.md diff --git a/apps/wikijs/README.md b/archive/apps/wikijs/README.md similarity index 100% rename from apps/wikijs/README.md rename to archive/apps/wikijs/README.md diff --git a/apps/wikijs/docker-compose.yml b/archive/apps/wikijs/docker-compose.yml similarity index 100% rename from apps/wikijs/docker-compose.yml rename to archive/apps/wikijs/docker-compose.yml diff --git a/apps/wikijs/src/README.md b/archive/apps/wikijs/src/README.md similarity index 100% rename from apps/wikijs/src/README.md rename to archive/apps/wikijs/src/README.md diff --git a/apps/wikijs/variables.json b/archive/apps/wikijs/variables.json similarity index 100% rename from apps/wikijs/variables.json rename to archive/apps/wikijs/variables.json diff --git a/apps/windmill/.env b/archive/apps/windmill/.env similarity index 100% rename from apps/windmill/.env rename to archive/apps/windmill/.env diff --git a/apps/wikijs/CHANGELOG.md b/archive/apps/windmill/CHANGELOG.md similarity index 100% rename from apps/wikijs/CHANGELOG.md rename to archive/apps/windmill/CHANGELOG.md diff --git a/apps/windmill/Notes.md b/archive/apps/windmill/Notes.md similarity index 100% rename from apps/windmill/Notes.md rename to archive/apps/windmill/Notes.md diff --git a/apps/windmill/README.md b/archive/apps/windmill/README.md similarity index 100% rename from apps/windmill/README.md rename to archive/apps/windmill/README.md diff --git a/apps/windmill/docker-compose.yml b/archive/apps/windmill/docker-compose.yml similarity index 100% rename from apps/windmill/docker-compose.yml rename to archive/apps/windmill/docker-compose.yml diff --git a/apps/windmill/src/Caddyfile b/archive/apps/windmill/src/Caddyfile similarity index 100% rename from apps/windmill/src/Caddyfile rename to archive/apps/windmill/src/Caddyfile diff --git a/apps/windmill/src/README.md b/archive/apps/windmill/src/README.md similarity index 100% rename from apps/windmill/src/README.md rename to archive/apps/windmill/src/README.md diff --git a/apps/windmill/variables.json b/archive/apps/windmill/variables.json similarity index 100% rename from apps/windmill/variables.json rename to archive/apps/windmill/variables.json diff --git a/apps/wireguard/.env b/archive/apps/wireguard/.env similarity index 100% rename from apps/wireguard/.env rename to archive/apps/wireguard/.env diff --git a/apps/windmill/CHANGELOG.md b/archive/apps/wireguard/CHANGELOG.md similarity index 100% rename from apps/windmill/CHANGELOG.md rename to archive/apps/wireguard/CHANGELOG.md diff --git a/apps/wireguard/Notes.md b/archive/apps/wireguard/Notes.md similarity index 100% rename from apps/wireguard/Notes.md rename to archive/apps/wireguard/Notes.md diff --git a/apps/wireguard/README.md b/archive/apps/wireguard/README.md similarity index 100% rename from apps/wireguard/README.md rename to archive/apps/wireguard/README.md diff --git a/apps/wireguard/docker-compose.yml b/archive/apps/wireguard/docker-compose.yml similarity index 100% rename from apps/wireguard/docker-compose.yml rename to archive/apps/wireguard/docker-compose.yml diff --git a/apps/wireguard/getkeys.sh b/archive/apps/wireguard/getkeys.sh similarity index 100% rename from apps/wireguard/getkeys.sh rename to archive/apps/wireguard/getkeys.sh diff --git a/apps/wireguard/src/README.md b/archive/apps/wireguard/src/README.md similarity index 100% rename from apps/wireguard/src/README.md rename to archive/apps/wireguard/src/README.md diff --git a/apps/wireguard/src/nginx_proxy.conf b/archive/apps/wireguard/src/nginx_proxy.conf similarity index 100% rename from apps/wireguard/src/nginx_proxy.conf rename to archive/apps/wireguard/src/nginx_proxy.conf diff --git a/apps/wireguard/variables.json b/archive/apps/wireguard/variables.json similarity index 100% rename from apps/wireguard/variables.json rename to archive/apps/wireguard/variables.json diff --git a/apps/zabbix/.env b/archive/apps/zabbix/.env similarity index 100% rename from apps/zabbix/.env rename to archive/apps/zabbix/.env diff --git a/apps/wireguard/CHANGELOG.md b/archive/apps/zabbix/CHANGELOG.md similarity index 100% rename from apps/wireguard/CHANGELOG.md rename to archive/apps/zabbix/CHANGELOG.md diff --git a/apps/zabbix/Notes.md b/archive/apps/zabbix/Notes.md similarity index 100% rename from apps/zabbix/Notes.md rename to archive/apps/zabbix/Notes.md diff --git a/apps/zabbix/README.md b/archive/apps/zabbix/README.md similarity index 100% rename from apps/zabbix/README.md rename to archive/apps/zabbix/README.md diff --git a/apps/zabbix/docker-compose.yml b/archive/apps/zabbix/docker-compose.yml similarity index 100% rename from apps/zabbix/docker-compose.yml rename to archive/apps/zabbix/docker-compose.yml diff --git a/apps/zabbix/src/README.md b/archive/apps/zabbix/src/README.md similarity index 100% rename from apps/zabbix/src/README.md rename to archive/apps/zabbix/src/README.md diff --git a/apps/zabbix/variables.json b/archive/apps/zabbix/variables.json similarity index 100% rename from apps/zabbix/variables.json rename to archive/apps/zabbix/variables.json diff --git a/apps/zerotier/.env b/archive/apps/zerotier/.env similarity index 100% rename from apps/zerotier/.env rename to archive/apps/zerotier/.env diff --git a/apps/zabbix/CHANGELOG.md b/archive/apps/zerotier/CHANGELOG.md similarity index 100% rename from apps/zabbix/CHANGELOG.md rename to archive/apps/zerotier/CHANGELOG.md diff --git a/apps/zerotier/Notes.md b/archive/apps/zerotier/Notes.md similarity index 100% rename from apps/zerotier/Notes.md rename to archive/apps/zerotier/Notes.md diff --git a/apps/zerotier/README.md b/archive/apps/zerotier/README.md similarity index 100% rename from apps/zerotier/README.md rename to archive/apps/zerotier/README.md diff --git a/apps/zerotier/docker-compose.yml b/archive/apps/zerotier/docker-compose.yml similarity index 100% rename from apps/zerotier/docker-compose.yml rename to archive/apps/zerotier/docker-compose.yml diff --git a/apps/zerotier/src/README.md b/archive/apps/zerotier/src/README.md similarity index 100% rename from apps/zerotier/src/README.md rename to archive/apps/zerotier/src/README.md diff --git a/apps/zerotier/variables.json b/archive/apps/zerotier/variables.json similarity index 100% rename from apps/zerotier/variables.json rename to archive/apps/zerotier/variables.json diff --git a/apps/zitadel/.env b/archive/apps/zitadel/.env similarity index 100% rename from apps/zitadel/.env rename to archive/apps/zitadel/.env diff --git a/apps/zerotier/CHANGELOG.md b/archive/apps/zitadel/CHANGELOG.md similarity index 100% rename from apps/zerotier/CHANGELOG.md rename to archive/apps/zitadel/CHANGELOG.md diff --git a/apps/zitadel/Notes.md b/archive/apps/zitadel/Notes.md similarity index 100% rename from apps/zitadel/Notes.md rename to archive/apps/zitadel/Notes.md diff --git a/apps/zitadel/README.md b/archive/apps/zitadel/README.md similarity index 100% rename from apps/zitadel/README.md rename to archive/apps/zitadel/README.md diff --git a/apps/zitadel/docker-compose.yml b/archive/apps/zitadel/docker-compose.yml similarity index 100% rename from apps/zitadel/docker-compose.yml rename to archive/apps/zitadel/docker-compose.yml diff --git a/apps/zitadel/src/README.md b/archive/apps/zitadel/src/README.md similarity index 100% rename from apps/zitadel/src/README.md rename to archive/apps/zitadel/src/README.md diff --git a/apps/zitadel/variables.json b/archive/apps/zitadel/variables.json similarity index 100% rename from apps/zitadel/variables.json rename to archive/apps/zitadel/variables.json diff --git a/apps/zookeeper/.env b/archive/apps/zookeeper/.env similarity index 100% rename from apps/zookeeper/.env rename to archive/apps/zookeeper/.env diff --git a/apps/zitadel/CHANGELOG.md b/archive/apps/zookeeper/CHANGELOG.md similarity index 100% rename from apps/zitadel/CHANGELOG.md rename to archive/apps/zookeeper/CHANGELOG.md diff --git a/apps/zookeeper/Notes.md b/archive/apps/zookeeper/Notes.md similarity index 100% rename from apps/zookeeper/Notes.md rename to archive/apps/zookeeper/Notes.md diff --git a/apps/zookeeper/README.md b/archive/apps/zookeeper/README.md similarity index 100% rename from apps/zookeeper/README.md rename to archive/apps/zookeeper/README.md diff --git a/apps/zookeeper/docker-compose.yml b/archive/apps/zookeeper/docker-compose.yml similarity index 100% rename from apps/zookeeper/docker-compose.yml rename to archive/apps/zookeeper/docker-compose.yml diff --git a/apps/zookeeper/src/README.md b/archive/apps/zookeeper/src/README.md similarity index 100% rename from apps/zookeeper/src/README.md rename to archive/apps/zookeeper/src/README.md diff --git a/apps/zookeeper/variables.json b/archive/apps/zookeeper/variables.json similarity index 100% rename from apps/zookeeper/variables.json rename to archive/apps/zookeeper/variables.json diff --git a/build/__pycache__/fetch_catalog.cpython-312.pyc b/build/__pycache__/fetch_catalog.cpython-312.pyc index a9191b20e..b6b5d7ea6 100644 Binary files a/build/__pycache__/fetch_catalog.cpython-312.pyc and b/build/__pycache__/fetch_catalog.cpython-312.pyc differ diff --git a/build/__pycache__/library_publish.cpython-312.pyc b/build/__pycache__/library_publish.cpython-312.pyc index 689f73a28..e3a08a5b4 100644 Binary files a/build/__pycache__/library_publish.cpython-312.pyc and b/build/__pycache__/library_publish.cpython-312.pyc differ diff --git a/build/fetch_catalog.py b/build/fetch_catalog.py index 99ea8639d..76fe99ebf 100644 --- a/build/fetch_catalog.py +++ b/build/fetch_catalog.py @@ -16,6 +16,12 @@ "en-US": "en", "zh-CN": "zh", } +# Fallback logos used when a Contentful product entry has no logo, so consumers +# do not reject the whole catalog for incomplete data. Keyed by locale short code. +DEFAULT_LOGO_URLS = { + "en": "https://libs.websoft9.com/Websoft9/logo/product/websoft9-en.png", + "zh": "https://libs.websoft9.com/Websoft9/logo/product/websoft9.png", +} CATALOG_QUERY = """ query($locale: String!) { catalog(id: \"2Yp0TY3kBHgG6VDjsHZNpK\", locale: $locale) { @@ -55,6 +61,7 @@ description screenshots distribution + production vcpu memory storage @@ -120,6 +127,15 @@ def fetch_catalog_entries(token: str, locale: str) -> list[dict]: return collection.get("items") or [] +def apply_default_logo(entry: dict, locale_code: str) -> dict: + """Return the entry with a usable logo URL, filling in a locale default when absent.""" + logo = entry.get("logo") or {} + if logo.get("imageurl"): + return entry + fallback = DEFAULT_LOGO_URLS.get(locale_code) or DEFAULT_LOGO_URLS["en"] + return {**entry, "logo": {"imageurl": fallback}} + + def fetch_product_entries(token: str, locale: str, production: bool | None) -> list[dict]: items: list[dict] = [] skip = 0 @@ -140,7 +156,9 @@ def fetch_product_entries(token: str, locale: str, production: bool | None) -> l duplicate_ids = sorted({entry_id for entry_id in entry_ids if entry_id and entry_ids.count(entry_id) > 1}) if duplicate_ids: raise SystemExit(f"duplicate Contentful product sys.id values: {', '.join(duplicate_ids)}") - return items + + locale_code = LOCALES.get(locale, "en") + return [apply_default_logo(entry, locale_code) for entry in items] def write_json(path: Path, payload: list[dict]) -> None: diff --git a/build/library_publish.py b/build/library_publish.py index 4962417b0..1040f491d 100644 --- a/build/library_publish.py +++ b/build/library_publish.py @@ -144,6 +144,30 @@ def write_checksum_file(path: Path) -> str: return checksum_path.name +def to_utc_z(value: str) -> str: + """Normalize an ISO 8601 timestamp to UTC with a Z suffix.""" + try: + parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return value + if parsed.tzinfo is None: + parsed = parsed.replace(tzinfo=timezone.utc) + return parsed.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + + +def compute_catalog_dataset_version(catalog_dir: Path) -> str: + """Derive catalog datasetVersion from the actual content of the catalog files. + + Must hash file contents, not checksum file names, otherwise the version is a + constant and consumers can never detect catalog changes. + """ + parts = [ + f"{file_name}={sha256_file(catalog_dir / file_name)}" + for file_name in CATALOG_FILE_NAMES + ] + return _hash_content(",".join(parts)) + + def create_zip_from_directory(source_dir: Path, destination_zip: Path) -> None: with ZipFile(destination_zip, "w", compression=ZIP_DEFLATED) as archive: for path in sorted(path for path in source_dir.rglob("*") if path.is_file()): @@ -194,7 +218,38 @@ def build_app_checksum_entry(app_name: str) -> dict: return {"latest": f"apps/{app_name}/{APP_PACKAGE_NAME}.sha256"} -def build_apps_index(dataset_version: str, channel: str, generated_at: str) -> dict: +def build_app_updated_at_map() -> dict[str, str]: + """Map each app to the commit date of its last change under apps/. + + git log lists commits newest first, so the first time an app path appears is + its most recent change. Author date is used because it survives rebases, + unlike the committer date. + """ + try: + output = run_git("log", "--format=@@%aI", "--name-only", "--", "apps") + except (subprocess.CalledProcessError, FileNotFoundError): + return {} + + updated: dict[str, str] = {} + current: str | None = None + for line in output.splitlines(): + if line.startswith("@@"): + current = line[2:].strip() + continue + if not current or not line.startswith("apps/"): + continue + parts = line.split("/", 2) + if len(parts) >= 2 and parts[1] and parts[1] not in updated: + updated[parts[1]] = to_utc_z(current) + return updated + + +def build_apps_index( + channel: str, + generated_at: str, + app_updated_at: dict[str, str] | None = None, +) -> dict: + updated_map = app_updated_at or {} apps = [] for app_dir in sorted(path for path in APPS_DIR.iterdir() if path.is_dir()): variables = load_variables_json(app_dir) @@ -208,6 +263,7 @@ def build_apps_index(dataset_version: str, channel: str, generated_at: str) -> d "versions": summarize_versions(variables.get("edition", [])), "path": f"apps/{app_name}", "hash": current_app_fingerprint(app_dir), + "updatedAt": updated_map.get(app_name, generated_at), "package": build_app_package_entry(app_name), "checksum": build_app_checksum_entry(app_name), } @@ -215,7 +271,6 @@ def build_apps_index(dataset_version: str, channel: str, generated_at: str) -> d return { "schemaVersion": "1", - "datasetVersion": dataset_version, "channel": channel, "generatedAt": generated_at, "appCount": len(apps), @@ -223,6 +278,23 @@ def build_apps_index(dataset_version: str, channel: str, generated_at: str) -> d } +def build_apps_index_with_version( + channel: str, + generated_at: str, + app_updated_at: dict[str, str] | None = None, +) -> tuple[dict, str]: + """Build the apps index and derive its datasetVersion from its own content. + + The version has to be computed before it is stored, otherwise the file would + carry a version that does not describe it (for example the catalog version). + """ + apps_index = build_apps_index(channel, generated_at, app_updated_at) + serialized = json.dumps(apps_index, sort_keys=True, ensure_ascii=False) + dataset_version = _hash_content(serialized) + apps_index["datasetVersion"] = dataset_version + return apps_index, dataset_version + + def apps_in_ref(from_ref: str | None) -> set[str]: if not from_ref: return set() @@ -563,8 +635,7 @@ def build_v2_appstore_artifacts( elif file_name == "product_zh.json": catalog_checksums["productZh"] = write_checksum_file(destination) - catalog_checksum_values = ",".join(f"{k}={v}" for k, v in sorted(catalog_checksums.items())) - catalog_dsv = _hash_content(catalog_checksum_values) + catalog_dsv = compute_catalog_dataset_version(catalog_dir) # ── catalog full package ───────────────────────────────── catalog_full_dir = catalog_dir / "full" @@ -593,9 +664,8 @@ def build_v2_appstore_artifacts( apps_packages_dir.mkdir(parents=True, exist_ok=True) # ── library – compute index & delta BEFORE per-app zips ── - apps_index = build_apps_index(catalog_dsv, channel, generated_at) - serialized_index = json.dumps(apps_index, sort_keys=True, ensure_ascii=False) - library_dsv = _hash_content(serialized_index) + app_updated_at = build_app_updated_at_map() + apps_index, library_dsv = build_apps_index_with_version(channel, generated_at, app_updated_at) full_latest_name = V2_FULL_LATEST_NAME with tempfile.TemporaryDirectory() as tmp_dir_name: diff --git a/cli/README.md b/cli/README.md index 811694445..d021382ff 100644 --- a/cli/README.md +++ b/cli/README.md @@ -34,7 +34,7 @@ Windows PowerShell: Or without activating: ```bash -make libs ARGS="check --app wordpress --json" +make libs ARGS="app-check --app wordpress --json" ``` One-off without install: @@ -81,6 +81,9 @@ py -m venv .venv - `libs appstore-sync --app --ssh-host [--progress] [--verbose]` - sync one app directory into the remote websoft9 container library and sync `metadata/catalog/.json` into the container catalog directory for appstore testing - `libs appstore-deploy --app --ssh-host [--progress] [--verbose]` - deploy one app into a websoft9 container appstore (not implemented yet; pending the websoft9 container CLI) - `libs websoft9-upgrade [--container ] [--tag ] [--tag-var ] [--compose-dir ] [--target local|remote] [--ssh-host ] [--progress] [--verbose]` - upgrade the Websoft9 platform container: set the image tag (default `dev`), run `docker compose pull`, then `docker compose up -d`; the compose project is discovered from the container labels unless `--compose-dir` is given +- `libs dns-bind [--domain ] [--target local|remote] [--ip ] [--json]` - point the wildcard record `*.` at the remote host or `127.0.0.1`; it creates the record when missing, updates it when the value differs, and is a no-op (`action = unchanged`) when the IP already matches, so repeated runs never error; `--domain` defaults to `ALIYUN_DNS_DOMAIN`, and `--target` defaults to `TARGET` in `.secrets/remote.env` (add `--ssh-host` to override) + - on success it also runs `websoft9 setconfig --section domain --key wildcard_domain --value ` inside the Websoft9 container to bind the platform domain; the container name comes from `CONTAINER` in `.secrets/remote.env` (default `websoft9`, override with `--container`); if the container is absent or unreachable this step is skipped silently and reported as `container_config.status = skipped`; pass `--no-container` to skip it explicitly +- `libs dns-delete [--domain ] [--json]` - delete the single wildcard record `*.` managed for the domain; errors when it is missing or ambiguous - remote-aware commands suppress the routine `known hosts` add warning from ephemeral SSH targets; real stderr still passes through - `libs proxy` - show, save, or clear the saved proxy - `libs help` - show help, same as `libs --help` @@ -103,8 +106,8 @@ Network behavior: - `cli/proxy.conf` is machine-local and gitignored Credentials: -- provider-specific token files live under `.secrets/`, for example `.secrets/contentful.env` and `.secrets/cloudflare.env` (gitignored) -- each provider file stores the token directly as a standard env var, e.g. `CONTENTFUL_ACCESS_TOKEN=...` or `CLOUDFLARE_API_TOKEN=...` +- provider-specific token files live under `.secrets/`, for example `.secrets/contentful.env`, `.secrets/cloudflare.env`, and `.secrets/aliyun.env` (gitignored) +- each provider file stores the token directly as a standard env var, e.g. `CONTENTFUL_ACCESS_TOKEN=...`, `CLOUDFLARE_API_TOKEN=...`, or `ALIYUN_ACCESS_KEY_ID=...` / `ALIYUN_ACCESS_KEY_SECRET=...` (with an optional `ALIYUN_DNS_DOMAIN=libs.websoft9.cn`) - a command may accept a per-invocation token flag (e.g. `--token`) and an explicit provider env file path (e.g. `--env-file`) as overrides - resolution order: command flag > explicit `--env-file` > environment variable > default provider file - CI keeps passing secrets as environment variables from GitHub Actions secrets; it does not use `.secrets/` diff --git a/cli/libs/__pycache__/app.cpython-312.pyc b/cli/libs/__pycache__/app.cpython-312.pyc index 096926548..9fc6ce212 100644 Binary files a/cli/libs/__pycache__/app.cpython-312.pyc and b/cli/libs/__pycache__/app.cpython-312.pyc differ diff --git a/cli/libs/__pycache__/app_build.cpython-312.pyc b/cli/libs/__pycache__/app_build.cpython-312.pyc index 78f458e77..21cc7cda4 100644 Binary files a/cli/libs/__pycache__/app_build.cpython-312.pyc and b/cli/libs/__pycache__/app_build.cpython-312.pyc differ diff --git a/cli/libs/__pycache__/app_tests.cpython-312.pyc b/cli/libs/__pycache__/app_tests.cpython-312.pyc index af476b09b..7708641ac 100644 Binary files a/cli/libs/__pycache__/app_tests.cpython-312.pyc and b/cli/libs/__pycache__/app_tests.cpython-312.pyc differ diff --git a/cli/libs/__pycache__/credentials.cpython-312.pyc b/cli/libs/__pycache__/credentials.cpython-312.pyc index eb8a19fee..0a8e647d0 100644 Binary files a/cli/libs/__pycache__/credentials.cpython-312.pyc and b/cli/libs/__pycache__/credentials.cpython-312.pyc differ diff --git a/cli/libs/__pycache__/main.cpython-312.pyc b/cli/libs/__pycache__/main.cpython-312.pyc index bb5fd27bb..a5ff0f37c 100644 Binary files a/cli/libs/__pycache__/main.cpython-312.pyc and b/cli/libs/__pycache__/main.cpython-312.pyc differ diff --git a/cli/libs/app.py b/cli/libs/app.py index 485fdad6c..b2ce9036a 100644 --- a/cli/libs/app.py +++ b/cli/libs/app.py @@ -24,11 +24,16 @@ def _app_names(root: Path) -> list[str]: return sorted(path.name for path in root.iterdir() if path.is_dir()) +def _has_root_dockerfile(target: Path | None) -> bool: + return bool(target and (target / "Dockerfile").exists()) + + def collect_apps(include_archived: bool = False, scope: str | None = None) -> list[dict]: names = [path.name for path in active_app_dirs()] output = [] for name in names: + target = app_dir(name) metadata = resolve_app_metadata(name) item = { "name": name, @@ -36,6 +41,7 @@ def collect_apps(include_archived: bool = False, scope: str | None = None) -> li "cadence": metadata.cadence, "update_policy": metadata.update_policy, "scope": _app_scope(name), + "dockerfile": _has_root_dockerfile(target), } if scope and item["scope"] != scope: continue @@ -43,6 +49,7 @@ def collect_apps(include_archived: bool = False, scope: str | None = None) -> li if include_archived: for name in _app_names(repo_path("archive", "apps")): + target = app_dir(name) metadata = resolve_app_metadata(name) item = { "name": name, @@ -50,6 +57,7 @@ def collect_apps(include_archived: bool = False, scope: str | None = None) -> li "cadence": metadata.cadence, "update_policy": metadata.update_policy, "scope": _app_scope(name), + "dockerfile": _has_root_dockerfile(target), } if scope and item["scope"] != scope: continue @@ -237,9 +245,16 @@ def list_apps( output = collect_apps(include_archived=include_archived, scope=scope) if not as_json: - table = Table("name", "status", "cadence", "update policy", "scope", header_style="dim", box=box.SIMPLE) + table = Table("name", "status", "cadence", "update policy", "scope", "dockerfile", header_style="dim", box=box.SIMPLE) for item in output: - table.add_row(item["name"], item["status"], item["cadence"], item["update_policy"], item["scope"]) + table.add_row( + item["name"], + item["status"], + item["cadence"], + item["update_policy"], + item["scope"], + "yes" if item["dockerfile"] else "no", + ) Console().print(table) return diff --git a/cli/libs/app_build.py b/cli/libs/app_build.py index a3ecb0fd0..bfd37fdfd 100644 --- a/cli/libs/app_build.py +++ b/cli/libs/app_build.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import platform as _host import re import subprocess from pathlib import Path @@ -16,6 +17,128 @@ DOCKERHUB_USER_ENV = "DOCKERHUB_USERNAME" DOCKERHUB_PASSWORD_ENV = "DOCKERHUB_PASSWORD" DOCKERHUB_TOKEN_ENV = "DOCKERHUB_TOKEN" +DOCKERHUB_ORG_ENV = "DOCKERHUB_ORG" + +DEFAULT_PLATFORM = "amd64" +PLATFORM_CHOICES = { + "amd64": "linux/amd64", + "arm64": "linux/arm64", + "both": "linux/amd64,linux/arm64", +} + + +BINFMT_HANDLERS = {"amd64": "qemu-x86_64", "arm64": "qemu-aarch64"} +BINFMT_IMAGE = "tonistiigi/binfmt" +BUILDER_HINT = ( + "the current buildx builder does not support multi-platform builds; create a container builder first: " + "docker buildx create --name multiarch --driver docker-container --use" +) + +GIT_SHA_RE = re.compile(r"^[0-9a-fA-F]{7,40}$") + + +def _promote_source_ref(source_sha: str | None) -> str: + """Resolve the promote source tag from an optional commit SHA. + + Returns ``dev-`` for a validated SHA, otherwise the rolling + ``dev-latest`` alias. Pinning the SHA keeps the promoted stable artifact + identical to the validated candidate instead of tracking a moving alias. + """ + candidate = (source_sha or "").strip() + if candidate.startswith("dev-"): + candidate = candidate[4:] + if not candidate: + return "dev-latest" + if not GIT_SHA_RE.fullmatch(candidate): + raise ValueError(f"source_sha must be a 7-40 character hex commit SHA, got: {source_sha!r}") + return f"dev-{candidate[:7]}" + + +def _resolve_platform(platform: str | None) -> str | None: + """Normalize a platform choice; None keeps the builder host-native. + + The CLI defaults to `amd64`; internal callers such as app-deploy pass None + so a build for the target host is not forced into a cross-build. + """ + if platform is None or not str(platform).strip(): + return None + key = str(platform).strip().lower() + if key not in PLATFORM_CHOICES: + raise ValueError(f"unsupported platform: {platform} (expected amd64, arm64, or both)") + return key + + +def _normalize_arch(machine: str | None) -> str | None: + value = (machine or "").strip().lower() + if value in {"x86_64", "amd64"}: + return "amd64" + if value in {"aarch64", "arm64"}: + return "arm64" + return None + + +def _local_arch() -> str | None: + return _normalize_arch(_host.machine()) + + +def _required_binfmt_archs(platform_key: str | None, host_arch: str | None) -> list[str]: + """Foreign arches that need emulation on the build host.""" + if not platform_key: + return [] + targets = ["amd64", "arm64"] if platform_key == "both" else [platform_key] + return [arch for arch in targets if arch != host_arch] + + +def _ensure_binfmt_local(archs: list[str], progress=None) -> None: + if not archs or _host.system().lower() != "linux": + return + for arch in archs: + if Path(f"/proc/sys/fs/binfmt_misc/{BINFMT_HANDLERS[arch]}").exists(): + continue + if progress: + progress(f"installing {arch} emulation via {BINFMT_IMAGE}") + result = subprocess.run( + ["docker", "run", "--privileged", "--rm", BINFMT_IMAGE, "--install", arch], + capture_output=True, + text=True, + check=False, + ) + if progress and result.stdout.strip(): + progress(result.stdout.strip()) + if result.returncode != 0: + raise RuntimeError( + result.stderr.strip() + or f"failed to install {arch} emulation; run: docker run --privileged --rm {BINFMT_IMAGE} --install {arch}" + ) + + +def _ensure_binfmt_remote(host: str, user: str, secret_path: Path, archs: list[str], progress=None) -> None: + if not archs: + return + checks = " ".join( + f"if [ ! -e /proc/sys/fs/binfmt_misc/{BINFMT_HANDLERS[arch]} ]; then " + f"echo installing {arch} emulation; docker run --privileged --rm {BINFMT_IMAGE} --install {arch}; fi;" + for arch in archs + ) + script = f'if [ "$(uname -s)" = "Linux" ]; then {checks} fi' + result = remote.stream_ssh(host, user, secret_path, script, on_line=progress) + if result.returncode != 0: + raise RuntimeError(result.stdout.strip() or "failed to install binfmt emulation on remote host") + + +def _remote_arch(host: str, user: str, secret_path: Path) -> str | None: + result = remote.run_command(remote.ssh_base(host, user, secret_path) + ["uname -m"]) + if result.returncode != 0: + return None + return _normalize_arch(result.stdout) + + +def _buildx_error(output: str) -> str: + text = output.strip() or "docker buildx build failed" + lowered = text.lower() + if "multiple platforms" in lowered or "not supported for docker driver" in lowered: + return f"{text}\n{BUILDER_HINT}" + return text def _env_map(target: Path) -> dict[str, str]: @@ -127,10 +250,26 @@ def resolve_image(app_name: str, image: str) -> str: return _resolve_image_template(image, _env_map(target)) -def _dockerfile_plan(app_name: str) -> dict: +def _namespace_image(image: str, org: str | None) -> str: + """Prefix a bare image repository with the default org; keep namespaced refs as-is. + + `wordpress:latest` -> `/wordpress:latest`; `websoft9dev/akeneo:v1` stays. + """ + if not org: + return image + slash = image.rfind("/") + colon = image.rfind(":") + repository = image[:colon] if colon > slash else image + if "/" in repository: + return image + return f"{org}/{image}" + + +def _dockerfile_plan(app_name: str, org: str | None = None) -> dict: """Plan a direct Dockerfile build (pull-only app). Per docs/image-tag-spec.md. - Returns {version_arg, w9_version, images}; build must run with CWD = app dir. + Returns {version_arg, w9_version, w9_repo, images}; build must run with CWD = app dir. + When `org` is set, a bare W9_REPO is published under that Docker Hub namespace. """ target = app_dir(app_name) if not target: @@ -151,11 +290,12 @@ def _dockerfile_plan(app_name: str) -> dict: raise ValueError(f"app {app_name} W9_VERSION missing in .env") if not w9_repo: raise ValueError(f"app {app_name} W9_REPO missing in .env") + repo = _namespace_image(w9_repo, org) return { "version_arg": version_arg, "w9_version": w9_version, - "w9_repo": w9_repo, - "images": [f"{w9_repo}:{w9_version}"], + "w9_repo": repo, + "images": [f"{repo}:{w9_version}"], } @@ -173,18 +313,34 @@ def _stable_tags(repo: str, version: str) -> list[str]: return tags -def build_plan(app_name: str, channel: str = "stable", git_sha: str | None = None, source_sha: str | None = None) -> dict: +def _resolve_dockerhub_org(env_file: str | None = None, org: str | None = None) -> str | None: + if org: + return org + value = resolve_secret(DOCKERHUB_ORG_ENV, "dockerhub", env_file=env_file) + return value or None + + +def build_plan( + app_name: str, + channel: str = "stable", + git_sha: str | None = None, + source_sha: str | None = None, + org: str | None = None, + env_file: str | None = None, +) -> dict: """Return the canonical image build/tag plan for one app. Channels: - stable: tags derived from W9_VERSION - dev: candidate tags dev- + dev-latest (build) - - promote: stable tags; source is dev-latest (re-tag, no build) + - promote: stable tags; source is dev- when provided, otherwise + the rolling dev-latest alias (re-tag, no build) This is the shared rules entrypoint for CI and controlled manual push. """ source, compose = _load_compose(app_name) - plan = _dockerfile_plan(app_name) + resolved_org = _resolve_dockerhub_org(env_file=env_file, org=org) + plan = _dockerfile_plan(app_name, org=resolved_org) channel = (channel or "stable").strip().lower() version = plan["w9_version"] repo = plan["w9_repo"] @@ -201,7 +357,7 @@ def build_plan(app_name: str, channel: str = "stable", git_sha: str | None = Non else: tags = _stable_tags(repo, version) if channel == "promote": - source_image = f"{repo}:dev-latest" + source_image = f"{repo}:{_promote_source_ref(source_sha)}" return { "app": app_name, @@ -213,6 +369,7 @@ def build_plan(app_name: str, channel: str = "stable", git_sha: str | None = Non "version_arg": plan["version_arg"], "w9_version": version, "w9_repo": repo, + "org": resolved_org, "tags": tags, "source_image": source_image, "primary_image": source_image or tags[0], @@ -301,6 +458,9 @@ def build_app( username: str | None = None, password: str | None = None, token: str | None = None, + org: str | None = None, + platform: str | None = None, + binfmt: bool = True, registry: str | None = None, skip_sync: bool = False, compose_env_file: str | None = None, @@ -308,12 +468,23 @@ def build_app( ) -> dict: source, compose = _load_compose(app_name) build_services = _build_services(compose) + platform_key = _resolve_platform(platform) + multi_arch = platform_key == "both" + platform_flag = PLATFORM_CHOICES[platform_key] if platform_key else None + if build_services and platform_key not in (None, DEFAULT_PLATFORM): + raise ValueError( + f"app {app_name} uses compose build services; --platform {platform_key} " + "is only supported for Dockerfile apps" + ) + if multi_arch and not push: + raise ValueError("--platform both builds a multi-arch manifest and requires --push") + resolved_org = _resolve_dockerhub_org(env_file=env_file, org=org) if push else None if build_services: images = _tagged_images(source, compose, build_services) build_services_out = build_services else: - plan = _dockerfile_plan(app_name) + plan = _dockerfile_plan(app_name, org=resolved_org) images = plan["images"] build_services_out = [] @@ -336,6 +507,9 @@ def build_app( mode = remote.default_target() if mode == "local": + if platform_key and not build_services and binfmt: + _ensure_binfmt_local(_required_binfmt_archs(platform_key, _local_arch()), progress=progress) + pushed: list[str] = [] if build_services: build_command = [ "docker", @@ -349,35 +523,69 @@ def build_app( "build", *build_services, ] - else: + build_result = _run_stream(build_command, progress=progress) + if build_result.returncode != 0: + raise RuntimeError(build_result.stdout.strip() or "docker build failed") + if push: + login_username, login_password = _resolve_dockerhub_credentials(env_file, username, password, token) + _docker_login(registry, login_username, login_password, progress=progress) + for image in images: + result = _run_stream(["docker", "push", image], progress=progress) + if result.returncode != 0: + raise RuntimeError(result.stdout.strip() or f"docker push failed for {image}") + pushed.append(image) + elif multi_arch: + login_username, login_password = _resolve_dockerhub_credentials(env_file, username, password, token) + _docker_login(registry, login_username, login_password, progress=progress) build_command = [ "docker", + "buildx", "build", + "--platform", + platform_flag, "-f", str(source / "Dockerfile"), "--build-arg", f"{plan['version_arg']}={plan['w9_version']}", "-t", images[0], + "--push", str(source), ] - build_result = _run_stream(build_command, progress=progress) - if build_result.returncode != 0: - raise RuntimeError(build_result.stdout.strip() or "docker build failed") - - pushed: list[str] = [] - if push: - login_username, login_password = _resolve_dockerhub_credentials(env_file, username, password, token) - _docker_login(registry, login_username, login_password, progress=progress) - for image in images: - result = _run_stream(["docker", "push", image], progress=progress) - if result.returncode != 0: - raise RuntimeError(result.stdout.strip() or f"docker push failed for {image}") - pushed.append(image) + build_result = _run_stream(build_command, progress=progress) + if build_result.returncode != 0: + raise RuntimeError(_buildx_error(build_result.stdout)) + pushed = list(images) + else: + build_command = ["docker", "build"] + if platform_flag: + build_command += ["--platform", platform_flag] + build_command += [ + "-f", + str(source / "Dockerfile"), + "--build-arg", + f"{plan['version_arg']}={plan['w9_version']}", + "-t", + images[0], + str(source), + ] + build_result = _run_stream(build_command, progress=progress) + if build_result.returncode != 0: + raise RuntimeError(build_result.stdout.strip() or "docker build failed") + if push: + login_username, login_password = _resolve_dockerhub_credentials(env_file, username, password, token) + _docker_login(registry, login_username, login_password, progress=progress) + for image in images: + result = _run_stream(["docker", "push", image], progress=progress) + if result.returncode != 0: + raise RuntimeError(result.stdout.strip() or f"docker push failed for {image}") + pushed.append(image) return { "app": app_name, "target": "local", + "org": resolved_org, + "platform": platform_key or "host", "build_services": build_services_out, "images": images, "pushed": pushed, @@ -398,29 +606,60 @@ def build_app( if not skip_sync: _sync_app_dir(app_name, host, user, secret_path, deploy_root_value, progress=progress) + if platform_key and not build_services and binfmt: + _ensure_binfmt_remote( + host, + user, + secret_path, + _required_binfmt_archs(platform_key, _remote_arch(host, user, secret_path)), + progress=progress, + ) + + pushed = [] if build_services: build_script = ( f"docker compose --progress plain -f {app_target}/docker-compose.yml " f"--env-file {app_target}/.env build {' '.join(build_services)}" ) + build_result = remote.stream_ssh(host, user, secret_path, build_script, on_line=progress) + if build_result.returncode != 0: + raise RuntimeError(build_result.stdout.strip() or "remote docker build failed") + if push: + login_username, login_password = _resolve_dockerhub_credentials(env_file, username, password, token) + _docker_login_remote(host, user, secret_path, registry, login_username, login_password, progress=progress) + for image in images: + result = remote.run_command(remote.ssh_base(host, user, secret_path) + [f"docker push {image}"]) + if result.returncode != 0: + raise RuntimeError(result.stdout.strip() or f"remote docker push failed for {image}") + pushed.append(image) + elif multi_arch: + login_username, login_password = _resolve_dockerhub_credentials(env_file, username, password, token) + _docker_login_remote(host, user, secret_path, registry, login_username, login_password, progress=progress) + build_script = ( + f"cd {app_target} && docker buildx build --platform {platform_flag} -f Dockerfile " + f"--build-arg {plan['version_arg']}={plan['w9_version']} -t {images[0]} --push ." + ) + build_result = remote.stream_ssh(host, user, secret_path, build_script, on_line=progress) + if build_result.returncode != 0: + raise RuntimeError(_buildx_error(build_result.stdout)) + pushed = list(images) else: + platform_arg = f" --platform {platform_flag}" if platform_flag else "" build_script = ( - f"cd {app_target} && docker build -f Dockerfile " + f"cd {app_target} && docker build{platform_arg} -f Dockerfile " f"--build-arg {plan['version_arg']}={plan['w9_version']} -t {images[0]} ." ) - build_result = remote.stream_ssh(host, user, secret_path, build_script, on_line=progress) - if build_result.returncode != 0: - raise RuntimeError(build_result.stdout.strip() or "remote docker build failed") - - pushed = [] - if push: - login_username, login_password = _resolve_dockerhub_credentials(env_file, username, password, token) - _docker_login_remote(host, user, secret_path, registry, login_username, login_password, progress=progress) - for image in images: - result = remote.run_command(remote.ssh_base(host, user, secret_path) + [f"docker push {image}"]) - if result.returncode != 0: - raise RuntimeError(result.stdout.strip() or f"remote docker push failed for {image}") - pushed.append(image) + build_result = remote.stream_ssh(host, user, secret_path, build_script, on_line=progress) + if build_result.returncode != 0: + raise RuntimeError(build_result.stdout.strip() or "remote docker build failed") + if push: + login_username, login_password = _resolve_dockerhub_credentials(env_file, username, password, token) + _docker_login_remote(host, user, secret_path, registry, login_username, login_password, progress=progress) + for image in images: + result = remote.run_command(remote.ssh_base(host, user, secret_path) + [f"docker push {image}"]) + if result.returncode != 0: + raise RuntimeError(result.stdout.strip() or f"remote docker push failed for {image}") + pushed.append(image) return { "app": app_name, @@ -430,6 +669,8 @@ def build_app( "ssh_secret_path": str(secret_path), "deploy_root": deploy_root_value, "app_target": app_target, + "org": resolved_org, + "platform": platform_key or "host", "build_services": build_services_out, "images": images, "pushed": pushed, diff --git a/cli/libs/app_tests.py b/cli/libs/app_tests.py index f361b3e50..d420f3f57 100644 --- a/cli/libs/app_tests.py +++ b/cli/libs/app_tests.py @@ -2,6 +2,7 @@ import json import re +import shlex import subprocess import time from collections.abc import Callable @@ -208,6 +209,28 @@ def _run_remote_script(remote_ctx: dict, script: str) -> subprocess.CompletedPro return remote.run_ssh(remote_ctx["host"], remote_ctx["user"], remote_ctx["secret_path"], script) +def _remote_script_command(remote_ctx: dict, app_name: str, script_name: str, env: dict, base_url: str | None) -> str: + """Build the command that runs a tests/