This is an automated email from the ASF dual-hosted git repository.

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git


The following commit(s) were added to refs/heads/master by this push:
     new 1334905  feat: use sqlite as the default database
1334905 is described below

commit 1334905fce4c39f7bd78600d001f348bc43c9a45
Author: Yang Luo <[email protected]>
AuthorDate: Mon Aug 17 12:26:03 2026 +0800

    feat: use sqlite as the default database
---
 .github/workflows/build.yml |   9 ----
 .gitignore                  |   6 +++
 Dockerfile                  |   9 ++--
 README.md                   |  41 +++++++--------
 conf/app.conf               |  12 +++--
 conf/conf.go                |  29 +++++++++++
 docker-compose.yml          |  20 +++----
 k8s/.env.example            |  11 ++--
 k8s/README.md               | 124 +++++++++++++++++++++++---------------------
 k8s/configmap.yaml          |   6 ++-
 k8s/deploy.sh               |  27 ++--------
 k8s/deployment.yaml         |  32 ++++--------
 k8s/kustomization.yaml      |   3 +-
 k8s/mysql.yaml              |  98 ----------------------------------
 k8s/namespace.yaml          |   5 ++
 k8s/pvc.yaml                |  13 +++++
 k8s/secret.yaml             |   3 --
 object/ormer.go             |  65 +++++++++++++++++++++--
 service/startup.go          |   9 +++-
 19 files changed, 250 insertions(+), 272 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 45530c7..771b9cc 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -13,15 +13,6 @@ jobs:
   go-tests:
     name: Running Go tests
     runs-on: ubuntu-latest
-    services:
-      mysql:
-        image: mysql:5.7
-        env:
-          MYSQL_DATABASE: casdoor
-          MYSQL_ROOT_PASSWORD: 123456
-        ports:
-          - 3306:3306
-        options: --health-cmd="mysqladmin ping" --health-interval=10s 
--health-timeout=5s --health-retries=3
     steps:
       - uses: actions/checkout@v3
       - uses: actions/setup-go@v4
diff --git a/.gitignore b/.gitignore
index 8611af4..87e9ff6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,12 @@ tmpFiles/
 *.tmp
 logs/
 data/agent-patches/
+
+# SQLite database and its WAL sidecar files
+*.db
+*.db-wal
+*.db-shm
+
 lastupdate.tmp
 commentsRouter*.go
 acme_account.key
diff --git a/Dockerfile b/Dockerfile
index fc4c663..fdfb531 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,15 +6,14 @@ RUN yarn install --frozen-lockfile --network-timeout 1000000 
&& yarn run build
 FROM golang:1.20.12 AS BACK
 WORKDIR /go/src/caswaf
 COPY . .
-RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server 
. \
-    && apt update && apt install wait-for-it && chmod +x /usr/bin/wait-for-it
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .
 
 FROM alpine:latest AS STANDARD
 LABEL MAINTAINER="https://caswaf.org/";
 
 COPY --from=BACK /go/src/caswaf/ ./
-COPY --from=BACK /usr/bin/wait-for-it ./
 RUN mkdir -p web/build && apk add --no-cache bash coreutils tzdata
 COPY --from=FRONT /web/build /web/build
-ENTRYPOINT ["./wait-for-it", "db:3306", "--", "./server"]
-
+# Holds the SQLite database at /data/caswaf.db.
+VOLUME /data
+ENTRYPOINT ["./server"]
diff --git a/README.md b/README.md
index 392744d..8c7e58f 100644
--- a/README.md
+++ b/README.md
@@ -49,11 +49,11 @@ Casbin Gateway contains 2 parts:
 | Name     | Description                            | Language               | 
Source code                                              |
 
|----------|----------------------------------------|------------------------|----------------------------------------------------------|
 | Frontend | Web frontend UI for Casbin Gateway     | Javascript + React     | 
https://github.com/apache/casbin-gateway/tree/master/web |
-| Backend  | RESTful API backend for Casbin Gateway | Golang + Beego + MySQL | 
https://github.com/apache/casbin-gateway                 |
+| Backend  | RESTful API backend for Casbin Gateway | Golang + Beego + XORM  | 
https://github.com/apache/casbin-gateway                 |
 
 ## Installation
 
-Casbin Gateway runs standalone out of the box: it signs users in against its 
own user table, seeding an `admin` account with the password `123` on first 
start. Connecting it to a [Casdoor](https://casdoor.org) instance is optional, 
and enables single sign-on plus the Casdoor-backed features listed under 
[Optional configuration](#optional-configuration).
+Casbin Gateway runs standalone out of the box: it stores its data in a local 
SQLite file, so there is no database server to install, and it signs users in 
against its own user table, seeding an `admin` account with the password `123` 
on first start. Connecting it to a [Casdoor](https://casdoor.org) instance is 
optional, and enables single sign-on plus the Casdoor-backed features listed 
under [Optional configuration](#optional-configuration).
 
 ### Deployment Options
 
@@ -65,19 +65,9 @@ The reverse-proxy gateway on ports 80 and 443 is disabled by 
default, so startin
 
 ### Quick start
 
-From nothing to a request flowing through the gateway, in five steps.
+From nothing to a request flowing through the gateway, in four steps. No 
database server is needed: Gateway creates `./data/caswaf.db` on first start.
 
-#### 1. Start MySQL
-
-Any MySQL 5.7+ reachable from Gateway will do. With Docker:
-
-```bash
-docker run -d --name caswaf-db -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123 
mysql:8.0.25
-```
-
-Gateway creates the `caswaf` database itself on first start. If your MySQL is 
elsewhere, or the password is not `123`, change `dataSourceName` in 
`conf/app.conf`.
-
-#### 2. Build the web UI
+#### 1. Build the web UI
 
 The backend serves the compiled frontend from `web/build`, so build it once 
before starting the backend:
 
@@ -87,7 +77,7 @@ cd web && yarn install && yarn build
 
 For frontend development, run `yarn start` instead. That serves the UI on 
http://localhost:16001 with hot reload and proxies API calls to the backend on 
port 17000, so both have to be running.
 
-#### 3. Run the backend
+#### 2. Run the backend
 
 ```bash
 go run main.go
@@ -104,7 +94,7 @@ It prints a summary of what it is actually doing — ports, 
whether the reverse
 | Reverse proxy  | enabled                                                    |
 | Gateway HTTP   | :8080                                                      |
 | Gateway HTTPS  | :8443                                                      |
-| Database       | mysql, database "caswaf" (connected)                       |
+| Database       | sqlite, file "./data/caswaf.db" (connected)                |
 | Sign-in        | built-in user table, Casdoor is not configured             |
 | App dir        | ./data/apps                                                |
 +----------------+-----------------------------------------------------------+
@@ -112,7 +102,7 @@ It prints a summary of what it is actually doing — ports, 
whether the reverse
 
 If a port is taken, Gateway says which process holds it and stops, rather than 
starting half-configured.
 
-#### 4. Sign in and add a site
+#### 3. Sign in and add a site
 
 Open http://localhost:17000, sign in as `admin` with the password `123`, and 
change it from the "My Account" page.
 
@@ -124,7 +114,7 @@ Then go to **Sites** → **Add**, and set:
 
 Save. Then set `gatewayEnabled = true` in `conf/app.conf` and restart the 
backend — the Sites page shows a warning while the reverse proxy is off, 
because site configurations do nothing until it is on.
 
-#### 5. Verify the forwarding
+#### 4. Verify the forwarding
 
 Start anything on the backend port you configured, for example:
 
@@ -156,13 +146,22 @@ git clone https://github.com/apache/casbin-gateway
 
 #### Setup database
 
-Casbin Gateway will store its users, nodes and topics information in a MySQL 
database named: `caswaf`, will create it if not existed. The DB connection 
string can be specified at: 
https://github.com/apache/casbin-gateway/blob/master/conf/app.conf
+Casbin Gateway stores its users, nodes and topics information in a SQLite 
file, created on first start. Nothing has to be installed or configured for 
this; the defaults in 
https://github.com/apache/casbin-gateway/blob/master/conf/app.conf are:
 
 ```ini
-dataSourceName = root:123@tcp(localhost:3306)/
+driverName = sqlite
+dataSourceName =
 ```
 
-Casbin Gateway uses XORM to connect to DB, so all DBs supported by XORM can 
also be used.
+An empty `dataSourceName` means `./data/caswaf.db`, relative to the working 
directory. Set it to another path to move the file.
+
+Casbin Gateway uses XORM to connect to DB, so all DBs supported by XORM can 
also be used. To use MySQL instead, point it at your server and Gateway creates 
the database named by `dbName` if it does not exist:
+
+```ini
+driverName = mysql
+dataSourceName = root:123@tcp(localhost:3306)/
+dbName = caswaf
+```
 
 #### Run Casbin Gateway
 
diff --git a/conf/app.conf b/conf/app.conf
index 0e1b530..05a080d 100644
--- a/conf/app.conf
+++ b/conf/app.conf
@@ -5,10 +5,14 @@ runmode = dev
 SessionOn = true
 copyrequestbody = true
 
-; Database. Casbin Gateway creates "dbName" on first start if it does not 
exist.
-; XORM is used, so mysql, postgres, mssql and sqlite are all supported.
-driverName = mysql
-dataSourceName = root:123@tcp(localhost:3306)/
+; Database. An empty "dataSourceName" means "./data/caswaf.db"; "dbName" is
+; unused by SQLite. XORM is used, so mysql, postgres, mssql and sqlite are all
+; supported. To use MySQL instead, replace the two lines below with:
+;   driverName = mysql
+;   dataSourceName = root:123@tcp(localhost:3306)/
+; Gateway then creates "dbName" on first start if it does not exist.
+driverName = sqlite
+dataSourceName =
 dbName = caswaf
 ; Optional Redis for sessions. Sessions are kept in ./tmp files when empty.
 redisEndpoint =
diff --git a/conf/conf.go b/conf/conf.go
index dc84c65..1de5baa 100644
--- a/conf/conf.go
+++ b/conf/conf.go
@@ -116,9 +116,38 @@ func GetConfigInt64(key string) (int64, error) {
        return num, err
 }
 
+// DefaultSqliteDataSourceName is used when "dataSourceName" is empty.
+const DefaultSqliteDataSourceName = "./data/caswaf.db"
+
+func GetConfigDriverName() string {
+       driverName := strings.Trim(GetConfigString("driverName"), `"' `)
+       if driverName == "" {
+               return "sqlite"
+       }
+
+       return driverName
+}
+
+// IsSqliteDriver accepts both spellings: modernc.org/sqlite registers itself 
as
+// "sqlite" while XORM calls its dialect "sqlite3".
+func IsSqliteDriver(driverName string) bool {
+       return driverName == "sqlite" || driverName == "sqlite3"
+}
+
 func GetConfigDataSourceName() string {
        dataSourceName := GetConfigString("dataSourceName")
 
+       // A SQLite data source is a file path, so the Docker host rewrite below
+       // does not apply to it.
+       if IsSqliteDriver(GetConfigDriverName()) {
+               dataSourceName = strings.Trim(dataSourceName, `"' `)
+               if dataSourceName == "" {
+                       return DefaultSqliteDataSourceName
+               }
+
+               return dataSourceName
+       }
+
        runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
        if runningInDocker == "true" {
                // 
https://stackoverflow.com/questions/48546124/what-is-linux-equivalent-of-host-docker-internal
diff --git a/docker-compose.yml b/docker-compose.yml
index 046d1fe..12ab16f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,22 +1,18 @@
 version: '3.1'
 services:
   caswaf:
+    restart: always
     build:
       context: ./
       dockerfile: Dockerfile
     ports:
       - "17000:17000"
-    depends_on:
-      - db
-    command: ["./wait-for-it", "db:3306", "--", "./server"]
-    volumes:
-      - ./conf:/conf/
-  db:
-    restart: always
-    image: mysql:8.0.25
-    ports:
-      - "3306:3306"
     environment:
-      MYSQL_ROOT_PASSWORD: 123
+      RUNNING_IN_DOCKER: "true"
     volumes:
-      - /usr/local/docker/mysql:/var/lib/mysql
\ No newline at end of file
+      - ./conf:/conf/
+      # Holds the SQLite database, so the data survives "docker compose down".
+      - caswaf-data:/data
+
+volumes:
+  caswaf-data:
diff --git a/k8s/.env.example b/k8s/.env.example
index 6ef8c78..ac74cf9 100644
--- a/k8s/.env.example
+++ b/k8s/.env.example
@@ -1,10 +1,9 @@
 # Example configuration for Casbin Gateway Kubernetes deployment
 # Copy this file and modify for your environment
 
-# MySQL Configuration
-MYSQL_ROOT_PASSWORD=changeme123
-MYSQL_HOST=caswaf-mysql
-MYSQL_PORT=3306
+# Database Configuration
+CASWAF_DRIVER_NAME=sqlite
+CASWAF_DATA_SOURCE_NAME=/data/caswaf.db
 
 # Casdoor Configuration
 CASDOOR_ENDPOINT=http://casdoor.casdoor-system.svc.cluster.local:8000
@@ -21,8 +20,8 @@ CASWAF_RUN_MODE=prod
 # Ingress Configuration
 CASWAF_DOMAIN=caswaf.example.com
 
-# Storage
-MYSQL_STORAGE_SIZE=10Gi
+# Storage for the SQLite database
+CASWAF_STORAGE_SIZE=10Gi
 
 # Resources (adjust based on your needs)
 CASWAF_CPU_REQUEST=100m
diff --git a/k8s/README.md b/k8s/README.md
index 0a8e62e..4ec5b5e 100644
--- a/k8s/README.md
+++ b/k8s/README.md
@@ -13,12 +13,16 @@ This guide provides instructions for deploying Casbin 
Gateway on Kubernetes.
 
 The deployment consists of:
 - **Casbin Gateway Application**: The main WAF application
-- **MySQL Database**: Stores Casbin Gateway configuration and data
-- **Secrets**: Stores sensitive credentials (Casdoor client ID/secret, MySQL 
password)
+- **PersistentVolumeClaim**: Holds the SQLite database at `/data/caswaf.db`. 
There is no database server to deploy
+- **Secrets**: Stores sensitive credentials (Casdoor client ID/secret)
 - **ConfigMap**: Contains Casbin Gateway configuration template
-- **Services**: Exposes Casbin Gateway and MySQL within the cluster
+- **Service**: Exposes Casbin Gateway within the cluster
 - **Ingress** (optional): Exposes Casbin Gateway externally
 
+Because SQLite is a single file written by one process, the Deployment stays at
+one replica and uses the `Recreate` strategy. Switch to MySQL or PostgreSQL 
(see
+[Using an external database](#using-an-external-database)) before scaling out.
+
 ## Quick Start
 
 ### 1. Deploy Casdoor (if not already deployed)
@@ -38,14 +42,11 @@ Edit `k8s/secret.yaml` and update the sensitive credentials:
 stringData:
   casdoor-client-id: "YOUR_ACTUAL_CLIENT_ID"
   casdoor-client-secret: "YOUR_ACTUAL_CLIENT_SECRET"
-  mysql-password: "YOUR_STRONG_PASSWORD"
 ```
 
 **Important**: 
 - **REQUIRED**: You must replace all placeholder values before deployment
 - Get `casdoor-client-id` and `casdoor-client-secret` from your Casdoor 
application settings
-- Use a strong password for `mysql-password` (min 12 characters recommended)
-- This password must match the one in `k8s/mysql.yaml`
 - The deployment will fail with validation errors if placeholders are not 
replaced
 
 **Security Best Practice**: Never commit actual secrets to version control. 
Consider using:
@@ -53,16 +54,11 @@ stringData:
 - [External Secrets Operator](https://external-secrets.io/)
 - Cloud provider secret management (AWS Secrets Manager, Azure Key Vault, GCP 
Secret Manager)
 
-### 3. Configure MySQL Password
-
-Edit `k8s/mysql.yaml` and update the MySQL root password:
+### 3. Review the data volume
 
-```bash
-# Generate base64 encoded password (use the same password as in secret.yaml)
-echo -n "YOUR_SECURE_PASSWORD" | base64
-```
-
-Then update the `mysql-root-password` in the Secret resource with the base64 
value.
+`k8s/pvc.yaml` requests 10Gi of `ReadWriteOnce` storage for the SQLite 
database.
+Adjust the size, or add a `storageClassName`, if your cluster has no default
+storage class.
 
 ### 4. Configure Casdoor Endpoint (Optional)
 
@@ -86,7 +82,7 @@ chmod +x deploy.sh
 
 The script will:
 - Validate your configuration
-- Deploy MySQL and wait for it to be ready
+- Create the namespace and the data volume
 - Deploy secrets and configuration
 - Deploy Casbin Gateway application
 - Optionally deploy Ingress
@@ -95,11 +91,11 @@ The script will:
 **Option B: Using individual files**
 
 ```bash
-# Create namespace and deploy MySQL
-kubectl apply -f k8s/mysql.yaml
+# Create the namespace
+kubectl apply -f k8s/namespace.yaml
 
-# Wait for MySQL to be ready
-kubectl wait --for=condition=ready pod -l app=caswaf-mysql -n caswaf 
--timeout=300s
+# Create the volume that holds the SQLite database
+kubectl apply -f k8s/pvc.yaml
 
 # Deploy Secrets
 kubectl apply -f k8s/secret.yaml
@@ -152,8 +148,7 @@ kubectl port-forward svc/caswaf 17000:17000 -n caswaf
 
 Stores sensitive credentials:
 - `casdoor-client-id`: Casdoor application client ID
-- `casdoor-client-secret`: Casdoor application client secret  
-- `mysql-password`: MySQL root password (must match mysql.yaml)
+- `casdoor-client-secret`: Casdoor application client secret
 
 **Security Note**: Never commit actual secrets to version control. Use 
sealed-secrets, external secret operators, or other secret management solutions 
in production.
 
@@ -165,9 +160,9 @@ Key configuration parameters:
 |-----------|-------------|---------|
 | `httpport` | Casbin Gateway HTTP port | `17000` |
 | `runmode` | Run mode (dev/prod) | `prod` |
-| `driverName` | Database driver | `mysql` |
-| `dataSourceName` | MySQL connection string | Uses secrets substitution |
-| `dbName` | Database name | `caswaf` |
+| `driverName` | Database driver | `sqlite` |
+| `dataSourceName` | SQLite file, or the connection string of a database 
server | `/data/caswaf.db` |
+| `dbName` | Database name (unused by SQLite) | `caswaf` |
 | `casdoorEndpoint` | Casdoor API endpoint | Required |
 | `casdoorInsecureSkipVerify` | Skip TLS verification for Casdoor | `true` |
 | `clientId` | Casdoor application client ID | Uses secrets substitution |
@@ -175,43 +170,38 @@ Key configuration parameters:
 | `casdoorOrganization` | Casdoor organization name | `built-in` |
 | `casdoorApplication` | Casdoor application name | Required |
 
-### MySQL Deployment (`mysql.yaml`)
+### Data Volume (`pvc.yaml`)
 
-- Uses MySQL 8.0.36 (latest stable)
-- Persistent storage with PVC (10Gi)
-- Includes health checks (liveness and readiness probes)
-- Root password stored in Kubernetes Secret with obvious placeholder
+- 10Gi `ReadWriteOnce` claim mounted at `/data`
+- Holds `caswaf.db`, the SQLite database, plus its `-wal` and `-shm` sidecar 
files
+- Deleting the claim deletes all Gateway data
 
 ### Casbin Gateway Deployment (`deployment.yaml`)
 
 Features:
-- Init containers:
-  - Wait for MySQL readiness
-  - Substitute secrets into configuration file
+- Init container that substitutes secrets into the configuration file
 - TCP-based liveness and readiness probes (no authentication required)
 - Resource limits and requests
 - Configuration mounted from ConfigMap with secret substitution
+- One replica with the `Recreate` strategy, because the SQLite file takes a
+  single writer
 
 ## Troubleshooting
 
 ### Common Issues
 
-#### 1. "wait-for-it: timeout occurred after waiting 15 seconds for db:3306"
-
-**Note**: This issue is fixed in the current deployment by using an init 
container instead of wait-for-it.
+#### 1. Pod stuck in `Pending`
 
-**Cause**: MySQL is not ready or not accessible
+**Cause**: The `caswaf-data-pvc` claim is unbound, usually because the cluster 
has no default storage class.
 
 **Solution**:
 ```bash
-# Check MySQL pod status
-kubectl get pods -n caswaf -l app=caswaf-mysql
-
-# Check MySQL logs
-kubectl logs -n caswaf -l app=caswaf-mysql
+# Check the claim and why it is not bound
+kubectl get pvc -n caswaf
+kubectl describe pvc caswaf-data-pvc -n caswaf
 
-# Verify MySQL service
-kubectl get svc -n caswaf caswaf-mysql
+# List the available storage classes, then set storageClassName in k8s/pvc.yaml
+kubectl get storageclass
 ```
 
 #### 2. "casdoorsdk.GetCerts() error: Unauthorized operation"
@@ -236,20 +226,26 @@ kubectl get svc -n caswaf caswaf-mysql
 
 #### 3. Database Connection Issues
 
+The startup summary in the pod log names the database Gateway actually opened,
+and says whether it answered.
+
 **Solution**:
 ```bash
-# Test MySQL connection from Casbin Gateway pod
+# Confirm the SQLite file is on the volume
+kubectl exec -it deployment/caswaf -n caswaf -- ls -l /data
+
+# For an external database server, test that it is reachable from the pod
 kubectl exec -it deployment/caswaf -n caswaf -- sh
-# Then inside the pod:
-# nc -zv caswaf-mysql 3306
+# Then inside the pod, e.g.:
+# nc -zv mysql.example.com 3306
 ```
 
 #### 4. Init Container Stuck
 
-If the init container is stuck waiting for MySQL:
+If the `setup-config` init container never finishes:
 ```bash
 # Check init container logs
-kubectl logs -n caswaf <pod-name> -c wait-for-mysql
+kubectl logs -n caswaf <pod-name> -c setup-config
 
 # Force restart
 kubectl rollout restart deployment/caswaf -n caswaf
@@ -261,16 +257,13 @@ kubectl rollout restart deployment/caswaf -n caswaf
 # Casbin Gateway logs
 kubectl logs -f deployment/caswaf -n caswaf
 
-# MySQL logs
-kubectl logs -f deployment/caswaf-mysql -n caswaf
-
 # All logs in namespace
 kubectl logs -f -n caswaf --all-containers=true
 ```
 
 ## Production Recommendations
 
-1. **Use External MySQL**: For production, consider using a managed MySQL 
service (AWS RDS, Google Cloud SQL, etc.)
+1. **Use an External Database**: The SQLite default is fine for a single 
replica. For production, consider a managed MySQL or PostgreSQL service (AWS 
RDS, Google Cloud SQL, etc.) — see [Using an external 
database](#using-an-external-database)
 
 2. **Configure TLS**: 
    - Set `casdoorInsecureSkipVerify = false`
@@ -288,8 +281,9 @@ kubectl logs -f -n caswaf --all-containers=true
    ```
 
 4. **High Availability**:
-   - Increase replicas for Casbin Gateway
-   - Use MySQL replication or managed service
+   - Move to an external database first: more than one replica cannot share 
the SQLite file
+   - Then increase replicas for Casbin Gateway
+   - Use database replication or a managed service
    - Configure proper health checks
 
 5. **Monitoring**: Set up monitoring and alerting:
@@ -297,7 +291,10 @@ kubectl logs -f -n caswaf --all-containers=true
    - Application logs
    - Resource usage
 
-6. **Backup**: Regular backups of MySQL data
+6. **Backup**: Regular backups of the database. For SQLite that is the 
`caswaf-data-pvc` volume:
+   ```bash
+   kubectl exec deployment/caswaf -n caswaf -- tar cf - /data > caswaf-data.tar
+   ```
 
 7. **Security**:
    - Use Kubernetes Secrets for sensitive data
@@ -325,7 +322,10 @@ kubectl rollout status deployment/caswaf -n caswaf
 kubectl delete -f k8s/ingress.yaml
 kubectl delete -f k8s/deployment.yaml
 kubectl delete -f k8s/configmap.yaml
-kubectl delete -f k8s/mysql.yaml
+kubectl delete -f k8s/secret.yaml
+
+# Deleting the claim destroys the SQLite database
+kubectl delete -f k8s/pvc.yaml
 
 # Or delete the entire namespace
 kubectl delete namespace caswaf
@@ -333,14 +333,18 @@ kubectl delete namespace caswaf
 
 ## Advanced Configuration
 
-### Using External MySQL
+### Using an external database
 
 Edit `configmap.yaml`:
 ```yaml
-dataSourceName: root:password@tcp(external-mysql.example.com:3306)/
+driverName = mysql
+dataSourceName = root:password@tcp(external-mysql.example.com:3306)/
+dbName = caswaf
 ```
 
-Then skip deploying `mysql.yaml`.
+Gateway creates `dbName` on first start if it does not exist. Once the data no
+longer lives on the volume you can drop the `data` volume and its mount from
+`deployment.yaml`, stop deploying `pvc.yaml`, and raise `replicas`.
 
 ### Using Redis for Sessions
 
diff --git a/k8s/configmap.yaml b/k8s/configmap.yaml
index cd775dc..854b7c0 100644
--- a/k8s/configmap.yaml
+++ b/k8s/configmap.yaml
@@ -11,8 +11,10 @@ data:
     runmode = prod
     SessionOn = true
     copyrequestbody = true
-    driverName = mysql
-    dataSourceName = root:MYSQL_PASSWORD_PLACEHOLDER@tcp(caswaf-mysql:3306)/
+    # A SQLite file on the PersistentVolumeClaim. For MySQL, set
+    # driverName = mysql and dataSourceName = root:PASSWORD@tcp(host:3306)/
+    driverName = sqlite
+    dataSourceName = /data/caswaf.db
     dbName = caswaf
     redisEndpoint =
     gatewayEnabled = false
diff --git a/k8s/deploy.sh b/k8s/deploy.sh
index 2a2c6df..41a6c53 100755
--- a/k8s/deploy.sh
+++ b/k8s/deploy.sh
@@ -42,7 +42,7 @@ if ! command -v kubectl &> /dev/null; then
 fi
 
 # Check if we're in the k8s directory
-if [ ! -f "secret.yaml" ] || [ ! -f "deployment.yaml" ]; then
+if [ ! -f "secret.yaml" ] || [ ! -f "deployment.yaml" ] || [ ! -f "pvc.yaml" 
]; then
     echo -e "${RED}Error: Please run this script from the k8s directory${NC}"
     exit 1
 fi
@@ -55,33 +55,16 @@ if grep -q "CHANGEME_INSECURE_DEFAULT_REPLACE_THIS" 
secret.yaml; then
     echo "Please edit k8s/secret.yaml and replace all placeholder values with 
your actual credentials:"
     echo "  - casdoor-client-id"
     echo "  - casdoor-client-secret"
-    echo "  - mysql-password"
-    echo
-    echo "Also update k8s/mysql.yaml with the base64 encoded password:"
-    echo "  echo -n 'your-password' | base64"
-    exit 1
-fi
-
-if grep -q "CHANGEME_INSECURE_DEFAULT_REPLACE_THIS" mysql.yaml; then
-    echo -e "${RED}Error: MySQL password has not been configured!${NC}"
-    echo "Please edit k8s/mysql.yaml and set a strong password (base64 
encoded)"
-    echo "  echo -n 'your-strong-password' | base64"
     exit 1
 fi
 
 echo -e "${GREEN}✓ Configuration looks good${NC}"
 echo
 
-echo -e "${YELLOW}Step 2: Deploying MySQL...${NC}"
-kubectl apply -f mysql.yaml
-
-echo "Waiting for MySQL to be ready..."
-kubectl wait --for=condition=ready pod -l app=caswaf-mysql -n caswaf 
--timeout=300s || {
-    echo -e "${RED}Error: MySQL failed to start${NC}"
-    echo "Check logs with: kubectl logs -n caswaf -l app=caswaf-mysql"
-    exit 1
-}
-echo -e "${GREEN}✓ MySQL is ready${NC}"
+echo -e "${YELLOW}Step 2: Creating the namespace and the data volume...${NC}"
+kubectl apply -f namespace.yaml
+kubectl apply -f pvc.yaml
+echo -e "${GREEN}✓ Namespace and data volume created${NC}"
 echo
 
 echo -e "${YELLOW}Step 3: Deploying Casbin Gateway configuration...${NC}"
diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml
index 9b4c177..357a15b 100644
--- a/k8s/deployment.yaml
+++ b/k8s/deployment.yaml
@@ -6,6 +6,9 @@ metadata:
   namespace: caswaf
 spec:
   replicas: 1
+  # The old pod has to release the SQLite file before the new one starts.
+  strategy:
+    type: Recreate
   selector:
     matchLabels:
       app: caswaf
@@ -15,17 +18,6 @@ spec:
         app: caswaf
     spec:
       initContainers:
-        - name: wait-for-mysql
-          image: busybox:1.36
-          command:
-            - sh
-            - -c
-            - |
-              until timeout 1 sh -c 'cat < /dev/null > 
/dev/tcp/caswaf-mysql/3306' 2>/dev/null; do
-                echo "Waiting for MySQL to be ready..."
-                sleep 2
-              done
-              echo "MySQL is ready!"
         - name: setup-config
           image: busybox:1.36
           command:
@@ -34,13 +26,8 @@ spec:
             - |
               # Define placeholder values for validation
               PLACEHOLDER="CHANGEME_INSECURE_DEFAULT_REPLACE_THIS"
-              
+
               # Validate that secrets have been replaced
-              if [ "$MYSQL_PASSWORD" = "$PLACEHOLDER" ]; then
-                echo "ERROR: MySQL password has not been configured!"
-                echo "Please update k8s/secret.yaml with your actual MySQL 
password"
-                exit 1
-              fi
               if [ "$CASDOOR_CLIENT_ID" = "$PLACEHOLDER" ]; then
                 echo "ERROR: Casdoor client ID has not been configured!"
                 echo "Please update k8s/secret.yaml with your actual Casdoor 
client ID"
@@ -54,16 +41,10 @@ spec:
               
               # All validations passed, proceed with configuration
               cp /tmp/config/app.conf /conf-out/app.conf
-              sed -i "s/MYSQL_PASSWORD_PLACEHOLDER/${MYSQL_PASSWORD}/g" 
/conf-out/app.conf
               sed -i "s/CLIENT_ID_PLACEHOLDER/${CASDOOR_CLIENT_ID}/g" 
/conf-out/app.conf
               sed -i "s/CLIENT_SECRET_PLACEHOLDER/${CASDOOR_CLIENT_SECRET}/g" 
/conf-out/app.conf
               echo "Configuration prepared successfully"
           env:
-            - name: MYSQL_PASSWORD
-              valueFrom:
-                secretKeyRef:
-                  name: caswaf-secrets
-                  key: mysql-password
             - name: CASDOOR_CLIENT_ID
               valueFrom:
                 secretKeyRef:
@@ -92,6 +73,8 @@ spec:
           volumeMounts:
             - name: config
               mountPath: /conf
+            - name: data
+              mountPath: /data
           livenessProbe:
             tcpSocket:
               port: 17000
@@ -119,6 +102,9 @@ spec:
             name: caswaf-config
         - name: config
           emptyDir: {}
+        - name: data
+          persistentVolumeClaim:
+            claimName: caswaf-data-pvc
 ---
 apiVersion: v1
 kind: Service
diff --git a/k8s/kustomization.yaml b/k8s/kustomization.yaml
index ee4ba03..92a9e19 100644
--- a/k8s/kustomization.yaml
+++ b/k8s/kustomization.yaml
@@ -5,7 +5,8 @@ kind: Kustomization
 namespace: caswaf
 
 resources:
-  - mysql.yaml
+  - namespace.yaml
+  - pvc.yaml
   - secret.yaml
   - configmap.yaml
   - deployment.yaml
diff --git a/k8s/mysql.yaml b/k8s/mysql.yaml
deleted file mode 100644
index 65fe544..0000000
--- a/k8s/mysql.yaml
+++ /dev/null
@@ -1,98 +0,0 @@
----
-apiVersion: v1
-kind: Namespace
-metadata:
-  name: caswaf
----
-apiVersion: v1
-kind: Secret
-metadata:
-  name: caswaf-mysql-secret
-  namespace: caswaf
-type: Opaque
-data:
-  # REQUIRED: Change this to your own strong password (base64 encoded)
-  # Generate with: echo -n "your-strong-password" | base64
-  # This is a placeholder that will fail validation if not replaced
-  mysql-root-password: Q0hBTkdFTUVfSU5TRUNVUkVfREVGQVVMVF9SRVBMQUNFX1RISVM=
----
-apiVersion: v1
-kind: PersistentVolumeClaim
-metadata:
-  name: mysql-pvc
-  namespace: caswaf
-spec:
-  accessModes:
-    - ReadWriteOnce
-  resources:
-    requests:
-      storage: 10Gi
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: caswaf-mysql
-  namespace: caswaf
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      app: caswaf-mysql
-  template:
-    metadata:
-      labels:
-        app: caswaf-mysql
-    spec:
-      containers:
-        - name: mysql
-          image: mysql:8.0.36
-          ports:
-            - containerPort: 3306
-              name: mysql
-          env:
-            - name: MYSQL_ROOT_PASSWORD
-              valueFrom:
-                secretKeyRef:
-                  name: caswaf-mysql-secret
-                  key: mysql-root-password
-          volumeMounts:
-            - name: mysql-storage
-              mountPath: /var/lib/mysql
-          livenessProbe:
-            exec:
-              command:
-                - mysqladmin
-                - ping
-                - -h
-                - localhost
-            initialDelaySeconds: 30
-            periodSeconds: 10
-            timeoutSeconds: 5
-          readinessProbe:
-            exec:
-              command:
-                - mysqladmin
-                - ping
-                - -h
-                - localhost
-            initialDelaySeconds: 10
-            periodSeconds: 5
-            timeoutSeconds: 1
-      volumes:
-        - name: mysql-storage
-          persistentVolumeClaim:
-            claimName: mysql-pvc
----
-apiVersion: v1
-kind: Service
-metadata:
-  name: caswaf-mysql
-  namespace: caswaf
-spec:
-  selector:
-    app: caswaf-mysql
-  ports:
-    - port: 3306
-      targetPort: 3306
-      name: mysql
-  clusterIP: None
diff --git a/k8s/namespace.yaml b/k8s/namespace.yaml
new file mode 100644
index 0000000..df1f468
--- /dev/null
+++ b/k8s/namespace.yaml
@@ -0,0 +1,5 @@
+---
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: caswaf
diff --git a/k8s/pvc.yaml b/k8s/pvc.yaml
new file mode 100644
index 0000000..bcd0d6c
--- /dev/null
+++ b/k8s/pvc.yaml
@@ -0,0 +1,13 @@
+---
+# Holds the SQLite database at /data/caswaf.db.
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: caswaf-data-pvc
+  namespace: caswaf
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 10Gi
diff --git a/k8s/secret.yaml b/k8s/secret.yaml
index 4ee3551..b6f8655 100644
--- a/k8s/secret.yaml
+++ b/k8s/secret.yaml
@@ -10,6 +10,3 @@ stringData:
   # Get these from your Casdoor application settings
   casdoor-client-id: "CHANGEME_INSECURE_DEFAULT_REPLACE_THIS"
   casdoor-client-secret: "CHANGEME_INSECURE_DEFAULT_REPLACE_THIS"
-  # MySQL password - REQUIRED: Replace with a strong password
-  # This must match the password in mysql.yaml secret
-  mysql-password: "CHANGEME_INSECURE_DEFAULT_REPLACE_THIS"
diff --git a/object/ormer.go b/object/ormer.go
index 7fb213b..d04ad29 100644
--- a/object/ormer.go
+++ b/object/ormer.go
@@ -18,6 +18,8 @@ import (
        "database/sql"
        "flag"
        "fmt"
+       "os"
+       "path/filepath"
        "runtime"
        "strings"
 
@@ -28,9 +30,15 @@ import (
        _ "github.com/lib/pq"                // db = postgres
        "github.com/xorm-io/core"
        "github.com/xorm-io/xorm"
-       _ "modernc.org/sqlite" // db = sqlite
+       sqlitedriver "modernc.org/sqlite" // db = sqlite
 )
 
+func init() {
+       // modernc.org/sqlite registers itself as "sqlite" only, so "sqlite3" in
+       // app.conf would otherwise fail with an unknown driver.
+       sql.Register("sqlite3", &sqlitedriver.Driver{})
+}
+
 var (
        ormer                   *Ormer = nil
        isCreateDatabaseDefined        = false
@@ -63,14 +71,16 @@ func InitConfig() {
 }
 
 func InitAdapter() {
+       driverName := conf.GetConfigDriverName()
+
        if createDatabase {
-               err := 
createDatabaseForPostgres(conf.GetConfigString("driverName"), 
conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
+               err := createDatabaseForPostgres(driverName, 
conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
                if err != nil {
                        panic(err)
                }
        }
 
-       ormer = NewAdapter(conf.GetConfigString("driverName"), 
conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
+       ormer = NewAdapter(driverName, conf.GetConfigDataSourceName(), 
conf.GetConfigString("dbName"))
 
        tableNamePrefix := conf.GetConfigString("tableNamePrefix")
        tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
@@ -98,7 +108,7 @@ func PingDatabase() error {
        return ormer.Engine.Ping()
 }
 
-// Ormer represents the MySQL adapter for policy storage.
+// Ormer represents the database adapter for policy storage.
 type Ormer struct {
        driverName     string
        dataSourceName string
@@ -156,6 +166,11 @@ func (a *Ormer) CreateDatabase() error {
                return nil
        }
 
+       // SQLite has no "CREATE DATABASE": open() creates the file.
+       if conf.IsSqliteDriver(a.driverName) {
+               return nil
+       }
+
        engine, err := xorm.NewEngine(a.driverName, a.dataSourceName)
        if err != nil {
                return err
@@ -172,14 +187,56 @@ func (a *Ormer) open() {
                dataSourceName = a.dataSourceName
        }
 
+       isSqlite := conf.IsSqliteDriver(a.driverName)
+       if isSqlite {
+               err := prepareSqliteDir(dataSourceName)
+               if err != nil {
+                       panic(err)
+               }
+
+               dataSourceName = withSqlitePragmas(dataSourceName)
+       }
+
        engine, err := xorm.NewEngine(a.driverName, dataSourceName)
        if err != nil {
                panic(err)
        }
 
+       if isSqlite {
+               // SQLite takes a single writer at a time.
+               engine.SetMaxOpenConns(1)
+       }
+
        a.Engine = engine
 }
 
+// prepareSqliteDir creates the directory holding the SQLite file, since a 
fresh
+// checkout has no ./data yet.
+func prepareSqliteDir(dataSourceName string) error {
+       dir := filepath.Dir(dataSourceName)
+       if dir == "" || dir == "." {
+               return nil
+       }
+
+       return os.MkdirAll(dir, 0o755)
+}
+
+// withSqlitePragmas keeps SQLite usable under concurrent requests: WAL lets
+// readers run alongside the writer, and busy_timeout waits instead of failing
+// with SQLITE_BUSY.
+func withSqlitePragmas(dataSourceName string) string {
+       if strings.Contains(dataSourceName, "_pragma=") {
+               return dataSourceName
+       }
+
+       separator := "?"
+       if strings.Contains(dataSourceName, "?") {
+               separator = "&"
+       }
+
+       return dataSourceName + separator + 
"_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)"
+}
+
 func (a *Ormer) close() {
        _ = a.Engine.Close()
        a.Engine = nil
diff --git a/service/startup.go b/service/startup.go
index 9dd105f..4aa5540 100644
--- a/service/startup.go
+++ b/service/startup.go
@@ -78,8 +78,13 @@ func describeGatewayPort(port int) string {
 
 func describeDatabase() string {
        // The connection string holds the database password, so only the 
driver and
-       // the database name are ever printed.
-       target := fmt.Sprintf("%s, database \"%s\"", 
unquote(conf.GetConfigString("driverName")), 
unquote(conf.GetConfigString("dbName")))
+       // the database name are ever printed. A SQLite path carries no 
credentials,
+       // so that one is printed in full.
+       driverName := conf.GetConfigDriverName()
+       target := fmt.Sprintf("%s, database \"%s\"", unquote(driverName), 
unquote(conf.GetConfigString("dbName")))
+       if conf.IsSqliteDriver(driverName) {
+               target = fmt.Sprintf("%s, file \"%s\"", driverName, 
conf.GetConfigDataSourceName())
+       }
 
        err := object.PingDatabase()
        if err != nil {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to