diff --git a/Dockerfile b/Dockerfile index 1358c32..40b82fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -257,6 +257,9 @@ COPY backend/migrations ./migrations # Copy UI files COPY --from=backend-build /app/ui/build ./ui/build +# Copy cloud static HTML template (injected into index.html at startup when IS_CLOUD=true) +COPY frontend/cloud-root-content.html /app/cloud-root-content.html + # Copy agent binaries (both architectures) — served by the backend # at GET /api/v1/system/agent?arch=amd64|arm64 COPY --from=agent-build /agent-binaries ./agent-binaries @@ -326,6 +329,23 @@ if [ -n "\${ANALYTICS_SCRIPT:-}" ]; then fi fi +# Inject static HTML into root div for cloud mode (payment system requires visible legal links) +if [ "\${IS_CLOUD:-false}" = "true" ]; then + if ! grep -q "cloud-static-content" /app/ui/build/index.html 2>/dev/null; then + echo "Injecting cloud static HTML content..." + perl -i -pe ' + BEGIN { + open my \$fh, "<", "/app/cloud-root-content.html" or die; + local \$/; + \$c = <\$fh>; + close \$fh; + \$c =~ s/\\n/ /g; + } + s/
<\\/div>/
\$c<\\/div>/ + ' /app/ui/build/index.html + fi +fi + # Ensure proper ownership of data directory echo "Setting up data directory permissions..." mkdir -p /databasus-data/pgdata @@ -439,7 +459,7 @@ fi echo "Setting up database and user..." gosu postgres \$PG_BIN/psql -p 5437 -h localhost -d postgres << 'SQL' -# We use stub password, because internal DB is not exposed outside container +-- We use stub password, because internal DB is not exposed outside container ALTER USER postgres WITH PASSWORD 'Q1234567'; SELECT 'CREATE DATABASE databasus OWNER postgres' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'databasus') diff --git a/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go index 79ae639..ddccf62 100644 --- a/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go @@ -109,6 +109,7 @@ func (uc *CreateMariadbBackupUsecase) buildMariadbDumpArgs( "--routines", "--quick", "--skip-extended-insert", + "--skip-add-locks", "--verbose", } @@ -129,6 +130,8 @@ func (uc *CreateMariadbBackupUsecase) buildMariadbDumpArgs( if mdb.IsHttps { args = append(args, "--ssl") args = append(args, "--skip-ssl-verify-server-cert") + } else { + args = append(args, "--skip-ssl") } if mdb.Database != nil && *mdb.Database != "" { diff --git a/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go index 0163f40..5dca193 100644 --- a/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go @@ -108,6 +108,7 @@ func (uc *CreateMysqlBackupUsecase) buildMysqldumpArgs(my *mysqltypes.MysqlDatab "--set-gtid-purged=OFF", "--quick", "--skip-extended-insert", + "--skip-add-locks", "--verbose", } @@ -126,6 +127,8 @@ func (uc *CreateMysqlBackupUsecase) buildMysqldumpArgs(my *mysqltypes.MysqlDatab if my.IsHttps { args = append(args, "--ssl-mode=REQUIRED") + } else { + args = append(args, "--ssl-mode=DISABLED") } if my.Database != nil && *my.Database != "" { @@ -326,6 +329,8 @@ port=%d if myConfig.IsHttps { content += "ssl-mode=REQUIRED\n" + } else { + content += "ssl-mode=DISABLED\n" } err = os.WriteFile(myCnfFile, []byte(content), 0o600) diff --git a/backend/internal/features/restores/usecases/mariadb/restore_backup_uc.go b/backend/internal/features/restores/usecases/mariadb/restore_backup_uc.go index 9996e93..059e536 100644 --- a/backend/internal/features/restores/usecases/mariadb/restore_backup_uc.go +++ b/backend/internal/features/restores/usecases/mariadb/restore_backup_uc.go @@ -77,6 +77,8 @@ func (uc *RestoreMariadbBackupUsecase) Execute( if mdb.IsHttps { args = append(args, "--ssl") args = append(args, "--skip-ssl-verify-server-cert") + } else { + args = append(args, "--skip-ssl") } if mdb.Database != nil && *mdb.Database != "" { diff --git a/backend/internal/features/restores/usecases/mysql/restore_backup_uc.go b/backend/internal/features/restores/usecases/mysql/restore_backup_uc.go index 46680ad..f0682fe 100644 --- a/backend/internal/features/restores/usecases/mysql/restore_backup_uc.go +++ b/backend/internal/features/restores/usecases/mysql/restore_backup_uc.go @@ -76,6 +76,8 @@ func (uc *RestoreMysqlBackupUsecase) Execute( if my.IsHttps { args = append(args, "--ssl-mode=REQUIRED") + } else { + args = append(args, "--ssl-mode=DISABLED") } if my.Database != nil && *my.Database != "" { @@ -305,6 +307,8 @@ port=%d if myConfig.IsHttps { content += "ssl-mode=REQUIRED\n" + } else { + content += "ssl-mode=DISABLED\n" } err = os.WriteFile(myCnfFile, []byte(content), 0o600) diff --git a/frontend/cloud-root-content.html b/frontend/cloud-root-content.html new file mode 100644 index 0000000..0dcb97f --- /dev/null +++ b/frontend/cloud-root-content.html @@ -0,0 +1,40 @@ +
+ +
+ + Databasus + Databasus + +
+ + +
+
+
Sign in
+ +
Your email
+ + +
Password
+ + + +
+
+ + +
diff --git a/frontend/src/features/playground/index.ts b/frontend/src/features/playground/index.ts deleted file mode 100644 index 428cd8e..0000000 --- a/frontend/src/features/playground/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { PlaygroundWarningComponent } from './ui/PlaygroundWarningComponent'; diff --git a/frontend/src/features/playground/ui/PlaygroundWarningComponent.tsx b/frontend/src/features/playground/ui/PlaygroundWarningComponent.tsx deleted file mode 100644 index 23d6ed4..0000000 --- a/frontend/src/features/playground/ui/PlaygroundWarningComponent.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { Modal } from 'antd'; -import type { JSX } from 'react'; -import { useEffect, useState } from 'react'; - -import { IS_CLOUD } from '../../../constants'; - -const STORAGE_KEY = 'databasus_playground_info_dismissed'; - -const TIMEOUT_SECONDS = 30; - -export const PlaygroundWarningComponent = (): JSX.Element => { - const [isVisible, setIsVisible] = useState(false); - const [remainingSeconds, setRemainingSeconds] = useState(TIMEOUT_SECONDS); - const [isButtonEnabled, setIsButtonEnabled] = useState(false); - - const handleClose = () => { - try { - localStorage.setItem(STORAGE_KEY, 'true'); - } catch (e) { - console.warn('Failed to save playground modal state to localStorage:', e); - } - setIsVisible(false); - }; - - useEffect(() => { - if (!IS_CLOUD) { - return; - } - - try { - const isDismissed = localStorage.getItem(STORAGE_KEY) === 'true'; - if (!isDismissed) { - setIsVisible(true); - } - } catch (e) { - console.warn('Failed to read playground modal state from localStorage:', e); - setIsVisible(true); - } - }, []); - - useEffect(() => { - if (!isVisible) { - return; - } - - const interval = setInterval(() => { - setRemainingSeconds((prev) => { - if (prev <= 1) { - setIsButtonEnabled(true); - clearInterval(interval); - return 0; - } - return prev - 1; - }); - }, 1000); - - return () => clearInterval(interval); - }, [isVisible]); - - return ( - - {isButtonEnabled ? 'Understood' : `${remainingSeconds}`} -
- } - okButtonProps={{ disabled: !isButtonEnabled }} - closable={false} - cancelButtonProps={{ style: { display: 'none' } }} - width={500} - centered - maskClosable={false} - > -
-
-

What is Playground?

-

- Playground is a dev environment of Databasus development team. It is used by Databasus - dev team to test new features and see issues which hard to detect when using self hosted - (without logs or reports).{' '} - Here you can make backups for small and not critical databases for free -

-
- -
-

What is limit?

-
    -
  • Single backup size - 100 MB (~1.5 GB database)
  • -
  • Store period - 7 days
  • -
-
- -
-

Is it secure?

-

- Yes, it's regular Databasus installation, secured and maintained by Databasus team. - More about security{' '} - - you can read here - -

-
- -
-

Can my data be currepted?

-

- No, because playground use only read-only users and cannot affect your DB. Only issue - you can face is instability: playground background workers frequently reloaded so backup - can be slower or be restarted due to app restart. Do not rely production DBs on - playground, please. At once we may clean backups or something like this. At least, check - your backups here once a week -

-
- -
-

What if I see an issue?

-

- Create{' '} - - GitHub issue - {' '} - or write{' '} - - to the community - -

-
-
- - ); -}; diff --git a/frontend/src/features/users/ui/SignInComponent.tsx b/frontend/src/features/users/ui/SignInComponent.tsx index 3e4a2a3..5c35431 100644 --- a/frontend/src/features/users/ui/SignInComponent.tsx +++ b/frontend/src/features/users/ui/SignInComponent.tsx @@ -166,6 +166,8 @@ export function SignInComponent({ )}
+ +
); } diff --git a/frontend/src/features/users/ui/SignUpComponent.tsx b/frontend/src/features/users/ui/SignUpComponent.tsx index 6528aee..830eae3 100644 --- a/frontend/src/features/users/ui/SignUpComponent.tsx +++ b/frontend/src/features/users/ui/SignUpComponent.tsx @@ -1,10 +1,10 @@ import { EyeInvisibleOutlined, EyeTwoTone } from '@ant-design/icons'; -import { App, Button, Input } from 'antd'; +import { App, Button, Checkbox, Input } from 'antd'; import { type JSX, useState } from 'react'; import { useCloudflareTurnstile } from '../../../shared/hooks/useCloudflareTurnstile'; -import { GITHUB_CLIENT_ID, GOOGLE_CLIENT_ID } from '../../../constants'; +import { GITHUB_CLIENT_ID, GOOGLE_CLIENT_ID, IS_CLOUD } from '../../../constants'; import { userApi } from '../../../entity/users'; import { StringUtils } from '../../../shared/lib'; import { FormValidator } from '../../../shared/lib/FormValidator'; @@ -34,6 +34,9 @@ export function SignUpComponent({ onSwitchToSignIn }: SignUpComponentProps): JSX const [signUpError, setSignUpError] = useState(''); + const [isTermsAccepted, setTermsAccepted] = useState(false); + const [isPolicyAccepted, setPolicyAccepted] = useState(false); + const { token, containerRef, resetCloudflareTurnstile } = useCloudflareTurnstile(); const validateFieldsForSignUp = (): boolean => { @@ -179,10 +182,49 @@ export function SignUpComponent({ onSwitchToSignIn }: SignUpComponentProps): JSX
+ {IS_CLOUD && ( +
+
+ setTermsAccepted(e.target.checked)} + > + I agree to{' '} + + Terms of Use + + +
+
+ setPolicyAccepted(e.target.checked)} + > + I agree to{' '} + + Privacy Policy + + +
+
+ )} +
)} + +
); } diff --git a/frontend/src/pages/AuthPageComponent.tsx b/frontend/src/pages/AuthPageComponent.tsx index a3f1a59..5eed537 100644 --- a/frontend/src/pages/AuthPageComponent.tsx +++ b/frontend/src/pages/AuthPageComponent.tsx @@ -2,8 +2,8 @@ import { LoadingOutlined } from '@ant-design/icons'; import { Spin } from 'antd'; import { useEffect, useState } from 'react'; +import { IS_CLOUD } from '../constants'; import { userApi } from '../entity/users'; -import { PlaygroundWarningComponent } from '../features/playground'; import { AdminPasswordComponent, AuthNavbarComponent, @@ -42,7 +42,7 @@ export function AuthPageComponent() { }, []); return ( -
+
{isLoading ? (
} size="large" /> @@ -84,7 +84,31 @@ export function AuthPageComponent() {
)} - + {IS_CLOUD && ( + + )}
); } diff --git a/frontend/src/widgets/main/MainScreenComponent.tsx b/frontend/src/widgets/main/MainScreenComponent.tsx index 69c9f79..142ba30 100644 --- a/frontend/src/widgets/main/MainScreenComponent.tsx +++ b/frontend/src/widgets/main/MainScreenComponent.tsx @@ -14,7 +14,6 @@ import { import { type WorkspaceResponse, workspaceApi } from '../../entity/workspaces'; import { DatabasesComponent } from '../../features/databases/ui/DatabasesComponent'; import { NotifiersComponent } from '../../features/notifiers/ui/NotifiersComponent'; -import { PlaygroundWarningComponent } from '../../features/playground'; import { SettingsComponent } from '../../features/settings'; import { StoragesComponent } from '../../features/storages/ui/StoragesComponent'; import { ProfileComponent } from '../../features/users'; @@ -381,8 +380,6 @@ export const MainScreenComponent = () => { workspacesCount={workspaces.length} /> )} - -
); };