fix(canary-phase1): clear all post-phase-1 audit observations + header normalization
No 'logged for later' — every non-blocking finding from the Phase 1 audits is fixed now, in this commit, before declaring the phase truly closed. Code cleanups (5 items): 1. Database.SQLDB() *sql.DB accessor on core.Database; main.go uses db.SQLDB() instead of the awkward db.DB.DB triple-chain. 2. Hoisted list-default literals (50, 20) to package-level defaultListLimit consts in token + event repositories. MEMORY.md 'no magic numbers' rule honored. 3. Moved ptr[T any] helper to internal/testutil/ptr.go as testutil.Ptr; removed local copies from token + event repository_test.go. 4. Renamed CHECK constraints in 0001_create_tokens.sql to match spec text exactly: chk_token_type → chk_type, chk_alert_channel → chk_channel. Spec was the original contract; impl now aligns. 5. Resolved APP_ENVIRONMENT vs ENVIRONMENT divergence per spec §12.1: - compose.yml: ENVIRONMENT=production → APP_ENVIRONMENT=production - dev.compose.yml: ENVIRONMENT=development → APP_ENVIRONMENT=development - config.go envKeyMap: ENVIRONMENT → APP_ENVIRONMENT mapping Concurrency bug fix (real, not just polish): - core/migrations.go: added sync.Mutex around goose calls. goose's package-level state (SetBaseFS, SetDialect) raced under t.Parallel() testcontainers tests. Race detector caught it under -race after parallel test counts climbed. Mutex serializes goose invocation globally; testcontainer-parallelism otherwise unaffected. Header normalization (50 files): - Bulk-normalized every project file's header to canonical '©AngelaMos | 2026' (no space, with © glyph, year 2026) per MEMORY.md style rule. Eliminated 4 distinct non-canonical variants: '// AngelaMos', '// ©AngelaMos | 2025', '// © AngelaMos | 202X', '# AngelaMos'. - Verified clean: grep returns zero non-canonical headers across the whole project tree (excluding gitignored docs/ and node_modules/). Backlog discipline: - Created docs/plans/BACKLOG.md with strict format: open items only, HIGH/MEDIUM/LOW severity, must-clear-before-ship contract. - BACKLOG currently has zero open items — every observation was fixed here, not deferred. Closed-items section logs what was cleared. Verification: - go build ./... clean - go vet ./... clean - go test -tags=integration -race ./internal/token/... 11/11 PASS - go test -tags=integration -race ./internal/event/... 9/9 PASS - docker compose -f compose.yml config parses - docker compose -f dev.compose.yml config parses - grep for non-canonical headers zero matches
This commit is contained in:
parent
7a98ef8d72
commit
697f4909d7
|
|
@ -1,4 +1,4 @@
|
|||
# AngelaMos | 2026
|
||||
# ©AngelaMos | 2026
|
||||
# .air.toml - Hot reload configuration
|
||||
|
||||
root = "."
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# AngelaMos | 2026
|
||||
# ©AngelaMos | 2026
|
||||
# .golangci.yml
|
||||
|
||||
version: "2"
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ func run(configPath string) error {
|
|||
}
|
||||
logger.Info("database connected")
|
||||
|
||||
if err := core.RunMigrations(db.DB.DB); err != nil {
|
||||
if err := core.RunMigrations(db.SQLDB()); err != nil {
|
||||
return fmt.Errorf("run migrations: %w", err)
|
||||
}
|
||||
logger.Info("migrations applied")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# AngelaMos | 2026
|
||||
# ©AngelaMos | 2026
|
||||
# config.yaml - Default configuration
|
||||
|
||||
app:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// config.go
|
||||
|
||||
package config
|
||||
|
|
@ -199,7 +199,7 @@ func loadDefaults(k *koanf.Koanf) error {
|
|||
var envKeyMap = map[string]string{
|
||||
"DATABASE_URL": "database.url",
|
||||
"REDIS_URL": "redis.url",
|
||||
"ENVIRONMENT": "app.environment",
|
||||
"APP_ENVIRONMENT": "app.environment",
|
||||
"HOST": "server.host",
|
||||
"PORT": "server.port",
|
||||
"LOG_LEVEL": "log.level",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// database.go
|
||||
|
||||
package core
|
||||
|
|
@ -20,6 +20,10 @@ type Database struct {
|
|||
DB *sqlx.DB
|
||||
}
|
||||
|
||||
func (d *Database) SQLDB() *sql.DB {
|
||||
return d.DB.DB
|
||||
}
|
||||
|
||||
func NewDatabase(
|
||||
ctx context.Context,
|
||||
cfg config.DatabaseConfig,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// errors.go
|
||||
|
||||
package core
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/pressly/goose/v3"
|
||||
)
|
||||
|
|
@ -14,7 +15,12 @@ import (
|
|||
//go:embed migrations/*.sql
|
||||
var migrationsFS embed.FS
|
||||
|
||||
var gooseMu sync.Mutex
|
||||
|
||||
func RunMigrations(db *sql.DB) error {
|
||||
gooseMu.Lock()
|
||||
defer gooseMu.Unlock()
|
||||
|
||||
goose.SetBaseFS(migrationsFS)
|
||||
|
||||
if err := goose.SetDialect("postgres"); err != nil {
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ CREATE TABLE tokens (
|
|||
|
||||
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
|
||||
CONSTRAINT chk_token_type CHECK (type IN
|
||||
CONSTRAINT chk_type CHECK (type IN
|
||||
('webbug', 'slowredirect', 'docx', 'pdf', 'kubeconfig', 'envfile', 'mysql')),
|
||||
CONSTRAINT chk_alert_channel CHECK (alert_channel IN ('telegram', 'webhook')),
|
||||
CONSTRAINT chk_channel CHECK (alert_channel IN ('telegram', 'webhook')),
|
||||
CONSTRAINT chk_telegram_complete CHECK (
|
||||
alert_channel <> 'telegram' OR
|
||||
(telegram_bot IS NOT NULL AND telegram_chat IS NOT NULL)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// redis.go
|
||||
|
||||
package core
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// response.go
|
||||
|
||||
package core
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// security.go
|
||||
|
||||
package core
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// telemetry.go
|
||||
|
||||
package core
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// validation.go
|
||||
|
||||
package core
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import (
|
|||
|
||||
var ErrNotFound = errors.New("event not found")
|
||||
|
||||
const defaultListLimit = 20
|
||||
|
||||
type Repository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
|
@ -77,7 +79,7 @@ func (r *Repository) ListByToken(
|
|||
ctx context.Context, tokenID string, opts ListOptions,
|
||||
) (ListResult, error) {
|
||||
if opts.Limit <= 0 {
|
||||
opts.Limit = 20
|
||||
opts.Limit = defaultListLimit
|
||||
}
|
||||
|
||||
q := `SELECT ` + selectColumns + `
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ import (
|
|||
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token"
|
||||
)
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func newRepos(t *testing.T) (*sqlx.DB, *token.Repository, *event.Repository) {
|
||||
t.Helper()
|
||||
db := sqlx.NewDb(testutil.NewTestDB(t), "pgx")
|
||||
|
|
@ -36,7 +34,7 @@ func seedToken(t *testing.T, repo *token.Repository, id string) *token.Token {
|
|||
Type: token.TypeWebbug,
|
||||
Memo: "event-test",
|
||||
AlertChannel: token.ChannelWebhook,
|
||||
WebhookURL: ptr("https://example.com/hook"),
|
||||
WebhookURL: testutil.Ptr("https://example.com/hook"),
|
||||
CreatedIP: "203.0.113.1",
|
||||
CreatedFP: "abcdef0123456789",
|
||||
Metadata: json.RawMessage(`{}`),
|
||||
|
|
@ -80,7 +78,7 @@ func TestRepository_ListByToken_CursorPagination(t *testing.T) {
|
|||
e := &event.Event{
|
||||
TokenID: tok.ID,
|
||||
SourceIP: "203.0.113.45",
|
||||
UserAgent: ptr(string(rune('A' + i))),
|
||||
UserAgent: testutil.Ptr(string(rune('A' + i))),
|
||||
}
|
||||
require.NoError(t, evtRepo.Insert(ctx, e))
|
||||
}
|
||||
|
|
@ -223,7 +221,7 @@ func TestRepository_PruneToLimit_KeepsNewest(t *testing.T) {
|
|||
|
||||
var ids []int64
|
||||
for i := range 5 {
|
||||
e := &event.Event{TokenID: tok.ID, SourceIP: "203.0.113.45", UserAgent: ptr(string(rune('A' + i)))}
|
||||
e := &event.Event{TokenID: tok.ID, SourceIP: "203.0.113.45", UserAgent: testutil.Ptr(string(rune('A' + i)))}
|
||||
require.NoError(t, evtRepo.Insert(ctx, e))
|
||||
ids = append(ids, e.ID)
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// handler.go
|
||||
|
||||
package health
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// headers.go
|
||||
|
||||
package middleware
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// logging.go
|
||||
|
||||
package middleware
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// ratelimit.go
|
||||
|
||||
package middleware
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// request_id.go
|
||||
|
||||
package middleware
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// server.go
|
||||
|
||||
package server
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
// ©AngelaMos | 2026
|
||||
// ptr.go
|
||||
|
||||
package testutil
|
||||
|
||||
func Ptr[T any](v T) *T { return &v }
|
||||
|
|
@ -14,6 +14,8 @@ import (
|
|||
|
||||
var ErrNotFound = errors.New("token not found")
|
||||
|
||||
const defaultListLimit = 50
|
||||
|
||||
type Repository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
|
@ -130,7 +132,7 @@ type ListOptions struct {
|
|||
|
||||
func (r *Repository) ListAll(ctx context.Context, opts ListOptions) ([]Token, error) {
|
||||
if opts.Limit <= 0 {
|
||||
opts.Limit = 50
|
||||
opts.Limit = defaultListLimit
|
||||
}
|
||||
q := `SELECT ` + selectColumns + ` FROM tokens
|
||||
ORDER BY created_at DESC
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ func newRepo(t *testing.T) *token.Repository {
|
|||
return token.NewRepository(db)
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func sampleWebhookToken(id string) *token.Token {
|
||||
return &token.Token{
|
||||
ID: id,
|
||||
|
|
@ -34,7 +32,7 @@ func sampleWebhookToken(id string) *token.Token {
|
|||
Type: token.TypeWebbug,
|
||||
Memo: "test webbug",
|
||||
AlertChannel: token.ChannelWebhook,
|
||||
WebhookURL: ptr("https://example.com/hook"),
|
||||
WebhookURL: testutil.Ptr("https://example.com/hook"),
|
||||
CreatedIP: "203.0.113.10",
|
||||
CreatedFP: "0123456789abcdef",
|
||||
Metadata: json.RawMessage(`{}`),
|
||||
|
|
@ -48,10 +46,10 @@ func sampleTelegramToken(id string) *token.Token {
|
|||
ManageID: uuid.New().String(),
|
||||
Type: token.TypeDocx,
|
||||
Memo: "Q4 bonuses",
|
||||
Filename: ptr("Q4_Bonuses_2024.docx"),
|
||||
Filename: testutil.Ptr("Q4_Bonuses_2024.docx"),
|
||||
AlertChannel: token.ChannelTelegram,
|
||||
TelegramBot: ptr("123456:ABCDEF"),
|
||||
TelegramChat: ptr("-1001234567890"),
|
||||
TelegramBot: testutil.Ptr("123456:ABCDEF"),
|
||||
TelegramChat: testutil.Ptr("-1001234567890"),
|
||||
CreatedIP: "198.51.100.5",
|
||||
CreatedFP: "fedcba9876543210",
|
||||
Metadata: json.RawMessage(`{"include_keys":["aws"]}`),
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ services:
|
|||
dockerfile: ../infra/docker/canary.prod
|
||||
container_name: ${APP_NAME:-canary-token-generator}-canary
|
||||
environment:
|
||||
- ENVIRONMENT=production
|
||||
- APP_ENVIRONMENT=production
|
||||
- DATABASE_URL=postgres://canary:${POSTGRES_PASSWORD}@postgres:5432/canary?sslmode=disable
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- PUBLIC_BASE_URL=${PUBLIC_BASE_URL}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ services:
|
|||
- canary_gocache:/root/.cache/go-build
|
||||
- canary_gomodcache:/go/pkg/mod
|
||||
environment:
|
||||
- ENVIRONMENT=development
|
||||
- APP_ENVIRONMENT=development
|
||||
- DATABASE_URL=postgres://canary:canary@postgres:5432/canary?sslmode=disable
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- PUBLIC_BASE_URL=${PUBLIC_BASE_URL:-http://localhost:58495}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# ©AngelaMos | 2025
|
||||
# ©AngelaMos | 2026
|
||||
# .stylelintignore
|
||||
|
||||
# Dependencies
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// index.ts
|
||||
// ===================
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2025
|
||||
// ©AngelaMos | 2026
|
||||
// index.ts
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// index.ts
|
||||
// ===================
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
* ©AngelaMos | 2025
|
||||
* ©AngelaMos | 2026
|
||||
* index.tsx
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// config.ts
|
||||
// ===================
|
||||
const API_VERSION = 'v1'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// api.config.ts
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* ©AngelaMos | 2025
|
||||
* ©AngelaMos | 2026
|
||||
* errors.ts
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2025
|
||||
// ©AngelaMos | 2026
|
||||
// index.ts
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2025
|
||||
// ©AngelaMos | 2026
|
||||
// query.config.ts
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// routers.tsx
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// shell.module.scss
|
||||
// ===================
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// toast.module.scss
|
||||
// ===================
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// index.ts
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* ©AngelaMos | 2025
|
||||
* ©AngelaMos | 2026
|
||||
* ui.store.ts
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===========================
|
||||
// ©AngelaMos | 2025
|
||||
// ©AngelaMos | 2026
|
||||
// main.tsx
|
||||
// ===========================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// dashboard.module.scss
|
||||
// ===================
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// index.tsx
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// landing.module.scss
|
||||
// ===================
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// settings.module.scss
|
||||
// ===================
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// styles.scss
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2025
|
||||
// ©AngelaMos | 2026
|
||||
// _fonts.scss
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2025
|
||||
// ©AngelaMos | 2026
|
||||
// _index.scss
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2025
|
||||
// ©AngelaMos | 2026
|
||||
// _mixins.scss
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2026
|
||||
// ©AngelaMos | 2026
|
||||
// _reset.scss
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// ===================
|
||||
// © AngelaMos | 2025
|
||||
// ©AngelaMos | 2026
|
||||
// _tokens.scss
|
||||
// ===================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// ©AngelaMos | 2025
|
||||
// ©AngelaMos | 2026
|
||||
// stylelint.config.js
|
||||
|
||||
/** @type {import('stylelint').Config} */
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* ©AngelaMos | 2025
|
||||
* ©AngelaMos | 2026
|
||||
* vite.config.ts
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# =============================================================================
|
||||
# AngelaMos | 2026
|
||||
# ©AngelaMos | 2026
|
||||
# dev.nginx
|
||||
# =============================================================================
|
||||
# Development server block: proxies to Vite dev server with HMR
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# AngelaMos | 2026
|
||||
# ©AngelaMos | 2026
|
||||
# nginx.conf
|
||||
# Development nginx configuration
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# AngelaMos | 2026
|
||||
# ©AngelaMos | 2026
|
||||
# nginx.prod.conf
|
||||
# Production nginx configuration
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# =============================================================================
|
||||
# AngelaMos | 2026
|
||||
# ©AngelaMos | 2026
|
||||
# prod.nginx
|
||||
# =============================================================================
|
||||
# Production server block: serves built static files
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# AngelaMos | 2026
|
||||
# ©AngelaMos | 2026
|
||||
# randomize-ports.sh
|
||||
# =============================================================================
|
||||
# Picks 4 unique random ports (10000-65000) and updates:
|
||||
|
|
|
|||
Loading…
Reference in New Issue