From 719cae5a526b91c4c82b6d9387cced11ea3b18db Mon Sep 17 00:00:00 2001 From: Yi-Ting Chiu Date: Sun, 28 Sep 2025 01:09:44 -0700 Subject: [PATCH 1/4] feat: implemented encryption at rest --- README.md | 36 +++++++++++++++++++ bin/memos/main.go | 26 +++++++++----- go.mod | 1 + go.sum | 2 ++ internal/profile/profile.go | 6 ++++ scripts/Dockerfile | 47 +++++++++++++++++++++++- server/router/frontend/dist/index.html | 19 +++++++--- store/db/sqlite/sqlcipher_disabled.go | 32 +++++++++++++++++ store/db/sqlite/sqlcipher_enabled.go | 49 ++++++++++++++++++++++++++ store/db/sqlite/sqlite.go | 42 ++++++++++++---------- 10 files changed, 227 insertions(+), 33 deletions(-) create mode 100644 store/db/sqlite/sqlcipher_disabled.go create mode 100644 store/db/sqlite/sqlcipher_enabled.go diff --git a/README.md b/README.md index 00f988054d703..0bf7e8ab0d8ae 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,42 @@ Access Memos at `http://localhost:5230` and complete the initial setup. **Pro Tip**: The data directory stores all your notes, uploads, and settings. Include it in your backup strategy! +### 🔒 Database Encryption (for SQLite) + +For enhanced security, Memos supports transparent, full-database encryption for SQLite using **SQLCipher**. This "Encryption at Rest" feature protects your database file even if your server's file system is compromised. + +> [!IMPORTANT] +> This is **not** End-to-End Encryption (E2E). The Memos server holds the key in memory to process data. It protects the database file on the disk, not data from an attacker who has compromised the running application. + +Enabling this feature is a two-step process: building a special version of Memos and providing a key at runtime. + +#### Using Docker (Recommended) + +1. **Build the SQLCipher-enabled image:** + ```bash + docker build \ + --build-arg CGO_ENABLED=1 \ + --build-arg MEMOS_BUILD_TAGS="memos_sqlcipher libsqlite3 sqlite_omit_load_extension" \ + -t memos-sqlcipher \ + -f scripts/Dockerfile . + ``` + +2. **Run the container with the encryption key:** + Provide your secret key via the `MEMOS_SQLITE_ENCRYPTION_KEY` environment variable. + ```bash + docker run -d \ + --name memos \ + -p 5230:5230 \ + -v ~/.memos:/var/opt/memos \ + -e MEMOS_SQLITE_ENCRYPTION_KEY="your-super-secret-key" \ + memos-sqlcipher + ``` + +> [!WARNING] +> **Key Management is Your Responsibility.** If you lose your encryption key, your data is **permanently unrecoverable**. Back up your key in a secure location like a password manager. + +For detailed instructions, including how to encrypt an existing database, please see our full documentation on **[Database Encryption](https://www.usememos.com/docs/advanced-settings/database-encryption)**. + ## Sponsors Memos is made possible by the generous support of our sponsors. Their contributions help ensure the project's continued development, maintenance, and growth. diff --git a/bin/memos/main.go b/bin/memos/main.go index 48dad202d5fee..a5eabd3d2094f 100644 --- a/bin/memos/main.go +++ b/bin/memos/main.go @@ -25,15 +25,16 @@ var ( Short: `An open source, lightweight note-taking service. Easily capture and share your great thoughts.`, Run: func(_ *cobra.Command, _ []string) { instanceProfile := &profile.Profile{ - Mode: viper.GetString("mode"), - Addr: viper.GetString("addr"), - Port: viper.GetInt("port"), - UNIXSock: viper.GetString("unix-sock"), - Data: viper.GetString("data"), - Driver: viper.GetString("driver"), - DSN: viper.GetString("dsn"), - InstanceURL: viper.GetString("instance-url"), - Version: version.GetCurrentVersion(viper.GetString("mode")), + Mode: viper.GetString("mode"), + Addr: viper.GetString("addr"), + Port: viper.GetInt("port"), + UNIXSock: viper.GetString("unix-sock"), + Data: viper.GetString("data"), + Driver: viper.GetString("driver"), + DSN: viper.GetString("dsn"), + SQLiteEncryptionKey: viper.GetString("sqlite-encryption-key"), + InstanceURL: viper.GetString("instance-url"), + Version: version.GetCurrentVersion(viper.GetString("mode")), } if err := instanceProfile.Validate(); err != nil { panic(err) @@ -100,6 +101,7 @@ func init() { rootCmd.PersistentFlags().String("data", "", "data directory") rootCmd.PersistentFlags().String("driver", "sqlite", "database driver") rootCmd.PersistentFlags().String("dsn", "", "database source name(aka. DSN)") + rootCmd.PersistentFlags().String("sqlite-encryption-key", "", "SQLCipher key used to unlock the SQLite database (requires binary built with memos_sqlcipher)") rootCmd.PersistentFlags().String("instance-url", "", "the url of your memos instance") if err := viper.BindPFlag("mode", rootCmd.PersistentFlags().Lookup("mode")); err != nil { @@ -123,6 +125,9 @@ func init() { if err := viper.BindPFlag("dsn", rootCmd.PersistentFlags().Lookup("dsn")); err != nil { panic(err) } + if err := viper.BindPFlag("sqlite-encryption-key", rootCmd.PersistentFlags().Lookup("sqlite-encryption-key")); err != nil { + panic(err) + } if err := viper.BindPFlag("instance-url", rootCmd.PersistentFlags().Lookup("instance-url")); err != nil { panic(err) } @@ -132,6 +137,9 @@ func init() { if err := viper.BindEnv("instance-url", "MEMOS_INSTANCE_URL"); err != nil { panic(err) } + if err := viper.BindEnv("sqlite-encryption-key", "MEMOS_SQLITE_ENCRYPTION_KEY"); err != nil { + panic(err) + } } func printGreetings(profile *profile.Profile) { diff --git a/go.mod b/go.mod index 25b3e0dbeed40..06c83442ed500 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/labstack/echo/v4 v4.13.4 github.com/lib/pq v1.10.9 github.com/lithammer/shortuuid/v4 v4.2.0 + github.com/mattn/go-sqlite3 v1.14.32 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.10.1 github.com/spf13/viper v1.20.1 diff --git a/go.sum b/go.sum index b831664f65e02..7d1ff8bf3d342 100644 --- a/go.sum +++ b/go.sum @@ -285,6 +285,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= diff --git a/internal/profile/profile.go b/internal/profile/profile.go index 8d551d6698a49..72727eb67d8d4 100644 --- a/internal/profile/profile.go +++ b/internal/profile/profile.go @@ -28,6 +28,8 @@ type Profile struct { // Driver is the database driver // sqlite, mysql Driver string + // SQLiteEncryptionKey unlocks SQLCipher-protected SQLite databases when provided. + SQLiteEncryptionKey string // Version is the current version of server Version string // InstanceURL is the url of your memos instance. @@ -88,5 +90,9 @@ func (p *Profile) Validate() error { p.DSN = filepath.Join(dataDir, dbFile) } + if p.SQLiteEncryptionKey != "" && p.Driver != "sqlite" { + return errors.New("sqlite encryption key is only supported when using the sqlite driver") + } + return nil } diff --git a/scripts/Dockerfile b/scripts/Dockerfile index 38456b37e86b4..4699700160d5c 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -1,4 +1,12 @@ FROM golang:1.25-alpine AS backend +ARG MEMOS_BUILD_TAGS="" +ARG CGO_ENABLED=0 +ARG CGO_CFLAGS="" +ARG CGO_LDFLAGS="" +ENV CGO_ENABLED=${CGO_ENABLED} +ENV MEMOS_BUILD_TAGS=${MEMOS_BUILD_TAGS} +ENV CGO_CFLAGS=${CGO_CFLAGS} +ENV CGO_LDFLAGS=${CGO_LDFLAGS} WORKDIR /backend-build COPY go.mod go.sum ./ RUN go mod download @@ -7,13 +15,50 @@ COPY . . # Refer to `pnpm release` in package.json for the build command. RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ - go build -ldflags="-s -w" -o memos ./bin/memos/main.go + /bin/sh -eux <<'EOF' +if [ "${CGO_ENABLED}" = "1" ]; then + apk add --no-cache --virtual .build-deps build-base pkgconf + if printf "%s" "${MEMOS_BUILD_TAGS}" | grep -q "memos_sqlcipher"; then + apk add --no-cache --virtual .sqlcipher-build sqlcipher-dev + SQLCIPHER_CFLAGS="$(pkg-config --cflags sqlcipher)" + SQLCIPHER_LDFLAGS="$(pkg-config --libs sqlcipher)" + if [ ! -e /usr/lib/libsqlite3.so ]; then + ln -s /usr/lib/libsqlcipher.so /usr/lib/libsqlite3.so + fi + if [ -z "${CGO_CFLAGS}" ]; then + export CGO_CFLAGS="${SQLCIPHER_CFLAGS} -DSQLITE_HAS_CODEC" + else + export CGO_CFLAGS="${CGO_CFLAGS} ${SQLCIPHER_CFLAGS} -DSQLITE_HAS_CODEC" + fi + if [ -z "${CGO_LDFLAGS}" ]; then + export CGO_LDFLAGS="${SQLCIPHER_LDFLAGS}" + else + export CGO_LDFLAGS="${CGO_LDFLAGS} ${SQLCIPHER_LDFLAGS}" + fi + fi +fi + +go build -ldflags="-s -w" -tags="${MEMOS_BUILD_TAGS}" -o memos ./bin/memos/main.go + +if [ "${CGO_ENABLED}" = "1" ]; then + if apk info -e .sqlcipher-build >/dev/null 2>&1; then + apk del .sqlcipher-build + fi + apk del .build-deps +fi +EOF # Make workspace with above generated files. FROM alpine:latest AS monolithic +ARG MEMOS_BUILD_TAGS="" WORKDIR /usr/local/memos RUN apk add --no-cache tzdata +RUN if printf "%s" "$MEMOS_BUILD_TAGS" | grep -q "memos_sqlcipher"; then \ + apk add --no-cache sqlcipher sqlcipher-libs && \ + if [ -e /usr/lib/libsqlcipher.so ]; then ln -sf /usr/lib/libsqlcipher.so /usr/lib/libsqlite3.so; fi && \ + if [ -e /usr/lib/libsqlcipher.so.0 ]; then ln -sf /usr/lib/libsqlcipher.so.0 /usr/lib/libsqlcipher.so; fi; \ + fi ENV TZ="UTC" COPY --from=backend /backend-build/memos /usr/local/memos/ diff --git a/server/router/frontend/dist/index.html b/server/router/frontend/dist/index.html index a612ed1f7de3a..f5f9862781e4e 100644 --- a/server/router/frontend/dist/index.html +++ b/server/router/frontend/dist/index.html @@ -1,11 +1,22 @@ - + - + + + + + Memos + + + + + + - - No embeddable frontend found. + +
+ diff --git a/store/db/sqlite/sqlcipher_disabled.go b/store/db/sqlite/sqlcipher_disabled.go new file mode 100644 index 0000000000000..ca0a376d1a6dc --- /dev/null +++ b/store/db/sqlite/sqlcipher_disabled.go @@ -0,0 +1,32 @@ +//go:build !memos_sqlcipher + +package sqlite + +import ( + "database/sql" + + "github.com/pkg/errors" + + "github.com/usememos/memos/internal/profile" + + // Import the pure-Go SQLite driver. + _ "modernc.org/sqlite" +) + +func openSQLiteDB(profile *profile.Profile) (*sql.DB, error) { + if profile.SQLiteEncryptionKey != "" { + return nil, errors.New("sqlite encryption key provided but binary is not built with SQLCipher support; rebuild with -tags memos_sqlcipher") + } + + sqliteDB, err := sql.Open(sqliteModernDriver, profile.DSN) + if err != nil { + return nil, errors.Wrapf(err, "failed to open db with dsn: %s", profile.DSN) + } + + if err := configureSQLiteConnection(sqliteDB); err != nil { + sqliteDB.Close() + return nil, err + } + + return sqliteDB, nil +} diff --git a/store/db/sqlite/sqlcipher_enabled.go b/store/db/sqlite/sqlcipher_enabled.go new file mode 100644 index 0000000000000..1b98aa4bf64b5 --- /dev/null +++ b/store/db/sqlite/sqlcipher_enabled.go @@ -0,0 +1,49 @@ +//go:build memos_sqlcipher + +package sqlite + +import ( + "database/sql" + "fmt" + "strings" + + "github.com/pkg/errors" + + "github.com/usememos/memos/internal/profile" + + // Import the CGO-backed SQLCipher-compatible SQLite driver. + _ "github.com/mattn/go-sqlite3" +) + +func openSQLiteDB(profile *profile.Profile) (*sql.DB, error) { + sqliteDB, err := sql.Open(sqliteCipherDriver, profile.DSN) + if err != nil { + return nil, errors.Wrapf(err, "failed to open db with dsn: %s", profile.DSN) + } + + if err := applySQLiteEncryptionKey(sqliteDB, profile.SQLiteEncryptionKey); err != nil { + sqliteDB.Close() + return nil, err + } + + if err := configureSQLiteConnection(sqliteDB); err != nil { + sqliteDB.Close() + return nil, err + } + + return sqliteDB, nil +} + +func applySQLiteEncryptionKey(db *sql.DB, key string) error { + if key == "" { + return nil + } + + escapedKey := strings.ReplaceAll(key, "'", "''") + pragma := fmt.Sprintf("PRAGMA key = '%s'", escapedKey) + if _, err := db.Exec(pragma); err != nil { + return errors.Wrap(err, "failed to apply sqlite encryption key; verify the binary is linked against SQLCipher") + } + + return nil +} diff --git a/store/db/sqlite/sqlite.go b/store/db/sqlite/sqlite.go index 3b4a30f8d4fba..31737fee41f17 100644 --- a/store/db/sqlite/sqlite.go +++ b/store/db/sqlite/sqlite.go @@ -3,12 +3,10 @@ package sqlite import ( "context" "database/sql" + "fmt" "github.com/pkg/errors" - // Import the SQLite driver. - _ "modernc.org/sqlite" - "github.com/usememos/memos/internal/profile" "github.com/usememos/memos/store" ) @@ -21,29 +19,21 @@ type DB struct { // NewDB opens a database specified by its database driver name and a // driver-specific data source name, usually consisting of at least a // database name and connection information. +const ( + sqliteBusyTimeout = 10000 + sqliteModernDriver = "sqlite" + sqliteCipherDriver = "sqlite3" +) + func NewDB(profile *profile.Profile) (store.Driver, error) { // Ensure a DSN is set before attempting to open the database. if profile.DSN == "" { return nil, errors.New("dsn required") } - // Connect to the database with some sane settings: - // - No shared-cache: it's obsolete; WAL journal mode is a better solution. - // - No foreign key constraints: it's currently disabled by default, but it's a - // good practice to be explicit and prevent future surprises on SQLite upgrades. - // - Journal mode set to WAL: it's the recommended journal mode for most applications - // as it prevents locking issues. - // - // Notes: - // - When using the `modernc.org/sqlite` driver, each pragma must be prefixed with `_pragma=`. - // - // References: - // - https://pkg.go.dev/modernc.org/sqlite#Driver.Open - // - https://www.sqlite.org/sharedcache.html - // - https://www.sqlite.org/pragma.html - sqliteDB, err := sql.Open("sqlite", profile.DSN+"?_pragma=foreign_keys(0)&_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)") + sqliteDB, err := openSQLiteDB(profile) if err != nil { - return nil, errors.Wrapf(err, "failed to open db with dsn: %s", profile.DSN) + return nil, err } driver := DB{db: sqliteDB, profile: profile} @@ -51,6 +41,20 @@ func NewDB(profile *profile.Profile) (store.Driver, error) { return &driver, nil } +func configureSQLiteConnection(db *sql.DB) error { + pragmas := []string{ + "PRAGMA foreign_keys = OFF", + fmt.Sprintf("PRAGMA busy_timeout = %d", sqliteBusyTimeout), + "PRAGMA journal_mode = WAL", + } + for _, pragma := range pragmas { + if _, err := db.Exec(pragma); err != nil { + return errors.Wrapf(err, "failed to execute %s", pragma) + } + } + return nil +} + func (d *DB) GetDB() *sql.DB { return d.db } From 32841dd00430d590b2b4b73f084f18f086e2c45f Mon Sep 17 00:00:00 2001 From: Yi-Ting Chiu Date: Sun, 28 Sep 2025 02:01:02 -0700 Subject: [PATCH 2/4] docs: Update README.md --- README.md | 98 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 0bf7e8ab0d8ae..ef5336095be7f 100644 --- a/README.md +++ b/README.md @@ -97,39 +97,75 @@ Access Memos at `http://localhost:5230` and complete the initial setup. ### 🔒 Database Encryption (for SQLite) -For enhanced security, Memos supports transparent, full-database encryption for SQLite using **SQLCipher**. This "Encryption at Rest" feature protects your database file even if your server's file system is compromised. +
+ +Memos can protect its SQLite database with **SQLCipher** so that the on-disk file is unreadable without a passphrase. This is *encryption at rest*: the server keeps the key in memory while running, so it does not provide end-to-end encryption for clients. > [!IMPORTANT] -> This is **not** End-to-End Encryption (E2E). The Memos server holds the key in memory to process data. It protects the database file on the disk, not data from an attacker who has compromised the running application. - -Enabling this feature is a two-step process: building a special version of Memos and providing a key at runtime. - -#### Using Docker (Recommended) - -1. **Build the SQLCipher-enabled image:** - ```bash - docker build \ - --build-arg CGO_ENABLED=1 \ - --build-arg MEMOS_BUILD_TAGS="memos_sqlcipher libsqlite3 sqlite_omit_load_extension" \ - -t memos-sqlcipher \ - -f scripts/Dockerfile . - ``` - -2. **Run the container with the encryption key:** - Provide your secret key via the `MEMOS_SQLITE_ENCRYPTION_KEY` environment variable. - ```bash - docker run -d \ - --name memos \ - -p 5230:5230 \ - -v ~/.memos:/var/opt/memos \ - -e MEMOS_SQLITE_ENCRYPTION_KEY="your-super-secret-key" \ - memos-sqlcipher - ``` - -> [!WARNING] -> **Key Management is Your Responsibility.** If you lose your encryption key, your data is **permanently unrecoverable**. Back up your key in a secure location like a password manager. - -For detailed instructions, including how to encrypt an existing database, please see our full documentation on **[Database Encryption](https://www.usememos.com/docs/advanced-settings/database-encryption)**. +> Losing the passphrase means losing your data. Store it safely (for example, in a password manager or a hardware secret vault). + +#### Enable SQLCipher Builds + +- **Docker (recommended)** + ```bash + docker build \ + --build-arg CGO_ENABLED=1 \ + --build-arg MEMOS_BUILD_TAGS="memos_sqlcipher libsqlite3 sqlite_omit_load_extension" \ + -t memos-sqlcipher \ + -f scripts/Dockerfile . + docker run -d \ + --name memos \ + -p 5230:5230 \ + -v ~/.memos:/var/opt/memos \ + -e MEMOS_SQLITE_ENCRYPTION_KEY="your-super-secret-key" \ + memos-sqlcipher + ``` + +- **Manual build** + ```bash + CGO_ENABLED=1 \ + CGO_CFLAGS="-I/usr/include/sqlcipher -DSQLITE_HAS_CODEC" \ + CGO_LDFLAGS="-lsqlcipher" \ + go build -tags "memos_sqlcipher libsqlite3 sqlite_omit_load_extension" -o memos-sqlcipher ./bin/memos + ./memos-sqlcipher --sqlite-encryption-key "your-super-secret-key" ... + ``` + +#### Migration Plan for Existing Deployments + +1. **Full backup** + ```bash + cp ~/.memos/memos_prod.db ~/.memos/memos_prod.db.bak + cp ~/.memos/memos_prod.db-wal ~/.memos/memos_prod.db-wal.bak 2>/dev/null || true + cp ~/.memos/memos_prod.db-shm ~/.memos/memos_prod.db-shm.bak 2>/dev/null || true + ``` + +2. **Stop every Memos instance** touching the database. + +3. **Build the SQLCipher-capable binary or Docker image** using the instructions above. The resulting image already contains the `sqlcipher` CLI. + +4. **Convert the database** using the SQLCipher CLI. You can do this without installing anything on the host: + ```bash + docker run --rm \ + -v ~/.memos:/data \ + memos-sqlcipher \ + sh -c "cd /data && sqlcipher memos_prod.db <<'EOS'\nATTACH DATABASE 'memos_encrypted.db' AS encrypted KEY 'your-super-secret-key';\nSELECT sqlcipher_export('encrypted');\nDETACH DATABASE encrypted;\nEOS" + ``` + If you prefer to run the command directly on the host, install `sqlcipher` (e.g. `brew install sqlcipher`, `apt install sqlcipher`) and execute the same `ATTACH ... sqlcipher_export` sequence locally. + +6. **Swap the files** + ```bash + mv memos_prod.db memos_prod.db.plaintext + mv memos_encrypted.db memos_prod.db + rm -f memos_prod.db-wal memos_prod.db-shm + ``` + +7. **Start the SQLCipher build of Memos** and pass the same key (`MEMOS_SQLITE_ENCRYPTION_KEY` or `--sqlite-encryption-key`). + +8. **Verify the upgrade** + - Log in and ensure your memos/attachments are intact. + - Confirm the file is encrypted: `sqlite3 memos_prod.db '.tables'` should now print `Error: file is not a database`. + +
## Sponsors From 054eca7bf26ef9a7e52e397555220b382951605f Mon Sep 17 00:00:00 2001 From: Yi-Ting Chiu Date: Sun, 28 Sep 2025 03:16:16 -0700 Subject: [PATCH 3/4] revert: server/router/frontend/dist/index.html This index.html probably modified after I ran pnpm command. Wonder why it's not in .gitignore... Anyway, it's not related to this pr, so I guess I will revert it to make sure the pr is clean. --- server/router/frontend/dist/index.html | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/server/router/frontend/dist/index.html b/server/router/frontend/dist/index.html index f5f9862781e4e..a612ed1f7de3a 100644 --- a/server/router/frontend/dist/index.html +++ b/server/router/frontend/dist/index.html @@ -1,22 +1,11 @@ - + - - - - - + Memos - - - - - - - -
- + + No embeddable frontend found. From 1bc53360843ac3d37f9e5ac9ca0ef87aa1998cae Mon Sep 17 00:00:00 2001 From: Yi-Ting Chiu Date: Sun, 28 Sep 2025 03:55:50 -0700 Subject: [PATCH 4/4] chore: fix linter --- store/db/sqlite/sqlcipher_enabled.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/store/db/sqlite/sqlcipher_enabled.go b/store/db/sqlite/sqlcipher_enabled.go index 1b98aa4bf64b5..d6b65f9e90696 100644 --- a/store/db/sqlite/sqlcipher_enabled.go +++ b/store/db/sqlite/sqlcipher_enabled.go @@ -11,8 +11,8 @@ import ( "github.com/usememos/memos/internal/profile" - // Import the CGO-backed SQLCipher-compatible SQLite driver. - _ "github.com/mattn/go-sqlite3" + // Import the CGO-backed SQLCipher-compatible SQLite driver. + _ "github.com/mattn/go-sqlite3" ) func openSQLiteDB(profile *profile.Profile) (*sql.DB, error) {