Files
gitea-mirror/scripts
ARUNAVO RAYandGitHub 0f700c7197 feat: per-repository and per-organization mirror option overrides (#362)
* feat: per-repository and per-organization mirror option overrides

Closes the gap reported in #361: a repository whose LFS fetch fails could
not be mirrored at all, because LFS was a single global switch. Gitea runs
the LFS fetch inside its own migration, so a failure aborts the whole
migration with nothing to salvage. The only fix available to us is to stop
asking for LFS on that repository.

Mirror options now resolve across three tiers, per flag, most specific
first: repository override, then organization override, then the global
config. NULL at a tier means inherit, so overriding LFS on one repo leaves
its other options alone.

Adds resolveMirrorOptions(), which replaces 24 scattered reads of
config.giteaConfig.* across three near-identical blocks: the two mirror
paths in gitea.ts and the sync path in gitea-enhanced.ts. The third was
easy to miss and mattered: without it, overrides would have been honored
on first mirror and silently ignored on every scheduled sync afterwards.

The starredCodeOnly clamp is folded into the resolver and deliberately
outranks explicit overrides, preserving the existing behavior that starred
repos mirror code only.

Editing is on the objects themselves, via the existing three-dot menus on
the Repositories and Organizations pages, not in Configuration, which keeps
holding the global defaults. A repositories filter and a row badge make it
possible to find which repos deviate.

Malformed override JSON degrades to "inherit" rather than throwing, so a
bad value can never break a mirror run.

* fix(ui): disable mirror toggles that cannot take effect, and explain why

A toggle the runtime will ignore should not look editable. Three cases
were doing exactly that, so they now share one mechanism:
getMirrorOverrideGating() returns a reason string per flag, and the dialog
disables the control and prints that reason underneath.

Starred clamp. When a repo is starred and starredCodeOnly is set, the
resolver forces every metadata flag off regardless of the override. The
dialog previously let the user set them anyway. The clamped set now lives
in STARRED_CLAMPED_KEYS, shared by the resolver and the gating helper so
the flags the UI disables cannot drift from the ones the runtime clamps.
LFS is deliberately excluded from that set, since turning LFS off per
repository is the point of #361, and a test pins that.

Labels. shouldMirrorLabels is `mirrorLabels && !mirrorIssues` in both
mirror paths, so labels cannot take effect while issues are mirrored. That
gate reacts live to the in-progress edit rather than only the saved state.

Inherit hint. For a repository the hint now reflects global -> org instead
of global alone, via a new name-keyed GET
/api/organizations/mirror-overrides that reuses
loadOrganizationMirrorOverrides. Personal repos skip the fetch, a failed
fetch degrades to the global values rather than blocking the dialog, and
the hint is suppressed while in flight so it is never briefly wrong.

Widens the UI to mirrorLabels and mirrorMilestones. mirrorMetadata stays
out: no mirror path reads it, so a per-object override would resolve
correctly and then do nothing. See the report for that finding.

useGiteaConfig additionally returns advancedOptions so callers can read
starredCodeOnly without a second request.

* fix(ui): read inherited mirror flags from mirrorOptions, not giteaConfig

/api/config does not return the mirror flags on giteaConfig. On the way
out, mapDbToUiConfig reshapes them into a separate mirrorOptions object
using different names (mirrorLFS, not lfs) and nesting the metadata flags
under metadataComponents with short names (issues, not mirrorIssues).

The dialog was written against the DB shape, so every lookup returned
undefined. Coerced to false, that is indistinguishable from a real "off",
which produced two symptoms: every inherit hint read "currently off"
regardless of the actual global config, and the labels gate never fired
because the effective issues value it keys on came from the same dead
source.

Adds mirrorOptionsToFlags() as the single conversion point, matching the
derivation in mapUiToDbConfig exactly, including that mirrorMetadata is a
master switch over the metadata components while lfs and mirrorReleases
sit outside it. Both dialogs now go through it, and useGiteaConfig returns
mirrorOptions alongside advancedOptions.

Server-side mirroring was never affected: the resolver reads config
straight from the DB, where the flags really do live on giteaConfig.

The existing tests could not catch this because they build a synthetic
config already in DB shape, so a client/server mismatch is invisible to
them. The new tests push flags through the real config-mapper in both
directions and assert the derived flags equal what mapUiToDbConfig would
persist, so they fail if that mapping changes again. One test pins the
root cause directly: that giteaConfig in the API payload carries no flags.

* fix(ui): surface the mirror-options filter on desktop, and on organizations

The overrides filter only existed inside the mobile filter drawer, so on
desktop the Repositories page showed a "Custom" badge on overridden rows
with no way to filter to them. Being able to answer "which repos deviate
from my defaults" on this page is the reason the Configuration-page
listing was dropped, so desktop could not do the one job it was given.

Adds the control to the desktop filter row next to status and sort, using
the bare Select-with-placeholder style those use rather than the drawer's
labelled markup. The mobile control is unchanged.

The Organizations page had a wider version of the same gap: it renders the
"Custom options" badge but never had this filter on either layout, and
OrganizationsList did not filter on it at all. Added the predicate plus
the control in both its layouts, so the two pages behave the same.

Also folds hasOverrides into activeFilterCount on both pages and into the
Organizations clear-all reset. Without that the mobile filter badge
undercounted an active overrides filter, and clearing filters left it set.

* fix(ui): stop double-applying the mirrorMetadata switch when reading config

mirrorOptionsToFlags ANDed each metadata component with mirrorMetadata on
the way in. That is a write-path rule: mapUiToDbConfig already applies it
when persisting, so a config saved through the settings UI has it baked
into the stored flag. Applying it again on read double-applies it.

The read path does not need it. mapDbToUiConfig puts the raw stored values
into metadataComponents, so metadataComponents.issues is exactly
giteaConfig.mirrorIssues, which is the field gitea.ts and
gitea-enhanced.ts read. Mapping the components straight through is 1:1
with runtime behavior.

Double-applying was invisible while the stored state was self-consistent,
since false && false is still false. It diverged when the state did not
come from the UI write path, which env vars allow: MIRROR_METADATA=false
with MIRROR_ISSUES=true stores mirrorMetadata:false, mirrorIssues:true.
The runtime mirrors issues; the dialog reported off. Measured against a
config in that state, five flags misreported.

I previously described this as an unrecoverable loss in the API shape.
That was wrong. Both values reach the client separately and unmerged, so
the payload carries full information and the loss was one we introduced.

Rewrites the test that asserted the derived flags match what
mapUiToDbConfig would persist. Comparing against the write derivation is
what pinned the bug in place. It now asserts the property that matters,
that flags derived from the API payload equal the stored flags the runtime
reads, plus a case checking agreement with resolveMirrorOptions on an
inconsistent config. Both fail if the AND returns.
2026-08-20 06:03:19 +05:30
..
2025-05-18 09:31:23 +05:30
2025-07-11 01:17:54 +05:30
2025-07-11 01:17:54 +05:30
2025-06-17 10:30:33 +05:30
2025-06-17 10:30:33 +05:30
2025-07-11 01:17:54 +05:30
2026-02-24 09:45:06 +05:30
2025-06-17 10:30:33 +05:30
2025-08-29 17:04:48 +05:30
2025-07-11 01:04:50 +05:30
2025-08-28 08:34:27 +05:30

Scripts Directory

This folder contains utility scripts for database management, event management, Docker builds, and LXC container deployment.

Database Management

Database Management Tool (manage-db.ts)

This is a consolidated database management tool that handles all database-related operations. It combines the functionality of the previous separate scripts into a single, more intelligent script that can check, fix, and initialize the database as needed.

Features

  • Check Mode: Validates the existence and integrity of the database
  • Init Mode: Creates the database only if it doesn't already exist
  • Fix Mode: Corrects database file location issues
  • Reset Users Mode: Removes all users and their data
  • Auto Mode: Automatically checks, fixes, and initializes the database if needed

Running the Database Management Tool

You can execute the database management tool using your package manager with various commands:

# Checks database status (default action if no command is specified)
bun run manage-db

# Check database status
bun run check-db

# Initialize the database (only if it doesn't exist)
bun run init-db

# Fix database location issues
bun run fix-db

# Automatic check, fix, and initialize if needed
bun run db-auto

# Reset all users (for testing signup flow)
bun run reset-users

# Remove database files completely
bun run cleanup-db

# Complete setup (install dependencies and initialize database)
bun run setup

# Start development server with a fresh database
bun run dev:clean

# Start production server with a fresh database
bun run start:fresh

Database File Location

The database file should be located in the ./data/gitea-mirror.db directory. If the file is found in the root directory, the fix mode will move it to the correct location.

Event Management

The following scripts help manage events in the SQLite database:

Note

: For a more user-friendly approach, you can use the cleanup button in the Activity Log page of the web interface to delete all activities with a single click.

Remove Duplicate Events (remove-duplicate-events.ts)

Specifically removes duplicate events based on deduplication keys without affecting old events.

# Remove duplicate events for all users
bun scripts/remove-duplicate-events.ts

# Remove duplicate events for a specific user
bun scripts/remove-duplicate-events.ts <userId>

Fix Interrupted Jobs (fix-interrupted-jobs.ts)

Fixes interrupted jobs that might be preventing cleanup by marking them as failed.

# Fix all interrupted jobs
bun scripts/fix-interrupted-jobs.ts

# Fix interrupted jobs for a specific user
bun scripts/fix-interrupted-jobs.ts <userId>

Use this script if you're having trouble cleaning up activities due to "interrupted" jobs that won't delete.

Startup Recovery (startup-recovery.ts)

Runs job recovery during application startup to handle any interrupted jobs from previous runs.

# Run startup recovery (normal mode)
bun scripts/startup-recovery.ts

# Force recovery even if recent attempt was made
bun scripts/startup-recovery.ts --force

# Set custom timeout (default: 30000ms)
bun scripts/startup-recovery.ts --timeout=60000

# Using npm scripts
bun run startup-recovery
bun run startup-recovery-force

This script is automatically run by the Docker entrypoint during container startup. It ensures that any jobs interrupted by container restarts or application crashes are properly recovered or marked as failed.

Deployment Scripts

Docker Deployment

  • build-docker.sh: Builds the Docker image for the application
  • docker-diagnostics.sh: Provides diagnostic information for Docker deployments

LXC Container Deployment

Two deployment options are available for LXC containers:

  1. Proxmox VE (online): Using the community-maintained script by Tobias (CrazyWolf13)

  2. gitea-mirror-lxc-local.sh: For offline/LAN-only deployment on a developer laptop

    • Pushes your local checkout + Bun ZIP to the container
    • Useful for testing without internet access

For detailed instructions on LXC deployment, see README-lxc.md.