# Upgrade Notes

This page explains how to upgrade the platform, and which changes to watch for when upgrading to 1.0.

# Upgrade Steps

For a platform running with Docker Deployment or Production Deployment, upgrade as follows:

  1. Back up the database and attachments (mandatory in production):

    docker compose exec -T database pg_dump -U postgres -Fc application > backup_$(date +%F).dump
    tar czf attachments_$(date +%F).tar.gz runtime/attachments
    
    1
    2
  2. Read the change notes later on this page and check whether any of them affect your existing business.

  3. Pull the new configuration and images and restart. In the platform directory, run:

    git pull && docker compose pull && docker compose up -d
    
    1

    If you have changed files such as docker-compose.yml or runtime/proxy/conf.d/default.conf locally, git pull may conflict. You can run git stash first, then git stash pop after pulling and check the differences.

  4. Check the version and status:

    docker compose images      # the TAG column is the running version
    docker compose ps          # startup is complete when server shows healthy
    
    1
    2

When the backend starts, it automatically imports the platform seed data and the application seed data under codes/data/csv; no extra steps are needed.

For upgrading a Dokku deployment, see Deploying the Backend on Dokku.

# Upgrading to 1.0.0-beta18: Anonymous Calls to Dynamic Services

# What Changed

Dynamic services (Development > Integration > Services) have a switch that allows anonymous calls, enableAnonymous (labeled Enable Anonymous in the UI), which defaults to false. Before 1.0.0-beta18 this switch had no effect: requests without a login could call any dynamic service (grails-platform#768).

Starting with 1.0.0-beta18, services whose enableAnonymous is false or not set reject requests without a login. In testing, a call without a token returns:

POST /api/service/Test%20Echo
HTTP 500
{"msg":"ErrorCode: 11004, ErrorMsg: AnonymousInvocation","errorCode":11004}
1
2
3

WARNING

The HTTP status code the platform defines for this error is 403, but the current version actually returns 500. Callers should check errorCode 11004 in the response body and not rely on the HTTP status code.

Impact: every service that has been called anonymously without having enableAnonymous turned on will fail after the upgrade. Typical cases are webhook callbacks, device reporting endpoints, and endpoints called from public pages.

# Checking Before the Upgrade

Run the following SQL against the database of the old version, before upgrading, to list the services that have been called anonymously and will be rejected after the upgrade:

-- The anonymous identity is serialized as "Anonymous" (AnonymousAuthenticationProvider.serializeAuthentication)
select s.name, s.enable_anonymous, count(*) as anonymous_calls, max(r.start_time) as last_call
from dynamic_service_exec_record r
join dynamic_service s on s.id = r.provider_id
where coalesce(s.enable_anonymous, false) = false
  and (r.exec_auth is null or r.exec_auth::text like '%Anonymous%')
group by 1, 2 order by 3 desc;
1
2
3
4
5
6
7

How to run it:

docker compose exec database psql -U postgres -d application
1

Example output (when there are anonymous call records):

   name    | enable_anonymous | anonymous_calls |         last_call
-----------+------------------+-----------------+----------------------------
 Test Echo | f                |               1 | 2026-09-23 13:28:02.959255
1
2
3

This SQL does not find everything

The platform only writes call records for services that have logging (enable_log) turned on. A service without logging will not show up in this SQL even if it has always been called anonymously. Use the SQL below to list every service that will reject anonymous calls, and confirm one by one whether its callers send a login token:

select name, active, enable_log
from dynamic_service
where coalesce(enable_anonymous, false) = false
order by name;
1
2
3
4

# What to Do

For services that really need anonymous access, set enableAnonymous to true:

  • In the UI: go to Development > Integration > Services, edit the service, and turn on Enable Anonymous;

  • Or set the enableAnonymous column in the seed CSV, for example:

    name(*),active,logic.name,enableAnonymous
    Device Report,T,Device Report Core Logic,true
    
    1
    2

Callers of the other services must log in first: get an access_token from POST /api/auth/login, then call the service with the Authorization: Bearer <access_token> request header.

For services with anonymous access turned on, validate the caller in the service logic yourself (for example by checking a signature or an agreed secret), and do not expose create, update or delete operations directly.

# Features Removed Since 1.0 and Their Replacements

Version 1.0 (Grails 7 / Java 25) removed a number of old features. When upgrading from 0.x, or when configuring by following old documentation, adjust according to the list below.

  • Reports and document printing (Jasper reports, DynamicReport, print actions)
    • Replacement: use dashboards for statistics; when you need to generate files, generate them yourself in dynamic logic or a plugin
    • Reference: Dashboard, Plugin Development
  • Wizards (Wizard, DynamicFormWizardStep)
    • Replacement: collect input with an object action that has a parameter form
    • Reference: Object Actions
  • Gantt chart forms
    • Replacement: no built-in replacement for now; you can develop a custom frontend component
    • Reference: Plugin Development
  • Dynamic field definitions / instances (DynamicFieldDefinition, DynamicFieldInstance, (#) columns in CSV)
    • Replacement: to add fields to an object, use model fields of a dynamic domain model; for action parameters, use the fields of an ACTION type form
    • Reference: Dynamic Domain Model, Object Actions
  • Field hooks (Field Hook: field default values, validation, linkage, search)
  • Display widget configuration (DomainColumnClientSideTypeConfig)
  • RequestMap access control
    • Replacement: CRUD permissions of domain models and access requirements of forms/actions/menus are all configured with RoleRequirement
    • Reference: Object Permission Control
  • Dynamic permissions via the object hook Update/delete ability (UPDATE_DELETE)
    • Replacement: the hook type can still be selected but is no longer executed; use custom logic in RoleRequirement instead
    • Reference: Object Permission Control
  • Organizations (Organization, Organization.csv, $ROOT_ORG$)
    • Replacement: multi-tenancy is distinguished by tenant (TENANT_ID) only
    • Reference: Docker Deployment
  • System integration DynamicIntegration: incoming integrations
  • System integration DynamicIntegration: outgoing integrations
  • Service consumers of dynamic services, user tokens (UserToken, the X-MY-Token request header), and jvm:// service discovery
    • Replacement: external calls use the standard login token (/api/auth/login); calls within the platform use dynamic RPC
    • Reference: Dynamic Service
  • The LLM_ENGINE logic engine, the smart assistant, and AI configuration such as OpenAI / Anthropic
    • Replacement: no built-in replacement; call the LLM's external HTTP API directly from the Groovy code of dynamic logic
  • Email sending (SMTP configuration, email templates, email sending records)
    • Replacement: no built-in replacement; call an email service's external HTTP API directly from the Groovy code of dynamic logic; for in-app messages use ๆถˆๆฏ > ๆถˆๆฏไธญๅฟƒ (this menu is shown in Chinese in the English UI)
  • Customizing API response data (the Object render hook)
  • Enable logic of scheduled tasks (enableLogic)
    • Replacement: decide whether to run at the beginning of the task's core logic yourself
    • Reference: Scheduled Tasks
  • The "Run at Startup" (RUN_AT_STARTUP) type of scheduled tasks
    • Replacement: put logic that must run at system startup in the onLoad() method of a plugin class that implements MuyanPlatformComponent (it also runs every time the plugin is reloaded, so the logic must be safe to run repeatedly); change such tasks before upgrading, otherwise this startup logic will no longer run after the upgrade
    • Reference: Scheduled Tasks, Plugin Development ยท Lifecycle Callbacks
  • Seed data actions Reload Seed Data and Import Seed Data Package, and the gradle task packageSeedData
    • Replacement: restart the backend to re-import the application seed data under codes/data/csv, or import data through a plugin
    • Reference: Data Import
  • Heroku deployment and the online trial environment
  • The all-in-one installer muyantech/installer (Docker Swarm + GlusterFS, JDK 11)

The legacy frontend (0.x) is still deployed together with Docker Compose at http://localhost:9080/legacy/, for transitional use only.

Last Updated: 9/24/2026, 2:27:35 PM