mirror of
https://github.com/shenxn/protonmail-bridge-docker.git
synced 2026-01-18 06:34:41 +01:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75e29dceeb | ||
|
|
5794e489e0 | ||
|
|
201bf1ed17 | ||
|
|
199df4b925 | ||
|
|
aef8462674 | ||
|
|
bc23882b91 | ||
|
|
8101ecda38 | ||
|
|
1c64b99201 | ||
|
|
c38ac6fbb5 | ||
|
|
a983e6e88a | ||
|
|
b76bca4819 | ||
|
|
fa9ac0152e | ||
|
|
62a2ad4034 | ||
|
|
dbb734eae8 | ||
|
|
46b3cf35a4 |
281
.github/workflows/build.yaml
vendored
281
.github/workflows/build.yaml
vendored
@@ -1,232 +1,93 @@
|
||||
name: build from source
|
||||
name: Build docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/build.yaml
|
||||
- build/*
|
||||
- VERSION
|
||||
branches:
|
||||
- master
|
||||
- beta
|
||||
- dev
|
||||
- http-rest
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/build.yaml
|
||||
- build/*
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GHCR_REPO: shenxn/protonmail-bridge-docker
|
||||
DOCKERHUB_REPO: shenxn/protonmail-bridge
|
||||
DOCKER_REPO_DEV: ghcr.io/shenxn/protonmail-bridge
|
||||
PLATFORMS: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/riscv64
|
||||
DOCKER_REPO: shenxn/protonmail-bridge
|
||||
DOCKER_REPO_DEV: ghcr.io/shenxn/protonmail-bridge-dev
|
||||
PLATFORMS: linux/amd64,linux/arm64/v8,linux/arm/v7
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref != 'refs/heads/master'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Set version
|
||||
id: version
|
||||
run: echo "version=`cat VERSION`" >> $GITHUB_ENV
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKER_REPO_DEV }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,"name=${{ env.DOCKER_REPO_DEV }}",push-by-digest=false,name-canonical=true,push=true
|
||||
context: ./build
|
||||
file: ./build/Dockerfile
|
||||
tags: "${{ env.DOCKER_REPO_DEV }}:dev-${{ github.ref_name }}"
|
||||
build-args: |
|
||||
version=${{ env.version }}
|
||||
|
||||
- name: Run Trivy vulnerability scan
|
||||
uses: aquasecurity/trivy-action@0.30.0
|
||||
with:
|
||||
image-ref: "${{ env.DOCKER_REPO_DEV }}:dev-${{ github.ref_name }}"
|
||||
format: 'sarif'
|
||||
exit-code: 0
|
||||
severity: 'CRITICAL,HIGH'
|
||||
output: 'trivy-results.sarif'
|
||||
|
||||
- name: Upload Trivy scan SARIF report
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64/v8
|
||||
- linux/arm/v7
|
||||
- linux/riscv64
|
||||
services:
|
||||
registry:
|
||||
image: registry:2
|
||||
ports:
|
||||
- 5000:5000
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set version
|
||||
- name: Set bridge version
|
||||
id: version
|
||||
run: echo "version=`cat VERSION`" >> $GITHUB_ENV
|
||||
|
||||
run: |
|
||||
echo "::set-output name=bridge_version::$(cat BRIDGE_VERSION)"
|
||||
echo "::set-output name=version::$(cat VERSION)"
|
||||
- name: Set repo
|
||||
id: repo
|
||||
run: |
|
||||
if [[ $GITHUB_REF == "refs/heads/master" || $GITHUB_REF == "refs/heads/beta" ]]; then
|
||||
echo "::set-output name=repo::${DOCKER_REPO}"
|
||||
else
|
||||
echo "::set-output name=repo::${DOCKER_REPO_DEV}"
|
||||
fi
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
id: docker_meta
|
||||
uses: crazy-max/ghaction-docker-meta@v1
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKERHUB_REPO }}
|
||||
${{ env.GHCR_REPO }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
images: ${{ steps.repo.outputs.repo }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,"name=name=${{ env.DOCKERHUB_REPO }},${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=false
|
||||
context: ./build
|
||||
file: ./build/Dockerfile
|
||||
tags: |
|
||||
"${{ env.DOCKERHUB_REPO }}:build"
|
||||
"${{ env.DOCKERHUB_REPO }}:${{ env.version }}-build"
|
||||
"${{ env.GHCR_REPO }}:build"
|
||||
"${{ env.GHCR_REPO }}:${{ env.version }}-build"
|
||||
provenance: false
|
||||
sbom: false
|
||||
build-args: |
|
||||
version=${{ env.version }}
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p ${{ runner.temp }}/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set version
|
||||
id: version
|
||||
run: echo "version=`cat VERSION`" >> $GITHUB_ENV
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
- name: Build image without push to registry
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKERHUB_REPO }}
|
||||
${{ env.GHCR_REPO }}
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
build-args: BRIDGE_VERSION=${{ steps.version.outputs.bridge_version }}
|
||||
push: true
|
||||
tags: localhost:5000/protonmail-bridge:latest
|
||||
- name: Scan image
|
||||
id: scan
|
||||
uses: anchore/scan-action@v2
|
||||
with:
|
||||
image: localhost:5000/protonmail-bridge:latest
|
||||
fail-build: true
|
||||
severity-cutoff: critical
|
||||
acs-report-enable: true
|
||||
- name: Upload Anchore scan SARIF report
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: ${{ steps.scan.outputs.sarif }}
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
if: ${{ github.event_name != 'pull_request' && steps.repo.outputs.repo == env.DOCKER_REPO }}
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
if: ${{ github.event_name != 'pull_request' && steps.repo.outputs.repo == env.DOCKER_REPO_DEV }}
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
- name: Push image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
build-args: BRIDGE_VERSION=${{ steps.version.outputs.bridge_version }}
|
||||
tags: |
|
||||
type=raw,enable=true,value=${{ env.version }}-build
|
||||
type=raw,enable=true,suffix=,value=build
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: ${{ runner.temp }}/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.DOCKERHUB_REPO }}@sha256:%s ' *)
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.GHCR_REPO }}@sha256:%s ' *)
|
||||
|
||||
- name: Run Trivy vulnerability scan
|
||||
uses: aquasecurity/trivy-action@0.30.0
|
||||
with:
|
||||
image-ref: "${{ env.DOCKERHUB_REPO }}:${{ env.version }}-build"
|
||||
format: 'sarif'
|
||||
exit-code: 0
|
||||
severity: 'CRITICAL,HIGH'
|
||||
output: 'trivy-results.sarif'
|
||||
- name: Upload Trivy scan SARIF report
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.DOCKERHUB_REPO }}:${{ steps.meta.outputs.version }}
|
||||
docker buildx imagetools inspect ${{ env.GHCR_REPO }}:${{ steps.meta.outputs.version }}
|
||||
${{ steps.repo.outputs.repo }}:beta
|
||||
${{ steps.repo.outputs.repo }}:${{ steps.version.outputs.version }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
79
.github/workflows/deb.yaml
vendored
79
.github/workflows/deb.yaml
vendored
@@ -1,79 +0,0 @@
|
||||
name: pack from deb
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/deb.yaml
|
||||
- deb/*
|
||||
- VERSION
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/deb.yaml
|
||||
- deb/*
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DOCKER_REPO: shenxn/protonmail-bridge
|
||||
DOCKER_REPO_DEV: ghcr.io/shenxn/protonmail-bridge-dev
|
||||
|
||||
jobs:
|
||||
deb:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
- name: Set version
|
||||
id: version
|
||||
run: echo "version=`cat VERSION`" >> $GITHUB_ENV
|
||||
- name: Set repo
|
||||
id: repo
|
||||
run: if [[ $GITHUB_REF == "refs/heads/master" ]]; then echo "::set-output name=repo::${DOCKER_REPO}"; else echo "::set-output name=repo::${DOCKER_REPO_DEV}"; fi
|
||||
- name: Docker meta
|
||||
id: docker_meta
|
||||
uses: crazy-max/ghaction-docker-meta@v1
|
||||
with:
|
||||
images: ${{ steps.repo.outputs.repo }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Build image without push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./deb
|
||||
file: ./deb/Dockerfile
|
||||
load: true
|
||||
tags: protonmail-bridge:latest
|
||||
- name: Scan image
|
||||
id: scan
|
||||
uses: anchore/scan-action@v2
|
||||
with:
|
||||
image: protonmail-bridge:latest
|
||||
fail-build: true
|
||||
severity-cutoff: critical
|
||||
acs-report-enable: true
|
||||
- name: Upload Anchore scan SARIF report
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: ${{ steps.scan.outputs.sarif }}
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
- name: Push image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./deb
|
||||
file: ./deb/Dockerfile
|
||||
tags: |
|
||||
${{ steps.repo.outputs.repo }}:latest
|
||||
${{ steps.repo.outputs.repo }}:${{ env.version }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
3
.github/workflows/update-check.yaml
vendored
3
.github/workflows/update-check.yaml
vendored
@@ -2,6 +2,9 @@ name: update check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths:
|
||||
- .github/workflows/update-check.yaml
|
||||
- update-check.py
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1 @@
|
||||
# Ignoring IDE-specific files
|
||||
.idea/*
|
||||
.vscode
|
||||
|
||||
1
BRIDGE_VERSION
Normal file
1
BRIDGE_VERSION
Normal file
@@ -0,0 +1 @@
|
||||
v2.2.0
|
||||
37
Dockerfile
Normal file
37
Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
FROM golang:1.15 AS build
|
||||
|
||||
# Install dependencies
|
||||
RUN apt-get update && apt-get install -y libsecret-1-dev
|
||||
|
||||
ARG BRIDGE_VERSION
|
||||
|
||||
# Build
|
||||
WORKDIR /build/
|
||||
COPY build.sh /build/
|
||||
COPY http_rest_frontend /build/http_rest_frontend
|
||||
RUN bash build.sh
|
||||
|
||||
FROM ubuntu:bionic
|
||||
LABEL maintainer="Xiaonan Shen <s@sxn.dev>"
|
||||
|
||||
EXPOSE 25/tcp
|
||||
EXPOSE 143/tcp
|
||||
|
||||
HEALTHCHECK --timeout=2s CMD nc -z 127.0.0.1 1025 && nc -z 127.0.0.1 1143
|
||||
|
||||
# Install dependencies and protonmail bridge
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends socat pass libsecret-1-0 ca-certificates curl gosu netcat \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN curl -sSL https://github.com/krallin/tini/releases/download/v0.19.0/tini-$(dpkg --print-architecture) -o /tini \
|
||||
&& chmod +x /tini
|
||||
|
||||
# Copy bash scripts
|
||||
COPY gpgparams entrypoint.sh run_protonmail_bridge.sh cli.sh /protonmail/script/
|
||||
RUN ln -s /protonmail/script/cli.sh /usr/local/bin/cli
|
||||
# Copy protonmail
|
||||
COPY --from=build /build/proton-bridge/proton-bridge /usr/local/bin/
|
||||
|
||||
VOLUME [ "/protonmail/data" ]
|
||||
|
||||
ENTRYPOINT ["/tini", "--", "/protonmail/script/entrypoint.sh"]
|
||||
107
README.md
107
README.md
@@ -12,15 +12,86 @@ Docker Hub: [https://hub.docker.com/r/shenxn/protonmail-bridge](https://hub.dock
|
||||
|
||||
GitHub: [https://github.com/shenxn/protonmail-bridge-docker](https://github.com/shenxn/protonmail-bridge-docker)
|
||||
|
||||
## ARM Support
|
||||
## ProtonMail Bridge Docker 2.0 Announcement
|
||||
|
||||
We now support ARM devices (`arm64` and `arm/v7`)! Use the images tagged with `build`. See next section for details.
|
||||
ProtonMail Bridge Docker 2.0 uses a completely different approach that makes the whole container much easier to use. First of all, it now supports setting up with environmental variables only. No longer messing with terminal commands. Secondly, it now provides a easy way to interact with the bridge, include getting account information, adding, and removing accounts, all after the first initialization. This version is now in beta. Feel free to test it by using the `beta` tag (`shenxn/protonmail-bridge:beta`).
|
||||
|
||||
## Tags
|
||||
### Environmental Variables
|
||||
|
||||
name | default | description
|
||||
-- | -- | --
|
||||
`PROTON_USERNAME` | | Username of your Proton account.
|
||||
`PROTON_PASSWORD` | | Password of your Proton account.
|
||||
`PROTON_2FA` | | Two-factor authentication code of your Proton account. Only needed if you have two factor authentication enabled.
|
||||
`PROTON_MAILBOX_PASSWORD` | | Mailbox password of your Proton account. Only needed if you have two-password mode enabled.
|
||||
`PROTON_PRINT_ACCOUNT_INFO` | `true` | Whether to print the local connection information (username, password, ports, security, etc.) on successful auto logins. Set to `false` to turn it off.
|
||||
`PROTON_IMAP_PORT` | `25` | IMAP port of the bridge.
|
||||
`PROTON_SMTP_PORT` | `143` | SMTP port of the bridge.
|
||||
`PROTON_SMTP_SECURITY` | `STARTTLS` | Connection security of SMTP. Can be set to `STARTTLS` or `SSL`.
|
||||
`PROTON_MANAGEMENT_PORT` | `1080` | Port of the management HTTP server.
|
||||
`PROTON_ALLOW_PROXY` | `true` | Allow or disallow the Bridge client to securely connect to Proton via a third party when it is being blocked.
|
||||
`PROTON_UID` | `1001` | UID of the user to run the bridge.
|
||||
`PROTON_GID` | `1001` | GID of the group to run the bridge.
|
||||
|
||||
### Data Volume
|
||||
|
||||
All data are stored under `/protonmail/data`.
|
||||
|
||||
### Auto Login
|
||||
|
||||
2.0 comes with auto login, a highly demanded feature. By set `PROTON_USERNAME`, `PROTON_PASSWORD`, `PROTON_2FA`, and `PROTON_MAILBOX_PASSWORD`, the container will automatically add your account to the bridge and print local connection info to the log. Note:
|
||||
- The auto login feature only kicks in when the bridge has zero account added. If there is at least one account, auto login will be skipped. Changing the environmental variables will neither add another account to the bridge nor replace the account with the new one. To do so, use [CLI](#cli).
|
||||
- Two factor authentication codes time out quickly (in 30 seconds). It is highly possible that the code is already expired at the time login take place. To help with that, you can try pull the image first before setting it. You can also wait for a new code so that the container will have full 30 seconds to boot up. If non of them works, try manual login with [CLI](#cli).
|
||||
- If your container logs can be accessed by someone else, it is recommended to turn of the account info printing by set `PROTON_PRINT_ACCOUNT_INFO` to `false`. Then you can use [CLI](#cli) to fetch the account info.
|
||||
|
||||
### CLI
|
||||
|
||||
2.0 comes with a CLI to easily interact with the bridge. There is no need to modify the entrypoint, manually kill any process, or restart the container when you are done. Everything you need is to `exec` into the container and run `cli`. With that, you can add, remove, list accounts and print account info. Changing the address mode of an account is currently not supported. To do so, simply delete the account and add it back again. Example:
|
||||
```
|
||||
➜ docker exec -it protonmail cli
|
||||
CLI to interacte with Proton Bridge HTTP REST interface
|
||||
Available commands:
|
||||
login: Calls up the login procedure to add or connect accounts.
|
||||
delete <account>: Remove the account from keychain. You can use index or account name as the parameter.
|
||||
list: Print list of your accounts.
|
||||
info <account>: Print account configuration. You can use index or account name as the parameter.
|
||||
help: Print help messages.
|
||||
exit: Exit the CLI
|
||||
|
||||
>> info bob
|
||||
Configuration for bob@proton.me
|
||||
IMAP port: 143
|
||||
IMAP security: STARTTLS
|
||||
SMTP port: 25
|
||||
SMTP security: STARTTLS
|
||||
Username: bob@proton.me
|
||||
Password: xxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
>> exit
|
||||
```
|
||||
|
||||
### Migrate from old version
|
||||
|
||||
To migrate, you just need to change the mount path from `/root` to `/protonmail/data` and the container will do the rest of the work. Note that this is a one way trip. There is no way to get back to the old version. You'll need to set up everything again from scratch to go back. Also, any bridge preferences (e.g. whether to use a proxy) will not be migrated. You'll need to set them again with the environmental variables. Not all preferences are supported yet. Feel free to open an issue or PR for options you want to change. If anything goes sideways, it can be caused by problematic migrating steps. You can try to clear your volume and start everything from the scratch.
|
||||
|
||||
## Supported architectures
|
||||
|
||||
architecture | status | note
|
||||
-- | --
|
||||
`amd64` | supported |
|
||||
`arm64/v8` | supported |
|
||||
`arm/v7` | supported | 32-bit platforms are no longer officially supported by Proton. Thanks to blumberg for the workaround [#40](https://github.com/shenxn/protonmail-bridge-docker/pull/40)
|
||||
`riscv64` | WIP | Check progress at [#54](https://github.com/shenxn/protonmail-bridge-docker/pull/54)
|
||||
|
||||
## Legacy Documentation
|
||||
|
||||
This section contains information for the old version. They are kept here for reference and will be removed once 2.0 graduates from beta.
|
||||
|
||||
### Tags
|
||||
|
||||
There are two types of images.
|
||||
- `deb`: Images based on the official [.deb release](https://protonmail.com/bridge/install). It only supports the `amd64` architecture.
|
||||
- `build`: Images based on the [source code](https://github.com/ProtonMail/proton-bridge). It supports `amd64`, `arm64`, `arm/v7` and `riscv64`. Supporting to more architectures is possible. PRs are welcome.
|
||||
- `build`: Images based on the [source code](https://github.com/ProtonMail/proton-bridge). It supports `amd64`, `arm64`, and `arm/v7`. Supporting to more architectures is possible. PRs are welcome.
|
||||
|
||||
tag | description
|
||||
-- | --
|
||||
@@ -29,7 +100,7 @@ tag | description
|
||||
`build` | latest `build` image
|
||||
`[version]-build` | `build` images
|
||||
|
||||
## Initialization
|
||||
### Initialization
|
||||
|
||||
To initialize and add account to the bridge, run the following command.
|
||||
|
||||
@@ -37,15 +108,9 @@ To initialize and add account to the bridge, run the following command.
|
||||
docker run --rm -it -v protonmail:/root shenxn/protonmail-bridge init
|
||||
```
|
||||
|
||||
If you want to use Docker Compose instead, you can create a copy of the provided example [docker-compose.yml](docker-compose.yml) file, modify it to suit your needs, and then run the following command:
|
||||
Wait for the bridge to startup, use `login` command and follow the instructions to add your account into the bridge. Then use `info` to see the configuration information (username and password). After that, use `exit` to exit the bridge. You may need `CTRL+C` to exit the docker entirely.
|
||||
|
||||
```
|
||||
docker compose run protonmail-bridge init
|
||||
```
|
||||
|
||||
Wait for the bridge to startup, then you will see a prompt appear for [Proton Mail Bridge interactive shell](https://proton.me/support/bridge-cli-guide). Use the `login` command and follow the instructions to add your account into the bridge. Then use `info` to see the configuration information (username and password). After that, use `exit` to exit the bridge. You may need `CTRL+C` to exit the docker entirely.
|
||||
|
||||
## Run
|
||||
### Run
|
||||
|
||||
To run the container, use the following command.
|
||||
|
||||
@@ -53,19 +118,13 @@ To run the container, use the following command.
|
||||
docker run -d --name=protonmail-bridge -v protonmail:/root -p 1025:25/tcp -p 1143:143/tcp --restart=unless-stopped shenxn/protonmail-bridge
|
||||
```
|
||||
|
||||
Or, if using Docker Compose, use the following command.
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Kubernetes
|
||||
### Kubernetes
|
||||
|
||||
If you want to run this image in a Kubernetes environment. You can use the [Helm](https://helm.sh/) chart (https://github.com/k8s-at-home/charts/tree/master/charts/stable/protonmail-bridge) created by [@Eagleman7](https://github.com/Eagleman7). More details can be found in [#23](https://github.com/shenxn/protonmail-bridge-docker/issues/23).
|
||||
|
||||
If you don't want to use Helm, you can also reference to the guide ([#6](https://github.com/shenxn/protonmail-bridge-docker/issues/6)) written by [@ghudgins](https://github.com/ghudgins).
|
||||
|
||||
## Security
|
||||
### Security
|
||||
|
||||
Please be aware that running the command above will expose your bridge to the network. Remember to use firewall if you are going to run this in an untrusted network or on a machine that has public IP address. You can also use the following command to publish the port to only localhost, which is the same behavior as the official bridge package.
|
||||
|
||||
@@ -75,15 +134,15 @@ docker run -d --name=protonmail-bridge -v protonmail:/root -p 127.0.0.1:1025:25/
|
||||
|
||||
Besides, you can publish only port 25 (SMTP) if you don't need to receive any email (e.g. as a email notification service).
|
||||
|
||||
## Compatibility
|
||||
### Compatibility
|
||||
|
||||
The bridge currently only supports some of the email clients. More details can be found on the official website. I've tested this on a Synology DiskStation and it runs well. However, you may need ssh onto it to run the interactive docker command to add your account. The main reason of using this instead of environment variables is that it seems to be the best way to support two-factor authentication.
|
||||
|
||||
## Bridge CLI Guide
|
||||
### Bridge CLI Guide
|
||||
|
||||
The initialization step exposes the bridge CLI so you can do things like switch between combined and split mode, change proxy, etc. The [official guide](https://protonmail.com/support/knowledge-base/bridge-cli-guide/) gives more information on to use the CLI.
|
||||
|
||||
## Build
|
||||
### Build
|
||||
|
||||
For anyone who want to build this container on your own (for development or security concerns), here is the guide to do so. First, you need to `cd` into the directory (`deb` or `build`, depending on which type of image you want). Then just run the docker build command
|
||||
```
|
||||
|
||||
24
build.sh
Normal file
24
build.sh
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
# Clone new code
|
||||
git clone https://github.com/ProtonMail/proton-bridge.git
|
||||
cd proton-bridge
|
||||
git checkout ${BRIDGE_VERSION}
|
||||
|
||||
# Patch HTTP REST frontend
|
||||
rm -rf internal/frontend/cli
|
||||
cp -r /build/http_rest_frontend/cli internal/frontend/
|
||||
|
||||
# Build
|
||||
if (! make build-nogui) && [[ $(getconf LONG_BIT) == "32" ]]; then
|
||||
# If build fails it's probably because it is a 32bit
|
||||
# system and there was a overflow error on the parser
|
||||
# This is a workaround for this problem found at:
|
||||
# https://github.com/antlr/antlr4/issues/2433#issuecomment-774514106
|
||||
find $(go env GOPATH)/pkg/mod/github.com/\!proton\!mail/go-rfc5322*/ -type f -exec sed -i.bak 's/(1<</(int64(1)<</g' {} +
|
||||
|
||||
# Try again after implementing the workaround
|
||||
make build-nogui
|
||||
fi
|
||||
@@ -1,33 +0,0 @@
|
||||
# The build image could be golang, but it currently does not support riscv64. Only debian:sid does, at the time of writing.
|
||||
FROM debian:sid-slim AS build
|
||||
|
||||
ARG version
|
||||
|
||||
# Install dependencies
|
||||
RUN apt-get update && apt-get install -y golang build-essential libsecret-1-dev
|
||||
|
||||
# Build
|
||||
ADD https://github.com/ProtonMail/proton-bridge.git#${version} /build/
|
||||
WORKDIR /build/
|
||||
RUN make build-nogui vault-editor
|
||||
|
||||
FROM debian:sid-slim
|
||||
LABEL maintainer="Simon Felding <sife@adm.ku.dk>"
|
||||
|
||||
EXPOSE 25/tcp
|
||||
EXPOSE 143/tcp
|
||||
|
||||
# Install dependencies and protonmail bridge
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends socat pass libsecret-1-0 ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy bash scripts
|
||||
COPY gpgparams entrypoint.sh /protonmail/
|
||||
|
||||
# Copy protonmail
|
||||
COPY --from=build /build/bridge /protonmail/
|
||||
COPY --from=build /build/proton-bridge /protonmail/
|
||||
COPY --from=build /build/vault-editor /protonmail/
|
||||
|
||||
ENTRYPOINT ["bash", "/protonmail/entrypoint.sh"]
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
# Initialize
|
||||
if [[ $1 == init ]]; then
|
||||
|
||||
# Initialize pass
|
||||
gpg --generate-key --batch /protonmail/gpgparams
|
||||
pass init pass-key
|
||||
|
||||
# Kill the other instance as only one can be running at a time.
|
||||
# This allows users to run entrypoint init inside a running conainter
|
||||
# which is useful in a k8s environment.
|
||||
# || true to make sure this would not fail in case there is no running instance.
|
||||
pkill protonmail-bridge || true
|
||||
|
||||
# Login
|
||||
/protonmail/proton-bridge --cli $@
|
||||
|
||||
else
|
||||
|
||||
# socat will make the conn appear to come from 127.0.0.1
|
||||
# ProtonMail Bridge currently expects that.
|
||||
# It also allows us to bind to the real ports :)
|
||||
socat TCP-LISTEN:25,fork TCP:127.0.0.1:1025 &
|
||||
socat TCP-LISTEN:143,fork TCP:127.0.0.1:1143 &
|
||||
|
||||
# Start protonmail
|
||||
# Fake a terminal, so it does not quit because of EOF...
|
||||
rm -f faketty
|
||||
mkfifo faketty
|
||||
cat faketty | /protonmail/proton-bridge --cli $@
|
||||
|
||||
fi
|
||||
79
cli.sh
Executable file
79
cli.sh
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
URL_BASE=http://127.0.0.1:${PROTON_MANAGEMENT_PORT:-1080}
|
||||
|
||||
print_help() {
|
||||
echo "Available commands:"
|
||||
echo " login: Calls up the login procedure to add or connect accounts."
|
||||
echo " delete <account>: Remove the account from keychain. You can use index or account name as the parameter."
|
||||
echo " list: Print list of your accounts."
|
||||
echo " info <account>: Print account configuration. You can use index or account name as the parameter."
|
||||
echo " help: Print help messages."
|
||||
echo " exit: Exit the CLI"
|
||||
}
|
||||
|
||||
account_list() {
|
||||
curl ${URL_BASE}/accounts
|
||||
}
|
||||
|
||||
account_login() {
|
||||
read -p "Username: " USERNAME
|
||||
read -sp "Password: " PASSWORD
|
||||
echo
|
||||
read -p "2FA Code (leave empty if not set): " TWO_FACTOR
|
||||
read -p "Mailbox Password (leave empty if not set): " MAILBOX_PASSWORD
|
||||
read -p "Address Mode (combined / split): " ADDRESS_MODE
|
||||
|
||||
curl ${URL_BASE}/accounts -XPUT \
|
||||
--data-urlencode "username=${USERNAME}" \
|
||||
--data-urlencode "password=${PASSWORD}" \
|
||||
--data-urlencode "two-factor=${TWO_FACTOR}" \
|
||||
--data-urlencode "mailbox-password=${MAILBOX_PASSWORD}" \
|
||||
--data-urlencode "address-mode=${ADDRESS_MODE}"
|
||||
}
|
||||
|
||||
account_delete() {
|
||||
if [[ -z $1 ]]; then
|
||||
echo "Error: delete requires one parameter, which is the index or account name."
|
||||
else
|
||||
read -p "Are you sure you want to delete account $1? " REPLY
|
||||
if [[ $REPLY =~ ^[Yy] ]]; then
|
||||
curl ${URL_BASE}/accounts/$1 -XDELETE
|
||||
else
|
||||
echo "Abort"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
account_info() {
|
||||
if [[ -z $1 ]]; then
|
||||
echo "Error: info requires one parameter, which is the index or account name."
|
||||
else
|
||||
curl ${URL_BASE}/accounts/$1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "CLI to interacte with Proton Bridge HTTP REST interface"
|
||||
print_help
|
||||
while true; do
|
||||
echo
|
||||
read -p ">> " COMMAND ARG
|
||||
case "$COMMAND" in
|
||||
login)
|
||||
account_login;;
|
||||
delete)
|
||||
account_delete $ARG;;
|
||||
list)
|
||||
account_list;;
|
||||
info)
|
||||
account_info $ARG;;
|
||||
help)
|
||||
print_help;;
|
||||
exit)
|
||||
exit 0;;
|
||||
*)
|
||||
echo "Invalid command"
|
||||
esac
|
||||
done
|
||||
@@ -1,28 +0,0 @@
|
||||
### The Deb install is just a repack of the official ProtonMail Bridge deb package with less dependencies.
|
||||
### I recommend you don't use this. It's here for legacy reasons.
|
||||
|
||||
FROM debian:sid-slim AS build
|
||||
|
||||
COPY install.sh PACKAGE /
|
||||
RUN apt-get update && apt-get install -y wget binutils
|
||||
|
||||
# Repack deb (removes unnecessary dependencies and produces /protonmail.deb)
|
||||
RUN bash /install.sh
|
||||
|
||||
FROM debian:sid-slim
|
||||
LABEL maintainer="Simon Felding <sife@adm.ku.dk>"
|
||||
|
||||
EXPOSE 25/tcp
|
||||
EXPOSE 143/tcp
|
||||
|
||||
WORKDIR /protonmail
|
||||
|
||||
# Copy bash scripts
|
||||
COPY gpgparams entrypoint.sh PACKAGE /protonmail/
|
||||
COPY --from=build /protonmail.deb /tmp/protonmail.deb
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends /tmp/protonmail.deb socat pass libsecret-1-0 ca-certificates procps \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
CMD ["bash", "/protonmail/entrypoint.sh"]
|
||||
@@ -1 +0,0 @@
|
||||
https://github.com/ProtonMail/proton-bridge/releases/download/v3.21.2/protonmail-bridge_3.21.2-1_amd64.deb
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
# Initialize
|
||||
if [[ $1 == init ]]; then
|
||||
|
||||
# # Parse parameters
|
||||
# TFP="" # Default empty two factor passcode
|
||||
# shift # skip `init`
|
||||
# while [[ $# -gt 0 ]]; do
|
||||
# key="$1"
|
||||
# case $key in
|
||||
# -u|--username)
|
||||
# USERNAME="$2"
|
||||
# ;;
|
||||
# -p|--password)
|
||||
# PASSWORD="$2"
|
||||
# ;;
|
||||
# -t|--twofactor)
|
||||
# TWOFACTOR="$2"
|
||||
# ;;
|
||||
# esac
|
||||
# shift
|
||||
# shift
|
||||
# done
|
||||
|
||||
# Initialize pass
|
||||
gpg --generate-key --batch /protonmail/gpgparams
|
||||
pass init pass-key
|
||||
|
||||
# Login
|
||||
protonmail-bridge --cli
|
||||
|
||||
else
|
||||
|
||||
# socat will make the conn appear to come from 127.0.0.1
|
||||
# ProtonMail Bridge currently expects that.
|
||||
# It also allows us to bind to the real ports :)
|
||||
socat TCP-LISTEN:25,fork TCP:127.0.0.1:1025 &
|
||||
socat TCP-LISTEN:143,fork TCP:127.0.0.1:1143 &
|
||||
|
||||
# Start protonmail
|
||||
# Fake a terminal, so it does not quit because of EOF...
|
||||
rm -f faketty
|
||||
mkfifo faketty
|
||||
cat faketty | protonmail-bridge --cli
|
||||
|
||||
fi
|
||||
@@ -1,8 +0,0 @@
|
||||
%no-protection
|
||||
%echo Generating a basic OpenPGP key
|
||||
Key-Type: RSA
|
||||
Key-Length: 2048
|
||||
Name-Real: pass-key
|
||||
Expire-Date: 0
|
||||
%commit
|
||||
%echo done
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
|
||||
# Repack deb (remove unnecessary dependencies)
|
||||
mkdir deb
|
||||
wget -i /PACKAGE -O /deb/protonmail.deb
|
||||
cd deb
|
||||
ar x -v protonmail.deb
|
||||
mkdir control
|
||||
tar zxvf control.tar.gz -C control
|
||||
sed -i "s/^Depends: .*$/Depends: libgl1, libc6, libsecret-1-0, libstdc++6, libgcc1/" control/control
|
||||
cd control
|
||||
tar zcvf ../control.tar.gz .
|
||||
cd ../
|
||||
|
||||
ar rcs -v /protonmail.deb debian-binary control.tar.gz data.tar.gz
|
||||
@@ -1,14 +0,0 @@
|
||||
version: '2.1'
|
||||
|
||||
services:
|
||||
protonmail-bridge:
|
||||
image: shenxn/protonmail-bridge
|
||||
ports:
|
||||
- 1025:25/tcp
|
||||
- 1143:143/tcp
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- protonmail:/root
|
||||
volumes:
|
||||
protonmail:
|
||||
name: protonmail
|
||||
37
entrypoint.sh
Executable file
37
entrypoint.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "Welcome to ProtonMail Bridge Docker 2.0. This version comes with plently of"
|
||||
echo "improvements. This version is not compatible with old configs and data volumes."
|
||||
echo "If you are coming from the old version, please clean up your volumes and set up"
|
||||
echo "everything from scratch. For more information about the new version, check"
|
||||
echo "https://github.com/shenxn/protonmail-bridge-docker"
|
||||
echo
|
||||
|
||||
if [ -d /root/.config/protonmail ]; then
|
||||
echo "Volume mount at `/root` for old version found. Please change your mount path to `/protonmail/data`."
|
||||
echo "See https://github.com/shenxn/protonmail-bridge-docker for detailed migration guide."
|
||||
echo "Exit in 30 seconds..."
|
||||
sleep 30
|
||||
exit 1
|
||||
fi
|
||||
|
||||
groupadd -g ${PROTON_GID:-1001} proton
|
||||
useradd -g proton -u ${PROTON_UID:-1001} -m proton
|
||||
chown proton:proton /protonmail/data
|
||||
|
||||
# Migrate old version data
|
||||
if [ -d /protonmail/data/.config/protonmail ]; then
|
||||
echo "Migrating legacy data. Note that this operation is irreversible so it is impossible"
|
||||
echo "to go back to the old version without clearing the volume."
|
||||
echo
|
||||
|
||||
mv /protonmail/data/.config/protonmail /protonmail/data/config
|
||||
rm -rf /protonmail/data/.config
|
||||
mv /protonmail/data/.cache/protonmail /protonmail/data/cache
|
||||
rm -rf /protonmail/data/.cache
|
||||
chown proton:proton -R /protonmail/data
|
||||
fi
|
||||
|
||||
exec gosu proton:proton /protonmail/script/run_protonmail_bridge.sh "$@"
|
||||
163
http_rest_frontend/cli/accounts.go
Normal file
163
http_rest_frontend/cli/accounts.go
Normal file
@@ -0,0 +1,163 @@
|
||||
// Package cli provides HTTP interface of the bridge
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) loginAccount(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "ParseForm() err: %v", err)
|
||||
return
|
||||
}
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
twoFactor := r.FormValue("two-factor")
|
||||
mailboxPassword := r.FormValue("mailbox-password")
|
||||
addressMode := r.FormValue("address-mode")
|
||||
if addressMode == "" {
|
||||
addressMode = "combined"
|
||||
}
|
||||
if addressMode != "combined" && addressMode != "split" {
|
||||
http.Error(w, fmt.Sprintf("%s is not a valid address mode. Choose from 'combined' and 'split'."), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
client, auth, err := f.bridge.Login(username, []byte(password))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
http.Error(w, fmt.Sprintf("Server error: %s", err.Error()), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if auth.HasTwoFactor() {
|
||||
if twoFactor == "" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
http.Error(w, "2FA enabled for the account but a 2FA code was not provided.", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
err = client.Auth2FA(context.Background(), twoFactor)
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
http.Error(w, fmt.Sprintf("Server error: %s", err.Error()), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if auth.HasMailboxPassword() {
|
||||
if mailboxPassword == "" {
|
||||
http.Error(w, "Two password mode enabled but a mailbox password was not provided.", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
mailboxPassword = password
|
||||
}
|
||||
user, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
http.Error(w, fmt.Sprintf("Server error: %s", err.Error()), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "Account %s was added successfully.\n", user.Username())
|
||||
|
||||
if addressMode == "split" {
|
||||
err = user.SwitchAddressMode()
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to switch address mode of %s to split: %s", user.Username(), err.Error())
|
||||
http.Error(w, "Failed to switch address mode to split", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
f.printAccountInfo(w, user)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) deleteAccount(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
account := params.ByName("account")
|
||||
user := f.getUserByIndexOrName(account)
|
||||
if user == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(w, "Account %s does not exist.\n", account)
|
||||
return
|
||||
}
|
||||
account = user.Username()
|
||||
if err := f.bridge.DeleteUser(user.ID(), true); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintln(w, "Cannot delete account: ", err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "Account %s was deleted successfully.\n", account)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) listAccounts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
users := f.bridge.GetUsers()
|
||||
if len(users) == 0 {
|
||||
fmt.Fprintln(w, "No account found.")
|
||||
return
|
||||
}
|
||||
spacing := "%-2d: %-20s (%-15s, %-15s)\n"
|
||||
fmt.Fprintf(w, strings.ReplaceAll(spacing, "d", "s"), "#", "account", "status", "address mode")
|
||||
for idx, user := range users {
|
||||
connected := "disconnected"
|
||||
if user.IsConnected() {
|
||||
connected = "connected"
|
||||
}
|
||||
mode := "split"
|
||||
if user.IsCombinedAddressMode() {
|
||||
mode = "combined"
|
||||
}
|
||||
fmt.Fprintf(w, spacing, idx, user.Username(), connected, mode)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) showAccountInfo(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
account := params.ByName("account")
|
||||
user := f.getUserByIndexOrName(account)
|
||||
if user == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(w, "Account %s does not exist.\n", account)
|
||||
return
|
||||
}
|
||||
if !user.IsConnected() {
|
||||
fmt.Fprintf(w, "Please login to %s to get email client configuration.\n", user.Username())
|
||||
return
|
||||
}
|
||||
f.printAccountInfo(w, user)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printAccountInfo(w io.Writer, user types.User) {
|
||||
if user.IsCombinedAddressMode() {
|
||||
f.printAccountAddressInfo(w, user, user.GetPrimaryAddress())
|
||||
} else {
|
||||
for _, address := range user.GetAddresses() {
|
||||
f.printAccountAddressInfo(w, user, address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printAccountAddressInfo(w io.Writer, user types.User, address string) {
|
||||
fmt.Fprintln(w, "Configuration for", address)
|
||||
smtpSecurity := "STARTTLS"
|
||||
if f.settings.GetBool(settings.SMTPSSLKey) {
|
||||
smtpSecurity = "SSL"
|
||||
}
|
||||
fmt.Fprintf(w, "IMAP port: %s\nIMAP security: %s\nSMTP port: %s\nSMTP security: %s\nUsername: %s\nPassword: %s\n",
|
||||
os.Getenv("PROTON_IMAP_PORT"),
|
||||
"STARTTLS",
|
||||
os.Getenv("PROTON_SMTP_PORT"),
|
||||
smtpSecurity,
|
||||
address,
|
||||
user.GetBridgePassword(),
|
||||
)
|
||||
fmt.Fprintln(w, "")
|
||||
}
|
||||
24
http_rest_frontend/cli/accounts_util.go
Normal file
24
http_rest_frontend/cli/accounts_util.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) getUserByIndexOrName(account string) types.User {
|
||||
users := f.bridge.GetUsers()
|
||||
numberOfAccounts := len(users)
|
||||
if index, err := strconv.Atoi(account); err == nil {
|
||||
if index < 0 || index >= numberOfAccounts {
|
||||
return nil
|
||||
}
|
||||
return users[index]
|
||||
}
|
||||
for _, user := range users {
|
||||
if user.Username() == account {
|
||||
return user
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
163
http_rest_frontend/cli/frontend.go
Normal file
163
http_rest_frontend/cli/frontend.go
Normal file
@@ -0,0 +1,163 @@
|
||||
// Package cli provides HTTP interface of the bridge
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type frontendCLI struct {
|
||||
*httprouter.Router
|
||||
|
||||
locations *locations.Locations
|
||||
settings *settings.Settings
|
||||
eventListener listener.Listener
|
||||
updater types.Updater
|
||||
bridge types.Bridger
|
||||
restarter types.Restarter
|
||||
}
|
||||
|
||||
func New(
|
||||
panicHandler types.PanicHandler,
|
||||
|
||||
locations *locations.Locations,
|
||||
settings *settings.Settings,
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
bridge types.Bridger,
|
||||
restarter types.Restarter,
|
||||
) *frontendCLI {
|
||||
fe := &frontendCLI{
|
||||
Router: httprouter.New(),
|
||||
locations: locations,
|
||||
settings: settings,
|
||||
eventListener: eventListener,
|
||||
updater: updater,
|
||||
bridge: bridge,
|
||||
restarter: restarter,
|
||||
}
|
||||
|
||||
fe.PUT("/accounts", fe.loginAccount)
|
||||
fe.GET("/accounts", fe.listAccounts)
|
||||
fe.GET("/accounts/:account", fe.showAccountInfo)
|
||||
fe.DELETE("/accounts/:account", fe.deleteAccount)
|
||||
|
||||
return fe
|
||||
}
|
||||
|
||||
func (f *frontendCLI) loginWithEnv() {
|
||||
if len(f.bridge.GetUsers()) > 0 {
|
||||
fmt.Println("More than 0 accounts found. Skip auto login.")
|
||||
return
|
||||
}
|
||||
username := os.Getenv("PROTON_USERNAME")
|
||||
password := os.Getenv("PROTON_PASSWORD")
|
||||
if username == "" {
|
||||
logrus.Info("PROTON_USERNAME and PROTON_PASSWORD are not set. Skip auto login.")
|
||||
return
|
||||
}
|
||||
client, auth, err := f.bridge.Login(username, []byte(password))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
logrus.WithError(err).Warn("Login failed.")
|
||||
return
|
||||
}
|
||||
|
||||
if auth.HasTwoFactor() {
|
||||
twoFactor := os.Getenv("PROTON_2FA")
|
||||
if twoFactor == "" {
|
||||
logrus.Warn("Login failed: 2FA enabled for the account but PROTON_2FA was not set.")
|
||||
return
|
||||
}
|
||||
err = client.Auth2FA(context.Background(), twoFactor)
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
logrus.WithError(err).Warn("Login failed.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mailboxPassword := password
|
||||
if auth.HasMailboxPassword() {
|
||||
mailboxPassword = os.Getenv("PROTON_MAILBOX_PASSWORD")
|
||||
if mailboxPassword == "" {
|
||||
logrus.Warn("Login failed: Two password mode enabled but PROTON_MAILBOX_PASSWORD was not set.")
|
||||
return
|
||||
}
|
||||
}
|
||||
user, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
logrus.WithError(err).Warn("Login failed.")
|
||||
return
|
||||
}
|
||||
logrus.Infof("Account %s was added successfully.\n", user.Username())
|
||||
if strings.ToLower(os.Getenv("PROTON_PRINT_ACCOUNT_INFO")) != "false" {
|
||||
f.printAccountInfo(os.Stdout, user)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) watchEvents() {
|
||||
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
|
||||
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
|
||||
internetConnChangedCh := f.eventListener.ProvideChannel(events.InternetConnChangedEvent)
|
||||
addressChangedCh := f.eventListener.ProvideChannel(events.AddressChangedEvent)
|
||||
addressChangedLogoutCh := f.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
|
||||
logoutCh := f.eventListener.ProvideChannel(events.LogoutEvent)
|
||||
certIssue := f.eventListener.ProvideChannel(events.TLSCertIssue)
|
||||
for {
|
||||
select {
|
||||
case errorDetails := <-errorCh:
|
||||
logrus.Error("Bridge failed:", errorDetails)
|
||||
case <-credentialsErrorCh:
|
||||
f.notifyCredentialsError()
|
||||
case stat := <-internetConnChangedCh:
|
||||
if stat == events.InternetOff {
|
||||
f.notifyInternetOff()
|
||||
}
|
||||
if stat == events.InternetOn {
|
||||
f.notifyInternetOn()
|
||||
}
|
||||
case address := <-addressChangedCh:
|
||||
fmt.Printf("Address changed for %s. You may need to reconfigure your email client.", address)
|
||||
case address := <-addressChangedLogoutCh:
|
||||
f.notifyLogout(address)
|
||||
case userID := <-logoutCh:
|
||||
user, err := f.bridge.GetUser(userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
f.notifyLogout(user.Username())
|
||||
case <-certIssue:
|
||||
f.notifyCertIssue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) Loop() error {
|
||||
f.loginWithEnv()
|
||||
managementPort := os.Getenv("PROTON_MANAGEMENT_PORT")
|
||||
if managementPort == "" {
|
||||
managementPort = "1080"
|
||||
}
|
||||
http.ListenAndServe(":"+managementPort, f)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *frontendCLI) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {}
|
||||
func (f *frontendCLI) WaitUntilFrontendIsReady() {}
|
||||
func (f *frontendCLI) SetVersion(version updater.VersionInfo) {}
|
||||
func (f *frontendCLI) NotifySilentUpdateInstalled() {}
|
||||
func (f *frontendCLI) NotifySilentUpdateError(err error) {}
|
||||
57
http_rest_frontend/cli/utils.go
Normal file
57
http_rest_frontend/cli/utils.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) processAPIError(err error) {
|
||||
switch err {
|
||||
case pmapi.ErrNoConnection:
|
||||
f.notifyInternetOff()
|
||||
case pmapi.ErrUpgradeApplication:
|
||||
f.notifyNeedUpgrade()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyInternetOff() {
|
||||
logrus.Warn("Internet connection is not available.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyInternetOn() {
|
||||
logrus.Info("Internet connection is available again.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyLogout(address string) {
|
||||
logrus.Infof("Account %s is disconnected. Login to continue using this account with email client.", address)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyNeedUpgrade() {
|
||||
logrus.Info("Upgrade needed. Please download and install the newest version of application.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyCredentialsError() {
|
||||
logrus.Error(`ProtonMail Bridge is not able to detect a supported password manager
|
||||
(secret-service or pass). Please install and set up a supported password manager
|
||||
and restart the application.
|
||||
`)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyCertIssue() {
|
||||
// Print in 80-column width.
|
||||
logrus.Error(`Connection security error: Your network connection to Proton services may
|
||||
be insecure.
|
||||
|
||||
Description:
|
||||
ProtonMail Bridge was not able to establish a secure connection to Proton
|
||||
servers due to a TLS certificate error. This means your connection may
|
||||
potentially be insecure and susceptible to monitoring by third parties.
|
||||
|
||||
Recommendation:
|
||||
* If you trust your network operator, you can continue to use ProtonMail
|
||||
as usual.
|
||||
* If you don't trust your network operator, reconnect to ProtonMail over a VPN
|
||||
(such as ProtonVPN) which encrypts your Internet connection, or use
|
||||
a different network to access ProtonMail.
|
||||
`)
|
||||
}
|
||||
72
run_protonmail_bridge.sh
Executable file
72
run_protonmail_bridge.sh
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Generate gpg keys
|
||||
GNUPG_PATH=/protonmail/data/.gnupg
|
||||
export GNUPGHOME=${GNUPG_PATH}
|
||||
if [ ! -d ${GNUPG_PATH} ]; then
|
||||
echo "Generateing gpg keys..."
|
||||
# set GNUPGHOME to a temp directory as a workaround for
|
||||
#
|
||||
# gpg-agent[106]: error binding socket to '/root/.gnupg/S.gpg-agent': File name too long
|
||||
#
|
||||
# when using docker volume mount
|
||||
#
|
||||
# ref: https://dev.gnupg.org/T2964
|
||||
#
|
||||
export GNUPGHOME=/tmp/gnupg
|
||||
mkdir ${GNUPGHOME}
|
||||
chmod 700 ${GNUPGHOME}
|
||||
gpg --generate-key --batch /protonmail/script/gpgparams
|
||||
pkill gpg-agent
|
||||
mv ${GNUPGHOME} ${GNUPG_PATH}
|
||||
export GNUPGHOME=${GNUPG_PATH}
|
||||
fi
|
||||
|
||||
# Initialize pass
|
||||
PASSWORD_STORE=/protonmail/data/.password-store
|
||||
if [ ! -d ${PASSWORD_STORE} ]; then
|
||||
echo "Initializing pass..."
|
||||
pass init pass-key
|
||||
# Move password store to /protonmail/data
|
||||
mv ${HOME}/.password-store ${PASSWORD_STORE}
|
||||
fi
|
||||
# Link the password store back to ~/.password-store
|
||||
# There is no easy way to change the path used by pass
|
||||
ln -s ${PASSWORD_STORE} ${HOME}/.password-store
|
||||
|
||||
# Link config and cache folders to /protonmail/data
|
||||
PROTON_CONFIG_PATH=/protonmail/data/config
|
||||
PROTON_CACHE_PATH=/protonmail/data/cache
|
||||
mkdir -p ${PROTON_CONFIG_PATH}
|
||||
mkdir -p ${HOME}/.config
|
||||
ln -s ${PROTON_CONFIG_PATH} ${HOME}/.config/protonmail
|
||||
mkdir -p ${PROTON_CACHE_PATH}
|
||||
mkdir ${HOME}/.cache
|
||||
ln -s ${PROTON_CACHE_PATH} ${HOME}/.cache/protonmail
|
||||
|
||||
# Generateing perfs.json
|
||||
mkdir -p ${PROTON_CONFIG_PATH}/bridge
|
||||
if [ ${PROTON_SMTP_SECURITY:-STARTTLS} == "SSL" ]; then
|
||||
PROTON_SSL_SMTP="true"
|
||||
else
|
||||
PROTON_SSL_SMTP="false"
|
||||
fi
|
||||
cat <<EOF > ${PROTON_CONFIG_PATH}/bridge/prefs.json
|
||||
{
|
||||
"allow_proxy": "${PROTON_ALLOW_PROXY:-true}",
|
||||
"autoupdate": "false",
|
||||
"user_ssl_smtp": "${PROTON_SSL_SMTP}"
|
||||
}
|
||||
EOF
|
||||
|
||||
export PROTON_SMTP_PORT=${PROTON_SMTP_PORT:-25}
|
||||
export PROTON_IMAP_PORT=${PROTON_IMAP_PORT:=143}
|
||||
# socat will make the conn appear to come from 127.0.0.1
|
||||
# ProtonMail Bridge currently expects that.
|
||||
# It also allows us to bind to the real ports :)
|
||||
socat TCP-LISTEN:${PROTON_SMTP_PORT},fork TCP:127.0.0.1:1025 &
|
||||
socat TCP-LISTEN:${PROTON_IMAP_PORT},fork TCP:127.0.0.1:1143 &
|
||||
|
||||
exec proton-bridge --cli "$@"
|
||||
@@ -1,37 +1,64 @@
|
||||
import requests, os, sys
|
||||
import sys
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
|
||||
def git(command):
|
||||
return os.system(f"git {command}")
|
||||
|
||||
|
||||
release = requests.get("https://api.github.com/repos/protonmail/proton-bridge/releases/latest").json()
|
||||
version = release['tag_name']
|
||||
deb = [asset for asset in release ['assets'] if asset['name'].endswith('.deb')][0]['browser_download_url']
|
||||
|
||||
print(f"Latest release is: {version}")
|
||||
|
||||
with open("VERSION", 'w') as f:
|
||||
f.write(version)
|
||||
|
||||
with open("deb/PACKAGE", 'w') as f:
|
||||
f.write(deb)
|
||||
|
||||
git("config --local user.name 'GitHub Actions'")
|
||||
git("config --local user.email 'actions@github.com'")
|
||||
|
||||
git("add -A")
|
||||
|
||||
if git("diff --cached --quiet") == 0: # Returns 0 if there are no changes
|
||||
print("Version didn't change")
|
||||
exit(0)
|
||||
|
||||
git(f"commit -m 'Bump version to {version}'")
|
||||
is_pull_request = sys.argv[1] == "true"
|
||||
print(f"is_pull_request={is_pull_request}")
|
||||
|
||||
if is_pull_request:
|
||||
print("This is a pull request, skipping push step.")
|
||||
exit(0)
|
||||
|
||||
if git("push") != 0:
|
||||
print("Git push failed!")
|
||||
exit(1)
|
||||
def check_version(directory, new_version):
|
||||
print(f"Checking version for {directory}")
|
||||
|
||||
if not new_version:
|
||||
print("Failed to get new version. Exiting.")
|
||||
exit(1)
|
||||
|
||||
with open(f"{directory}/VERSION", "r") as f:
|
||||
old_version = f.read().rstrip()
|
||||
|
||||
print(f"Up-to-date version {new_version}")
|
||||
print(f"Current version: {old_version}")
|
||||
|
||||
if old_version != new_version:
|
||||
print(f"New release found: {new_version}")
|
||||
|
||||
# bump up to new release
|
||||
with open(f"{directory}/VERSION", "w") as f:
|
||||
f.write(new_version)
|
||||
# commit
|
||||
result = os.system(f"git config --local user.email 'actions@github.com' \
|
||||
&& git config --local user.name 'GitHub Actions' \
|
||||
&& git add {directory}/VERSION \
|
||||
&& git commit -m 'Bump {directory} version to {new_version}'")
|
||||
if result != 0:
|
||||
print("Failed to commit the bump. Exiting")
|
||||
exit(1)
|
||||
if is_pull_request:
|
||||
print("Action triggered by pull request. Do not push.")
|
||||
else:
|
||||
result = os.system("git push")
|
||||
if result != 0:
|
||||
print("Failed to push. Exiting")
|
||||
exit(1)
|
||||
else:
|
||||
print(f"Already newest version {old_version}")
|
||||
|
||||
|
||||
# check deb version
|
||||
response = requests.get("https://protonmail.com/download/current_version_linux.json")
|
||||
content = json.loads(response.content)
|
||||
version = re.match(".*_([0-9.-]+)_amd64\.deb", content["DebFile"]).group(1)
|
||||
check_version("deb", version)
|
||||
|
||||
|
||||
# check build version
|
||||
response = requests.get(
|
||||
"https://api.github.com/repos/ProtonMail/proton-bridge/tags",
|
||||
headers={"Accept": "application/vnd.github.v3+json"},
|
||||
)
|
||||
tags = json.loads(response.content)
|
||||
version_re = re.compile("v\d+\.\d+\.\d+")
|
||||
releases = [tag["name"][1:] for tag in tags if version_re.match(tag["name"])]
|
||||
check_version("build", releases[0])
|
||||
|
||||
Reference in New Issue
Block a user