Latest commit

History

1,459 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

build

This repo contains the build code used for production releases. The goal is to publish reproducible builds so that anyone can build and verify the code in production.

The main branch of all repos is the latest production-ready code.

The project uses warp versions, yyyy.mm.dd-version_code, so that versions are tied to dates and not release schedules. The project does not plan to have specific release schedules in the near future, so that we can iterate more fluidly on major features. We will maintain backwards compatibility as much as possible. Anytime there is a breaking change we will document it in the corresponding repo.

metadata/en-US/changelogs will be populated for each version code that is published in a store.

Changelogs

Changelogs are generated from the commits, not written by hand. all/changelog.py reads the submodule pins of two releases -- a release tag in this repo pins every submodule at an exact commit -- walks the commit range that fell between them in each component, and renders two different artifacts, because there are two audiences with very different limits:

  • the store note, metadata/en-US/changelogs/<version code>.txt. Capped at 500 characters: F-Droid truncates at 500 silently (fdroidserver's char_limits['whatsNew']) and Google Play rejects an over-length note outright. The generator packs whole bullets inside that budget, so it is never a store that does the cutting. It draws only on the components that ship inside the Android artifact, and holds back commits that touched only CI, tests, docs or build scripts -- those did not change the app anyone installed.
  • the full changelog, in the GitHub release body. Every commit in every submodule, grouped by component, with the first paragraph of each commit body and a link to each commit. Budgeted against GitHub's 125,000-character release body limit.

all/run.sh generates both during a release. It runs under warn_trap: if the generator fails for any reason the release still completes and the store note falls back to metadata/en-US/changelogs/pending.txt, which is also where you can put a human-written headline for the next release -- it is placed above the generated bullets.

The same text is written under the base version code and under each offset in FDROID_VERSION_CODE_OFFSETS (default 0 2 3), plus default.txt. That is not redundancy: fdroiddata's recipe declares VercodeOperation: ['%c + 2', '%c + 3'] for the ABI-split APKs and fdroidserver matches changelog files by exact filename, so a base-code-only file means F-Droid shows no changelog at all.

Set BUILD_RELEASE_BODY_CHANGELOG= to publish releases without the full changelog in the body.

Run it by hand for any pair of releases -- it is deterministic, and it needs no token for these repos because they are all public:

all/changelog.py --from v2026.8.21-1025339670 --to v2026.8.21-1025613560
all/changelog.py --help # filters, budgets, audiences
all/changelog.py --self-test # no network

Build and deploy all

all/run.sh runs daily:

  • builds all clients and services from main
  • runs local tests
  • branches and tags repos for reproducible builds
  • uploads clients to test tracks
  • deploys services to production over 16 hours

If you want to join the test tracks to get regular test builds via TestFlight and Google Play, please send a request to support@ur.io.

Reproducible builds

Builds use the semver <version>-<version_code>. Each semver has a corresponding tag in each repo, v<version>-<version_code>. You can check out the build repo at this tag to get all the repos at the correct tag also.

Because warp versions are yyyy.mm.dd, the go modules in the version branches are translated to the vyyyy major by appending /vyyyy to the end. This makes it so that any go module can be imported as <module>/vyyyy.

ABI-specific versions codes

If the version code ends in non-zero, it means you have an architecture-specific build that was done to reduce download size. In this case, just change the last digit to zero to find the git tags and branch names.

e.g.

614087741 # specific to arm64
614087740 # the version code to use for git tags, branches, etc.

Ungoogle flavor

We maintain a flavor of each version that removes all google dependencies. This is the most compatible with global devices.

Use the tag v<version>-<version_code>-ungoogle of the build repo to get the ungoogle variant.

Example build android client

If you downloaded the app from a store, go to Apps -> URnetwork to view the version and version code for the app. You can then build your own debug copy of the app with the following.

# check your apk for the version and version code
# git checkout v<version>-<version_code>
git checkout v2025.4.1-58770364
git submodule update --init
cd android/app
./gradlew assembleDebug

Resource notes

On macOS, the docker engine must have at least 16GiB of memory allocated.

DockerHub Images

Production images can be found at DockerHub. These are deployed with the warpctl tool.

About

Reproducible builds

Resources

Stars

44 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

1,459 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

build

This repo contains the build code used for production releases. The goal is to publish reproducible builds so that anyone can build and verify the code in production.

The main branch of all repos is the latest production-ready code.

The project uses warp versions, yyyy.mm.dd-version_code, so that versions are tied to dates and not release schedules. The project does not plan to have specific release schedules in the near future, so that we can iterate more fluidly on major features. We will maintain backwards compatibility as much as possible. Anytime there is a breaking change we will document it in the corresponding repo.

metadata/en-US/changelogs will be populated for each version code that is published in a store.

Changelogs

Changelogs are generated from the commits, not written by hand. all/changelog.py reads the submodule pins of two releases -- a release tag in this repo pins every submodule at an exact commit -- walks the commit range that fell between them in each component, and renders two different artifacts, because there are two audiences with very different limits:

  • the store note, metadata/en-US/changelogs/<version code>.txt. Capped at 500 characters: F-Droid truncates at 500 silently (fdroidserver's char_limits['whatsNew']) and Google Play rejects an over-length note outright. The generator packs whole bullets inside that budget, so it is never a store that does the cutting. It draws only on the components that ship inside the Android artifact, and holds back commits that touched only CI, tests, docs or build scripts -- those did not change the app anyone installed.
  • the full changelog, in the GitHub release body. Every commit in every submodule, grouped by component, with the first paragraph of each commit body and a link to each commit. Budgeted against GitHub's 125,000-character release body limit.

all/run.sh generates both during a release. It runs under warn_trap: if the generator fails for any reason the release still completes and the store note falls back to metadata/en-US/changelogs/pending.txt, which is also where you can put a human-written headline for the next release -- it is placed above the generated bullets.

The same text is written under the base version code and under each offset in FDROID_VERSION_CODE_OFFSETS (default 0 2 3), plus default.txt. That is not redundancy: fdroiddata's recipe declares VercodeOperation: ['%c + 2', '%c + 3'] for the ABI-split APKs and fdroidserver matches changelog files by exact filename, so a base-code-only file means F-Droid shows no changelog at all.

Set BUILD_RELEASE_BODY_CHANGELOG= to publish releases without the full changelog in the body.

Run it by hand for any pair of releases -- it is deterministic, and it needs no token for these repos because they are all public:

all/changelog.py --from v2026.8.21-1025339670 --to v2026.8.21-1025613560
all/changelog.py --help # filters, budgets, audiences
all/changelog.py --self-test # no network

Build and deploy all

all/run.sh runs daily:

  • builds all clients and services from main
  • runs local tests
  • branches and tags repos for reproducible builds
  • uploads clients to test tracks
  • deploys services to production over 16 hours

If you want to join the test tracks to get regular test builds via TestFlight and Google Play, please send a request to support@ur.io.

Reproducible builds

Builds use the semver <version>-<version_code>. Each semver has a corresponding tag in each repo, v<version>-<version_code>. You can check out the build repo at this tag to get all the repos at the correct tag also.

Because warp versions are yyyy.mm.dd, the go modules in the version branches are translated to the vyyyy major by appending /vyyyy to the end. This makes it so that any go module can be imported as <module>/vyyyy.

ABI-specific versions codes

If the version code ends in non-zero, it means you have an architecture-specific build that was done to reduce download size. In this case, just change the last digit to zero to find the git tags and branch names.

e.g.

614087741 # specific to arm64
614087740 # the version code to use for git tags, branches, etc.

Ungoogle flavor

We maintain a flavor of each version that removes all google dependencies. This is the most compatible with global devices.

Use the tag v<version>-<version_code>-ungoogle of the build repo to get the ungoogle variant.

Example build android client

If you downloaded the app from a store, go to Apps -> URnetwork to view the version and version code for the app. You can then build your own debug copy of the app with the following.

# check your apk for the version and version code
# git checkout v<version>-<version_code>
git checkout v2025.4.1-58770364
git submodule update --init
cd android/app
./gradlew assembleDebug

Resource notes

On macOS, the docker engine must have at least 16GiB of memory allocated.

DockerHub Images

Production images can be found at DockerHub. These are deployed with the warpctl tool.

About

Reproducible builds

Resources

Stars

44 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

1,459 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

build

This repo contains the build code used for production releases. The goal is to publish reproducible builds so that anyone can build and verify the code in production.

The main branch of all repos is the latest production-ready code.

The project uses warp versions, yyyy.mm.dd-version_code, so that versions are tied to dates and not release schedules. The project does not plan to have specific release schedules in the near future, so that we can iterate more fluidly on major features. We will maintain backwards compatibility as much as possible. Anytime there is a breaking change we will document it in the corresponding repo.

metadata/en-US/changelogs will be populated for each version code that is published in a store.

Changelogs

Changelogs are generated from the commits, not written by hand. all/changelog.py reads the submodule pins of two releases -- a release tag in this repo pins every submodule at an exact commit -- walks the commit range that fell between them in each component, and renders two different artifacts, because there are two audiences with very different limits:

  • the store note, metadata/en-US/changelogs/<version code>.txt. Capped at 500 characters: F-Droid truncates at 500 silently (fdroidserver's char_limits['whatsNew']) and Google Play rejects an over-length note outright. The generator packs whole bullets inside that budget, so it is never a store that does the cutting. It draws only on the components that ship inside the Android artifact, and holds back commits that touched only CI, tests, docs or build scripts -- those did not change the app anyone installed.
  • the full changelog, in the GitHub release body. Every commit in every submodule, grouped by component, with the first paragraph of each commit body and a link to each commit. Budgeted against GitHub's 125,000-character release body limit.

all/run.sh generates both during a release. It runs under warn_trap: if the generator fails for any reason the release still completes and the store note falls back to metadata/en-US/changelogs/pending.txt, which is also where you can put a human-written headline for the next release -- it is placed above the generated bullets.

The same text is written under the base version code and under each offset in FDROID_VERSION_CODE_OFFSETS (default 0 2 3), plus default.txt. That is not redundancy: fdroiddata's recipe declares VercodeOperation: ['%c + 2', '%c + 3'] for the ABI-split APKs and fdroidserver matches changelog files by exact filename, so a base-code-only file means F-Droid shows no changelog at all.

Set BUILD_RELEASE_BODY_CHANGELOG= to publish releases without the full changelog in the body.

Run it by hand for any pair of releases -- it is deterministic, and it needs no token for these repos because they are all public:

all/changelog.py --from v2026.8.21-1025339670 --to v2026.8.21-1025613560
all/changelog.py --help # filters, budgets, audiences
all/changelog.py --self-test # no network

Build and deploy all

all/run.sh runs daily:

  • builds all clients and services from main
  • runs local tests
  • branches and tags repos for reproducible builds
  • uploads clients to test tracks
  • deploys services to production over 16 hours

If you want to join the test tracks to get regular test builds via TestFlight and Google Play, please send a request to support@ur.io.

Reproducible builds

Builds use the semver <version>-<version_code>. Each semver has a corresponding tag in each repo, v<version>-<version_code>. You can check out the build repo at this tag to get all the repos at the correct tag also.

Because warp versions are yyyy.mm.dd, the go modules in the version branches are translated to the vyyyy major by appending /vyyyy to the end. This makes it so that any go module can be imported as <module>/vyyyy.

ABI-specific versions codes

If the version code ends in non-zero, it means you have an architecture-specific build that was done to reduce download size. In this case, just change the last digit to zero to find the git tags and branch names.

e.g.

614087741 # specific to arm64
614087740 # the version code to use for git tags, branches, etc.

Ungoogle flavor

We maintain a flavor of each version that removes all google dependencies. This is the most compatible with global devices.

Use the tag v<version>-<version_code>-ungoogle of the build repo to get the ungoogle variant.

Example build android client

If you downloaded the app from a store, go to Apps -> URnetwork to view the version and version code for the app. You can then build your own debug copy of the app with the following.

# check your apk for the version and version code
# git checkout v<version>-<version_code>
git checkout v2025.4.1-58770364
git submodule update --init
cd android/app
./gradlew assembleDebug

Resource notes

On macOS, the docker engine must have at least 16GiB of memory allocated.

DockerHub Images

Production images can be found at DockerHub. These are deployed with the warpctl tool.

About

Reproducible builds

Resources

Stars

44 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

1,459 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

build

This repo contains the build code used for production releases. The goal is to publish reproducible builds so that anyone can build and verify the code in production.

The main branch of all repos is the latest production-ready code.

The project uses warp versions, yyyy.mm.dd-version_code, so that versions are tied to dates and not release schedules. The project does not plan to have specific release schedules in the near future, so that we can iterate more fluidly on major features. We will maintain backwards compatibility as much as possible. Anytime there is a breaking change we will document it in the corresponding repo.

metadata/en-US/changelogs will be populated for each version code that is published in a store.

Changelogs

Changelogs are generated from the commits, not written by hand. all/changelog.py reads the submodule pins of two releases -- a release tag in this repo pins every submodule at an exact commit -- walks the commit range that fell between them in each component, and renders two different artifacts, because there are two audiences with very different limits:

  • the store note, metadata/en-US/changelogs/<version code>.txt. Capped at 500 characters: F-Droid truncates at 500 silently (fdroidserver's char_limits['whatsNew']) and Google Play rejects an over-length note outright. The generator packs whole bullets inside that budget, so it is never a store that does the cutting. It draws only on the components that ship inside the Android artifact, and holds back commits that touched only CI, tests, docs or build scripts -- those did not change the app anyone installed.
  • the full changelog, in the GitHub release body. Every commit in every submodule, grouped by component, with the first paragraph of each commit body and a link to each commit. Budgeted against GitHub's 125,000-character release body limit.

all/run.sh generates both during a release. It runs under warn_trap: if the generator fails for any reason the release still completes and the store note falls back to metadata/en-US/changelogs/pending.txt, which is also where you can put a human-written headline for the next release -- it is placed above the generated bullets.

The same text is written under the base version code and under each offset in FDROID_VERSION_CODE_OFFSETS (default 0 2 3), plus default.txt. That is not redundancy: fdroiddata's recipe declares VercodeOperation: ['%c + 2', '%c + 3'] for the ABI-split APKs and fdroidserver matches changelog files by exact filename, so a base-code-only file means F-Droid shows no changelog at all.

Set BUILD_RELEASE_BODY_CHANGELOG= to publish releases without the full changelog in the body.

Run it by hand for any pair of releases -- it is deterministic, and it needs no token for these repos because they are all public:

all/changelog.py --from v2026.8.21-1025339670 --to v2026.8.21-1025613560
all/changelog.py --help # filters, budgets, audiences
all/changelog.py --self-test # no network

Build and deploy all

all/run.sh runs daily:

  • builds all clients and services from main
  • runs local tests
  • branches and tags repos for reproducible builds
  • uploads clients to test tracks
  • deploys services to production over 16 hours

If you want to join the test tracks to get regular test builds via TestFlight and Google Play, please send a request to support@ur.io.

Reproducible builds

Builds use the semver <version>-<version_code>. Each semver has a corresponding tag in each repo, v<version>-<version_code>. You can check out the build repo at this tag to get all the repos at the correct tag also.

Because warp versions are yyyy.mm.dd, the go modules in the version branches are translated to the vyyyy major by appending /vyyyy to the end. This makes it so that any go module can be imported as <module>/vyyyy.

ABI-specific versions codes

If the version code ends in non-zero, it means you have an architecture-specific build that was done to reduce download size. In this case, just change the last digit to zero to find the git tags and branch names.

e.g.

614087741 # specific to arm64
614087740 # the version code to use for git tags, branches, etc.

Ungoogle flavor

We maintain a flavor of each version that removes all google dependencies. This is the most compatible with global devices.

Use the tag v<version>-<version_code>-ungoogle of the build repo to get the ungoogle variant.

Example build android client

If you downloaded the app from a store, go to Apps -> URnetwork to view the version and version code for the app. You can then build your own debug copy of the app with the following.

# check your apk for the version and version code
# git checkout v<version>-<version_code>
git checkout v2025.4.1-58770364
git submodule update --init
cd android/app
./gradlew assembleDebug

Resource notes

On macOS, the docker engine must have at least 16GiB of memory allocated.

DockerHub Images

Production images can be found at DockerHub. These are deployed with the warpctl tool.

About

Reproducible builds

Resources

Stars

44 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

1,459 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

build

This repo contains the build code used for production releases. The goal is to publish reproducible builds so that anyone can build and verify the code in production.

The main branch of all repos is the latest production-ready code.

The project uses warp versions, yyyy.mm.dd-version_code, so that versions are tied to dates and not release schedules. The project does not plan to have specific release schedules in the near future, so that we can iterate more fluidly on major features. We will maintain backwards compatibility as much as possible. Anytime there is a breaking change we will document it in the corresponding repo.

metadata/en-US/changelogs will be populated for each version code that is published in a store.

Changelogs

Changelogs are generated from the commits, not written by hand. all/changelog.py reads the submodule pins of two releases -- a release tag in this repo pins every submodule at an exact commit -- walks the commit range that fell between them in each component, and renders two different artifacts, because there are two audiences with very different limits:

  • the store note, metadata/en-US/changelogs/<version code>.txt. Capped at 500 characters: F-Droid truncates at 500 silently (fdroidserver's char_limits['whatsNew']) and Google Play rejects an over-length note outright. The generator packs whole bullets inside that budget, so it is never a store that does the cutting. It draws only on the components that ship inside the Android artifact, and holds back commits that touched only CI, tests, docs or build scripts -- those did not change the app anyone installed.
  • the full changelog, in the GitHub release body. Every commit in every submodule, grouped by component, with the first paragraph of each commit body and a link to each commit. Budgeted against GitHub's 125,000-character release body limit.

all/run.sh generates both during a release. It runs under warn_trap: if the generator fails for any reason the release still completes and the store note falls back to metadata/en-US/changelogs/pending.txt, which is also where you can put a human-written headline for the next release -- it is placed above the generated bullets.

The same text is written under the base version code and under each offset in FDROID_VERSION_CODE_OFFSETS (default 0 2 3), plus default.txt. That is not redundancy: fdroiddata's recipe declares VercodeOperation: ['%c + 2', '%c + 3'] for the ABI-split APKs and fdroidserver matches changelog files by exact filename, so a base-code-only file means F-Droid shows no changelog at all.

Set BUILD_RELEASE_BODY_CHANGELOG= to publish releases without the full changelog in the body.

Run it by hand for any pair of releases -- it is deterministic, and it needs no token for these repos because they are all public:

all/changelog.py --from v2026.8.21-1025339670 --to v2026.8.21-1025613560
all/changelog.py --help # filters, budgets, audiences
all/changelog.py --self-test # no network

Build and deploy all

all/run.sh runs daily:

  • builds all clients and services from main
  • runs local tests
  • branches and tags repos for reproducible builds
  • uploads clients to test tracks
  • deploys services to production over 16 hours

If you want to join the test tracks to get regular test builds via TestFlight and Google Play, please send a request to support@ur.io.

Reproducible builds

Builds use the semver <version>-<version_code>. Each semver has a corresponding tag in each repo, v<version>-<version_code>. You can check out the build repo at this tag to get all the repos at the correct tag also.

Because warp versions are yyyy.mm.dd, the go modules in the version branches are translated to the vyyyy major by appending /vyyyy to the end. This makes it so that any go module can be imported as <module>/vyyyy.

ABI-specific versions codes

If the version code ends in non-zero, it means you have an architecture-specific build that was done to reduce download size. In this case, just change the last digit to zero to find the git tags and branch names.

e.g.

614087741 # specific to arm64
614087740 # the version code to use for git tags, branches, etc.

Ungoogle flavor

We maintain a flavor of each version that removes all google dependencies. This is the most compatible with global devices.

Use the tag v<version>-<version_code>-ungoogle of the build repo to get the ungoogle variant.

Example build android client

If you downloaded the app from a store, go to Apps -> URnetwork to view the version and version code for the app. You can then build your own debug copy of the app with the following.

# check your apk for the version and version code
# git checkout v<version>-<version_code>
git checkout v2025.4.1-58770364
git submodule update --init
cd android/app
./gradlew assembleDebug

Resource notes

On macOS, the docker engine must have at least 16GiB of memory allocated.

DockerHub Images

Production images can be found at DockerHub. These are deployed with the warpctl tool.

About

Reproducible builds

Resources

Stars

44 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

1,459 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

build

This repo contains the build code used for production releases. The goal is to publish reproducible builds so that anyone can build and verify the code in production.

The main branch of all repos is the latest production-ready code.

The project uses warp versions, yyyy.mm.dd-version_code, so that versions are tied to dates and not release schedules. The project does not plan to have specific release schedules in the near future, so that we can iterate more fluidly on major features. We will maintain backwards compatibility as much as possible. Anytime there is a breaking change we will document it in the corresponding repo.

metadata/en-US/changelogs will be populated for each version code that is published in a store.

Changelogs

Changelogs are generated from the commits, not written by hand. all/changelog.py reads the submodule pins of two releases -- a release tag in this repo pins every submodule at an exact commit -- walks the commit range that fell between them in each component, and renders two different artifacts, because there are two audiences with very different limits:

  • the store note, metadata/en-US/changelogs/<version code>.txt. Capped at 500 characters: F-Droid truncates at 500 silently (fdroidserver's char_limits['whatsNew']) and Google Play rejects an over-length note outright. The generator packs whole bullets inside that budget, so it is never a store that does the cutting. It draws only on the components that ship inside the Android artifact, and holds back commits that touched only CI, tests, docs or build scripts -- those did not change the app anyone installed.
  • the full changelog, in the GitHub release body. Every commit in every submodule, grouped by component, with the first paragraph of each commit body and a link to each commit. Budgeted against GitHub's 125,000-character release body limit.

all/run.sh generates both during a release. It runs under warn_trap: if the generator fails for any reason the release still completes and the store note falls back to metadata/en-US/changelogs/pending.txt, which is also where you can put a human-written headline for the next release -- it is placed above the generated bullets.

The same text is written under the base version code and under each offset in FDROID_VERSION_CODE_OFFSETS (default 0 2 3), plus default.txt. That is not redundancy: fdroiddata's recipe declares VercodeOperation: ['%c + 2', '%c + 3'] for the ABI-split APKs and fdroidserver matches changelog files by exact filename, so a base-code-only file means F-Droid shows no changelog at all.

Set BUILD_RELEASE_BODY_CHANGELOG= to publish releases without the full changelog in the body.

Run it by hand for any pair of releases -- it is deterministic, and it needs no token for these repos because they are all public:

all/changelog.py --from v2026.8.21-1025339670 --to v2026.8.21-1025613560
all/changelog.py --help # filters, budgets, audiences
all/changelog.py --self-test # no network

Build and deploy all

all/run.sh runs daily:

  • builds all clients and services from main
  • runs local tests
  • branches and tags repos for reproducible builds
  • uploads clients to test tracks
  • deploys services to production over 16 hours

If you want to join the test tracks to get regular test builds via TestFlight and Google Play, please send a request to support@ur.io.

Reproducible builds

Builds use the semver <version>-<version_code>. Each semver has a corresponding tag in each repo, v<version>-<version_code>. You can check out the build repo at this tag to get all the repos at the correct tag also.

Because warp versions are yyyy.mm.dd, the go modules in the version branches are translated to the vyyyy major by appending /vyyyy to the end. This makes it so that any go module can be imported as <module>/vyyyy.

ABI-specific versions codes

If the version code ends in non-zero, it means you have an architecture-specific build that was done to reduce download size. In this case, just change the last digit to zero to find the git tags and branch names.

e.g.

614087741 # specific to arm64
614087740 # the version code to use for git tags, branches, etc.

Ungoogle flavor

We maintain a flavor of each version that removes all google dependencies. This is the most compatible with global devices.

Use the tag v<version>-<version_code>-ungoogle of the build repo to get the ungoogle variant.

Example build android client

If you downloaded the app from a store, go to Apps -> URnetwork to view the version and version code for the app. You can then build your own debug copy of the app with the following.

# check your apk for the version and version code
# git checkout v<version>-<version_code>
git checkout v2025.4.1-58770364
git submodule update --init
cd android/app
./gradlew assembleDebug

Resource notes

On macOS, the docker engine must have at least 16GiB of memory allocated.

DockerHub Images

Production images can be found at DockerHub. These are deployed with the warpctl tool.

About

Reproducible builds

Resources

Stars

44 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

1,459 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

build

This repo contains the build code used for production releases. The goal is to publish reproducible builds so that anyone can build and verify the code in production.

The main branch of all repos is the latest production-ready code.

The project uses warp versions, yyyy.mm.dd-version_code, so that versions are tied to dates and not release schedules. The project does not plan to have specific release schedules in the near future, so that we can iterate more fluidly on major features. We will maintain backwards compatibility as much as possible. Anytime there is a breaking change we will document it in the corresponding repo.

metadata/en-US/changelogs will be populated for each version code that is published in a store.

Changelogs

Changelogs are generated from the commits, not written by hand. all/changelog.py reads the submodule pins of two releases -- a release tag in this repo pins every submodule at an exact commit -- walks the commit range that fell between them in each component, and renders two different artifacts, because there are two audiences with very different limits:

  • the store note, metadata/en-US/changelogs/<version code>.txt. Capped at 500 characters: F-Droid truncates at 500 silently (fdroidserver's char_limits['whatsNew']) and Google Play rejects an over-length note outright. The generator packs whole bullets inside that budget, so it is never a store that does the cutting. It draws only on the components that ship inside the Android artifact, and holds back commits that touched only CI, tests, docs or build scripts -- those did not change the app anyone installed.
  • the full changelog, in the GitHub release body. Every commit in every submodule, grouped by component, with the first paragraph of each commit body and a link to each commit. Budgeted against GitHub's 125,000-character release body limit.

all/run.sh generates both during a release. It runs under warn_trap: if the generator fails for any reason the release still completes and the store note falls back to metadata/en-US/changelogs/pending.txt, which is also where you can put a human-written headline for the next release -- it is placed above the generated bullets.

The same text is written under the base version code and under each offset in FDROID_VERSION_CODE_OFFSETS (default 0 2 3), plus default.txt. That is not redundancy: fdroiddata's recipe declares VercodeOperation: ['%c + 2', '%c + 3'] for the ABI-split APKs and fdroidserver matches changelog files by exact filename, so a base-code-only file means F-Droid shows no changelog at all.

Set BUILD_RELEASE_BODY_CHANGELOG= to publish releases without the full changelog in the body.

Run it by hand for any pair of releases -- it is deterministic, and it needs no token for these repos because they are all public:

all/changelog.py --from v2026.8.21-1025339670 --to v2026.8.21-1025613560
all/changelog.py --help # filters, budgets, audiences
all/changelog.py --self-test # no network

Build and deploy all

all/run.sh runs daily:

  • builds all clients and services from main
  • runs local tests
  • branches and tags repos for reproducible builds
  • uploads clients to test tracks
  • deploys services to production over 16 hours

If you want to join the test tracks to get regular test builds via TestFlight and Google Play, please send a request to support@ur.io.

Reproducible builds

Builds use the semver <version>-<version_code>. Each semver has a corresponding tag in each repo, v<version>-<version_code>. You can check out the build repo at this tag to get all the repos at the correct tag also.

Because warp versions are yyyy.mm.dd, the go modules in the version branches are translated to the vyyyy major by appending /vyyyy to the end. This makes it so that any go module can be imported as <module>/vyyyy.

ABI-specific versions codes

If the version code ends in non-zero, it means you have an architecture-specific build that was done to reduce download size. In this case, just change the last digit to zero to find the git tags and branch names.

e.g.

614087741 # specific to arm64
614087740 # the version code to use for git tags, branches, etc.

Ungoogle flavor

We maintain a flavor of each version that removes all google dependencies. This is the most compatible with global devices.

Use the tag v<version>-<version_code>-ungoogle of the build repo to get the ungoogle variant.

Example build android client

If you downloaded the app from a store, go to Apps -> URnetwork to view the version and version code for the app. You can then build your own debug copy of the app with the following.

# check your apk for the version and version code
# git checkout v<version>-<version_code>
git checkout v2025.4.1-58770364
git submodule update --init
cd android/app
./gradlew assembleDebug

Resource notes

On macOS, the docker engine must have at least 16GiB of memory allocated.

DockerHub Images

Production images can be found at DockerHub. These are deployed with the warpctl tool.

About

Reproducible builds

Resources

Stars

44 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

1,459 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

build

This repo contains the build code used for production releases. The goal is to publish reproducible builds so that anyone can build and verify the code in production.

The main branch of all repos is the latest production-ready code.

The project uses warp versions, yyyy.mm.dd-version_code, so that versions are tied to dates and not release schedules. The project does not plan to have specific release schedules in the near future, so that we can iterate more fluidly on major features. We will maintain backwards compatibility as much as possible. Anytime there is a breaking change we will document it in the corresponding repo.

metadata/en-US/changelogs will be populated for each version code that is published in a store.

Changelogs

Changelogs are generated from the commits, not written by hand. all/changelog.py reads the submodule pins of two releases -- a release tag in this repo pins every submodule at an exact commit -- walks the commit range that fell between them in each component, and renders two different artifacts, because there are two audiences with very different limits:

  • the store note, metadata/en-US/changelogs/<version code>.txt. Capped at 500 characters: F-Droid truncates at 500 silently (fdroidserver's char_limits['whatsNew']) and Google Play rejects an over-length note outright. The generator packs whole bullets inside that budget, so it is never a store that does the cutting. It draws only on the components that ship inside the Android artifact, and holds back commits that touched only CI, tests, docs or build scripts -- those did not change the app anyone installed.
  • the full changelog, in the GitHub release body. Every commit in every submodule, grouped by component, with the first paragraph of each commit body and a link to each commit. Budgeted against GitHub's 125,000-character release body limit.

all/run.sh generates both during a release. It runs under warn_trap: if the generator fails for any reason the release still completes and the store note falls back to metadata/en-US/changelogs/pending.txt, which is also where you can put a human-written headline for the next release -- it is placed above the generated bullets.

The same text is written under the base version code and under each offset in FDROID_VERSION_CODE_OFFSETS (default 0 2 3), plus default.txt. That is not redundancy: fdroiddata's recipe declares VercodeOperation: ['%c + 2', '%c + 3'] for the ABI-split APKs and fdroidserver matches changelog files by exact filename, so a base-code-only file means F-Droid shows no changelog at all.

Set BUILD_RELEASE_BODY_CHANGELOG= to publish releases without the full changelog in the body.

Run it by hand for any pair of releases -- it is deterministic, and it needs no token for these repos because they are all public:

all/changelog.py --from v2026.8.21-1025339670 --to v2026.8.21-1025613560
all/changelog.py --help # filters, budgets, audiences
all/changelog.py --self-test # no network

Build and deploy all

all/run.sh runs daily:

  • builds all clients and services from main
  • runs local tests
  • branches and tags repos for reproducible builds
  • uploads clients to test tracks
  • deploys services to production over 16 hours

If you want to join the test tracks to get regular test builds via TestFlight and Google Play, please send a request to support@ur.io.

Reproducible builds

Builds use the semver <version>-<version_code>. Each semver has a corresponding tag in each repo, v<version>-<version_code>. You can check out the build repo at this tag to get all the repos at the correct tag also.

Because warp versions are yyyy.mm.dd, the go modules in the version branches are translated to the vyyyy major by appending /vyyyy to the end. This makes it so that any go module can be imported as <module>/vyyyy.

ABI-specific versions codes

If the version code ends in non-zero, it means you have an architecture-specific build that was done to reduce download size. In this case, just change the last digit to zero to find the git tags and branch names.

e.g.

614087741 # specific to arm64
614087740 # the version code to use for git tags, branches, etc.

Ungoogle flavor

We maintain a flavor of each version that removes all google dependencies. This is the most compatible with global devices.

Use the tag v<version>-<version_code>-ungoogle of the build repo to get the ungoogle variant.

Example build android client

If you downloaded the app from a store, go to Apps -> URnetwork to view the version and version code for the app. You can then build your own debug copy of the app with the following.

# check your apk for the version and version code
# git checkout v<version>-<version_code>
git checkout v2025.4.1-58770364
git submodule update --init
cd android/app
./gradlew assembleDebug

Resource notes

On macOS, the docker engine must have at least 16GiB of memory allocated.

DockerHub Images

Production images can be found at DockerHub. These are deployed with the warpctl tool.

About

Reproducible builds

Resources

Stars

44 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages