Skip to content

Instantly share code, notes, and snippets.

@lsd-cat
Last active February 2, 2025 21:01
Show Gist options
  • Save lsd-cat/cc475179a99a8c2a33094b7886a0ac96 to your computer and use it in GitHub Desktop.
Save lsd-cat/cc475179a99a8c2a33094b7886a0ac96 to your computer and use it in GitHub Desktop.
Gogs 0.13 (sqlite) to Gitea 1.0.2

Gogs 0.13 (sqlite) to Gitea 1.0.2 (and upper)

Quoting the Gitea docs:

Upgrading from Gogs 0.12.x and above will be increasingly more difficult as the projects diverge further apart in configuration and schema.

Since gogs has now multiple unfixed RCEs and has for a while, it is imperative to either turn it off or migrate. The problem is, what to do if running a version > 0.11.x?

Step 1

Backup everything, if possible do both an archive of your gogs home dire and run ./gogs backup.

Step 2

Download and install Gogs 0.11.86:

Install it under a different folder/user, be careful not to overwrite anything. The install data do not matter, just use sqlite3 and install it from scratch.

Step 3 downgrade 0.13 schema to 0.11

Once Gogs 0.11.86 has been installed, copy the sqlite database of the 0.11.86 version as 11.db. Copy the database of the 0.13 version to be migrated as 13.db.

Run the attached script, as migrate.py 13.db 11.db.

What the script does is a lossy migration from the newer schema to the older one. It empty the content of the default install, ignore system tables and then copy table by table the data from the newer one to the older one. Some data are discarded, so depending on your configuration this might lose data. However the schema changes are minimal, it will likely not affect you.

Sample output:

python3 migrate.py 13_test.db 11_test.db 
Cleared all records from table 'user'.
Cleared all records from table 'public_key'.
Cleared all records from table 'access_token'.
Cleared all records from table 'two_factor'.
Cleared all records from table 'two_factor_recovery_code'.
Cleared all records from table 'repository'.
Cleared all records from table 'deploy_key'.
Cleared all records from table 'collaboration'.
Cleared all records from table 'access'.
Cleared all records from table 'upload'.
Cleared all records from table 'watch'.
Cleared all records from table 'star'.
Cleared all records from table 'follow'.
Cleared all records from table 'action'.
Cleared all records from table 'issue'.
Cleared all records from table 'pull_request'.
Cleared all records from table 'comment'.
Cleared all records from table 'attachment'.
Cleared all records from table 'issue_user'.
Cleared all records from table 'label'.
Cleared all records from table 'issue_label'.
Cleared all records from table 'milestone'.
Cleared all records from table 'mirror'.
Cleared all records from table 'release'.
Cleared all records from table 'login_source'.
Cleared all records from table 'webhook'.
Cleared all records from table 'hook_task'.
Cleared all records from table 'protect_branch'.
Cleared all records from table 'protect_branch_whitelist'.
Cleared all records from table 'team'.
Cleared all records from table 'org_user'.
Cleared all records from table 'team_user'.
Cleared all records from table 'team_repo'.
Cleared all records from table 'notice'.
Cleared all records from table 'email_address'.
Cleared all records from table 'version'.
Database cleared of all records.
Migrated table 'user' with 42 rows.
Migrated table 'public_key' with 20 rows.
Migrated table 'access_token' with 1 rows.
Migrated table 'two_factor' with 3 rows.
Migrated table 'two_factor_recovery_code' with 30 rows.
Migrated table 'repository' with 128 rows.
Migrated table 'deploy_key' with 1 rows.
Migrated table 'collaboration' with 55 rows.
Migrated table 'access' with 263 rows.
Migrated table 'upload' with 0 rows.
Migrated table 'watch' with 245 rows.
Migrated table 'star' with 7 rows.
Migrated table 'follow' with 3 rows.
Migrated table 'action' with 3620 rows.
Migrated table 'issue' with 21 rows.
Migrated table 'pull_request' with 5 rows.
Migrated table 'comment' with 18 rows.
Migrated table 'attachment' with 1 rows.
Migrated table 'issue_user' with 53 rows.
Migrated table 'label' with 0 rows.
Migrated table 'issue_label' with 0 rows.
Migrated table 'milestone' with 0 rows.
Migrated table 'mirror' with 0 rows.
Migrated table 'release' with 0 rows.
Migrated table 'login_source' with 0 rows.
Migrated table 'webhook' with 4 rows.
Migrated table 'hook_task' with 135 rows.
Migrated table 'protect_branch' with 0 rows.
Migrated table 'protect_branch_whitelist' with 0 rows.
Migrated table 'team' with 5 rows.
Migrated table 'org_user' with 30 rows.
Migrated table 'team_user' with 30 rows.
Migrated table 'team_repo' with 35 rows.
Migrated table 'notice' with 1 rows.
Migrated table 'email_address' with 1 rows.
Migrated table 'version' with 1 rows.
Migration complete.

At the end, rollback the version in the target database:

UPDATE version SET version = 13;

Test Gogs 0.11

Now, copy 11.db back to your Gogs 0.11.86 install and test that it still works. It will require you copying repository data from 0.13 to 0.11 too and maybe some minor configuration changes (check paths, security key).

Check Gogs logs for errors.

Download and run Gitea 1.0.2

read the official Gitea documentation and the issues discussing migration from 0.11.x:

Download Gitea 1.0.2: https://dl.gitea.com/gitea/1.0.2/

Follow the official gitea documentation: copy the config from 0.13 to the gitea default path. TYPE under [database] should be renamed to DB_TYPE. In general try to run ./gitea web a few times and check output/logs.

Chage all the paths according to the migration procedure described in the documentation, proceed incrementally between major Gitea versions.

In my case I retained users, repositories, groups, avatars, issues, webhooks. Hooks and more features to be checked.

Extra steps

In the admi panel, run

  • "Rewrite all update hook of repositories (needed when custom config path is changed)"
  • "Rewrite '.ssh/authorized_keys' file (caution: non-Gitea keys will be lost)"

For some reason hooks update did not work for me and the action seems not to do anything. I manually deleted all of them as I did not had any custom one.

# Courtesy of ChatGPT :)
import sqlite3
import argparse
def get_schema(db_path):
"""Extracts the schema of all user-defined tables from the SQLite database."""
schema = {}
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Get all user-defined table names, excluding internal SQLite tables
cursor.execute("""
SELECT name FROM sqlite_master
WHERE type='table' AND name NOT LIKE 'sqlite_%'
""")
tables = [row[0] for row in cursor.fetchall()]
for table in tables:
cursor.execute(f"PRAGMA table_info(\"{table}\")")
columns = cursor.fetchall()
# Store as {table_name: {column_name: column_type}}
schema[table] = {column[1]: column[2] for column in columns}
conn.close()
except sqlite3.Error as e:
print(f"Error reading database {db_path}: {e}")
return schema
def clear_tables(db_path):
"""Deletes all records from all user-defined tables in the database."""
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Get all user-defined table names, excluding internal SQLite tables
cursor.execute("""
SELECT name FROM sqlite_master
WHERE type='table' AND name NOT LIKE 'sqlite_%'
""")
tables = [row[0] for row in cursor.fetchall()]
# Clear each table
for table in tables:
cursor.execute(f"DELETE FROM \"{table}\"")
print(f"Cleared all records from table '{table}'.")
conn.commit()
conn.close()
print("Database cleared of all records.")
except sqlite3.Error as e:
print(f"Error clearing database {db_path}: {e}")
def migrate_data(db1_path, db2_path, schema1, schema2):
"""Migrates data from db1 to db2, omitting missing tables/columns."""
try:
conn1 = sqlite3.connect(db1_path)
conn2 = sqlite3.connect(db2_path)
cursor1 = conn1.cursor()
cursor2 = conn2.cursor()
for table in schema2.keys():
if table not in schema1:
print(f"Skipping table '{table}' as it is missing in the source database.")
continue
# Get columns common to both schemas
common_columns = set(schema1[table].keys()).intersection(set(schema2[table].keys()))
if not common_columns:
print(f"Skipping table '{table}' as no columns match between databases.")
continue
# Generate column list for SELECT and INSERT
column_list = ", ".join([f"\"{col}\"" for col in common_columns])
query = f"SELECT {column_list} FROM \"{table}\""
# Fetch data from source database
cursor1.execute(query)
rows = cursor1.fetchall()
# Prepare insert statement for target database
placeholders = ", ".join(["?"] * len(common_columns))
insert_query = f"INSERT INTO \"{table}\" ({column_list}) VALUES ({placeholders})"
# Insert data into the target database
for row in rows:
cursor2.execute(insert_query, row)
print(f"Migrated table '{table}' with {len(rows)} rows.")
conn2.commit()
conn1.close()
conn2.close()
print("Migration complete.")
except sqlite3.Error as e:
print(f"Error during migration: {e}")
def main():
parser = argparse.ArgumentParser(description="Perform lossy migration between SQLite databases.")
parser.add_argument("db1", help="Path to the source SQLite database.")
parser.add_argument("db2", help="Path to the target SQLite database.")
args = parser.parse_args()
schema1 = get_schema(args.db1)
schema2 = get_schema(args.db2)
# Clear database 2
clear_tables(args.db2)
# Migrate data from database 1 to database 2
migrate_data(args.db1, args.db2, schema1, schema2)
if __name__ == "__main__":
main()
@fidojones
Copy link

yo save me, thanks

@arbv
Copy link

arbv commented Dec 1, 2024

Thank you sir! That worked. Very appreciated.

@arbv
Copy link

arbv commented Dec 1, 2024

Oh, it might be worth noting that if one is to migrate to forgejo, then it could be accomplished by migrating the database until the gitea version 1.21.5 (just like described in the linked Gitea docs) and then switching to forgejo version 7.0.

It seems that upgrading to gitea version 1.6.4 is enough before using gitea version 1.21.5 (skipping all other "major" Gitea versions) and then forgejo version 7.0. At least, that worked for me, not sure if it is recommended.

I lost a part of actions logs and avatars - not a big deal.

@lsd-cat
Copy link
Author

lsd-cat commented Dec 3, 2024

I lost a part of actions logs and avatars - not a big deal.

Thanks for your update! I also lost avatars in the end, but I think that happened in one of the gitea internal migrations, even though I did not pay too much attention to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment