crazy-max / docker-firefox-syncserver Goto Github PK
View Code? Open in Web Editor NEWFirefox Sync Server Docker image
License: MIT License
Firefox Sync Server Docker image
License: MIT License
Container crash at startup because it can't access/find/create the sqlite file
docker-compose up
The container should not crash at startup
The Container crash at startup because it can't access/find/create the sqlite file
Docker version 19.03.2, build 6a30dfca03
docker-compose version 1.24.1, build 4667896
Debian 9
docker-compose.yml
: firefox-sync:
container_name: firefox-sync
environment:
- TZ=${TZ}
- FF_SYNCSERVER_PUBLIC_URL=${FF_SYNCSERVER_PUBLIC_URL}
- FF_SYNCSERVER_SECRET=${FF_SYNCSERVER_SECRET}
- FF_SYNCSERVER_ALLOW_NEW_USERS=${FF_SYNCSERVER_ALLOW_NEW_USERS}
- FF_SYNCSERVER_FORCE_WSGI_ENVIRON=true
hostname: ${DOCKERHOSTNAME}
image: crazymax/firefox-syncserver
logging:
driver: json-file
options:
max-file: ${DOCKERLOGGING_MAXFILE}
max-size: ${DOCKERLOGGING_MAXSIZE}
ports:
- ${FF_SYNCSERVER_PORT}:5000
restart: unless-stopped
volumes:
- /etc/localtime:/etc/localtime:ro
- ${DOCKERCONFDIR}/firefox-sync:/data
.env
:TZ=Europe/Paris
FF_SYNCSERVER_ALLOW_NEW_USERS=true
FF_SYNCSERVER_PORT=127.0.0.1:5000
FF_SYNCSERVER_PUBLIC_URL=not relevent
FF_SYNCSERVER_SECRET=not relevent
DOCKERCONFDIR=/home/zayon/.config/appdata
Client:
Debug Mode: false
Server:
Containers: 9
Running: 8
Paused: 0
Stopped: 1
Images: 10
Server Version: 19.03.2
Storage Driver: devicemapper
Pool Name: docker-9:3-1056766-pool
Pool Blocksize: 65.54kB
Base Device Size: 10.74GB
Backing Filesystem: xfs
Udev Sync Supported: true
Data file: /dev/loop0
Metadata file: /dev/loop1
Data loop file: /var/lib/docker/devicemapper/devicemapper/data
Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata
Data Space Used: 3.21GB
Data Space Total: 107.4GB
Data Space Available: 15.24GB
Metadata Space Used: 21.92MB
Metadata Space Total: 2.147GB
Metadata Space Available: 2.126GB
Thin Pool Minimum Free Space: 10.74GB
Deferred Removal Enabled: true
Deferred Deletion Enabled: true
Deferred Deleted Device Count: 0
Library Version: 1.02.137 (2016-11-30)
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 894b81a4b802e4eb2a91d1ce216b8817763c29fb
runc version: 425e105d5a03fabd737a126ad93d62a9eeede87f
init version: fec3683
Security Options:
seccomp
Profile: default
Kernel Version: 4.19.0-ovh-xxxx-std-ipv6-64
Operating System: Debian GNU/Linux 9 (stretch)
OSType: linux
Architecture: x86_64
CPUs: 8
Total Memory: 15.7GiB
Name: ks-11.zayon.eu
ID: ZUMQ:Y2AF:FT2N:SUWX:IARP:FUWA:3KLH:KRKY:HGCT:6K3U:EFPJ:OE7I
Docker Root Dir: /var/lib/docker
Debug Mode: false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
127.0.0.0/8
Live Restore Enabled: false
WARNING: No cpu cfs quota support
WARNING: No cpu cfs period support
WARNING: the devicemapper storage-driver is deprecated, and will be removed in a future release.
WARNING: devicemapper: usage of loopback devices is strongly discouraged for production use.
Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.
[ERROR] Exception in worker process
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 557, in spawn_worker
worker.init_process()
File "/usr/local/lib/python2.7/site-packages/gunicorn/workers/base.py", line 126, in init_process
self.load_wsgi()
File "/usr/local/lib/python2.7/site-packages/gunicorn/workers/base.py", line 136, in load_wsgi
self.wsgi = self.app.wsgi()
File "/usr/local/lib/python2.7/site-packages/gunicorn/app/base.py", line 67, in wsgi
self.callable = self.load()
File "/usr/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 63, in load
return self.load_pasteapp()
File "/usr/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 59, in load_pasteapp
return load_pasteapp(self.cfgurl, self.relpath, global_conf=None)
File "/usr/local/lib/python2.7/site-packages/gunicorn/app/pasterapp.py", line 69, in load_pasteapp
global_conf=global_conf)
File "/usr/local/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 253, in loadapp
return loadobj(APP, uri, name=name, **kw)
File "/usr/local/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 278, in loadobj
return context.create()
File "/usr/local/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 715, in create
return self.object_type.invoke(self)
File "/usr/local/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 152, in invoke
return fix_call(context.object, context.global_conf, **context.local_conf)
File "/usr/local/lib/python2.7/site-packages/paste/deploy/util.py", line 55, in fix_call
val = callable(*args, **kw)
File "/app/syncserver/__init__.py", line 265, in main
config = get_configurator(global_config, **settings)
File "/app/syncserver/__init__.py", line 257, in get_configurator
config.include(includeme)
File "/usr/local/lib/python2.7/site-packages/pyramid/config/__init__.py", line 754, in include
c(configurator)
File "/usr/local/lib/python2.7/site-packages/syncstorage/storage/__init__.py", line 561, in includeme
storage = load_storage_from_settings("storage", settings)
File "/usr/local/lib/python2.7/site-packages/syncstorage/storage/__init__.py", line 585, in load_storage_from_settings
return klass(**section_settings)
File "/usr/local/lib/python2.7/site-packages/syncstorage/storage/sql/__init__.py", line 130, in __init__
self.dbconnector = DBConnector(sqluri, **dbkwds)
File "/usr/local/lib/python2.7/site-packages/syncstorage/storage/sql/dbconnect.py", line 363, in __init__
collections.create(self.engine, checkfirst=True)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/sql/schema.py", line 860, in create
bind._run_visitor(ddl.SchemaGenerator, self, checkfirst=checkfirst)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 2032, in _run_visitor
with self._optional_conn_ctx_manager(connection) as conn:
File "/usr/local/lib/python2.7/contextlib.py", line 17, in __enter__
return self.gen.next()
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 2024, in _optional_conn_ctx_manager
with self._contextual_connect() as conn:
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 2226, in _contextual_connect
self._wrap_pool_connect(self.pool.connect, None),
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 2266, in _wrap_pool_connect
e, dialect, self
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1536, in _handle_dbapi_exception_noconnection
util.raise_from_cause(sqlalchemy_exception, exc_info)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/util/compat.py", line 383, in raise_from_cause
reraise(type(exception), exception, tb=exc_tb, cause=cause)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 2262, in _wrap_pool_connect
return fn()
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool/base.py", line 363, in connect
return _ConnectionFairy._checkout(self)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool/base.py", line 760, in _checkout
fairy = _ConnectionRecord.checkout(pool)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool/base.py", line 492, in checkout
rec = pool._do_get()
File "/usr/local/lib/python2.7/site-packages/mozsvc/metrics.py", line 183, in timed_func
return func(*args, **kwds)
File "/usr/local/lib/python2.7/site-packages/syncstorage/storage/sql/dbconnect.py", line 283, in _do_get
return QueuePool._do_get(self)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool/impl.py", line 139, in _do_get
self._dec_overflow()
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool/impl.py", line 136, in _do_get
return self._create_connection()
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool/base.py", line 308, in _create_connection
return _ConnectionRecord(self)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool/base.py", line 437, in __init__
self.__connect(first_connect_check=True)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool/base.py", line 639, in __connect
connection = pool._invoke_creator(self)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect
return dialect.connect(*cargs, **cparams)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/default.py", line 453, in connect
return self.dbapi.connect(*cargs, **cparams)
OperationalError: (sqlite3.OperationalError) unable to open database file
(Background on this error at: http://sqlalche.me/e/e3q8)
Just FYI, Mozilla released SyncStorage. Allowing you to use with your SyncServer to not only sync but also store the data on your server. Without that, if you ever delete your profile, you loose everything. Except if you still have another active Firefox instance with your profile.
https://github.com/mozilla-services/syncstorage-rs/
https://www.ghacks.net/2020/09/19/mozilla-improves-the-reliability-of-firefoxs-sync-service/
Upon running "docker-compose up," I seem to be getting the following errors:
Creating dockerfirefoxsyncserver_traefik_1 ... error
Creating firefox-syncserver ...
ERROR: for dockerfirefoxsyncserver_traefik_1 Cannot start service traefik: driver failed programming external connectivity on endpoint dockerfirefoxsyncserver_traefik_1 (372065602c403e7b6c05b6ae5864492940b12a3c13abcefcb99c1f42fcfec7fc): ErCreating firefox-syncserver ... done
use
ERROR: for traefik Cannot start service traefik: driver failed programming external connectivity on endpoint dockerfirefoxsyncserver_traefik_1 (372065602c403e7b6c05b6ae5864492940b12a3c13abcefcb99c1f42fcfec7fc): Error starting userland proxy: listen tcp 0.0.0.0:443: listen: address already in use
ERROR: Encountered errors while bringing up the project.
I presume this is because port 443 is being used on the host (it's a QNap NAS), and the solution is that I just need to change one of the 443s to an available open port? For reference (since the setup seems to have changed a bit since the readme was written), the things I've currently changed in docker-compose.yml are:
traefik.port=5099
traefik.frontend.rule=Host:myexamplehost.com
FF_SYNCSERVER_PUBLIC_URL=https://myexamplehost.com:5099
FF_SYNCSERVER_SECRET=make a random i.e. 20-digit key
If my thinking is right, which of the 3x "443"s would need to be changed?
entryPoints=Name:https Address::443 TLS
target: 443
published: 443
?
I have successfully set up firefox-syncserver via docker compose and followed the info here to change the sync url on 1 of my devices:
https://mozilla-services.readthedocs.io/en/latest/howtos/run-sync-1.5.html#howto-run-sync15
To configure desktop Firefox to talk to your new Sync server, go to “about:config”, search for “identity.sync.tokenserver.uri” and change its value to be the public URL of your server with a path of “token/1.0/sync/1.5”:
identity.sync.tokenserver.uri: http://localhost:5000/token/1.0/sync/1.5
In my case the url is: https://firefox.mydomain.cloud:5000/token/1.0/sync/1.5
Snippet of my compose:
##_____________________ Firefox Sync [CLOUD/Browser]
# generate secret.txt first see docker-config.sh
firefox-syncserver:
image: crazymax/firefox-syncserver:latest
container_name: firefox_syncserver
restart: always
environment:
- PUID=$PUID
- PGID=$PGID
- TZ=$TZ
- FF_SYNCSERVER_PUBLIC_URL=https://firefox.$DOMAIN
- FF_SYNCSERVER_SECRET=$FFSYNCSECRET
- FF_SYNCSERVER_FORWARDED_ALLOW_IPS=*
- FF_SYNCSERVER_FORCE_WSGI_ENVIRON=true
volumes:
- "$USERDIR/docker/firefox-syncserver:/data"
ports:
- 5000:5000
labels:
- traefik.enable=true
- traefik.http.middlewares.firefox-redirect.redirectscheme.scheme=https
- traefik.http.routers.firefox-redirect.entrypoints=web
- traefik.http.routers.firefox-redirect.middlewares=firefox-redirect
- traefik.http.routers.firefox-redirect.rule=Host(`firefox.$DOMAIN`)
- traefik.http.routers.firefox.entrypoints=websecure
- traefik.http.routers.firefox.rule=Host(`firefox.$DOMAIN`)
- traefik.http.routers.firefox.tls.certresolver=letsencrypt
- traefik.http.services.firefox.loadbalancer.server.port=5000
log file shows (notice the depricated environment warning, I did not state any depricated env vars in my Compose):
Setting timezone to Europe/Amsterdam...
Checking prerequisites...
Generating configuration...
Fixing perms...
[2020-10-21 15:54:39 +0000] [1] [INFO] Starting gunicorn 19.6.0
[2020-10-21 15:54:39 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2020-10-21 15:54:39 +0000] [1] [INFO] Using worker: sync
[2020-10-21 15:54:39 +0000] [15] [INFO] Booting worker with pid: 15
/usr/local/lib/python2.7/site-packages/pyramid/path.py:341: PkgResourcesDeprecationWarning: Parameters to load are deprecated. Call .resolve and .require separately.
'x=%s' % value).load(False)
But how can I verify if it is actually syncing everything to my server instead of a Mozilla server? I restarted my browser, manually initiated a sync via the menu. I can of course change the url on all my devices, but even if syncing still works.. is there a way to check within the Docker container whether things are happening or not?
Very nice work, can you also do this way firefox account server?
I accidentally left FF_SYNCSERVER_ALLOW_NEW_USERS: 'true' for a couple of months.
Strangers could have used it to sync with my server. How can I check what accounts are currently linked to my server?
Hi,
I'm starting "docker-firefox-syncserver" like this...
docker run -d -p 5000:5000 --name firefox_syncserver -e TZ="Europe/Paris" -e FF_SYNCSERVER_SECRET="xxxxxxxxxxx" -e FF_SYNCSERVER_FORWARDED_ALLOW_IPS="*" -e FF_SYNCSERVER_PUBLIC_URL="http://sync.domain.tld" -e force_wsgi_environ="true" -e FF_SYNCSERVER_ALLOW_NEW_USERS="true" -v $(pwd)/ff-sync:/data crazymax/firefox-syncserver:latest
I'm getting "WARNING:mozsvc.user:Authentication Failed: invalid hawk signature"
Can someone help me with that issue??
Thanks, -Tom-
I need help setting this up. Forgive me for my lack of knowledge in docker, this is the first time I'm attempting to use it.
My configuration
Debian 9, nginx webserver, 4gb ram SBC, arm64
What I have installed which I think could help me get this done
I noticed that you have traefik for let’ s encrypt. I already have certbot setup on my server so I think I'll be able to skip this.
What I hope to achieve:
Hypothetically, I have this network/server configuration
Questions:
-How should I go about configuring my docker-compose.yml file and how should I run it?
-Should I forward port 5000 from my router?
I would greatly appreciate if you could guide me in setting this up. Thanks.
I recently switched from Traefik to Caddy as it is unbelievably simple to set up. I think others would benefit from an extra example in your documentation, next to Traefik. This is really all you need, no need to install Caddy on the host, no need to create a Caddy config file:
##_____________________ Caddy [CLOUD/web-proxy]
caddy:
container_name: caddy-proxy
image: lucaslorentz/caddy-docker-proxy:ci-alpine
restart: always
networks:
- web-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- $DOCKERDIR/caddy/caddy_data:/data
- $DOCKERDIR/caddy/config:/config
labels:
caddy.email: $EMAIL
ports:
- 80:80
- 443:443
##_____________________ Firefox Sync [CLOUD/Browser]
# generate secret.txt first see docker-config.sh
firefox-syncserver:
image: crazymax/firefox-syncserver:latest
container_name: firefox_syncserver
restart: always
networks:
- web-proxy
environment:
FF_SYNCSERVER_PUBLIC_URL: firefox.$DOMAIN
FF_SYNCSERVER_SECRET: $FFSYNCSECRET
FF_SYNCSERVER_FORWARDED_ALLOW_IPS: '*'
FF_SYNCSERVER_FORCE_WSGI_ENVIRON: 'true'
FF_SYNCSERVER_ALLOW_NEW_USERS: 'true'
FF_SYNCSERVER_LOGLEVEL: debug
FF_SYNCSERVER_ACCESSLOG: 'true'
volumes:
- $DOCKERDIR/firefox-syncserver:/data
labels:
caddy: firefox.$DOMAIN
caddy.reverse_proxy: "{{upstreams 5000}}"
Syncing should work between all three devices.
Syncing works between the desktop devices (I add a bookmark on one, inititate sync manually, then inititiate sync manually on the other desktop and the bookmark appears).
Problem:
On Android, "last time sync" shows 1 jan 1970 and syncing stops immediately when you hit the Sync Now button and it then says "last time sync: Never."
It seems it does not connect to my server at all.
I can access https://myserver/token/
There are no errors.
I am on Ubuntu 20.04.1
firefox-syncserver:
image: crazymax/firefox-syncserver:latest
container_name: firefox_syncserver
restart: always
environment:
- PUID=$PUID
- PGID=$PGID
- TZ=$TZ
- FF_SYNCSERVER_PUBLIC_URL=https://firefox.$DOMAIN
- FF_SYNCSERVER_SECRET=$FFSYNCSECRET
# - FF_SYNCSERVER_FORWARDED_ALLOW_IPS=*
- FF_SYNCSERVER_FORCE_WSGI_ENVIRON=true
- FF_SYNCSERVER_ALLOW_NEW_USERS=true
- FF_SYNCSERVER_LOGLEVEL=debug
volumes:
- "$USERDIR/docker/firefox-syncserver:/data"
ports:
- 5000:5000
labels:
- traefik.enable=true
- traefik.http.middlewares.firefox-redirect.redirectscheme.scheme=https
- traefik.http.routers.firefox-redirect.entrypoints=web
- traefik.http.routers.firefox-redirect.middlewares=firefox-redirect
- traefik.http.routers.firefox-redirect.rule=Host(`firefox.$DOMAIN`)
- traefik.http.routers.firefox.entrypoints=websecure
- traefik.http.routers.firefox.rule=Host(`firefox.$DOMAIN`)
- traefik.http.routers.firefox.tls.certresolver=letsencrypt
- traefik.http.services.firefox.loadbalancer.server.port=5000
about:sync-log does not work on Firefox for Android.
Thanks a ton for putting this together :) Trying to get it up & running, but it looks like "docker-compose up" is generating the error:
ERROR: The Compose file './docker-compose.yml' is invalid because:
services.traefik.ports is invalid: Invalid port "{'target': 443, 'protocol': 'tcp', 'mode': 'host', 'published': 443}", should be [[remote_ip:]remote_port[-remote_port]:]port[/protocol]
services.traefik.ports contains an invalid type, it should be a string, or a number
services.traefik.ports is invalid: Invalid port "{'target': 80, 'protocol': 'tcp', 'mode': 'host', 'published': 80}", should be [[remote_ip:]remote_port[-remote_port]:]port[/protocol]
services.traefik.ports contains an invalid type, it should be a string, or a number
Running Docker on an RPi.
No apparent sync. The DB file size is unchanged (61440) from fresh start.
Docker compose...
version: "3.2"
services:
firefox-syncserver:
image: crazymax/firefox-syncserver:latest
container_name: firefox_syncserver
ports:
- 5000:5000
environment:
- PUID=1000
- PGID=1000
- FF_SYNCSERVER_PUBLIC_URL='http://192.168.1.10:5000'
- FF_SYNCSERVER_SECRET='somerandomstufftypedhere'
- TZ=America/Chicago
volumes:
- /home/david/docker/ffsync/data:/data
restart: always
I'm using http://192.168.1.10:5000/token/1.0/sync/1.5/
in the identity.sync.tokenserver.uri
section of about:config
. I tried this with and without the trailing slash after 1.5
.
I must be missing something obvious. It can't be this hard... can it? Thanks!
Hi. I'm running into an issue trying to use an external mariadb. I provided the appropriate sqluri but running into this error.
[2019-05-13 21:58:10 +0000] [1] [INFO] Starting gunicorn 19.6.0
[2019-05-13 21:58:10 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2019-05-13 21:58:10 +0000] [1] [INFO] Using worker: sync
[2019-05-13 21:58:10 +0000] [12] [INFO] Booting worker with pid: 12
[2019-05-13 21:58:10 +0000] [12] [ERROR] Exception in worker process
and also
ImportError: No module named MySQLdb
[2019-05-13 21:58:10 +0000] [12] [INFO] Worker exiting (pid: 12)
[2019-05-13 21:58:10 +0000] [1] [INFO] Shutting down: Master
[2019-05-13 21:58:10 +0000] [1] [INFO] Reason: Worker failed to boot.
Any suggestions?
Great work, got this Docker image working without any big issues!
I had one question: does the Docker container support MySQL by default or does it need additional libraries for that? If so, maybe you could add the sqluri as variable so you can use any supported SQL server in your configuration.
Hello,
I use your docker image, but cant sync with my Android Firefox.
http://domain.tld:5000/token/1.0/sync/1.5
Shows me at the Page and
{"status": "error", "errors": [{"location": "body", "name": "", "description": "Unauthorized"}]}
http://domain.tld:5000 "it works!"
Docker shows the this
[2018-06-22 11:13:20 +0000] [1] [INFO] Starting gunicorn 19.6.0
[2018-06-22 11:13:20 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2018-06-22 11:13:20 +0000] [1] [INFO] Using worker: sync
[2018-06-22 11:13:20 +0000] [12] [INFO] Booting worker with pid: 12
Does this image support mariadb as an alternitave database? I can seem to get it to work with maraidb.
Tell me what should happen
Tell me what happens instead
docker --version
) :docker-compose --version
) :docker-compose.yml
, .env
, ...> Output of command `docker info`
> Container logs (set FF_SYNCSERVER_LOGLEVEL to debug if applicable)
I had been using this image successfully for a long time, syncing a few different client devices together without issue.
Today I found that I have no sync functionality at all.
To troubleshoot this, I backed up my syncserver.db file first and then destroyed & recreated my container completely.
The sync server is responding to regular incoming web requests, I can get to the "it works" status page as expected.
Regardless of what I do, when I have a client device attempt to sync against the server, I'm getting the following output from "docker logs ":
Setting timezone to America/Los_Angeles... Checking prerequisites... Generating configuration... [2019-07-24 18:53:26 +0000] [1] [INFO] Starting gunicorn 19.6.0 [2019-07-24 18:53:26 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1) [2019-07-24 18:53:26 +0000] [1] [INFO] Using worker: sync [2019-07-24 18:53:26 +0000] [13] [INFO] Booting worker with pid: 13 /usr/local/lib/python2.7/site-packages/pyramid/path.py:341: PkgResourcesDeprecationWarning: Parameters to load are deprecated. Call .resolve and .require separately. 'x=%s' % value).load(False) ERROR:tokenserver:Unexpected verification error Traceback (most recent call last): File "/usr/local/lib/python2.7/site-packages/tokenserver/views.py", line 110, in _valid_browserid_assertion assertion = verifier.verify(assertion) File "/usr/local/lib/python2.7/site-packages/tokenserver/verifiers.py", line 92, in verify data = super(LocalBrowserIdVerifier, self).verify(assertion, audience) File "/usr/local/lib/python2.7/site-packages/browserid/verifiers/local.py", line 115, in verify cert = self.verify_certificate_chain(certificates, now=now) File "/usr/local/lib/python2.7/site-packages/browserid/verifiers/local.py", line 161, in verify_certificate_chain root_key = self.supportdocs.get_key(root_issuer) File "/usr/local/lib/python2.7/site-packages/browserid/supportdoc.py", line 63, in get_key supportdoc = self.get_support_document(hostname) File "/usr/local/lib/python2.7/site-packages/browserid/supportdoc.py", line 58, in get_support_document raise error ConnectionError: Failed to GET https://api.accounts.firefox.com/.well-known/browserid. Reason: HTTPSConnectionPool(host='api.accounts.firefox.com', port=443): Max retries exceeded with url: /.well-known/browserid (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7f278ff45390>: Failed to establish a new connection: [Errno -3] Try again',)) [2019-07-24 18:54:50 +0000] [1] [INFO] Handling signal: term [2019-07-24 18:54:50 +0000] [13] [INFO] Worker exiting (pid: 13) [2019-07-24 18:54:50 +0000] [1] [INFO] Shutting down: Master
On a Windows client machine, I'm getting the following in Firefox's about:sync-log:
trace: [email protected]:102:16 [email protected]:311:19 [email protected]:248:19 1563994093038 Sync.Status DEBUG Status.login: error.login.reason.network => error.login.reason.network 1563994093038 Sync.Status DEBUG Status.service: error.login.failed => error.login.failed 1563994093038 Sync.ErrorHandler ERROR Sync encountered a login error 1563994093039 Sync.SyncScheduler DEBUG Clearing sync triggers and the global score. 1563994093042 Sync.SyncScheduler DEBUG Next sync in 3600000 ms. (why=schedule) 1563994093049 Sync.Service DEBUG Exception calling WrappedLock: Error: Login failed: error.login.reason.network(resource://services-sync/service.js:869:15) JS Stack trace: [email protected]:869:15 1563994093052 Sync.Service DEBUG Not syncing: login returned false.
Steps To Reproduce:
-Start or create a Docker container running the image crazymax/firefox-syncserver:latest
-Have any Firefox client device attempt to sync against the server hosted in that container.
I am currently deploying via Docker Compose. I'm confident the other elements in my stack are working as expected. I am able to use other services hosted on the same system without issue and, like I said, I can hit the container's web interface.
I honestly can't tell if this is simply a service outtage of some kind, an undocumented change on the part of Mozilla, or what. I can't seem to find any public sites giving status updates on Mozilla's hosted services.
Is this just me?
As my Nginx server with the reverse proxy is on another machine than the docker, I'm in need of the "forwarded_allow_ips" setting set to * (or the IP of my Nginx server), but I can't get the setting to be applied to Gunicorn. It always keeps the default of 127.0.0.1 - see line from the debug log:
firefox_syncserver | forwarded_allow_ips: ['127.0.0.1']
FF_SYNCSERVER_FORWARDED_ALLOW_IPS = *
gunicorn should display set forward_allow_ips
variable instead of the default. This would prevent gunicorn from mistrusting the https protocol from the proxy and allowing the syncserver to properly work.
gunicorn always uses default forwarded_allow_ips = 127.0.0.1
and per the note from the official syncserver docs:
Note
If you see errors about a mismatch between public_url and application_url, you may need to tell gunicorn that it should trust the X-Forwarded-Proto header being sent by nginx. Add the following to the gunicorn configuration in syncserver.ini:
forwarded_allow_ips = *
this prevents the sync server from actually syncing as the sync process then attempts to use http:// instead of the secured one, which fails as my server isn't set to accept insecure requests.
docker --version
) : 20.10.17, build 100c701docker-compose --version
) : 1.29.2uname -a
) : 5.15.43-sunxi64 #22.05.1 SMP Sat May 28 08:25:20 UTC 2022 aarch64 GNU/Linuxversion: "3.2"
services:
firefox-syncserver:
image: crazymax/firefox-syncserver
container_name: firefox_syncserver
ports:
- target: 5000
published: 5000
protocol: tcp
volumes:
- "./data:/data"
env_file:
- "./firefox-syncserver.env"
restart: always
firefox-syncserver.env:
TZ=Europe/Paris
PUID=1000
PGID=1000
FF_SYNCSERVER_PUBLIC_URL=https://[myserver]/moz-sync
FF_SYNCSERVER_SECRET=[mysecret]
FF_SYNCSERVER_ALLOW_NEW_USERS=true
FF_SYNCSERVER_FORCE_WSGI_ENVIRON=false
FF_SYNCSERVER_FORWARDED_ALLOW_IPS=*
FF_SYNCSERVER_LOGLEVEL=debug
contents of /opt/syncserver/syncserver.ini from within running docker container:
bash-5.0# cat /opt/syncserver/syncserver.ini
[server:main]
use = egg:gunicorn
host = 0.0.0.0
port = 5000
workers = 1
timeout = 30
loglevel = debug
[app:main]
use = egg:syncserver
[syncserver]
# This must be edited to point to the public URL of your server,
# i.e. the URL as seen by Firefox.
public_url = https://[myserver]/moz-sync
# This defines the database in which to store all server data.
sqluri = sqlite:///data/syncserver.db
# This is a secret key used for signing authentication tokens.
# It should be long and randomly-generated.
# The following command will give a suitable value on *nix systems:
#
# head -c 20 /dev/urandom | sha1sum
#
# If not specified then the server will generate a temporary one at startup.
secret = d0a07aea2425a116f688aed63bd60e1278fcc512
# Set this to "false" to disable new-user signups on the server.
# Only request by existing accounts will be honoured.
allow_new_users = true
# Set this to "true" to work around a mismatch between public_url and
# the application URL as seen by python, which can happen in certain reverse-
# proxy hosting setups. It will overwrite the WSGI environ dict with the
# details from public_url. This could have security implications if e.g.
# you tell the app that it's on HTTPS but it's really on HTTP, so it should
# only be used as a last resort and after careful checking of server config.
force_wsgi_environ = false
# Uncomment and edit the following to use a local BrowserID verifier
# rather than posting assertions to the mozilla-hosted verifier.
# Audiences should be set to your public_url without a trailing slash.
#[browserid]
#backend = tokenserver.verifiers.LocalVerifier
#audiences = https://localhost:5000
# If you are running Nginx on a different host than the ff sync server the ff snyc server have to trust the X-Forwarded-* headers sent by Nginx.
forwarded_allow_ips = *
Include all necessary configuration files : docker-compose.yml
, .env
, ...
Client:
Context: default
Debug Mode: false
Plugins:
app: Docker App (Docker Inc., v0.9.1-beta3)
buildx: Docker Buildx (Docker Inc., v0.8.2-docker)
compose: Docker Compose (Docker Inc., v2.6.0)
Server:
Containers: 9
Running: 8
Paused: 0
Stopped: 1
Images: 7
Server Version: 20.10.17
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Native Overlay Diff: true
userxattr: false
Logging Driver: json-file
Cgroup Driver: systemd
Cgroup Version: 2
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
Swarm: inactive
Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 0197261a30bf81f1ee8e6a4dd2dea0ef95d67ccb
runc version: v1.1.3-0-g6724737
init version: de40ad0
Security Options:
apparmor
seccomp
Profile: default
cgroupns
Kernel Version: 5.15.43-sunxi64
Operating System: Debian GNU/Linux 11 (bullseye)
OSType: linux
Architecture: aarch64
CPUs: 4
Total Memory: 1.943GiB
Name: pine64
ID: KWYO:YKZ5:RMQF:347Q:QUYU:XDYJ:36VS:ZUVK:SI57:2EIF:F4FX:W5XW
Docker Root Dir: /var/lib/docker
Debug Mode: false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
127.0.0.0/8
Live Restore Enabled: false
Attaching to firefox_syncserver
firefox_syncserver | Setting timezone to Europe/Paris...
firefox_syncserver | Checking prerequisites...
firefox_syncserver | Generating configuration...
firefox_syncserver | Fixing perms...
firefox_syncserver | [2022-09-08 11:00:32 +0000] [1] [DEBUG] Current configuration:
firefox_syncserver | secure_scheme_headers: {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}
firefox_syncserver | proxy_protocol: False
firefox_syncserver | worker_connections: 1000
firefox_syncserver | statsd_host: None
firefox_syncserver | max_requests_jitter: 0
firefox_syncserver | post_fork: <function post_fork at 0xffff9cfc2f50>
firefox_syncserver | pythonpath: None
firefox_syncserver | enable_stdio_inheritance: False
firefox_syncserver | worker_class: sync
firefox_syncserver | ssl_version: 3
firefox_syncserver | suppress_ragged_eofs: True
firefox_syncserver | syslog: False
firefox_syncserver | syslog_facility: user
firefox_syncserver | when_ready: <function when_ready at 0xffff9cfc2c50>
firefox_syncserver | pre_fork: <function pre_fork at 0xffff9cfc2dd0>
firefox_syncserver | cert_reqs: 0
firefox_syncserver | preload_app: False
firefox_syncserver | keepalive: 2
firefox_syncserver | accesslog: None
firefox_syncserver | group: 1000
firefox_syncserver | graceful_timeout: 30
firefox_syncserver | do_handshake_on_connect: False
firefox_syncserver | spew: False
firefox_syncserver | workers: 1
firefox_syncserver | proc_name: None
firefox_syncserver | sendfile: None
firefox_syncserver | pidfile: None
firefox_syncserver | umask: 0
firefox_syncserver | on_reload: <function on_reload at 0xffff9cfc2ad0>
firefox_syncserver | pre_exec: <function pre_exec at 0xffff9cfc85d0>
firefox_syncserver | worker_tmp_dir: None
firefox_syncserver | post_worker_init: <function post_worker_init at 0xffff9cfc8150>
firefox_syncserver | limit_request_fields: 100
firefox_syncserver | on_exit: <function on_exit at 0xffff9cfc8cd0>
firefox_syncserver | config: None
firefox_syncserver | logconfig: None
firefox_syncserver | check_config: False
firefox_syncserver | statsd_prefix:
firefox_syncserver | proxy_allow_ips: ['127.0.0.1']
firefox_syncserver | pre_request: <function pre_request at 0xffff9cfc8750>
firefox_syncserver | post_request: <function post_request at 0xffff9cfc8850>
firefox_syncserver | user: 1000
firefox_syncserver | forwarded_allow_ips: ['127.0.0.1']
firefox_syncserver | worker_int: <function worker_int at 0xffff9cfc82d0>
firefox_syncserver | threads: 1
firefox_syncserver | max_requests: 0
firefox_syncserver | limit_request_line: 4094
firefox_syncserver | access_log_format: %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"
firefox_syncserver | certfile: None
firefox_syncserver | worker_exit: <function worker_exit at 0xffff9cfc89d0>
firefox_syncserver | chdir: /
firefox_syncserver | paste: /opt/syncserver/syncserver.ini
firefox_syncserver | default_proc_name: /opt/syncserver/syncserver.ini
firefox_syncserver | errorlog: -
firefox_syncserver | loglevel: debug
firefox_syncserver | capture_output: False
firefox_syncserver | syslog_addr: udp://localhost:514
firefox_syncserver | syslog_prefix: None
firefox_syncserver | daemon: False
firefox_syncserver | ciphers: TLSv1
firefox_syncserver | on_starting: <function on_starting at 0xffff9cfc2950>
firefox_syncserver | worker_abort: <function worker_abort at 0xffff9cfc8450>
firefox_syncserver | bind: ['0.0.0.0:5000']
firefox_syncserver | raw_env: []
firefox_syncserver | reload: False
firefox_syncserver | limit_request_field_size: 8190
firefox_syncserver | nworkers_changed: <function nworkers_changed at 0xffff9cfc8b50>
firefox_syncserver | timeout: 30
firefox_syncserver | ca_certs: None
firefox_syncserver | django_settings: None
firefox_syncserver | tmp_upload_dir: None
firefox_syncserver | keyfile: None
firefox_syncserver | backlog: 2048
firefox_syncserver | logger_class: gunicorn.glogging.Logger
firefox_syncserver | [2022-09-08 11:00:32 +0000] [1] [INFO] Starting gunicorn 19.6.0
firefox_syncserver | [2022-09-08 11:00:32 +0000] [1] [DEBUG] Arbiter booted
firefox_syncserver | [2022-09-08 11:00:32 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
firefox_syncserver | [2022-09-08 11:00:32 +0000] [1] [INFO] Using worker: sync
firefox_syncserver | [2022-09-08 11:00:32 +0000] [16] [INFO] Booting worker with pid: 16
firefox_syncserver | [2022-09-08 11:00:32 +0000] [1] [DEBUG] 1 workers
firefox_syncserver | /usr/local/lib/python2.7/site-packages/pyramid/path.py:341: PkgResourcesDeprecationWarning: Parameters to load are deprecated. Call .resolve and .require separately.
firefox_syncserver | 'x=%s' % value).load(False)
firefox_syncserver | /usr/local/lib/python2.7/site-packages/jwt/utils.py:8: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release.
firefox_syncserver | from cryptography.hazmat.primitives.asymmetric.utils import (
nginx reverse proxy config:
location /moz-sync {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect off;
proxy_read_timeout 120;
proxy_connect_timeout 10;
proxy_pass http://[DockerMachineIP]:5000/;
}
Currently the secret key itself needs to be given in an environmental variable.
Please allow the ability to link to a file instead, which should contain the secret key.
something like:
volumes:
- home/user/docker/firefox/secret:data/secret
Tell me what should happen
Tell me what happens instead
docker --version
) : 18.0.9docker-compose --version
) :docker-compose.yml
, .env
, ...> Output of command `docker info`
ConnectionError: Failed to GET https://api.accounts.firefox.com/.well-known/browserid. Reason:
HTTPSConnectionPool(host='api.accounts.firefox.com', port=443): Max retries exceeded with url:
/.well-known/browserid (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7f02a004a0d0>:
Failed to establish a new connection: [Errno -3] Try again',)) | stderr
-- | --
Hi,
I can’t seem to get the sync to work with iOS Firefox. It only works on the desktop (mac/windows). In iOS I also set the url to https://sync.mydomain/token/1.0/sync/1.5 but nothing happens no bookmarks are synced. I have also tried https://sync.mydomain/ but also nothing.
I followed the steps as described here https://daniel-ziegler.com/computer/netzwerk/linux/2019/07/07/Firefox-Sync-iOS/
Do you have any ideas how to fix this?
command run as root :
docker run -p 5000:5000 --name firefox_sync -e TZ="Europe/Paris" -e FF_SYNCSERVER_SECRET="1up3rS7zptn" -v /data:/data crazymax/firefox-syncserver:latest
problem :
standard_init_linux.go:207: exec user process caused "exec format error"
container launching
standard_init_linux.go:207: exec user process caused "exec format error"
docker --version
) :Docker version 18.09.0, build 4d60db4starts without unhealthy error
Get unhealthy error after latest docker image, before the update, no problems.
docker-compose.yml
, .env
, ...> Output of command `docker info`
Setting timezone to Europe/Amsterdam...,
'x=%s' % value).load(False),
[2019-08-05 08:21:20 +0000] [12] [INFO] Booting worker with pid: 12,
/usr/local/lib/python2.7/site-packages/pyramid/path.py:341: PkgResourcesDeprecationWarning: Parameters to load are deprecated. Call .resolve and .require separately.,
[2019-08-05 08:21:20 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1),
[2019-08-05 08:21:20 +0000] [1] [INFO] Starting gunicorn 19.6.0,
Generating configuration...,
Checking prerequisites...,
[2019-08-05 08:21:20 +0000] [1] [INFO] Using worker: sync
Compose:
firefoxsync:
container_name: firefox
restart: unless-stopped
image: crazymax/firefox-syncserver
volumes:
- ${USERDIR}/firefoxsync:/data
networks:
- reverseproxy
env_file:
- "./firefoxsync.env"
environment:
- TZ=${TZ}
labels:
- "traefik.docker.network=reverseproxy"
- "traefik.backend=firefox"
- "traefik.enable=true"
- "traefik.web.frontend.rule=Host:firefoxsync.******.com"
- "traefik.web.port=5000"
- "traefik.frontend.headers.SSLRedirect=true"
- "traefik.frontend.headers.STSSeconds=315360000"
- "traefik.frontend.headers.browserXSSFilter=true"
- "traefik.frontend.headers.contentTypeNosniff=true"
- "traefik.frontend.headers.forceSTSHeader=true"
- "traefik.frontend.headers.SSLHost=***.com"
- "traefik.frontend.headers.STSIncludeSubdomains=true"
- "traefik.frontend.headers.STSPreload=true"
- "traefik.frontend.headers.frameDeny=true"
.env file
FF_SYNCSERVER_PUBLIC_URL=https://firefoxsync.********.com
FF_SYNCSERVER_SECRET=***********
FF_SYNCSERVER_ALLOW_NEW_USERS=true
FF_SYNCSERVER_FORCE_WSGI_ENVIRON=true
FF_SYNCSERVER_FORCE_WSGI_ENVIRON=sqlite:///data/syncserver.db
Everythin should be running
My container is crashlooping with these logs:
Setting timezone to Europe/Paris...
Checking prerequisites...
Generating configuration...
Fixing perms...
[2022-03-21 16:21:40 +0000] [1] [INFO] Starting gunicorn 19.6.0
[2022-03-21 16:21:40 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2022-03-21 16:21:40 +0000] [1] [INFO] Using worker: sync
[2022-03-21 16:21:40 +0000] [14] [INFO] Booting worker with pid: 14
[2022-03-21 16:21:41 +0000] [14] [ERROR] Exception in worker process
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 557, in spawn_worker
worker.init_process()
File "/usr/local/lib/python2.7/site-packages/gunicorn/workers/base.py", line 126, in init_process
self.load_wsgi()
File "/usr/local/lib/python2.7/site-packages/gunicorn/workers/base.py", line 136, in load_wsgi
self.wsgi = self.app.wsgi()
File "/usr/local/lib/python2.7/site-packages/gunicorn/app/base.py", line 67, in wsgi
self.callable = self.load()
File "/usr/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 63, in load
return self.load_pasteapp()
File "/usr/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 59, in load_pasteapp
return load_pasteapp(self.cfgurl, self.relpath, global_conf=None)
File "/usr/local/lib/python2.7/site-packages/gunicorn/app/pasterapp.py", line 69, in load_pasteapp
global_conf=global_conf)
File "/usr/local/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 253, in loadapp
return loadobj(APP, uri, name=name, **kw)
File "/usr/local/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 278, in loadobj
return context.create()
File "/usr/local/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 715, in create
return self.object_type.invoke(self)
File "/usr/local/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 152, in invoke
return fix_call(context.object, context.global_conf, **context.local_conf)
File "/usr/local/lib/python2.7/site-packages/paste/deploy/util.py", line 55, in fix_call
val = callable(*args, **kw)
File "/app/syncserver/__init__.py", line 265, in main
config = get_configurator(global_config, **settings)
File "/app/syncserver/__init__.py", line 257, in get_configurator
config.include(includeme)
File "/usr/local/lib/python2.7/site-packages/pyramid/config/__init__.py", line 754, in include
c(configurator)
File "/app/syncserver/__init__.py", line 149, in includeme
config.include("syncstorage", route_prefix="/storage")
File "/usr/local/lib/python2.7/site-packages/pyramid/config/__init__.py", line 754, in include
c(configurator)
File "/usr/local/lib/python2.7/site-packages/syncstorage/__init__.py", line 18, in includeme
config.include("syncstorage.storage")
File "/usr/local/lib/python2.7/site-packages/pyramid/config/__init__.py", line 754, in include
c(configurator)
File "/usr/local/lib/python2.7/site-packages/syncstorage/storage/__init__.py", line 561, in includeme
storage = load_storage_from_settings("storage", settings)
File "/usr/local/lib/python2.7/site-packages/syncstorage/storage/__init__.py", line 585, in load_storage_from_settings
return klass(**section_settings)
File "/usr/local/lib/python2.7/site-packages/syncstorage/storage/sql/__init__.py", line 130, in __init__
self.dbconnector = DBConnector(sqluri, **dbkwds)
File "/usr/local/lib/python2.7/site-packages/syncstorage/storage/sql/dbconnect.py", line 357, in __init__
self.engine = create_engine(sqluri, **sqlkw)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/__init__.py", line 435, in create_engine
return strategy.create(*args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/strategies.py", line 87, in create
dbapi = dialect_cls.dbapi(**dbapi_args)
File "/usr/local/lib/python2.7/site-packages/sqlalchemy/dialects/postgresql/psycopg2.py", line 632, in dbapi
import psycopg2
ImportError: No module named psycopg2
[2022-03-21 16:21:41 +0000] [14] [INFO] Worker exiting (pid: 14)
[2022-03-21 16:21:41 +0000] [1] [INFO] Shutting down: Master
[2022-03-21 16:21:41 +0000] [1] [INFO] Reason: Worker failed to boot.
Mozilla also allows you to pull in syncserver via their docker image:
https://github.com/mozilla-services/syncserver
Is your better/easier/faster?
Also, I noticed support for MariaDB/MySQL is available. I thought MariaDB is much better for large databases versus SQLite. Is there a good argument to use it for Firefox Syncserver?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.