Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4f1684304 | ||
|
|
8ed3bec511 | ||
|
|
58faf2e57f | ||
|
|
8e5f35f40c |
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,9 @@ Seven specs, designed 2026-07-24. Build in this order.
|
||||
|---|---|---|---|
|
||||
| 0a | [shared-module](2026-07-24-shared-module-design.md) | [plan](../plans/2026-07-24-shared-module.md) | **shipped** |
|
||||
| 0b | [instance-rename](2026-07-24-instance-rename-design.md) | [plan](../plans/2026-07-24-instance-rename.md) | **shipped**, migration verified on live |
|
||||
| 1 | [licensing-core](2026-07-24-licensing-core-design.md) | [plan](../plans/2026-07-24-licensing-core.md) | planned |
|
||||
| 2 | [instance-licensing](2026-07-24-instance-licensing-design.md) | [plan](../plans/2026-07-24-instance-licensing.md) | planned |
|
||||
| 3 | [admin-backend](2026-07-24-admin-backend-design.md) | — | blocks 4 and 5 |
|
||||
| 1 | [licensing-core](2026-07-24-licensing-core-design.md) | [plan](../plans/2026-07-24-licensing-core.md) | **shipped** |
|
||||
| 2 | [instance-licensing](2026-07-24-instance-licensing-design.md) | [plan](../plans/2026-07-24-instance-licensing.md) | **shipped**, no grandfathering — existing cloud instances are read-only until admin backfills |
|
||||
| 3 | [admin-backend](2026-07-24-admin-backend-design.md) | [plan](../plans/2026-07-24-admin-backend.md) | planned |
|
||||
| 4 | [admin-site](2026-07-24-admin-site-design.md) | — | needs 3 |
|
||||
| 5 | [paddle-billing](2026-07-24-paddle-billing-design.md) | — | needs 3 |
|
||||
|
||||
|
||||
@@ -59,13 +59,6 @@ func main() {
|
||||
log.Fatalf("scoped collection check failed: %v", assertErr)
|
||||
}
|
||||
|
||||
gfCtx, gfCancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
gfErr := services.MigrateGrandfatherLicences(gfCtx, db.Database)
|
||||
gfCancel()
|
||||
if gfErr != nil {
|
||||
log.Fatalf("licence grandfather migration failed: %v", gfErr)
|
||||
}
|
||||
|
||||
if err := services.EnsureAuthIndexes(); err != nil {
|
||||
log.Fatalf("failed to ensure auth indexes: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/mrhid6/vantage/shared/license"
|
||||
"github.com/mrhid6/vantage/shared/models"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||
)
|
||||
|
||||
// MigrateGrandfatherLicences stores pre-issued licences on instances that have
|
||||
// none. Cloud only.
|
||||
//
|
||||
// The blobs are supplied through VANTAGE_GRANDFATHER_BLOBS, a JSON object
|
||||
// mapping instance_id to licence blob, because this process cannot sign: it
|
||||
// holds no private key and the signing code is compiled out. Cut the blobs
|
||||
// beforehand with lkctl.
|
||||
//
|
||||
// The variable is single-use. Unset it on the next deploy.
|
||||
func MigrateGrandfatherLicences(ctx context.Context, db *mongo.Database) error {
|
||||
const marker = "0005_grandfather_licences"
|
||||
|
||||
if n, _ := db.Collection("migrations").CountDocuments(ctx, bson.M{"_id": marker}); n > 0 {
|
||||
return nil
|
||||
}
|
||||
if DeploymentMode() != license.DeploymentCloud {
|
||||
log.Printf("0005: not a cloud deployment, skipping")
|
||||
return nil
|
||||
}
|
||||
|
||||
raw := os.Getenv("VANTAGE_GRANDFATHER_BLOBS")
|
||||
if raw == "" {
|
||||
log.Printf("0005: VANTAGE_GRANDFATHER_BLOBS not set, skipping (no marker recorded)")
|
||||
return nil
|
||||
}
|
||||
|
||||
var blobs map[string]string
|
||||
if err := json.Unmarshal([]byte(raw), &blobs); err != nil {
|
||||
return fmt.Errorf("0005: VANTAGE_GRANDFATHER_BLOBS is not valid JSON: %w", err)
|
||||
}
|
||||
|
||||
cur, err := db.Collection("instances").Find(ctx, bson.M{
|
||||
"$or": []bson.M{
|
||||
{"license_blob": bson.M{"$exists": false}},
|
||||
{"license_blob": ""},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("0005: list instances: %w", err)
|
||||
}
|
||||
var instances []models.Instance
|
||||
if err := cur.All(ctx, &instances); err != nil {
|
||||
return fmt.Errorf("0005: decode instances: %w", err)
|
||||
}
|
||||
|
||||
var stored, missing int
|
||||
for _, inst := range instances {
|
||||
blob, ok := blobs[inst.InstanceID]
|
||||
if !ok || blob == "" {
|
||||
log.Printf("0005: no blob supplied for instance %s (%s)", inst.InstanceID, inst.Slug)
|
||||
missing++
|
||||
continue
|
||||
}
|
||||
|
||||
res := license.Verify(blob, license.VerifyOpts{
|
||||
InstanceID: inst.InstanceID,
|
||||
Deployment: license.DeploymentCloud,
|
||||
})
|
||||
if res.State == license.StateInvalid {
|
||||
return fmt.Errorf("0005: blob for instance %s is rejected: %s", inst.InstanceID, res.Reason)
|
||||
}
|
||||
|
||||
if _, err := db.Collection("instances").UpdateOne(ctx,
|
||||
bson.M{"instance_id": inst.InstanceID},
|
||||
bson.M{"$set": bson.M{
|
||||
"license_blob": blob,
|
||||
"license_tier": res.License.Tier,
|
||||
"license_expiry": res.License.ExpiresAt,
|
||||
}}); err != nil {
|
||||
return fmt.Errorf("0005: store blob for %s: %w", inst.InstanceID, err)
|
||||
}
|
||||
log.Printf("0005: stored %s licence for instance %s (%s), expires %s",
|
||||
res.License.Tier, inst.InstanceID, inst.Slug,
|
||||
res.License.ExpiresAt.Format(time.RFC3339))
|
||||
stored++
|
||||
}
|
||||
|
||||
if missing > 0 {
|
||||
return fmt.Errorf("0005: %d instance(s) had no blob supplied; issue them with lkctl and rerun", missing)
|
||||
}
|
||||
|
||||
_, err = db.Collection("migrations").InsertOne(ctx,
|
||||
bson.M{"_id": marker, "applied_at": time.Now()})
|
||||
log.Printf("0005: grandfathered %d instance(s)", stored)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user