From 40e79ac6dfdeef9a7dd08b567adeb2b7b39f6568 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sat, 20 Jun 2026 18:06:41 +0000 Subject: [PATCH 01/28] chore(version): pre-release bump to 06.18.04-dev [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index e37779f96d..bd20f138d1 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.18.03 + 06.18.04 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index d162991038..c44f9fb3bd 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.18.03 +# VERSION: 06.18.04 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0 From 86bbad9fa18084433c4884d085a219069cbd1102 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 15:50:25 -0500 Subject: [PATCH 02/28] =?UTF-8?q?feat:=20add=20licensing=20tables=20?= =?UTF-8?q?=E2=80=94=20license,=20entitlement,=20activation,=20product=5Ft?= =?UTF-8?q?ier=20(#617)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration v359 creates 4 tables for the consumer DLID licensing system: - license: DLID-based licenses with tier, status, expiry - license_entitlement: per-license product/repo access mapping - license_activation: domain tracking with limit enforcement - product_tier: 13 seeded tiers from base to enterprise Models in models/licensing/ with CRUD, DLID generation (CRC32 checksum), entitlement rebuild on tier change, and domain activation with upsert. --- models/licensing/activation.go | 105 +++++++++++++++++++++ models/licensing/entitlement.go | 109 ++++++++++++++++++++++ models/licensing/license.go | 153 +++++++++++++++++++++++++++++++ models/licensing/product_tier.go | 58 ++++++++++++ models/migrations/migrations.go | 1 + models/migrations/v1_27/v359.go | 99 ++++++++++++++++++++ 6 files changed, 525 insertions(+) create mode 100644 models/licensing/activation.go create mode 100644 models/licensing/entitlement.go create mode 100644 models/licensing/license.go create mode 100644 models/licensing/product_tier.go create mode 100644 models/migrations/v1_27/v359.go diff --git a/models/licensing/activation.go b/models/licensing/activation.go new file mode 100644 index 0000000000..289204c68b --- /dev/null +++ b/models/licensing/activation.go @@ -0,0 +1,105 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later + +package licensing + +import ( + "context" + + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil" +) + +func init() { + db.RegisterModel(new(LicenseActivation)) +} + +// LicenseActivation tracks a domain that has activated a license. +type LicenseActivation struct { + ID int64 `xorm:"pk autoincr"` + LicenseID int64 `xorm:"INDEX NOT NULL"` + Domain string `xorm:"VARCHAR(255) NOT NULL"` + IPAddress string `xorm:"VARCHAR(64)"` + JoomlaVer string `xorm:"VARCHAR(20)"` + ActivatedAt timeutil.TimeStamp `xorm:"CREATED"` + LastSeenAt timeutil.TimeStamp +} + +func (LicenseActivation) TableName() string { + return "license_activation" +} + +// GetActivationsByLicense returns all domain activations for a license. +func GetActivationsByLicense(ctx context.Context, licenseID int64) ([]*LicenseActivation, error) { + var acts []*LicenseActivation + return acts, db.GetEngine(ctx).Where("license_id = ?", licenseID).Find(&acts) +} + +// CountActivations returns the number of activated domains for a license. +func CountActivations(ctx context.Context, licenseID int64) (int64, error) { + return db.GetEngine(ctx).Where("license_id = ?", licenseID).Count(new(LicenseActivation)) +} + +// ActivateDomain registers a domain for a license. Returns the activation +// (existing or new) and whether it was newly created. +func ActivateDomain(ctx context.Context, licenseID int64, domain, ip, joomlaVer string, maxDomains int) (*LicenseActivation, bool, error) { + // Check if already activated + existing := new(LicenseActivation) + has, err := db.GetEngine(ctx). + Where("license_id = ? AND domain = ?", licenseID, domain). + Get(existing) + if err != nil { + return nil, false, err + } + if has { + // Update last seen + existing.LastSeenAt = timeutil.TimeStampNow() + existing.IPAddress = ip + if joomlaVer != "" { + existing.JoomlaVer = joomlaVer + } + _, _ = db.GetEngine(ctx).ID(existing.ID).Cols("last_seen_at", "ip_address", "joomla_ver").Update(existing) + return existing, false, nil + } + + // Check domain limit (0 = unlimited) + if maxDomains > 0 { + count, err := CountActivations(ctx, licenseID) + if err != nil { + return nil, false, err + } + if count >= int64(maxDomains) { + return nil, false, ErrDomainLimitReached{LicenseID: licenseID, Max: maxDomains} + } + } + + act := &LicenseActivation{ + LicenseID: licenseID, + Domain: domain, + IPAddress: ip, + JoomlaVer: joomlaVer, + } + _, err = db.GetEngine(ctx).Insert(act) + if err != nil { + return nil, false, err + } + return act, true, nil +} + +// DeactivateDomain removes a domain activation. +func DeactivateDomain(ctx context.Context, licenseID int64, domain string) error { + _, err := db.GetEngine(ctx). + Where("license_id = ? AND domain = ?", licenseID, domain). + Delete(new(LicenseActivation)) + return err +} + +// ErrDomainLimitReached is returned when a license has reached its max activated domains. +type ErrDomainLimitReached struct { + LicenseID int64 + Max int +} + +func (e ErrDomainLimitReached) Error() string { + return "license domain limit reached" +} diff --git a/models/licensing/entitlement.go b/models/licensing/entitlement.go new file mode 100644 index 0000000000..d7c9fb4376 --- /dev/null +++ b/models/licensing/entitlement.go @@ -0,0 +1,109 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later + +package licensing + +import ( + "context" + + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil" +) + +func init() { + db.RegisterModel(new(LicenseEntitlement)) +} + +// LicenseEntitlement maps a license to an individual product (repo) it can access. +type LicenseEntitlement struct { + ID int64 `xorm:"pk autoincr"` + LicenseID int64 `xorm:"INDEX NOT NULL"` + ProductCode string `xorm:"VARCHAR(30) NOT NULL"` + RepoOwner string `xorm:"VARCHAR(100) NOT NULL DEFAULT 'MokoConsulting'"` + RepoName string `xorm:"VARCHAR(100) NOT NULL"` + IsCustom bool `xorm:"NOT NULL DEFAULT false"` // true = manually added, survives tier changes + CreatedAt timeutil.TimeStamp `xorm:"CREATED"` +} + +func (LicenseEntitlement) TableName() string { + return "license_entitlement" +} + +// GetEntitlementsByLicense returns all entitlements for a license. +func GetEntitlementsByLicense(ctx context.Context, licenseID int64) ([]*LicenseEntitlement, error) { + var ents []*LicenseEntitlement + return ents, db.GetEngine(ctx).Where("license_id = ?", licenseID).Find(&ents) +} + +// HasEntitlement checks if a license has access to a specific product code. +func HasEntitlement(ctx context.Context, licenseID int64, productCode string) (bool, error) { + return db.GetEngine(ctx). + Where("license_id = ? AND product_code = ?", licenseID, productCode). + Exist(new(LicenseEntitlement)) +} + +// AddCustomEntitlement adds a manual entitlement that survives tier changes. +func AddCustomEntitlement(ctx context.Context, licenseID int64, productCode, repoName string) error { + ent := &LicenseEntitlement{ + LicenseID: licenseID, + ProductCode: productCode, + RepoOwner: "MokoConsulting", + RepoName: repoName, + IsCustom: true, + } + _, err := db.GetEngine(ctx).Insert(ent) + return err +} + +// RebuildEntitlements deletes non-custom entitlements and rebuilds from the product tier. +// Custom entitlements (manually added) are preserved. +func RebuildEntitlements(ctx context.Context, licenseID int64, tierKey string) error { + // Delete non-custom entitlements + _, err := db.GetEngine(ctx). + Where("license_id = ? AND is_custom = ?", licenseID, false). + Delete(new(LicenseEntitlement)) + if err != nil { + return err + } + + // Look up tier + tier, err := GetProductTierByKey(ctx, tierKey) + if err != nil || tier == nil { + return err + } + + // Parse repos JSON + var repos []string + if err := json.Unmarshal([]byte(tier.Repos), &repos); err != nil { + return err + } + + // Build product code from repo name (lowercase, stripped) + for _, repoName := range repos { + productCode := repoName + // Check if this entitlement already exists (custom) + exists, err := db.GetEngine(ctx). + Where("license_id = ? AND product_code = ?", licenseID, productCode). + Exist(new(LicenseEntitlement)) + if err != nil { + return err + } + if exists { + continue + } + + ent := &LicenseEntitlement{ + LicenseID: licenseID, + ProductCode: productCode, + RepoOwner: "MokoConsulting", + RepoName: repoName, + IsCustom: false, + } + if _, err := db.GetEngine(ctx).Insert(ent); err != nil { + return err + } + } + + return nil +} diff --git a/models/licensing/license.go b/models/licensing/license.go new file mode 100644 index 0000000000..a162db9fae --- /dev/null +++ b/models/licensing/license.go @@ -0,0 +1,153 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later + +package licensing + +import ( + "context" + "crypto/rand" + "encoding/hex" + "fmt" + "hash/crc32" + "strings" + "time" + + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil" +) + +func init() { + db.RegisterModel(new(License)) +} + +// License represents a consumer-facing license with a DLID (Download ID). +type License struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"INDEX NOT NULL"` + DLID string `xorm:"VARCHAR(36) UNIQUE NOT NULL"` + Tier string `xorm:"VARCHAR(30) NOT NULL DEFAULT 'base'"` + MaxDomains int `xorm:"NOT NULL DEFAULT 1"` + Status string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'active'"` // active, expired, revoked, suspended + ExpiresAt timeutil.TimeStamp `xorm:"INDEX"` + Notes string `xorm:"TEXT"` + CreatedAt timeutil.TimeStamp `xorm:"INDEX CREATED"` + UpdatedAt timeutil.TimeStamp `xorm:"UPDATED"` +} + +func (License) TableName() string { + return "license" +} + +// IsExpired returns true if the license has a set expiry that has passed. +func (l *License) IsExpired() bool { + if l.ExpiresAt == 0 { + return false + } + return time.Unix(int64(l.ExpiresAt), 0).Before(time.Now()) +} + +// IsActive returns true if the license status is "active" and not expired. +func (l *License) IsActive() bool { + return l.Status == "active" && !l.IsExpired() +} + +// GenerateDLID creates a new DLID: 28 random hex chars + 4 CRC32 checksum chars, +// formatted as 8-8-8-8 groups. +func GenerateDLID() (string, error) { + b := make([]byte, 14) // 14 bytes = 28 hex chars + if _, err := rand.Read(b); err != nil { + return "", err + } + prefix := hex.EncodeToString(b) + checksum := crc32.ChecksumIEEE([]byte(prefix)) + full := fmt.Sprintf("%s%04x", prefix, checksum&0xFFFF) + // Format as 8-8-8-8 + return fmt.Sprintf("%s-%s-%s-%s", full[0:8], full[8:16], full[16:24], full[24:32]), nil +} + +// ValidateDLIDFormat checks if a DLID has valid format and CRC32 checksum. +// This is a client-side check that catches typos without a database hit. +func ValidateDLIDFormat(dlid string) bool { + clean := strings.ReplaceAll(dlid, "-", "") + if len(clean) != 32 { + return false + } + // Validate hex + if _, err := hex.DecodeString(clean); err != nil { + return false + } + // CRC32 check: last 4 chars should match CRC32 of first 28 + prefix := clean[:28] + expected := fmt.Sprintf("%04x", crc32.ChecksumIEEE([]byte(prefix))&0xFFFF) + return clean[28:32] == expected +} + +// CreateLicense creates a new license with an auto-generated DLID. +func CreateLicense(ctx context.Context, userID int64, tier string, maxDomains int, expiresAt timeutil.TimeStamp) (*License, error) { + dlid, err := GenerateDLID() + if err != nil { + return nil, err + } + + license := &License{ + UserID: userID, + DLID: dlid, + Tier: tier, + MaxDomains: maxDomains, + Status: "active", + ExpiresAt: expiresAt, + } + + _, err = db.GetEngine(ctx).Insert(license) + if err != nil { + return nil, err + } + return license, nil +} + +// GetLicenseByDLID looks up a license by its DLID string. +func GetLicenseByDLID(ctx context.Context, dlid string) (*License, error) { + license := new(License) + has, err := db.GetEngine(ctx).Where("dlid = ?", dlid).Get(license) + if err != nil { + return nil, err + } + if !has { + return nil, nil + } + return license, nil +} + +// GetLicenseByID returns a license by primary key. +func GetLicenseByID(ctx context.Context, id int64) (*License, error) { + license := new(License) + has, err := db.GetEngine(ctx).ID(id).Get(license) + if err != nil { + return nil, err + } + if !has { + return nil, nil + } + return license, nil +} + +// GetLicensesByUser returns all licenses for a user. +func GetLicensesByUser(ctx context.Context, userID int64) ([]*License, error) { + var licenses []*License + return licenses, db.GetEngine(ctx).Where("user_id = ?", userID).Find(&licenses) +} + +// UpdateLicenseTier changes a license's tier and rebuilds entitlements. +func UpdateLicenseTier(ctx context.Context, licenseID int64, newTier string) error { + _, err := db.GetEngine(ctx).ID(licenseID).Cols("tier", "updated_at").Update(&License{Tier: newTier}) + if err != nil { + return err + } + return RebuildEntitlements(ctx, licenseID, newTier) +} + +// SetLicenseStatus updates the status field (active, expired, revoked, suspended). +func SetLicenseStatus(ctx context.Context, licenseID int64, status string) error { + _, err := db.GetEngine(ctx).ID(licenseID).Cols("status", "updated_at").Update(&License{Status: status}) + return err +} diff --git a/models/licensing/product_tier.go b/models/licensing/product_tier.go new file mode 100644 index 0000000000..01428e05e5 --- /dev/null +++ b/models/licensing/product_tier.go @@ -0,0 +1,58 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later + +package licensing + +import ( + "context" + + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json" +) + +func init() { + db.RegisterModel(new(ProductTier)) +} + +// ProductTier defines a licensing tier and its entitled repositories. +type ProductTier struct { + ID int64 `xorm:"pk autoincr"` + TierKey string `xorm:"VARCHAR(30) UNIQUE NOT NULL"` + TierName string `xorm:"VARCHAR(100) NOT NULL"` + Repos string `xorm:"TEXT"` // JSON array of repo names + MaxDomains int `xorm:"NOT NULL DEFAULT 1"` + SortOrder int `xorm:"NOT NULL DEFAULT 0"` +} + +func (ProductTier) TableName() string { + return "product_tier" +} + +// RepoList parses the Repos JSON field into a string slice. +func (t *ProductTier) RepoList() []string { + var repos []string + if t.Repos == "" { + return repos + } + _ = json.Unmarshal([]byte(t.Repos), &repos) + return repos +} + +// GetProductTierByKey looks up a tier by its key (e.g. "pos", "suite"). +func GetProductTierByKey(ctx context.Context, key string) (*ProductTier, error) { + tier := new(ProductTier) + has, err := db.GetEngine(ctx).Where("tier_key = ?", key).Get(tier) + if err != nil { + return nil, err + } + if !has { + return nil, nil + } + return tier, nil +} + +// GetAllProductTiers returns all tiers ordered by sort_order. +func GetAllProductTiers(ctx context.Context) ([]*ProductTier, error) { + var tiers []*ProductTier + return tiers, db.GetEngine(ctx).OrderBy("sort_order ASC").Find(&tiers) +} diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 2437a2faad..fc5ac382d9 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -435,6 +435,7 @@ func prepareMigrationTasks() []*migration { newMigration(355, "Migrate update server metadata to repo manifest", v1_27.MigrateUpdateServerFieldsToManifest), newMigration(356, "Rename package_type to extension_type in repo manifest", v1_27.RenamePackageTypeToExtensionType), newMigration(357, "Drop display_name from repo manifest and update stream config", v1_27.DropDisplayNameColumns), + newMigration(358, "Add licensing tables (license, entitlement, activation, product_tier)", v1_27.AddLicensingTables), } return preparedMigrations } diff --git a/models/migrations/v1_27/v359.go b/models/migrations/v1_27/v359.go new file mode 100644 index 0000000000..ab35f207b1 --- /dev/null +++ b/models/migrations/v1_27/v359.go @@ -0,0 +1,99 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later + +package v1_27 + +import ( + "xorm.io/xorm" +) + +// AddLicensingTables creates the license, license_entitlement, license_activation, +// and product_tier tables for the consumer-facing DLID licensing system (#617). +func AddLicensingTables(x *xorm.Engine) error { + type License struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"INDEX NOT NULL"` + DLID string `xorm:"VARCHAR(36) UNIQUE NOT NULL"` + Tier string `xorm:"VARCHAR(30) NOT NULL DEFAULT 'base'"` + MaxDomains int `xorm:"NOT NULL DEFAULT 1"` + Status string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'active'"` + ExpiresAt int64 `xorm:"INDEX"` + Notes string `xorm:"TEXT"` + CreatedAt int64 `xorm:"INDEX CREATED"` + UpdatedAt int64 `xorm:"UPDATED"` + } + + type LicenseEntitlement struct { + ID int64 `xorm:"pk autoincr"` + LicenseID int64 `xorm:"INDEX NOT NULL"` + ProductCode string `xorm:"VARCHAR(30) NOT NULL"` + RepoOwner string `xorm:"VARCHAR(100) NOT NULL DEFAULT 'MokoConsulting'"` + RepoName string `xorm:"VARCHAR(100) NOT NULL"` + IsCustom bool `xorm:"NOT NULL DEFAULT false"` + CreatedAt int64 `xorm:"CREATED"` + } + + type LicenseActivation struct { + ID int64 `xorm:"pk autoincr"` + LicenseID int64 `xorm:"INDEX NOT NULL"` + Domain string `xorm:"VARCHAR(255) NOT NULL"` + IPAddress string `xorm:"VARCHAR(64)"` + JoomlaVer string `xorm:"VARCHAR(20)"` + ActivatedAt int64 `xorm:"CREATED"` + LastSeenAt int64 + } + + type ProductTier struct { + ID int64 `xorm:"pk autoincr"` + TierKey string `xorm:"VARCHAR(30) UNIQUE NOT NULL"` + TierName string `xorm:"VARCHAR(100) NOT NULL"` + Repos string `xorm:"TEXT"` + MaxDomains int `xorm:"NOT NULL DEFAULT 1"` + SortOrder int `xorm:"NOT NULL DEFAULT 0"` + } + + if err := x.Sync(new(License), new(LicenseEntitlement), new(LicenseActivation), new(ProductTier)); err != nil { + return err + } + + // Add composite unique indexes + if _, err := x.Exec("CREATE UNIQUE INDEX IF NOT EXISTS UQE_license_entitlement_lic_prod ON license_entitlement (license_id, product_code)"); err != nil { + // MySQL doesn't support IF NOT EXISTS for indexes — try without + x.Exec("CREATE UNIQUE INDEX UQE_license_entitlement_lic_prod ON license_entitlement (license_id, product_code)") + } + if _, err := x.Exec("CREATE UNIQUE INDEX IF NOT EXISTS UQE_license_activation_lic_domain ON license_activation (license_id, domain)"); err != nil { + x.Exec("CREATE UNIQUE INDEX UQE_license_activation_lic_domain ON license_activation (license_id, domain)") + } + + // Seed product tiers + tiers := []ProductTier{ + {TierKey: "base", TierName: "MokoSuite Base", Repos: `["MokoSuite"]`, MaxDomains: 1, SortOrder: 0}, + {TierKey: "crm", TierName: "MokoSuite CRM", Repos: `["MokoSuite","MokoSuiteCRM"]`, MaxDomains: 3, SortOrder: 10}, + {TierKey: "erp", TierName: "MokoSuite ERP", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP"]`, MaxDomains: 3, SortOrder: 20}, + {TierKey: "child", TierName: "MokoSuite Child", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteChild"]`, MaxDomains: 3, SortOrder: 25}, + {TierKey: "create", TierName: "MokoSuite Create", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteCreate"]`, MaxDomains: 3, SortOrder: 26}, + {TierKey: "npo", TierName: "MokoSuite NPO", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteNPO"]`, MaxDomains: 3, SortOrder: 27}, + {TierKey: "hrm", TierName: "MokoSuite HRM", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteHRM"]`, MaxDomains: 3, SortOrder: 30}, + {TierKey: "mrp", TierName: "MokoSuite MRP", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuiteMRP"]`, MaxDomains: 3, SortOrder: 35}, + {TierKey: "pos", TierName: "MokoSuite POS", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuitePOS"]`, MaxDomains: 5, SortOrder: 40}, + {TierKey: "shop", TierName: "MokoSuite Shop", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuiteShop"]`, MaxDomains: 5, SortOrder: 45}, + {TierKey: "restaurant", TierName: "MokoSuite Restaurant", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuitePOS","MokoSuiteRestaurant"]`, MaxDomains: 5, SortOrder: 50}, + {TierKey: "suite", TierName: "MokoSuite Suite", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuitePOS","MokoSuiteShop","MokoSuiteHRM","MokoSuiteMRP","MokoSuiteChild","MokoSuiteCreate","MokoSuiteNPO","MokoSuiteRestaurant","MokoSuiteForms"]`, MaxDomains: 10, SortOrder: 90}, + {TierKey: "enterprise", TierName: "MokoSuite Enterprise", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuitePOS","MokoSuiteShop","MokoSuiteHRM","MokoSuiteMRP","MokoSuiteChild","MokoSuiteCreate","MokoSuiteNPO","MokoSuiteRestaurant","MokoSuiteForms","MokoSuiteCommunity","MokoSuiteBackup","MokoSuiteStoreLocator","MokoSuiteOpenGraph","MokoSuiteCross"]`, MaxDomains: 0, SortOrder: 100}, + } + + for _, t := range tiers { + // Only insert if the tier doesn't already exist + count, err := x.Where("tier_key = ?", t.TierKey).Count(new(ProductTier)) + if err != nil { + return err + } + if count == 0 { + if _, err := x.Insert(&t); err != nil { + return err + } + } + } + + return nil +} -- 2.52.0 From 77da746d612ed90f9dccd27194b71d3e4dafa4cd Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sat, 20 Jun 2026 20:52:08 +0000 Subject: [PATCH 03/28] chore(version): pre-release bump to 06.18.05-dev [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index bd20f138d1..1aceae08a1 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.18.04 + 06.18.05 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index c44f9fb3bd..6bba08ea90 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.18.04 +# VERSION: 06.18.05 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0 From 7d5eff194dc80433369ef498b871779084e460a1 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 16:27:47 -0500 Subject: [PATCH 04/28] feat: add license CRUD, audit logging, status transitions (#618) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RevokeLicense, SuspendLicense, ReactivateLicense convenience methods - LicenseAuditLog table for tracking status and tier changes - UpdateLicenseTier now logs old→new tier before rebuilding entitlements - SetLicenseStatus logs old→new status transitions --- models/licensing/audit.go | 50 +++++++++++++++++++++++++++++++++ models/licensing/license.go | 41 +++++++++++++++++++++++---- models/migrations/v1_27/v359.go | 11 +++++++- 3 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 models/licensing/audit.go diff --git a/models/licensing/audit.go b/models/licensing/audit.go new file mode 100644 index 0000000000..cc97f38e8b --- /dev/null +++ b/models/licensing/audit.go @@ -0,0 +1,50 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later + +package licensing + +import ( + "context" + + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil" +) + +func init() { + db.RegisterModel(new(LicenseAuditLog)) +} + +// LicenseAuditLog records status transitions and other license events. +type LicenseAuditLog struct { + ID int64 `xorm:"pk autoincr"` + LicenseID int64 `xorm:"INDEX NOT NULL"` + Action string `xorm:"VARCHAR(50) NOT NULL"` // status_change, tier_change, domain_activate, domain_deactivate + OldValue string `xorm:"VARCHAR(100)"` + NewValue string `xorm:"VARCHAR(100)"` + CreatedAt timeutil.TimeStamp `xorm:"INDEX CREATED"` +} + +func (LicenseAuditLog) TableName() string { + return "license_audit_log" +} + +// LogLicenseAudit records a license event. +func LogLicenseAudit(ctx context.Context, licenseID int64, action, oldVal, newVal string) error { + entry := &LicenseAuditLog{ + LicenseID: licenseID, + Action: action, + OldValue: oldVal, + NewValue: newVal, + } + _, err := db.GetEngine(ctx).Insert(entry) + return err +} + +// GetAuditLog returns audit entries for a license, newest first. +func GetAuditLog(ctx context.Context, licenseID int64) ([]*LicenseAuditLog, error) { + var entries []*LicenseAuditLog + return entries, db.GetEngine(ctx). + Where("license_id = ?", licenseID). + OrderBy("created_at DESC"). + Find(&entries) +} diff --git a/models/licensing/license.go b/models/licensing/license.go index a162db9fae..c5daabf204 100644 --- a/models/licensing/license.go +++ b/models/licensing/license.go @@ -137,17 +137,48 @@ func GetLicensesByUser(ctx context.Context, userID int64) ([]*License, error) { return licenses, db.GetEngine(ctx).Where("user_id = ?", userID).Find(&licenses) } -// UpdateLicenseTier changes a license's tier and rebuilds entitlements. +// UpdateLicenseTier changes a license's tier, rebuilds entitlements, and logs the change. func UpdateLicenseTier(ctx context.Context, licenseID int64, newTier string) error { - _, err := db.GetEngine(ctx).ID(licenseID).Cols("tier", "updated_at").Update(&License{Tier: newTier}) + license, err := GetLicenseByID(ctx, licenseID) + if err != nil || license == nil { + return err + } + oldTier := license.Tier + _, err = db.GetEngine(ctx).ID(licenseID).Cols("tier", "updated_at").Update(&License{Tier: newTier}) if err != nil { return err } + if err := LogLicenseAudit(ctx, licenseID, "tier_change", oldTier, newTier); err != nil { + return err + } return RebuildEntitlements(ctx, licenseID, newTier) } -// SetLicenseStatus updates the status field (active, expired, revoked, suspended). +// SetLicenseStatus updates the status field and logs the transition. func SetLicenseStatus(ctx context.Context, licenseID int64, status string) error { - _, err := db.GetEngine(ctx).ID(licenseID).Cols("status", "updated_at").Update(&License{Status: status}) - return err + license, err := GetLicenseByID(ctx, licenseID) + if err != nil || license == nil { + return err + } + oldStatus := license.Status + _, err = db.GetEngine(ctx).ID(licenseID).Cols("status", "updated_at").Update(&License{Status: status}) + if err != nil { + return err + } + return LogLicenseAudit(ctx, licenseID, "status_change", oldStatus, status) +} + +// RevokeLicense permanently revokes a license. +func RevokeLicense(ctx context.Context, licenseID int64) error { + return SetLicenseStatus(ctx, licenseID, "revoked") +} + +// SuspendLicense temporarily suspends a license. +func SuspendLicense(ctx context.Context, licenseID int64) error { + return SetLicenseStatus(ctx, licenseID, "suspended") +} + +// ReactivateLicense restores a suspended or expired license to active. +func ReactivateLicense(ctx context.Context, licenseID int64) error { + return SetLicenseStatus(ctx, licenseID, "active") } diff --git a/models/migrations/v1_27/v359.go b/models/migrations/v1_27/v359.go index ab35f207b1..143f8e2e7b 100644 --- a/models/migrations/v1_27/v359.go +++ b/models/migrations/v1_27/v359.go @@ -52,7 +52,16 @@ func AddLicensingTables(x *xorm.Engine) error { SortOrder int `xorm:"NOT NULL DEFAULT 0"` } - if err := x.Sync(new(License), new(LicenseEntitlement), new(LicenseActivation), new(ProductTier)); err != nil { + type LicenseAuditLog struct { + ID int64 `xorm:"pk autoincr"` + LicenseID int64 `xorm:"INDEX NOT NULL"` + Action string `xorm:"VARCHAR(50) NOT NULL"` + OldValue string `xorm:"VARCHAR(100)"` + NewValue string `xorm:"VARCHAR(100)"` + CreatedAt int64 `xorm:"INDEX CREATED"` + } + + if err := x.Sync(new(License), new(LicenseEntitlement), new(LicenseActivation), new(ProductTier), new(LicenseAuditLog)); err != nil { return err } -- 2.52.0 From dada56899ca9c13ee181cb7769a77b74d10e77a9 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sat, 20 Jun 2026 21:30:12 +0000 Subject: [PATCH 05/28] chore(version): pre-release bump to 06.18.06-dev [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index 1aceae08a1..1e40625b6c 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.18.05 + 06.18.06 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 6bba08ea90..8959de1a58 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.18.05 +# VERSION: 06.18.06 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0 From 212b8c9e6ded52634be27f35cf86c9df6abbe77f Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 17:14:40 -0500 Subject: [PATCH 06/28] feat: add DLID-gated update XML endpoint for Joomla updater (#621) GET /api/v1/licensing/updates/{product}.xml?dlid=XXX&domain=YYY Validates DLID format (CRC32), checks license status and entitlement, auto-activates domain, returns Joomla-compatible update XML from the entitled repo's stable release. Invalid/expired returns empty . --- routers/api/v1/api.go | 6 + routers/api/v1/licensing/updates.go | 241 ++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 routers/api/v1/licensing/updates.go diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index d810b4ddeb..16605b5558 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -82,6 +82,7 @@ import ( "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/api/v1/activitypub" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/api/v1/admin" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/api/v1/licensing" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/api/v1/misc" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/api/v1/notify" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/api/v1/org" @@ -1858,6 +1859,11 @@ func Routes() *web.Router { m.Group("/topics", func() { m.Get("/search", repo.TopicSearch) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) + + // Licensing endpoints — DLID-gated, no token required + m.Group("/licensing", func() { + m.Get("/updates/{product}", licensing.ServeUpdates) + }) }, sudo()) return m diff --git a/routers/api/v1/licensing/updates.go b/routers/api/v1/licensing/updates.go new file mode 100644 index 0000000000..7a768d14b9 --- /dev/null +++ b/routers/api/v1/licensing/updates.go @@ -0,0 +1,241 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later + +package licensing + +import ( + "encoding/xml" + "fmt" + "net/http" + "strings" + + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db" + licensing_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licensing" + repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context" +) + +// Joomla update XML structures. + +type xmlUpdates struct { + XMLName xml.Name `xml:"updates"` + Updates []xmlUpdate `xml:"update"` +} + +type xmlUpdate struct { + Name string `xml:"name"` + Element string `xml:"element"` + Type string `xml:"type"` + Version string `xml:"version"` + Tag string `xml:"tag"` + DownloadURL xmlDownload `xml:"downloadurl"` + TargetPlatform xmlTarget `xml:"targetplatform"` + PHPMinimum string `xml:"php_minimum,omitempty"` +} + +type xmlDownload struct { + Type string `xml:"type,attr"` + Format string `xml:"format,attr"` + URL string `xml:",chardata"` +} + +type xmlTarget struct { + Name string `xml:"name,attr"` + Version string `xml:"version,attr"` +} + +// ServeUpdates handles GET /api/v1/licensing/updates/{product}.xml?dlid=XXX&domain=YYY +func ServeUpdates(ctx *context.APIContext) { + productFile := ctx.PathParam("product") + productCode := strings.TrimSuffix(productFile, ".xml") + dlid := ctx.FormString("dlid") + domain := ctx.FormString("domain") + + // Always return XML content type + ctx.Resp.Header().Set("Content-Type", "application/xml; charset=utf-8") + + // Validation failure → empty + if dlid == "" || !licensing_model.ValidateDLIDFormat(dlid) { + writeEmptyUpdates(ctx) + return + } + + // Look up license + license, err := licensing_model.GetLicenseByDLID(ctx, dlid) + if err != nil { + log.Error("ServeUpdates: GetLicenseByDLID: %v", err) + writeEmptyUpdates(ctx) + return + } + if license == nil || !license.IsActive() { + writeEmptyUpdates(ctx) + return + } + + // Check entitlement + hasAccess, err := licensing_model.HasEntitlement(ctx, license.ID, productCode) + if err != nil { + log.Error("ServeUpdates: HasEntitlement: %v", err) + writeEmptyUpdates(ctx) + return + } + if !hasAccess { + writeEmptyUpdates(ctx) + return + } + + // Auto-activate domain + if domain != "" { + ip := ctx.Req.RemoteAddr + if idx := strings.LastIndex(ip, ":"); idx >= 0 { + ip = ip[:idx] + } + _, _, err := licensing_model.ActivateDomain(ctx, license.ID, domain, ip, ctx.FormString("joomla_version"), license.MaxDomains) + if err != nil { + if _, ok := err.(licensing_model.ErrDomainLimitReached); ok { + writeEmptyUpdates(ctx) + return + } + log.Error("ServeUpdates: ActivateDomain: %v", err) + } + } + + // Resolve repo from entitlement + ents, err := licensing_model.GetEntitlementsByLicense(ctx, license.ID) + if err != nil { + log.Error("ServeUpdates: GetEntitlementsByLicense: %v", err) + writeEmptyUpdates(ctx) + return + } + + var repoOwner, repoName string + for _, ent := range ents { + if ent.ProductCode == productCode { + repoOwner = ent.RepoOwner + repoName = ent.RepoName + break + } + } + if repoName == "" { + writeEmptyUpdates(ctx) + return + } + + // Find the repo + repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, repoOwner, repoName) + if err != nil || repo == nil { + log.Error("ServeUpdates: repo %s/%s not found: %v", repoOwner, repoName, err) + writeEmptyUpdates(ctx) + return + } + + // Get stable release + releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{ + RepoID: repo.ID, + ListOptions: db.ListOptions{PageSize: 1, Page: 1}, + IncludeDrafts: false, + IncludeTags: false, + TagNames: []string{"stable"}, + }) + if err != nil || len(releases) == 0 { + // Try latest non-draft release + releases, err = db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{ + RepoID: repo.ID, + ListOptions: db.ListOptions{PageSize: 1, Page: 1}, + IncludeDrafts: false, + IncludeTags: false, + SortType: "newest", + }) + if err != nil || len(releases) == 0 { + writeEmptyUpdates(ctx) + return + } + } + + rel := releases[0] + version := extractVersion(rel.TagName) + if version == "" && rel.Title != "" { + version = extractVersion(rel.Title) + } + if version == "" { + version = rel.TagName + } + + // Get repo metadata for element name, type, etc. + manifest, _ := repo_model.GetRepoMetadata(ctx, repo.ID) + element := strings.ToLower(repoName) + extType := "package" + phpMin := "8.1" + targetVer := "6..*" + displayName := repoName + + if manifest != nil { + if e := manifest.FullElementName(); e != "" { + element = e + } + if manifest.ExtensionType != "" { + extType = manifest.ExtensionType + } + if manifest.PHPMinimum != "" { + phpMin = manifest.PHPMinimum + } + if manifest.TargetVersion != "" { + targetVer = manifest.TargetVersion + } + displayName = manifest.DerivedDisplayName() + } + + // Build download URL + baseURL := setting.AppURL + downloadURL := fmt.Sprintf("%sapi/v1/licensing/download/%s/%s.zip?dlid=%s", + baseURL, productCode, version, dlid) + + updates := xmlUpdates{ + Updates: []xmlUpdate{ + { + Name: displayName, + Element: element, + Type: extType, + Version: version, + Tag: "stable", + DownloadURL: xmlDownload{ + Type: "full", + Format: "zip", + URL: downloadURL, + }, + TargetPlatform: xmlTarget{ + Name: "joomla", + Version: targetVer, + }, + PHPMinimum: phpMin, + }, + }, + } + + output, err := xml.MarshalIndent(updates, "", " ") + if err != nil { + log.Error("ServeUpdates: xml.MarshalIndent: %v", err) + writeEmptyUpdates(ctx) + return + } + + ctx.Resp.WriteHeader(http.StatusOK) + _, _ = ctx.Resp.Write([]byte(xml.Header)) + _, _ = ctx.Resp.Write(output) +} + +func writeEmptyUpdates(ctx *context.APIContext) { + ctx.Resp.WriteHeader(http.StatusOK) + _, _ = ctx.Resp.Write([]byte(xml.Header + "\n")) +} + +// extractVersion strips common tag prefixes to get a clean version. +func extractVersion(s string) string { + v := s + for _, prefix := range []string{"v", "release-", "release/", "stable-"} { + v = strings.TrimPrefix(v, prefix) + } + return v +} -- 2.52.0 From efb1fe1f70383f9658cb5c151c75a16f8b97e287 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sat, 20 Jun 2026 22:16:44 +0000 Subject: [PATCH 07/28] chore(version): pre-release bump to 06.18.07-dev [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index 1e40625b6c..d542303e2c 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.18.06 + 06.18.07 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 8959de1a58..4d56e23292 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.18.06 +# VERSION: 06.18.07 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0 From 6f6a8b0f2cbf15b0c609ac665d609db9e56bb77c Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 17:18:33 -0500 Subject: [PATCH 08/28] fix: remove invalid SortType field from FindReleasesOptions --- routers/api/v1/licensing/updates.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/routers/api/v1/licensing/updates.go b/routers/api/v1/licensing/updates.go index 7a768d14b9..e2568e62f4 100644 --- a/routers/api/v1/licensing/updates.go +++ b/routers/api/v1/licensing/updates.go @@ -140,13 +140,12 @@ func ServeUpdates(ctx *context.APIContext) { TagNames: []string{"stable"}, }) if err != nil || len(releases) == 0 { - // Try latest non-draft release + // Try latest non-draft release (default order is newest first) releases, err = db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{ RepoID: repo.ID, ListOptions: db.ListOptions{PageSize: 1, Page: 1}, IncludeDrafts: false, IncludeTags: false, - SortType: "newest", }) if err != nil || len(releases) == 0 { writeEmptyUpdates(ctx) -- 2.52.0 From 88e5def821b95369e728b32643338b2a03954284 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sat, 20 Jun 2026 22:19:00 +0000 Subject: [PATCH 09/28] chore(release): build 06.19.00-rc [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- CHANGELOG.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index d542303e2c..0a9062b5ea 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.18.07 + 06.19.00 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 4d56e23292..8c7582d077 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.18.07 +# VERSION: 06.19.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/CHANGELOG.md b/CHANGELOG.md index 6065f4dd25..9f70340980 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,6 @@ ## [06.19.00] --- 2026-06-20 -## [06.19.00] --- 2026-06-19 +## [06.19.00] --- 2026-06-20 -## [06.18.00] --- 2026-06-19 +## [06.19.00] --- 2026-06-19 -- 2.52.0 From 612736c0f0e06f8a2d710b321887df6b5002af68 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sat, 20 Jun 2026 22:22:52 +0000 Subject: [PATCH 10/28] chore(version): pre-release bump to 06.20.00-rc [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index 0a9062b5ea..c7e7a8fd95 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.19.00 + 06.20.00 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 8c7582d077..929dce185e 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.19.00 +# VERSION: 06.20.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0 From ae8b110e89fb3f10c84a7857928bf350a0ab89fe Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 17:51:27 -0500 Subject: [PATCH 11/28] chore: add changelog entries for licensing system and profile aliases --- CHANGELOG.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f70340980..6db812c7e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,16 @@ # Changelog + ## [Unreleased] -## [06.19.00] --- 2026-06-20 +### Added +- DLID licensing system: license, entitlement, activation, product_tier, audit_log tables (v359 migration) +- License CRUD with CRC32-checksummed DLID generation and format validation +- Entitlement model with tier-based rebuild and custom entitlement preservation +- Domain activation tracking with limit enforcement and auto-activate on first use +- 13 seeded product tiers from base to enterprise +- DLID-gated update XML endpoint: GET /api/v1/licensing/updates/{product}.xml +- Profile repo fallback chain: .mokogitea > .profile > .github ## [06.19.00] --- 2026-06-20 -## [06.19.00] --- 2026-06-20 - -## [06.19.00] --- 2026-06-20 - -## [06.19.00] --- 2026-06-19 +## [06.18.00] --- 2026-06-19 -- 2.52.0 From a83b78c7cde0269d5b0ff924385320d4162a70c2 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sat, 20 Jun 2026 22:52:38 +0000 Subject: [PATCH 12/28] chore(version): pre-release bump to 06.21.00-rc [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index c7e7a8fd95..1902f6b610 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.20.00 + 06.21.00 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 929dce185e..7d6c5e42d4 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.20.00 +# VERSION: 06.21.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0 From c1841a32ce5b65e5b9615d1b85fc55ab0155ccc9 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 18:03:44 -0500 Subject: [PATCH 13/28] chore: trigger PR checks [skip bump] -- 2.52.0 From 77e5c8a5eff438e0415de584a63e416fd6c8bce9 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sat, 20 Jun 2026 23:04:39 +0000 Subject: [PATCH 14/28] chore(version): pre-release bump to 06.22.00-rc [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index 1902f6b610..1a66bbfe8a 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.21.00 + 06.22.00 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 7d6c5e42d4..9214be9e9d 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.21.00 +# VERSION: 06.22.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0 From b28fce232871b68933f05eeb6964f9fb4703f523 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 18:10:45 -0500 Subject: [PATCH 15/28] refactor: merge 3 PR workflows into pr-check, reduce CI noise - Delete pr-branch-check.yml (redundant with pr-check Branch Policy job) - Move gitleaks PR scan into pr-check.yml as Secret Scan job - Remove pull_request trigger from ci-generic.yml (irrelevant for Go repo) - Remove pull_request trigger from gitleaks.yml (keep schedule + dispatch) Reduces PR-triggered workflows from 7 to 4. --- .mokogitea/workflows/ci-generic.yml | 6 -- .mokogitea/workflows/gitleaks.yml | 4 -- .mokogitea/workflows/pr-branch-check.yml | 90 ------------------------ .mokogitea/workflows/pr-check.yml | 26 +++++++ 4 files changed, 26 insertions(+), 100 deletions(-) delete mode 100644 .mokogitea/workflows/pr-branch-check.yml diff --git a/.mokogitea/workflows/ci-generic.yml b/.mokogitea/workflows/ci-generic.yml index 92d26853d1..18ae768ce5 100644 --- a/.mokogitea/workflows/ci-generic.yml +++ b/.mokogitea/workflows/ci-generic.yml @@ -13,12 +13,6 @@ name: "Generic: Project CI" on: - pull_request: - branches: - - main - - dev - - dev/** - - rc/** workflow_dispatch: permissions: diff --git a/.mokogitea/workflows/gitleaks.yml b/.mokogitea/workflows/gitleaks.yml index 0c076124ab..196cf0c726 100644 --- a/.mokogitea/workflows/gitleaks.yml +++ b/.mokogitea/workflows/gitleaks.yml @@ -25,10 +25,6 @@ name: "Universal: Secret Scanning" on: - pull_request: - branches: - - main - - 'dev/**' schedule: - cron: '0 5 * * 1' # Weekly Monday 05:00 UTC workflow_dispatch: diff --git a/.mokogitea/workflows/pr-branch-check.yml b/.mokogitea/workflows/pr-branch-check.yml deleted file mode 100644 index debdd13b30..0000000000 --- a/.mokogitea/workflows/pr-branch-check.yml +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# Enforces branch merge policy: -# feature/* → dev only -# fix/* → dev only -# hotfix/* → dev or main (emergency) -# dev → main only -# alpha/* → dev only -# beta/* → dev only -# rc/* → main only - -name: Branch Policy Check - -on: - pull_request: - types: [opened, synchronize, reopened, edited] - -jobs: - check-target: - name: Verify merge target - runs-on: ubuntu-latest - steps: - - name: Check branch policy - run: | - HEAD="${{ github.head_ref }}" - BASE="${{ github.base_ref }}" - - echo "PR: ${HEAD} → ${BASE}" - - ALLOWED=true - REASON="" - - case "$HEAD" in - feature/*|feat/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Feature branches must target 'dev', not '${BASE}'" - fi - ;; - fix/*|bugfix/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Fix branches must target 'dev', not '${BASE}'" - fi - ;; - hotfix/*) - if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'" - fi - ;; - alpha/*|beta/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Pre-release branches must target 'dev', not '${BASE}'" - fi - ;; - rc/*) - if [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Release candidate branches must target 'main', not '${BASE}'" - fi - ;; - dev) - if [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Dev branch can only merge into 'main', not '${BASE}'" - fi - ;; - esac - - if [ "$ALLOWED" = false ]; then - echo "::error::${REASON}" - echo "" - echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "${REASON}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY - echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - echo "Branch policy: OK (${HEAD} → ${BASE})" - echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml index 4d78d7a445..d34108ce5d 100644 --- a/.mokogitea/workflows/pr-check.yml +++ b/.mokogitea/workflows/pr-check.yml @@ -96,6 +96,32 @@ jobs: echo "Branch policy: OK (${HEAD} → ${BASE})" echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY + # ── Secret Scanning ────────────────────────────────────────────────── + gitleaks: + name: Secret Scan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Gitleaks + run: | + GITLEAKS_VERSION="8.21.2" + curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ + | tar -xz -C /usr/local/bin gitleaks + + - name: Scan PR commits for secrets + run: | + if gitleaks detect --source . --verbose \ + --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} 2>&1; then + echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY + else + echo "::error::Potential secrets detected in PR commits" + exit 1 + fi + # ── Code Validation ──────────────────────────────────────────────────── validate: name: Validate PR -- 2.52.0 From fc97bb94994370bb446780051e67ca00212e3f45 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sat, 20 Jun 2026 23:12:02 +0000 Subject: [PATCH 16/28] chore(version): pre-release bump to 06.23.00-rc [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index 1a66bbfe8a..76fd410126 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.22.00 + 06.23.00 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 9214be9e9d..63f2c1eb59 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.22.00 +# VERSION: 06.23.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0 From 8a3d16fbd6700c16063c5f58114dc552b458b566 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 18:19:46 -0500 Subject: [PATCH 17/28] chore: retrigger CI [skip bump] -- 2.52.0 From c3d4fdea65ee98bd98080877cac52041257d7bd3 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sat, 20 Jun 2026 23:20:28 +0000 Subject: [PATCH 18/28] chore(version): pre-release bump to 06.24.00-rc [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index 76fd410126..b72d553754 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.23.00 + 06.24.00 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 63f2c1eb59..6d0afddfe9 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.23.00 +# VERSION: 06.24.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0 From 5b78a059ed1e857114e2f7d928a218e2d5e5aba6 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 18:28:14 -0500 Subject: [PATCH 19/28] chore: retrigger checks after runner restart [skip bump] -- 2.52.0 From 306cc3be9269c043096015346bff4d24d771636c Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sat, 20 Jun 2026 23:29:00 +0000 Subject: [PATCH 20/28] chore(version): pre-release bump to 06.25.00-rc [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index b72d553754..b8e72be860 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.24.00 + 06.25.00 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 6d0afddfe9..6c5d15a93a 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.24.00 +# VERSION: 06.25.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0 From 1d7792ee0fd9e7eee586a19181fa3bbca42b2bad Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 18:31:21 -0500 Subject: [PATCH 21/28] chore: trigger PR checks [skip bump] -- 2.52.0 From cb67713ee6f54d5c4a6700f78101442889ddc0e3 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sat, 20 Jun 2026 23:33:03 +0000 Subject: [PATCH 22/28] chore(version): pre-release bump to 06.26.00-rc [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index b8e72be860..3372c53f51 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.25.00 + 06.26.00 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 6c5d15a93a..62c70da6dc 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.25.00 +# VERSION: 06.26.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0 From 3002f02b2ddeb8bf1a9e5e77322e26afdbd65447 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 18:33:31 -0500 Subject: [PATCH 23/28] fix: add [skip bump] guard to pre-release to prevent CI cycle --- .mokogitea/workflows/pre-release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index 24c47e0828..cb64778903 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -49,8 +49,10 @@ jobs: name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})" runs-on: release if: >- - github.event_name == 'workflow_dispatch' || - github.event_name == 'push' + (github.event_name == 'workflow_dispatch' || + github.event_name == 'push') && + !contains(github.event.head_commit.message, '[skip ci]') && + !contains(github.event.head_commit.message, '[skip bump]') steps: - name: Checkout -- 2.52.0 From 4b6779da3b54c0f7187c144158f283ba3b596bbb Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 18:57:44 -0500 Subject: [PATCH 24/28] chore: retrigger after queue cleanup [skip bump] -- 2.52.0 From 407cd2e15df50c3dc6255b4a798419884510ea91 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 19:04:59 -0500 Subject: [PATCH 25/28] fix: remove Build RC Package and Report Issues jobs from pr-check (redundant, causes stuck queue) --- .mokogitea/workflows/pr-check.yml | 45 ------------------------------- 1 file changed, 45 deletions(-) diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml index d34108ce5d..409fea5a1f 100644 --- a/.mokogitea/workflows/pr-check.yml +++ b/.mokogitea/workflows/pr-check.yml @@ -487,48 +487,3 @@ jobs: echo "Source: ${FILE_COUNT} files" [ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; } - # ── Pre-Release RC Build ───────────────────────────────────────────────── - pre-release: - name: Build RC Package - runs-on: ubuntu-latest - needs: [branch-policy, validate] - - steps: - - name: Trigger RC pre-release - env: - GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - REPO: ${{ github.repository }} - BRANCH: ${{ github.head_ref }} - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - run: | - curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}" - echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY - echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY - - # ── Issue Reporter ────────────────────────────────────────────────────── - report-issues: - name: Report Issues - runs-on: ubuntu-latest - needs: [branch-policy, validate] - if: >- - always() && - needs.validate.result == 'failure' - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - sparse-checkout: automation/ci-issue-reporter.sh - sparse-checkout-cone-mode: false - - - name: "File issue for PR validation failure" - env: - GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - run: | - chmod +x automation/ci-issue-reporter.sh - ./automation/ci-issue-reporter.sh \ - --gate "PR Validation" \ - --workflow "PR Check" \ - --severity error \ - --details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed." -- 2.52.0 From cc740ca8c5dbe49ade184fd2898fbe2cba260789 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sun, 21 Jun 2026 00:05:52 +0000 Subject: [PATCH 26/28] chore(version): pre-release bump to 06.27.00-rc [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index 3372c53f51..e12f85a7ee 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.26.00 + 06.27.00 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 62c70da6dc..6fbaa9c4b9 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 06.26.00 +# VERSION: 06.27.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0 From 5f81837fc22ce4c821f33b98697c1da43f367a17 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 19:09:06 -0500 Subject: [PATCH 27/28] chore: final CI trigger [skip bump] -- 2.52.0 From e677c2e0bd19545d8a218b58c9f65ad320251306 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sun, 21 Jun 2026 00:16:41 +0000 Subject: [PATCH 28/28] chore(version): pre-release bump to 06.20.00-rc [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index 0a9062b5ea..c7e7a8fd95 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -4,7 +4,7 @@ MokoGitea MokoConsulting Moko fork of Gitea - adding project board REST API endpoints and custom enhancements - 06.19.00 + 06.20.00 v1.26.1+MOKO GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index c2b02a6f91..47cccacebf 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: moko-platform.Automation -# VERSION: 01.00.00 +# VERSION: 06.20.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" -- 2.52.0