Comments (13)
@leodoi3 hi, have you got that docker compose working? can you share final results and configs ?
Unfortunately I wasn't able to get my setup working as I have eventually gave up. Hopefully you will be able to find something and get yours to work.
from app.
@leodoi3 please post this issue in my repo as it seems you are using my simple Docker Compose for SimpleLogin.
However, it seems you rely on existing Web Server and SSL certs.
This Docker Compose assumes postfix is dockerized and using the same network. Is that the case for you?
if not, please, change your .env
file:
- POSTFIX_SERVER=postfix
+ POSTFIX_SERVER=10.0.0.1
If postfix is running in a Docker container, we do not need to expose the postgresql TCP ports. However, if that’s not the case you will need to expose the TCP ports as suggested in the original documentation. Please, change docker-compose.yaml
to:
postgres:
image: postgres:12.1
container_name: sl-db
env_file: .env
healthcheck:
test: [ "CMD", "pg_isready", "-d", "simplelogin", "-U", "myuser" ]
interval: 10s
retries: 3
start_period: 1s
volumes:
- ./db:/var/lib/postgresql/data
+ ports:
+ - "5432:5432"
networks:
- sl_network
restart: unless-stopped
Finally, this Docker Compose also assumes nginx is running inside a Docker container. If that is not the case, you will need to expose the SimpleLogin TCP ports explicitely as you have done.
from app.
I have updated the docker-compose.yaml
to include postfix using this repo . And also made the other changes. However it still hasn't changed and nothing is happening when trying to register.
docker-compose:
version: "3.8"
services:
## SIMPLE LOGIN
## ============
postgres:
image: postgres:12.1
container_name: sl-db
env_file: .env
healthcheck:
test: [ "CMD", "pg_isready", "-d", "simplelogin", "-U", "myuser" ]
interval: 10s
retries: 3
start_period: 1s
volumes:
- ./db:/var/lib/postgresql/data
networks:
- sl_network
restart: unless-stopped
postfix:
container_name: simplelogin-postfix
image: simplelogin/postfix
environment:
ALIASES_DEFAULT_DOMAIN: mydomain.com
DB_USER: myuser
DB_PASSWORD: mypassword
DB_HOST: postgresql://myuser:mypassword@sl-db:5432/simplelogin
#DB_PASSWORD_FILE: '/run/secrets/db_password'
DB_NAME: simplelogin
LETSENCRYPT_EMAIL: [email protected]
EMAIL_HANDLER_HOST: 127.0.0.1
POSTFIX_FQDN: mail.mydomain.com
RELAY_HOST: myisp.internet.com
# SSL_CERT_FOLDER: /mnt/certs
volumes:
- ./postfix:/ect/postfix
networks:
- sl_network
migration:
image: simplelogin/app:$SL_VERSION
command: [ "flask", "db", "upgrade" ]
container_name: sl-migration
env_file: .env
volumes:
- ./pgp:/sl/pgp
- ./upload:/code/static/upload
- ./dkim.key:/dkim.key
- ./dkim.pub.key:/dkim.pub.key
networks:
- sl_network
depends_on:
postgres:
condition: service_healthy
init:
image: simplelogin/app:$SL_VERSION
command: [ "python", "init_app.py" ]
container_name: sl-init
env_file: .env
volumes:
- ./pgp:/sl/pgp
- ./upload:/code/static/upload
- ./dkim.key:/dkim.key
- ./dkim.pub.key:/dkim.pub.key
networks:
- sl_network
depends_on:
migration:
condition: service_completed_successfully
app:
image: simplelogin/app:$SL_VERSION
container_name: sl-app
env_file: .env
ports:
- 7777:7777
volumes:
- ./pgp:/sl/pgp
- ./upload:/code/static/upload
- ./dkim.key:/dkim.key
- ./dkim.pub.key:/dkim.pub.key
restart: unless-stopped
networks:
- sl_network
depends_on:
init:
condition: service_completed_successfully
email:
image: simplelogin/app:$SL_VERSION
command: ["python", "email_handler.py"]
container_name: sl-email
env_file: .env
ports:
- 20381:20381
volumes:
- ./pgp:/sl/pgp
- ./upload:/code/static/upload
- ./dkim.key:/dkim.key
- ./dkim.pub.key:/dkim.pub.key
restart: unless-stopped
networks:
- sl_network
depends_on:
init:
condition: service_completed_successfully
job-runner:
image: simplelogin/app:$SL_VERSION
command: ["python", "job_runner.py"]
container_name: sl-job-runner
env_file: .env
volumes:
- ./pgp:/sl/pgp
- ./upload:/code/static/upload
- ./dkim.key:/dkim.key
- ./dkim.pub.key:/dkim.pub.key
restart: unless-stopped
networks:
- sl_network
depends_on:
init:
condition: service_completed_successfully
networks:
sl_network:
name: sl_network
driver: bridge
ipam:
driver: default
config:
- subnet: 10.0.0.0/24
gateway: 10.0.0.1
.env:
# Docker
# ======
SL_VERSION=3.4.0
# acme.sh
# =======
# Set requested ACME CHALLENGE [ "DNS-01" ] | "HTTP-01"
# see https://letsencrypt.org/docs/challenge-types/
# Please, review the following files for ssl cert locations
#
# - nginx/conf.d/default.conf.tpl
# - postfix/conf.d/main.cf.tpl
# When using DNS-01, a wildcard certificate is requested
# and will be available at the following location:
#
# ${ACME_SH}/*.${DOMAIN}_ecc/\*.${DOMAIN}.cer
# ${ACME_SH}/*.${DOMAIN}_ecc/\*.${DOMAIN}.key
#
# When using HTTP-01, a certificate with alternate names
# is requested and available at the following location:
#
# ${ACME_SH}/${DOMAIN}_ecc/${DOMAIN}.cer
# ${ACME_SH}/${DOMAIN}_ecc/${DOMAIN}.key
#
ACME_CHALLENGE=DNS-01
LE_STAGING=true
# Azure DNS credentials
# see: https://github.com/acmesh-official/acme.sh/wiki/dnsapi#37-use-azure-dns
AZUREDNS_TENANTID=paste-tenant-id-here
AZUREDNS_SUBSCRIPTIONID=paste-subscription-id-here
AZUREDNS_APPID=paste-service-principal-client-id-here
AZUREDNS_CLIENTSECRET=paste-service-principal-client-secret-here
# Postgres Database
# =================
POSTGRES_DB=simplelogin
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
# SimpleLogin App
# ===============
# domain used to create alias
DOMAIN=testdomain.com
EMAIL_DOMAIN=$DOMAIN
# WebApp URL
URL=https://app.$DOMAIN
# transactional email is sent from this email address
SUPPORT_EMAIL=support@DOMAIN
# custom domain needs to point to these MX servers
EMAIL_SERVERS_WITH_PRIORITY=[(10, "app.$EMAIL_DOMAIN.")]
# By default, new aliases must end with ".{random_word}". This is to avoid a person taking all "nice" aliases.
# this option doesn't make sense in self-hosted. Set this variable to disable this option.
DISABLE_ALIAS_SUFFIX=1
# the DKIM private key used to compute DKIM-Signature
DKIM_PRIVATE_KEY_PATH=/dkim.key
# DB Connection
DB_URI=postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@sl-db:5432/$POSTGRES_DB
FLASK_SECRET=flasksecret
GNUPGHOME=/sl/pgp
LOCAL_FILE_UPLOAD=1
POSTFIX_SERVER=simplelogin-postfix
## DISABLE_REGISTRATION=1
## DISABLE_ONBOARDING=true
from app.
I'm not sure about your postfix configuration.
Can you please stop and restart the full stack and share the logs from the simplelogin-postfix
container after a fresh start ?
Can you then use the register/onboard a new user from the web app and share the resulting logs from the simplelogin-posfix
container ?
I suspect there might be a issue with the SSL cert configurations.
from app.
I haven't set up SSL yet but I don't think it should make a difference why nothing is happening.
This is the logs from simplelogin-postfix
Simplelogin Compatibility Mode is set to: v3
Using Let's Encrypt certificate
Certificate files are missing: /etc/letsencrypt/live/mail.mydomain.com/fullchain.pem and /etc/letsencrypt/live/mail.mydomain.com/privkey.pem
Using Let's Encrypt certificate
Certificate files are missing: /etc/letsencrypt/live/mail.mydomain.com/fullchain.pem and /etc/letsencrypt/live/mail.mydomain.com/privkey.pem
Using Let's Encrypt certificate
Certificate files are missing: /etc/letsencrypt/live/mail.mydomain.com/fullchain.pem and /etc/letsencrypt/live/mail.mydomain.com/privkey.pem
/usr/sbin/postconf: fatal: file /etc/postfix/master.cf: line 2: bad field count
Oct 10 04:40:32 mail postfix/postfix-script[13]: fatal: cannot execute /usr/sbin/postconf!
./docker-entrypoint.sh: line 91: HEALTHCHECK: command not found
And here is the logs from sl-app
[2023-10-10 04:41:04 +0000] [1] [INFO] Starting gunicorn 20.0.4
[2023-10-10 04:41:04 +0000] [1] [INFO] Listening at: http://0.0.0.0:7777 (1)
[2023-10-10 04:41:04 +0000] [1] [INFO] Using worker: sync
[2023-10-10 04:41:04 +0000] [10] [INFO] Booting worker with pid: 10
[2023-10-10 04:41:04 +0000] [11] [INFO] Booting worker with pid: 11
>>> URL: https://app.testdomain.com
MAX_NB_EMAIL_FREE_PLAN is not set, use 5 as default value
Upload files to local dir
>>> init logging <<<
2023-10-10 04:41:12,780 - SL - DEBUG - "/code/app/utils.py:11" - <module>() - - load words file: /code/local_data/words_alpha.txt
2023-10-10 04:41:18,852 - SL - DEBUG - "/code/server.py:444" - after_request() - - 192.168.0.165 GET / ImmutableMultiDict([]) 302
2023-10-10 04:41:18,994 - SL - DEBUG - "/code/server.py:444" - after_request() - - 192.168.0.165 GET /auth/login ImmutableMultiDict([]) 200
2023-10-10 04:41:27,499 - SL - DEBUG - "/code/server.py:444" - after_request() - - 192.168.0.165 POST /auth/login ImmutableMultiDict([]) 200
2023-10-10 04:41:32,386 - SL - DEBUG - "/code/server.py:444" - after_request() - - 192.168.0.165 POST /auth/login ImmutableMultiDict([]) 200
This is how I currently have postfix set up in the compose yaml:
postfix:
container_name: simplelogin-postfix
image: simplelogin/postfix
environment:
ALIASES_DEFAULT_DOMAIN: mydomain.com
DB_USER: myuser
DB_PASSWORD: mypassword
DB_HOST: postgresql://myuser:mypassword@sl-db:5432/simplelogin
#DB_PASSWORD_FILE: '/run/secrets/db_password'
DB_NAME: simplelogin
LETSENCRYPT_EMAIL: [email protected]
EMAIL_HANDLER_HOST: 127.0.0.1
POSTFIX_FQDN: mail.mydomain.com
RELAY_HOST: myisp.internet.com
SIMPLELOGIN_COMPATIBILITY_MODE: v3
# SSL_CERT_FOLDER: /mnt/certs
volumes:
- ./postfix:/etc/postfix
networks:
- sl_network
depends_on:
postgres:
condition: service_healthy
This is the content of /etc/postfix/master.cf
# POSTFIX config file, adapted for SimpleLogin
smtpd_banner = $myhostname ESMTP $mail_name
biff = no
# Redirect logs to stdout (better with Docker).
maillog_file = /dev/stdout
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = no
# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
# fresh installs.
compatibility_level = 3.6
# TLS parameters for SMTP (outgoing mail)
smtp_use_tls = yes
smtp_tls_security_level = may
# TLS parameters for SMPTD (incoming mail)
smtpd_use_tls = no
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
alias_maps = lmdb:/etc/postfix/custom-data/aliases
# Allow containers on the same private network to send emails via Postfix.
mynetworks_style = subnet
# Set your domain here
mydestination =
myhostname = mail.mydomain.com
mydomain = mydomain.com
myorigin = mydomain.com
relay_domains = pgsql:/etc/postfix/pgsql-relay-domains.cf
transport_maps = pgsql:/etc/postfix/pgsql-transport-maps.cf
# HELO restrictions
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtpd_helo_restrictions =
permit_mynetworks,
reject_non_fqdn_helo_hostname,
reject_invalid_helo_hostname,
permit
# Sender restrictions:
smtpd_sender_restrictions =
permit_mynetworks,
reject_non_fqdn_sender,
reject_unknown_sender_domain,
permit
# Recipient restrictions:
smtpd_recipient_restrictions =
reject_unauth_pipelining,
reject_non_fqdn_recipient,
reject_unknown_recipient_domain,
permit_mynetworks,
reject_unauth_destination,
reject_rbl_client zen.spamhaus.org,
reject_rbl_client bl.spamcop.net,
permit
# General TLS security improvements
# Based on: https://kruyt.org/postfix-and-tls-encryption/
smtpd_tls_protocols = !SSLv2,!SSLv3,!TLSv1
smtp_tls_protocols = !SSLv2,!SSLv3,!TLSv1
smtp_tls_ciphers = high
smtpd_tls_ciphers = high
smtpd_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1
smtp_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1
smtp_tls_mandatory_ciphers = high
smtpd_tls_mandatory_ciphers = high
# Disable depricated encryption ciphers
smtpd_tls_mandatory_exclude_ciphers = MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL
smtpd_tls_exclude_ciphers = MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL
smtp_tls_mandatory_exclude_ciphers = MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL
smtp_tls_exclude_ciphers = MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL
tls_preempt_cipherlist = yes
# NO_RENEGOTIATION postfix 3.4 and openssl >1.1.1
tls_ssl_options = NO_RENEGOTIATION
# Set email relay host:
relayhost = myisp.internet.com
from app.
You are right, SSL/TLS should not be an issue initially for postfix even though I would not recommend starting without a proper configuration.
This is really difficult for me to help you further as I do not know which postfix image you are using.
Also, I do not understand how to reproduce your issue as you seem to be using extra configuration that I do not have:
- You are using
mail.mydomain.com
as theMX
instead of the defaultapp.mydomain.com
which also hosts the app. - You are using a
RELAY_HOST
which I do not understand and am not able to reproduce. - You are using a
EMAIL_HANDLER_HOST
which I do not understand either.
I have removed/adjusted those from your example and deployed on my host and it works right away even without SSL.
I suggest you start from scratch with my config which is known to work out of the box.
If you clone my repository, you will be able to slightly change it towards your configuration and commit each working state so that you can understand what is the issue for you.
from app.
@springcomp I have tried from scratch again by cloning your repo and running it from there, but I still get the same problem where nothing happens when trying to create an account. I am trying to run this on a Synology NAS if that helps. I had to make a few changes to the docker compose file since some ports were already in use like 443,80 and 25, so I commented those out. Also my version of docker-compose did not support the includes tag so I manually merged all the other yamls together. I did not set up any of the SSL or domain yet since I just wanted it to work. I ran ./up.sh --build && docker logs -f acme.sh
, I checked all the logs and did not see any errors. This is how my yaml looks using from your repo:
version: "3.8"
services:
## NGINX & SSL CERTS
## =================
nginx:
image: nginx:1.25.2
container_name: nginx
volumes:
- ./nginx/conf.d/:/etc/nginx/conf.d/:ro
- ./nginx/ssl/:/etc/nginx/ssl/:ro
- ./acme.sh/conf.d/:/etc/acme.sh/:ro
- ./acme.sh/www/:/var/www/:ro
# ports:
# - "80:80"
# - "443:443"
restart: unless-stopped
acme.sh:
image: private/acme.sh:latest
build:
context: ./acme.sh/Dockerfiles/
dockerfile: Dockerfile
container_name: acme.sh
env_file: .env
volumes:
- ./acme.sh/www/:/var/www/:rw
- ./acme.sh/conf.d/:/root/.acme.sh/:rw
- ./nginx/conf.d/:/etc/nginx/conf.d/:rw
- /var/run/docker.sock:/var/run/docker.sock
depends_on:
- nginx
## SIMPLE LOGIN
## ============
postgres:
image: postgres:12.1
container_name: sl-db
env_file: .env
healthcheck:
test: [ "CMD", "pg_isready", "-d", "simplelogin", "-U", "paste-user-here" ]
interval: 10s
retries: 3
start_period: 1s
volumes:
- ./db:/var/lib/postgresql/data
restart: unless-stopped
migration:
image: simplelogin/app:$SL_VERSION
command: [ "flask", "db", "upgrade" ]
container_name: sl-migration
env_file: .env
volumes:
- ./pgp:/sl/pgp
- ./upload:/code/static/upload
- ./dkim.key:/dkim.key
- ./dkim.pub.key:/dkim.pub.key
depends_on:
postgres:
condition: service_healthy
init:
image: simplelogin/app:$SL_VERSION
command: [ "python", "init_app.py" ]
container_name: sl-init
env_file: .env
volumes:
- ./pgp:/sl/pgp
- ./upload:/code/static/upload
- ./dkim.key:/dkim.key
- ./dkim.pub.key:/dkim.pub.key
depends_on:
migration:
condition: service_completed_successfully
app:
image: simplelogin/app:$SL_VERSION
container_name: sl-app
env_file: .env
ports:
- '7777:7777'
volumes:
- ./pgp:/sl/pgp
- ./upload:/code/static/upload
- ./dkim.key:/dkim.key
- ./dkim.pub.key:/dkim.pub.key
restart: unless-stopped
depends_on:
init:
condition: service_completed_successfully
email:
image: simplelogin/app:$SL_VERSION
command: ["python", "email_handler.py"]
container_name: sl-email
env_file: .env
volumes:
- ./pgp:/sl/pgp
- ./upload:/code/static/upload
- ./dkim.key:/dkim.key
- ./dkim.pub.key:/dkim.pub.key
restart: unless-stopped
depends_on:
init:
condition: service_completed_successfully
job-runner:
image: simplelogin/app:$SL_VERSION
command: ["python", "job_runner.py"]
container_name: sl-job-runner
env_file: .env
volumes:
- ./pgp:/sl/pgp
- ./upload:/code/static/upload
- ./dkim.key:/dkim.key
- ./dkim.pub.key:/dkim.pub.key
restart: unless-stopped
depends_on:
init:
condition: service_completed_successfully
## POSTFIX
## =================
postfix:
image: private/postfix:latest
build:
context: ./postfix/Dockerfiles/
dockerfile: Dockerfile
container_name: postfix
env_file: .env
environment:
MAIL_CONFIG: /etc/postfix/conf.d/
ports:
# - '25:25'
- '587:587'
volumes:
- ./postfix/conf.d/:/etc/postfix/conf.d:rw
- ./acme.sh/conf.d/:/etc/acme.sh/:ro
restart: unless-stopped
depends_on:
- email
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: 10.0.0.0/24
gateway: 10.0.0.1
from app.
@leodoi3
Please, be aware that if you remove port 25
you will not be able to demonstrate receiving emails.
I have pushed a "poc" branch and demonstrated that it sort of works but you do not really want to go down that path. Unfortunately, there is an issue in SimpleLogin that makes it seemingly impossible to work if not properly exposed on TCP port 443
. That seems to be hardcoded in the service.
If you do enable TCP ports 80
and 443
for the app behind the nginx proxy, you will be able to successfully register a new user and the app will attempt to send an email. That email is unfortunately rejected by most providers as it fails proper checks such as SPF.
Basically, there are no shortcuts here, you have to use proper SSL certificates (self-signed will work for demonstration purposes) and proper expected TCP ports for end-to-end demonstration. And you absolutely have to use proper DNS configuration for the mail server as well.
I’m not sure what you are after with your attempts. The full stack works out of the box. You can change the nginx/acme.sh part of the stack with something else to better suit your needs but I suggest you start with a properly setup domain, with SSL certs and use the provided postfix configuration from my repo.
Once you are happy, you can start fiddling with the postfix configuration to better reflect your setup.
from app.
@leodoi3 hi, have you got that docker compose working? can you share final results and configs ?
from app.
@uhlhosting @leodoi3 I’m interested in understanding how is your intended usage, as this docker is 100% working right outside of the box on a freshly provisioned and booted server.
- I suspect most people already have a separate NGINX / Certbot configuration and do not want to use mine.
In that case, you need to remove the relevant configurations from my docker-compose and update the paths to the SSL certificates. You also have to comment some lines from the up.sh
script as related to nginx configuration.
- Others may already have an existing postfix installation that they want to bring along.
My docker-compose set is specifically designed to include a dedicated postfix installation which is barebones and simple but works right outside the box. If you need more elaborate postfix configuration, I guess there is some work to do.
That said, the pure SimpleLogin part of the docker-compose source code, without NGINX / Certbox and postfix is also known to be working. If you rewind to early commits in the repository, you will see that was how I was running SimpleLogin initially. NGINX and acme.sh were running bare metal. As was running postfix, following the instructions from this repository. Only the SimpleLogin images were included in the docker-compose.
Please, let me know what is your setup and what is not working for you ?
from app.
@springcomp Hi,
Thanks for the follow up!
I am deploying all my docker apps in caprover. So my final purpose here is to find a working vetted solution. That has all required for the app to work. Yet without acme and nginx part. Since they are handled by caprover. So will try to addapt your docker compose into working caprover template and see how that works. Will keep you posted in this thread.
from app.
@uhlhosting Hi,
Please allow me to inquire if you finally succeeded in using Docker-Compose to self-host SimpleLogin behing CaRover ?
from app.
@springcomp Hi,
Unfortunately time wasn't on my side to work our the issues. Will check back the deployment and see if any changes were pushed. I cannot recall now what actions I have made or made not.
from app.
Related Issues (20)
- You are going to be blocked! please take measures! HOT 1
- Unable to send Verification email - Self Hosted HOT 15
- You got blocked. Please take measures
- This lists blocked SimpleLogin HOT 3
- You got blocked :( HOT 1
- Uppercase alias in CC of response causing response email dropped
- Block registration with maskmy.id (it is violating ToS) HOT 5
- Your domains got blocked in disposable list HOT 1
- Hotel HOT 1
- Remove sensitive words HOT 1
- Wording change HOT 1
- Small fixes HOT 1
- Remove sensitive words HOT 1
- Remove useless HOT 1
- SimpleLogin websites problems report HOT 3
- New fixes due to SL movement HOT 1
- Small fixes HOT 1
- cannot receive emails from Spotify HOT 1
- Sending signed emails are silently ignored
- Private vulnerability reporting ?
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from app.