diff --git a/Makefile b/Makefile
index fdb54bfc1a..77f690265e 100644
--- a/Makefile
+++ b/Makefile
@@ -95,7 +95,7 @@ VERSION = ${GITEA_VERSION}
 # SemVer
 FORGEJO_VERSION := 6.0.0+0-gitea-1.21.0
 
-LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "code.gitea.io/gitea/routers/api/forgejo/v1.ForgejoVersion=$(FORGEJO_VERSION)"
+LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "code.gitea.io/gitea/routers/api/forgejo/v1.ForgejoVersion=$(FORGEJO_VERSION)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"
 
 LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
 
diff --git a/main.go b/main.go
index 775c729c56..700b75d748 100644
--- a/main.go
+++ b/main.go
@@ -31,8 +31,11 @@ var (
 	MakeVersion = ""            // "make" program version if built with make
 )
 
+var ForgejoVersion = "1.0.0"
+
 func init() {
 	setting.AppVer = Version
+	setting.ForgejoVersion = ForgejoVersion
 	setting.AppBuiltWith = formatBuiltWith()
 	setting.AppStartTime = time.Now().UTC()
 }
diff --git a/models/db/engine.go b/models/db/engine.go
index 182d8cd993..b5a41f93e3 100755
--- a/models/db/engine.go
+++ b/models/db/engine.go
@@ -47,6 +47,7 @@ type Engine interface {
 	Incr(column string, arg ...any) *xorm.Session
 	Insert(...any) (int64, error)
 	Iterate(any, xorm.IterFunc) error
+	IsTableExist(any) (bool, error)
 	Join(joinOperator string, tablename, condition any, args ...any) *xorm.Session
 	SQL(any, ...any) *xorm.Session
 	Where(any, ...any) *xorm.Session
diff --git a/models/db/engine_test.go b/models/db/engine_test.go
index e3dbfbdc24..c9ae5f1542 100644
--- a/models/db/engine_test.go
+++ b/models/db/engine_test.go
@@ -68,6 +68,7 @@ func TestPrimaryKeys(t *testing.T) {
 
 	whitelist := map[string]string{
 		"the_table_name_to_skip_checking": "Write a note here to explain why",
+		"forgejo_sem_ver":                 "seriously dude",
 	}
 
 	for _, bean := range beans {
diff --git a/models/forgejo/semver/main_test.go b/models/forgejo/semver/main_test.go
new file mode 100644
index 0000000000..34c4f55395
--- /dev/null
+++ b/models/forgejo/semver/main_test.go
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+
+package semver
+
+import (
+	"path/filepath"
+	"testing"
+
+	"code.gitea.io/gitea/models/unittest"
+
+	_ "code.gitea.io/gitea/models"
+	_ "code.gitea.io/gitea/models/actions"
+	_ "code.gitea.io/gitea/models/activities"
+)
+
+func TestMain(m *testing.M) {
+	unittest.MainTest(m, &unittest.TestOptions{
+		GiteaRootPath: filepath.Join("..", "..", ".."),
+	})
+}
diff --git a/models/forgejo/semver/semver.go b/models/forgejo/semver/semver.go
new file mode 100644
index 0000000000..50923d0a09
--- /dev/null
+++ b/models/forgejo/semver/semver.go
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: MIT
+
+package semver
+
+import (
+	"context"
+
+	"code.gitea.io/gitea/models/db"
+
+	"github.com/hashicorp/go-version"
+)
+
+func init() {
+	db.RegisterModel(new(ForgejoSemVer))
+}
+
+type ForgejoSemVer struct {
+	Version string
+}
+
+func GetVersion(ctx context.Context) (*version.Version, error) {
+	return GetVersionWithEngine(db.GetEngine(ctx))
+}
+
+func GetVersionWithEngine(e db.Engine) (*version.Version, error) {
+	versionString := "v1.0.0"
+	exists, err := e.IsTableExist("forgejo_sem_ver")
+	if err != nil {
+		return nil, err
+	}
+	if exists {
+		var semver ForgejoSemVer
+		has, err := e.Get(&semver)
+		if err != nil {
+			return nil, err
+		} else if has {
+			versionString = semver.Version
+		}
+	}
+
+	v, err := version.NewVersion(versionString)
+	if err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+func SetVersionString(ctx context.Context, versionString string) error {
+	return SetVersionStringWithEngine(db.GetEngine(ctx), versionString)
+}
+
+func SetVersionStringWithEngine(e db.Engine, versionString string) error {
+	v, err := version.NewVersion(versionString)
+	if err != nil {
+		return err
+	}
+	return SetVersionWithEngine(e, v)
+}
+
+func SetVersion(ctx context.Context, v *version.Version) error {
+	return SetVersionWithEngine(db.GetEngine(ctx), v)
+}
+
+func SetVersionWithEngine(e db.Engine, v *version.Version) error {
+	var semver ForgejoSemVer
+	has, err := e.Get(&semver)
+	if err != nil {
+		return err
+	}
+
+	if !has {
+		_, err = e.Exec("insert into forgejo_sem_ver values (?)", v.String())
+	} else {
+		_, err = e.Exec("update forgejo_sem_ver set version = ?", v.String())
+	}
+	return err
+}
diff --git a/models/forgejo/semver/semver_test.go b/models/forgejo/semver/semver_test.go
new file mode 100644
index 0000000000..8aca7bee57
--- /dev/null
+++ b/models/forgejo/semver/semver_test.go
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: MIT
+
+package semver
+
+import (
+	"testing"
+
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/models/unittest"
+
+	"github.com/hashicorp/go-version"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestForgejoSemVerSetGet(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+	ctx := db.DefaultContext
+
+	newVersion, err := version.NewVersion("v1.2.3")
+	assert.NoError(t, err)
+	assert.NoError(t, SetVersionString(ctx, newVersion.String()))
+	databaseVersion, err := GetVersion(ctx)
+	assert.NoError(t, err)
+	assert.EqualValues(t, newVersion.String(), databaseVersion.String())
+	assert.True(t, newVersion.Equal(databaseVersion))
+}
+
+func TestForgejoSemVerMissing(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+	ctx := db.DefaultContext
+	e := db.GetEngine(ctx)
+
+	_, err := e.Exec("delete from forgejo_sem_ver")
+	assert.NoError(t, err)
+
+	v, err := GetVersion(ctx)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "1.0.0", v.String())
+
+	_, err = e.Exec("drop table forgejo_sem_ver")
+	assert.NoError(t, err)
+
+	v, err = GetVersion(ctx)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "1.0.0", v.String())
+}
diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go
index 268edef4f9..2becf1b713 100644
--- a/models/forgejo_migrations/migrate.go
+++ b/models/forgejo_migrations/migrate.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"os"
 
+	"code.gitea.io/gitea/models/forgejo/semver"
 	forgejo_v1_20 "code.gitea.io/gitea/models/forgejo_migrations/v1_20"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
@@ -141,5 +142,10 @@ func Migrate(x *xorm.Engine) error {
 			return err
 		}
 	}
-	return nil
+
+	if err := x.Sync(new(semver.ForgejoSemVer)); err != nil {
+		return fmt.Errorf("sync: %w", err)
+	}
+
+	return semver.SetVersionStringWithEngine(x, setting.ForgejoVersion)
 }
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index d444d9a017..ebfd3b27be 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -15,6 +15,8 @@ import (
 	"code.gitea.io/gitea/modules/user"
 )
 
+var ForgejoVersion = "1.0.0"
+
 // settings
 var (
 	// AppVer is the version of the current build of Gitea. It is set in main.go from main.Version.