Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-7276] Setup Bitwarden F-Droid repo #3

Merged
merged 10 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .github/qrcode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 90 additions & 0 deletions .github/workflows/fdroid.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Generate F-Droid repo
SaintPatrck marked this conversation as resolved.
Show resolved Hide resolved

on:
push:
branches: [ main ]
SaintPatrck marked this conversation as resolved.
Show resolved Hide resolved
workflow_dispatch:
schedule:
- cron: "45 2 * * *"
SaintPatrck marked this conversation as resolved.
Show resolved Hide resolved

jobs:
apps:
name: "Generate repo from apps listing"
runs-on: ubuntu-latest
SaintPatrck marked this conversation as resolved.
Show resolved Hide resolved

steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
SaintPatrck marked this conversation as resolved.
Show resolved Hide resolved

- name: Create basic directory structure
run: mkdir -p fdroid/repo

- name: Restore correct mtime
run: |
sudo apt install git-restore-mtime
SaintPatrck marked this conversation as resolved.
Show resolved Hide resolved
git restore-mtime

- name: Install F-Droid server software
run: |
sudo add-apt-repository ppa:fdroid/fdroidserver
sudo apt-get update
sudo apt-get install fdroidserver

- name: Set up Git credentials
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: |
git config --global credential.helper store
echo "https://${ACCESS_TOKEN}:[email protected]" >> ~/.git-credentials
git config --global user.email "[email protected]"
git config --global user.name "Bitwarden CI"

- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
cred: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}

- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-action/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key,
github-gpg-private-key-passphrase,
github-pat-bitwarden-devops-mobile-fdroid"

- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
with:
gpg_private_key: ${{ steps.retrive-secrets.outputs.github-gpg-private-key }}
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
git_user_signingkey: true
git_commit_gpgsign: true

- name: Download secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
run: |
mkdir -p $HOME/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name store_fdroid-keystore.jks fdroid/keystore.p12 --output none

- name: Configure F-Droid server
env:
FDROID_STORE_KEYSTORE_PASSWORD: ${{ secrets.FDROID_STORE_KEYSTORE_PASSWORD }}
run: |
chmod 600 fdroid/config.yml
echo "keypass: $FDROID_STORE_KEYSTORE_PASSWORD" >> fdroid/config.yml
echo "keystorepass: $FDROID_STORE_KEYSTORE_PASSWORD" >> fdroid/config.yml
echo "archive_older: 0" >> fdroid/config.yml
echo "repo_url: https://raw.githubusercontent.com/bitwarden/fdroid/main/fdroid/repo" >> fdroid/config.yml
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how the current F-Droid config.yml is being constructed in bitwarden/mobile. Depending on how https://bitwarden.atlassian.net/servicedesk/customer/portal/5/TSD-797 is resolved, this may change. I have recommended that config.yml be stored as a precompiled file in Azure Vault instead of compiling it during workflow execution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing the completed config.yml file in an Azure Storage Account will work great.


- name: Set up Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: '^1.17.0'

SaintPatrck marked this conversation as resolved.
Show resolved Hide resolved
- name: Run update script
run: bash update.sh 2>&1
env:
GH_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,19 @@ Thumbs.db
# Node
node_modules
npm-debug.log

# Secrets
config.yml
keystore.p12
keystore.jks

# F-Droid tools
fdroid/tmp
fdroid/repo/status
fdroid/metadata/**/phoneScreenshots

# Metascoop
metascoop/metascoop
config.py
out.txt
**.tmp
21 changes: 21 additions & 0 deletions .thirdparty_licenses/xarantolus-LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 xarantolus

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
# Template Repository
# fdroid
This repository hosts an [F-Droid](https://f-droid.org/) repo for Bitwarden apps. This allows you to install and update Bitwarden apps very easily.

This repository serves as a template for others and establishes very basic structure and tooling setup for later customization.
### Bitwarden Apps

<!-- This table is auto-generated. Do not edit -->
| Icon | Name | Description | Version |
|------|------|-------------|---------|
<!-- end apps table -->

### How to use
1. At first, you should [install the F-Droid app](https://f-droid.org/), it's an alternative app store for Android.
2. Now you can copy the following [link](https://raw.githubusercontent.com/bitwatrden/fdroid/main/fdroid/repo?fingerprint={fingerprint-hash}), then add this repository to your F-Droid client:

```
https://raw.githubusercontent.com/bitwarden/fdroid/main/fdroid/repo?fingerprint={fingerprint-hash}
```

Alternatively, you can also scan this QR code:

<p align="center">
<img src=".github/qrcode.png?raw=true" alt="F-Droid repo QR code"/>
</p>

3. Open the link in F-Droid. It will ask you to add the repository. Everything should already be filled in correctly, so just press "OK".
4. You can now install Bitwarden apps, e.g. start by searching for "Bitwarden" in the F-Droid client.

Please note that some apps published here might contain [Anti-Features](https://f-droid.org/en/docs/Anti-Features/). If you can't find an app by searching for it, you can go to settings and enable "Include anti-feature apps".
16 changes: 16 additions & 0 deletions apps.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
bitwarden:
git: https://github.com/bitwarden/android
name: "Bitwarden"
description: |
This repo has the new native Android app, currently in Beta. Looking for the legacy .NET MAUI apps? Head on over to bitwarden/mobile.
vvolkgang marked this conversation as resolved.
Show resolved Hide resolved
categories:
- Security

bitwarden-beta:
git: https://github.com/bitwarden/android
name: "Bitwarden Beta"
prerelease: true
description: |
This repo has the new native Android app. Looking for the legacy .NET MAUI apps? Head on over to bitwarden/mobile.
categories:
- Security
vvolkgang marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions fdroid/repo/categories.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Security
fdroid
Binary file added fdroid/repo/icons/fdroid-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added fdroid/repo/icons/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added fdroid/repo/index.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
80 changes: 80 additions & 0 deletions metascoop/apps/apps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package apps

import (
"fmt"
"net/url"
"os"
"strings"

"gopkg.in/yaml.v3"
)

type AppInfo struct {
GitURL string `yaml:"git"`
Summary string `yaml:"summary"`

AuthorName string `yaml:"author"`
repoAuthor string

FriendlyName string `yaml:"name"`
keyName string

Description string `yaml:"description"`

Categories []string `yaml:"categories"`

AntiFeatures []string `yaml:"anti_features"`

ReleaseDescription string

License string

Prerelease bool `yaml:"prerelease"`
}

func (a AppInfo) Name() string {
return a.keyName
}

func (a AppInfo) Author() string {
if a.AuthorName != "" {
return a.AuthorName
}
return a.repoAuthor
}

// ParseAppFile returns the list of apps from the app file
func ParseAppFile(filepath string) (list []AppInfo, err error) {
f, err := os.Open(filepath)
if err != nil {
return
}
defer f.Close()

var apps map[string]AppInfo

err = yaml.NewDecoder(f).Decode(&apps)
if err != nil {
return
}

for k, a := range apps {
a.keyName = k

u, uerr := url.ParseRequestURI(a.GitURL)
if uerr != nil {
err = fmt.Errorf("problem with given git URL %q for app with key=%q, name=%q: %w", a.GitURL, k, a.Name(), uerr)
return
}

split := strings.Split(strings.Trim(u.Path, "/"), "/")
if len(split) == 0 {
return
}
a.repoAuthor = split[0]

list = append(list, a)
}

return
}
107 changes: 107 additions & 0 deletions metascoop/apps/index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package apps

import (
"encoding/json"
"os"
"reflect"
"sort"
"strings"

"github.com/hashicorp/go-version"
"github.com/r3labs/diff/v2"
)

type RepoIndex struct {
Repo map[string]interface{} `json:"repo"`
Requests map[string]interface{} `json:"requests"`
Apps []map[string]interface{} `json:"apps"`

Packages map[string][]PackageInfo `json:"packages"`
}

type PackageInfo struct {
Added int64 `json:"added"`
ApkName string `json:"apkName"`
Hash string `json:"hash"`
HashType string `json:"hashType"`
MinSdkVersion int `json:"minSdkVersion"`
Nativecode []string `json:"nativecode"`
PackageName string `json:"packageName"`
Sig string `json:"sig"`
Signer string `json:"signer"`
Size int `json:"size"`
TargetSdkVersion int `json:"targetSdkVersion"`
VersionCode int `json:"versionCode,omitempty"`
VersionName string `json:"versionName"`
}

func (r *RepoIndex) FindLatestPackage(pkgName string) (p PackageInfo, ok bool) {
pkgs, ok := r.Packages[pkgName]
if !ok {
return p, false
}

sort.Slice(pkgs, func(i, j int) bool {
if pkgs[i].VersionCode != pkgs[j].VersionCode {
return pkgs[i].VersionCode < pkgs[j].VersionCode
}

v1, err := version.NewVersion(pkgs[i].VersionName)
if err != nil {
return true
}

v2, err := version.NewVersion(pkgs[i].VersionName)
if err != nil {
return false
}

return v1.LessThan(v2)
})

// Return the one with the latest version
return pkgs[len(pkgs)-1], true
}

func ReadIndex(path string) (index *RepoIndex, err error) {
f, err := os.Open(path)
if err != nil {
return
}
defer f.Close()

err = json.NewDecoder(f).Decode(&index)

return
}

func HasSignificantChanges(old, new *RepoIndex) (changedPath string, changed bool) {
changelog, err := diff.Diff(old, new)
if err != nil {
panic("diffing fdroid index structs: " + err.Error())
}

for _, change := range changelog {
if change.Type != diff.UPDATE {
return strings.Join(change.Path, "."), true
}

var isIgnoredChange = false

// Fdroid seems to update the "added" timestamp of apps every time we run the command
if len(change.Path) > 0 && (strings.EqualFold(change.Path[len(change.Path)-1], "added") || strings.EqualFold(change.Path[len(change.Path)-1], "lastUpdated")) {
isIgnoredChange = true
}

// Also it updates the repo timestamp
if reflect.DeepEqual(change.Path, []string{"Repo", "timestamp"}) {
isIgnoredChange = true
}

if !isIgnoredChange {
return strings.Join(change.Path, "."), true
}
}

return "", false
}
Loading
Loading