├── .gitignore
├── HACKING
├── conf.py
├── listings
├── gmaps_get_cid.sh
├── gmaps_getall.sh
├── gpx-update.py
├── pull-apk.sh
├── restore-apk-data.sh
├── sanitise-google-calendar.py
└── sanitise-google-contacts.py
└── pages
├── android-overview.rst
├── hw
└── nospy.rst
├── index.rst
├── misc
├── force-adb.rst
├── migrate.rst
└── screencast.rst
├── setup-enc-cm.rst
└── sw
├── firewall.rst
├── location.rst
└── owndata.rst
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[co]
2 | /cache
3 | /.doit.db
4 | /__pycache__
5 | /output
6 |
--------------------------------------------------------------------------------
/HACKING:
--------------------------------------------------------------------------------
1 | Install build-deps:
2 |
3 | $ apt-get install nikola ghp-import
4 |
5 | Build:
6 |
7 | $ nikola build
8 |
9 | Deploy to Github Pages:
10 |
11 | $ nikola github_deploy
12 |
13 | More info:
14 |
15 | https://getnikola.com/handbook.html
16 |
--------------------------------------------------------------------------------
/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import unicode_literals
4 | import time
5 |
6 | # !! This is the configuration of Nikola. !! #
7 | # !! You should edit it to your liking. !! #
8 |
9 |
10 | # ! Some settings can be different in different languages.
11 | # ! A comment stating (translatable) is used to denote those.
12 | # ! There are two ways to specify a translatable setting:
13 | # ! (a) BLOG_TITLE = "My Blog"
14 | # ! (b) BLOG_TITLE = {"en": "My Blog", "es": "Mi Blog"}
15 | # ! Option (a) is used when you don't want that setting translated.
16 | # ! Option (b) is used for settings that are different in different languages.
17 |
18 |
19 | # Data about this site
20 | BLOG_AUTHOR = "Ximin Luo" # (translatable)
21 | BLOG_TITLE = "Android FOSS+H hacks" # (translatable)
22 | # This is the main URL for your site. It will be used
23 | # in a prominent link. Don't forget the protocol (http/https)!
24 | SITE_URL = "https://infinity0.github.io/droid-hacks/"
25 | # This is the URL where Nikola's output will be deployed.
26 | # If not set, defaults to SITE_URL
27 | # BASE_URL = "https://example.com/"
28 | BLOG_EMAIL = "infinity0@pwned.gg"
29 | BLOG_DESCRIPTION = "A collection of hacks to free your Android device" # (translatable)
30 |
31 | # Nikola is multilingual!
32 | #
33 | # Currently supported languages are:
34 | #
35 | # en English
36 | # ar Arabic
37 | # az Azerbaijani
38 | # bg Bulgarian
39 | # ca Catalan
40 | # cs Czech [ALTERNATIVELY cz]
41 | # da Danish
42 | # de German
43 | # el Greek [NOT gr]
44 | # eo Esperanto
45 | # es Spanish
46 | # et Estonian
47 | # eu Basque
48 | # fa Persian
49 | # fi Finnish
50 | # fr French
51 | # hi Hindi
52 | # hr Croatian
53 | # id Indonesian
54 | # it Italian
55 | # ja Japanese [NOT jp]
56 | # ko Korean
57 | # nb Norwegian Bokmål
58 | # nl Dutch
59 | # pa Punjabi
60 | # pl Polish
61 | # pt_br Portuguese (Brasil)
62 | # ru Russian
63 | # sk Slovak
64 | # sl Slovene
65 | # sr Serbian (Cyrillic)
66 | # sv Swedish
67 | # tr Turkish [NOT tr_TR]
68 | # uk Ukrainian
69 | # ur Urdu
70 | # zh_cn Chinese (Simplified)
71 | #
72 | # If you want to use Nikola with a non-supported language you have to provide
73 | # a module containing the necessary translations
74 | # (cf. the modules at nikola/data/themes/base/messages/).
75 | # If a specific post is not translated to a language, then the version
76 | # in the default language will be shown instead.
77 |
78 | # What is the default language?
79 | DEFAULT_LANG = "en"
80 |
81 | # What other languages do you have?
82 | # The format is {"translationcode" : "path/to/translation" }
83 | # the path will be used as a prefix for the generated pages location
84 | TRANSLATIONS = {
85 | DEFAULT_LANG: "",
86 | # Example for another language:
87 | # "es": "./es",
88 | }
89 |
90 | # What will translated input files be named like?
91 |
92 | # If you have a page something.rst, then something.pl.rst will be considered
93 | # its Polish translation.
94 | # (in the above example: path == "something", ext == "rst", lang == "pl")
95 | # this pattern is also used for metadata:
96 | # something.meta -> something.pl.meta
97 |
98 | TRANSLATIONS_PATTERN = "{path}.{lang}.{ext}"
99 |
100 | # Links for the sidebar / navigation bar. (translatable)
101 | # This is a dict. The keys are languages, and values are tuples.
102 | #
103 | # For regular links:
104 | # ('https://getnikola.com/', 'Nikola Homepage')
105 | #
106 | # For submenus:
107 | # (
108 | # (
109 | # ('http://apple.com/', 'Apple'),
110 | # ('http://orange.com/', 'Orange'),
111 | # ),
112 | # 'Fruits'
113 | # )
114 | #
115 | # WARNING: Support for submenus is theme-dependent.
116 | # Only one level of submenus is supported.
117 | # WARNING: Some themes, including the default Bootstrap 3 theme,
118 | # may present issues if the menu is too large.
119 | # (in bootstrap3, the navbar can grow too large and cover contents.)
120 | # WARNING: If you link to directories, make sure to follow
121 | # ``STRIP_INDEXES``. If it’s set to ``True``, end your links
122 | # with a ``/``, otherwise end them with ``/index.html`` — or
123 | # else they won’t be highlighted when active.
124 |
125 | NAVIGATION_LINKS = {
126 | DEFAULT_LANG: (
127 | ("/archive.html", "Archive"),
128 | ("/listings/", "Code"),
129 | ("/categories/", "Tags"),
130 | ("/rss.xml", "RSS feed"),
131 | ),
132 | }
133 |
134 | # Name of the theme to use.
135 | THEME = "bootstrap3"
136 |
137 | # Below this point, everything is optional
138 |
139 | # Post's dates are considered in UTC by default, if you want to use
140 | # another time zone, please set TIMEZONE to match. Check the available
141 | # list from Wikipedia:
142 | # http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
143 | # (e.g. 'Europe/Zurich')
144 | # Also, if you want to use a different time zone in some of your posts,
145 | # you can use the ISO 8601/RFC 3339 format (ex. 2012-03-30T23:00:00+02:00)
146 | TIMEZONE = "UTC"
147 |
148 | # If you want to use ISO 8601 (also valid RFC 3339) throughout Nikola
149 | # (especially in new_post), set this to True.
150 | # Note that this does not affect DATE_FORMAT.
151 | # FORCE_ISO8601 = False
152 |
153 | # Date format used to display post dates.
154 | # (str used by datetime.datetime.strftime)
155 | # DATE_FORMAT = '%Y-%m-%d %H:%M'
156 |
157 | # Date format used to display post dates, if local dates are used.
158 | # (str used by moment.js)
159 | # JS_DATE_FORMAT = 'YYYY-MM-DD HH:mm'
160 |
161 | # Date fanciness.
162 | #
163 | # 0 = using DATE_FORMAT and TIMEZONE
164 | # 1 = using JS_DATE_FORMAT and local user time (via moment.js)
165 | # 2 = using a string like “2 days ago”
166 | #
167 | # Your theme must support it, bootstrap and bootstrap3 already do.
168 | # DATE_FANCINESS = 0
169 |
170 | # While Nikola can select a sensible locale for each language,
171 | # sometimes explicit control can come handy.
172 | # In this file we express locales in the string form that
173 | # python's locales will accept in your OS, by example
174 | # "en_US.utf8" in Unix-like OS, "English_United States" in Windows.
175 | # LOCALES = dict mapping language --> explicit locale for the languages
176 | # in TRANSLATIONS. You can omit one or more keys.
177 | # LOCALE_FALLBACK = locale to use when an explicit locale is unavailable
178 | # LOCALE_DEFAULT = locale to use for languages not mentioned in LOCALES; if
179 | # not set the default Nikola mapping is used.
180 |
181 | # POSTS and PAGES contains (wildcard, destination, template) tuples.
182 | #
183 | # The wildcard is used to generate a list of reSt source files
184 | # (whatever/thing.txt).
185 | #
186 | # That fragment could have an associated metadata file (whatever/thing.meta),
187 | # and optionally translated files (example for Spanish, with code "es"):
188 | # whatever/thing.es.txt and whatever/thing.es.meta
189 | #
190 | # This assumes you use the default TRANSLATIONS_PATTERN.
191 | #
192 | # From those files, a set of HTML fragment files will be generated:
193 | # cache/whatever/thing.html (and maybe cache/whatever/thing.html.es)
194 | #
195 | # These files are combined with the template to produce rendered
196 | # pages, which will be placed at
197 | # output / TRANSLATIONS[lang] / destination / pagename.html
198 | #
199 | # where "pagename" is the "slug" specified in the metadata file.
200 | #
201 | # The difference between POSTS and PAGES is that POSTS are added
202 | # to feeds and are considered part of a blog, while PAGES are
203 | # just independent HTML pages.
204 | #
205 |
206 | POSTS = [] # [("posts/*.txt", "blog", "post.tmpl", True)]
207 | PAGES = [("pages/%s*.rst" % subdir, "", "story.tmpl")
208 | for subdir in ("hw/", "sw/", "misc/", "")]
209 | # we need to do this otherwise we get urls like sw/sw/owndata
210 | # TODO: file upstream to nikola
211 |
212 | # One or more folders containing files to be copied as-is into the output.
213 | # The format is a dictionary of {source: relative destination}.
214 | # Default is:
215 | # FILES_FOLDERS = {'files': ''}
216 | # Which means copy 'files' into 'output'
217 |
218 | # One or more folders containing listings to be processed and stored into
219 | # the output. The format is a dictionary of {source: relative destination}.
220 | # Default is:
221 | # LISTINGS_FOLDERS = {'listings': 'listings'}
222 | # Which means process listings from 'listings' into 'output/listings'
223 |
224 | # A mapping of languages to file-extensions that represent that language.
225 | # Feel free to add or delete extensions to any list, but don't add any new
226 | # compilers unless you write the interface for it yourself.
227 | #
228 | # 'rest' is reStructuredText
229 | # 'markdown' is MarkDown
230 | # 'html' assumes the file is HTML and just copies it
231 | COMPILERS = {
232 | "rest": ('.rst', '.txt'),
233 | "markdown": ('.md', '.mdown', '.markdown'),
234 | "textile": ('.textile',),
235 | "txt2tags": ('.t2t',),
236 | "bbcode": ('.bb',),
237 | "wiki": ('.wiki',),
238 | "ipynb": ('.ipynb',),
239 | "html": ('.html', '.htm'),
240 | # PHP files are rendered the usual way (i.e. with the full templates).
241 | # The resulting files have .php extensions, making it possible to run
242 | # them without reconfiguring your server to recognize them.
243 | "php": ('.php',),
244 | # Pandoc detects the input from the source filename
245 | # but is disabled by default as it would conflict
246 | # with many of the others.
247 | # "pandoc": ('.rst', '.md', '.txt'),
248 | }
249 |
250 | # Create by default posts in one file format?
251 | # Set to False for two-file posts, with separate metadata.
252 | # ONE_FILE_POSTS = True
253 |
254 | # If this is set to True, the DEFAULT_LANG version will be displayed for
255 | # untranslated posts.
256 | # If this is set to False, then posts that are not translated to a language
257 | # LANG will not be visible at all in the pages in that language.
258 | # Formerly known as HIDE_UNTRANSLATED_POSTS (inverse)
259 | # SHOW_UNTRANSLATED_POSTS = True
260 |
261 | # Nikola supports logo display. If you have one, you can put the URL here.
262 | # Final output is .
263 | # The URL may be relative to the site root.
264 | # LOGO_URL = ''
265 |
266 | # If you want to hide the title of your website (for example, if your logo
267 | # already contains the text), set this to False.
268 | # SHOW_BLOG_TITLE = True
269 |
270 | # Writes tag cloud data in form of tag_cloud_data.json.
271 | # Warning: this option will change its default value to False in v8!
272 | WRITE_TAG_CLOUD = True
273 |
274 | # Paths for different autogenerated bits. These are combined with the
275 | # translation paths.
276 |
277 | # Final locations are:
278 | # output / TRANSLATION[lang] / TAG_PATH / index.html (list of tags)
279 | # output / TRANSLATION[lang] / TAG_PATH / tag.html (list of posts for a tag)
280 | # output / TRANSLATION[lang] / TAG_PATH / tag.xml (RSS feed for a tag)
281 | # TAG_PATH = "categories"
282 |
283 | # If TAG_PAGES_ARE_INDEXES is set to True, each tag's page will contain
284 | # the posts themselves. If set to False, it will be just a list of links.
285 | # TAG_PAGES_ARE_INDEXES = False
286 |
287 | # Set descriptions for tag pages to make them more interesting. The
288 | # default is no description. The value is used in the meta description
289 | # and displayed underneath the tag list or index page’s title.
290 | # TAG_PAGES_DESCRIPTIONS = {
291 | # DEFAULT_LANG: {
292 | # "blogging": "Meta-blog posts about blogging about blogging.",
293 | # "open source": "My contributions to my many, varied, ever-changing, and eternal libre software projects."
294 | # },
295 | # }
296 |
297 |
298 | # If you do not want to display a tag publicly, you can mark it as hidden.
299 | # The tag will not be displayed on the tag list page, the tag cloud and posts.
300 | # Tag pages will still be generated.
301 | HIDDEN_TAGS = ['mathjax']
302 |
303 | # Only include tags on the tag list/overview page if there are at least
304 | # TAGLIST_MINIMUM_POSTS number of posts or more with every tag. Every tag
305 | # page is still generated, linked from posts, and included in the sitemap.
306 | # However, more obscure tags can be hidden from the tag index page.
307 | # TAGLIST_MINIMUM_POSTS = 1
308 |
309 | # Final locations are:
310 | # output / TRANSLATION[lang] / CATEGORY_PATH / index.html (list of categories)
311 | # output / TRANSLATION[lang] / CATEGORY_PATH / CATEGORY_PREFIX category.html (list of posts for a category)
312 | # output / TRANSLATION[lang] / CATEGORY_PATH / CATEGORY_PREFIX category.xml (RSS feed for a category)
313 | # CATEGORY_PATH = "categories"
314 | # CATEGORY_PREFIX = "cat_"
315 |
316 | # If CATEGORY_ALLOW_HIERARCHIES is set to True, categories can be organized in
317 | # hierarchies. For a post, the whole path in the hierarchy must be specified,
318 | # using a forward slash ('/') to separate paths. Use a backslash ('\') to escape
319 | # a forward slash or a backslash (i.e. '\//\\' is a path specifying the
320 | # subcategory called '\' of the top-level category called '/').
321 | CATEGORY_ALLOW_HIERARCHIES = False
322 | # If CATEGORY_OUTPUT_FLAT_HIERARCHY is set to True, the output written to output
323 | # contains only the name of the leaf category and not the whole path.
324 | CATEGORY_OUTPUT_FLAT_HIERARCHY = False
325 |
326 | # If CATEGORY_PAGES_ARE_INDEXES is set to True, each category's page will contain
327 | # the posts themselves. If set to False, it will be just a list of links.
328 | # CATEGORY_PAGES_ARE_INDEXES = False
329 |
330 | # Set descriptions for category pages to make them more interesting. The
331 | # default is no description. The value is used in the meta description
332 | # and displayed underneath the category list or index page’s title.
333 | # CATEGORY_PAGES_DESCRIPTIONS = {
334 | # DEFAULT_LANG: {
335 | # "blogging": "Meta-blog posts about blogging about blogging.",
336 | # "open source": "My contributions to my many, varied, ever-changing, and eternal libre software projects."
337 | # },
338 | # }
339 |
340 | # If you do not want to display a category publicly, you can mark it as hidden.
341 | # The category will not be displayed on the category list page.
342 | # Category pages will still be generated.
343 | HIDDEN_CATEGORIES = []
344 |
345 | # Final location for the main blog page and sibling paginated pages is
346 | # output / TRANSLATION[lang] / INDEX_PATH / index-*.html
347 | INDEX_PATH = "blog"
348 |
349 | # Create per-month archives instead of per-year
350 | # CREATE_MONTHLY_ARCHIVE = False
351 | # Create one large archive instead of per-year
352 | # CREATE_SINGLE_ARCHIVE = False
353 | # Create year, month, and day archives each with a (long) list of posts
354 | # (overrides both CREATE_MONTHLY_ARCHIVE and CREATE_SINGLE_ARCHIVE)
355 | # CREATE_FULL_ARCHIVES = False
356 | # If monthly archives or full archives are created, adds also one archive per day
357 | # CREATE_DAILY_ARCHIVE = False
358 | # Final locations for the archives are:
359 | # output / TRANSLATION[lang] / ARCHIVE_PATH / ARCHIVE_FILENAME
360 | # output / TRANSLATION[lang] / ARCHIVE_PATH / YEAR / index.html
361 | # output / TRANSLATION[lang] / ARCHIVE_PATH / YEAR / MONTH / index.html
362 | # output / TRANSLATION[lang] / ARCHIVE_PATH / YEAR / MONTH / DAY / index.html
363 | # ARCHIVE_PATH = ""
364 | # ARCHIVE_FILENAME = "archive.html"
365 |
366 | # If ARCHIVES_ARE_INDEXES is set to True, each archive page which contains a list
367 | # of posts will contain the posts themselves. If set to False, it will be just a
368 | # list of links.
369 | # ARCHIVES_ARE_INDEXES = False
370 |
371 | # URLs to other posts/pages can take 3 forms:
372 | # rel_path: a relative URL to the current page/post (default)
373 | # full_path: a URL with the full path from the root
374 | # absolute: a complete URL (that includes the SITE_URL)
375 | # URL_TYPE = 'rel_path'
376 |
377 | # Final location for the blog main RSS feed is:
378 | # output / TRANSLATION[lang] / RSS_PATH / rss.xml
379 | # RSS_PATH = ""
380 |
381 | # Number of posts in RSS feeds
382 | # FEED_LENGTH = 10
383 |
384 | # Slug the Tag URL easier for users to type, special characters are
385 | # often removed or replaced as well.
386 | # SLUG_TAG_PATH = True
387 |
388 | # A list of redirection tuples, [("foo/from.html", "/bar/to.html")].
389 | #
390 | # A HTML file will be created in output/foo/from.html that redirects
391 | # to the "/bar/to.html" URL. notice that the "from" side MUST be a
392 | # relative URL.
393 | #
394 | # If you don't need any of these, just set to []
395 | REDIRECTIONS = []
396 |
397 | # Presets of commands to execute to deploy. Can be anything, for
398 | # example, you may use rsync:
399 | # "rsync -rav --delete output/ joe@my.site:/srv/www/site"
400 | # And then do a backup, or run `nikola ping` from the `ping`
401 | # plugin (`nikola plugin -i ping`). Or run `nikola check -l`.
402 | # You may also want to use github_deploy (see below).
403 | # You can define multiple presets and specify them as arguments
404 | # to `nikola deploy`. If no arguments are specified, a preset
405 | # named `default` will be executed. You can use as many presets
406 | # in a `nikola deploy` command as you like.
407 | # DEPLOY_COMMANDS = {
408 | # 'default': [
409 | # "rsync -rav --delete output/ joe@my.site:/srv/www/site",
410 | # ]
411 | # }
412 |
413 | # For user.github.io OR organization.github.io pages, the DEPLOY branch
414 | # MUST be 'master', and 'gh-pages' for other repositories.
415 | # GITHUB_SOURCE_BRANCH = 'master'
416 | # GITHUB_DEPLOY_BRANCH = 'gh-pages'
417 |
418 | # The name of the remote where you wish to push to, using github_deploy.
419 | # GITHUB_REMOTE_NAME = 'origin'
420 |
421 | # Where the output site should be located
422 | # If you don't use an absolute path, it will be considered as relative
423 | # to the location of conf.py
424 | # OUTPUT_FOLDER = 'output'
425 |
426 | # where the "cache" of partial generated content should be located
427 | # default: 'cache'
428 | # CACHE_FOLDER = 'cache'
429 |
430 | # Filters to apply to the output.
431 | # A directory where the keys are either: a file extensions, or
432 | # a tuple of file extensions.
433 | #
434 | # And the value is a list of commands to be applied in order.
435 | #
436 | # Each command must be either:
437 | #
438 | # A string containing a '%s' which will
439 | # be replaced with a filename. The command *must* produce output
440 | # in place.
441 | #
442 | # Or:
443 | #
444 | # A python callable, which will be called with the filename as
445 | # argument.
446 | #
447 | # By default, only .php files uses filters to inject PHP into
448 | # Nikola’s templates. All other filters must be enabled through FILTERS.
449 | #
450 | # Many filters are shipped with Nikola. A list is available in the manual:
451 | #
452 | #
453 | # from nikola import filters
454 | # FILTERS = {
455 | # ".html": [filters.typogrify],
456 | # ".js": [filters.closure_compiler],
457 | # ".jpg": ["jpegoptim --strip-all -m75 -v %s"],
458 | # }
459 |
460 | # Expert setting! Create a gzipped copy of each generated file. Cheap server-
461 | # side optimization for very high traffic sites or low memory servers.
462 | # GZIP_FILES = False
463 | # File extensions that will be compressed
464 | # GZIP_EXTENSIONS = ('.txt', '.htm', '.html', '.css', '.js', '.json', '.atom', '.xml')
465 | # Use an external gzip command? None means no.
466 | # Example: GZIP_COMMAND = "pigz -k {filename}"
467 | # GZIP_COMMAND = None
468 | # Make sure the server does not return a "Accept-Ranges: bytes" header for
469 | # files compressed by this option! OR make sure that a ranged request does not
470 | # return partial content of another representation for these resources. Do not
471 | # use this feature if you do not understand what this means.
472 |
473 | # Compiler to process LESS files.
474 | # LESS_COMPILER = 'lessc'
475 |
476 | # A list of options to pass to the LESS compiler.
477 | # Final command is: LESS_COMPILER LESS_OPTIONS file.less
478 | # LESS_OPTIONS = []
479 |
480 | # Compiler to process Sass files.
481 | # SASS_COMPILER = 'sass'
482 |
483 | # A list of options to pass to the Sass compiler.
484 | # Final command is: SASS_COMPILER SASS_OPTIONS file.s(a|c)ss
485 | # SASS_OPTIONS = []
486 |
487 | # #############################################################################
488 | # Image Gallery Options
489 | # #############################################################################
490 |
491 | # One or more folders containing galleries. The format is a dictionary of
492 | # {"source": "relative_destination"}, where galleries are looked for in
493 | # "source/" and the results will be located in
494 | # "OUTPUT_PATH/relative_destination/gallery_name"
495 | # Default is:
496 | # GALLERY_FOLDERS = {"galleries": "galleries"}
497 | # More gallery options:
498 | # THUMBNAIL_SIZE = 180
499 | # MAX_IMAGE_SIZE = 1280
500 | # USE_FILENAME_AS_TITLE = True
501 | # EXTRA_IMAGE_EXTENSIONS = []
502 | #
503 | # If set to False, it will sort by filename instead. Defaults to True
504 | # GALLERY_SORT_BY_DATE = True
505 | #
506 | # Folders containing images to be used in normal posts or pages. Images will be
507 | # scaled down according to IMAGE_THUMBNAIL_SIZE and MAX_IMAGE_SIZE options, but
508 | # will have to be referenced manually to be visible on the site
509 | # (the thumbnail has ``.thumbnail`` added before the file extension).
510 | # The format is a dictionary of {source: relative destination}.
511 |
512 | IMAGE_FOLDERS = {'images': 'images'}
513 | # IMAGE_THUMBNAIL_SIZE = 400
514 |
515 | # #############################################################################
516 | # HTML fragments and diverse things that are used by the templates
517 | # #############################################################################
518 |
519 | # Data about post-per-page indexes.
520 | # INDEXES_PAGES defaults to ' old posts, page %d' or ' page %d' (translated),
521 | # depending on the value of INDEXES_PAGES_MAIN.
522 | #
523 | # (translatable) If the following is empty, defaults to BLOG_TITLE:
524 | # INDEXES_TITLE = ""
525 | #
526 | # (translatable) If the following is empty, defaults to ' [old posts,] page %d' (see above):
527 | # INDEXES_PAGES = ""
528 | #
529 | # If the following is True, INDEXES_PAGES is also displayed on the main (the
530 | # newest) index page (index.html):
531 | # INDEXES_PAGES_MAIN = False
532 | #
533 | # If the following is True, index-1.html has the oldest posts, index-2.html the
534 | # second-oldest posts, etc., and index.html has the newest posts. This ensures
535 | # that all posts on index-x.html will forever stay on that page, now matter how
536 | # many new posts are added.
537 | # If False, index-1.html has the second-newest posts, index-2.html the third-newest,
538 | # and index-n.html the oldest posts. When this is active, old posts can be moved
539 | # to other index pages when new posts are added.
540 | # INDEXES_STATIC = True
541 | #
542 | # (translatable) If PRETTY_URLS is set to True, this setting will be used to create
543 | # prettier URLs for index pages, such as page/2/index.html instead of index-2.html.
544 | # Valid values for this settings are:
545 | # * False,
546 | # * a list or tuple, specifying the path to be generated,
547 | # * a dictionary mapping languages to lists or tuples.
548 | # Every list or tuple must consist of strings which are used to combine the path;
549 | # for example:
550 | # ['page', '{number}', '{index_file}']
551 | # The replacements
552 | # {number} --> (logical) page number;
553 | # {old_number} --> the page number inserted into index-n.html before (zero for
554 | # the main page);
555 | # {index_file} --> value of option INDEX_FILE
556 | # are made.
557 | # Note that in case INDEXES_PAGES_MAIN is set to True, a redirection will be created
558 | # for the full URL with the page number of the main page to the normal (shorter) main
559 | # page URL.
560 | # INDEXES_PRETTY_PAGE_URL = False
561 |
562 | # Color scheme to be used for code blocks. If your theme provides
563 | # "assets/css/code.css" this is ignored.
564 | # Can be any of:
565 | # algol
566 | # algol_nu
567 | # arduino
568 | # autumn
569 | # borland
570 | # bw
571 | # colorful
572 | # default
573 | # emacs
574 | # friendly
575 | # fruity
576 | # igor
577 | # lovelace
578 | # manni
579 | # monokai
580 | # murphy
581 | # native
582 | # paraiso_dark
583 | # paraiso_light
584 | # pastie
585 | # perldoc
586 | # rrt
587 | # tango
588 | # trac
589 | # vim
590 | # vs
591 | # xcode
592 | # This list MAY be incomplete since pygments adds styles every now and then.
593 | # CODE_COLOR_SCHEME = 'default'
594 |
595 | # If you use 'site-reveal' theme you can select several subthemes
596 | # THEME_REVEAL_CONFIG_SUBTHEME = 'sky'
597 | # You can also use: beige/serif/simple/night/default
598 |
599 | # Again, if you use 'site-reveal' theme you can select several transitions
600 | # between the slides
601 | # THEME_REVEAL_CONFIG_TRANSITION = 'cube'
602 | # You can also use: page/concave/linear/none/default
603 |
604 | # FAVICONS contains (name, file, size) tuples.
605 | # Used to create favicon link like this:
606 | #
607 | # FAVICONS = (
608 | # ("icon", "/favicon.ico", "16x16"),
609 | # ("icon", "/icon_128x128.png", "128x128"),
610 | # )
611 |
612 | # Show teasers (instead of full posts) in indexes? Defaults to False.
613 | # INDEX_TEASERS = False
614 |
615 | # HTML fragments with the Read more... links.
616 | # The following tags exist and are replaced for you:
617 | # {link} A link to the full post page.
618 | # {read_more} The string “Read more” in the current language.
619 | # {reading_time} An estimate of how long it will take to read the post.
620 | # {remaining_reading_time} An estimate of how long it will take to read the post, sans the teaser.
621 | # {min_remaining_read} The string “{remaining_reading_time} min remaining to read” in the current language.
622 | # {paragraph_count} The amount of paragraphs in the post.
623 | # {remaining_paragraph_count} The amount of paragraphs in the post, sans the teaser.
624 | # {{ A literal { (U+007B LEFT CURLY BRACKET)
625 | # }} A literal } (U+007D RIGHT CURLY BRACKET)
626 |
627 | # 'Read more...' for the index page, if INDEX_TEASERS is True (translatable)
628 | INDEX_READ_MORE_LINK = '
811 | #
812 | #
813 | # """
814 |
815 | # Show link to source for the posts?
816 | # Formerly known as HIDE_SOURCELINK (inverse)
817 | # SHOW_SOURCELINK = True
818 | # Copy the source files for your pages?
819 | # Setting it to False implies SHOW_SOURCELINK = False
820 | # COPY_SOURCES = True
821 |
822 | # Modify the number of Post per Index Page
823 | # Defaults to 10
824 | # INDEX_DISPLAY_POST_COUNT = 10
825 |
826 | # By default, Nikola generates RSS files for the website and for tags, and
827 | # links to it. Set this to False to disable everything RSS-related.
828 | # GENERATE_RSS = True
829 |
830 | # By default, Nikola does not generates Atom files for indexes and links to
831 | # them. Generate Atom for tags by setting TAG_PAGES_ARE_INDEXES to True.
832 | # Atom feeds are built based on INDEX_DISPLAY_POST_COUNT and not FEED_LENGTH
833 | # Switch between plain-text summaries and full HTML content using the
834 | # RSS_TEASER option. RSS_LINKS_APPEND_QUERY is also respected. Atom feeds
835 | # are generated even for old indexes and have pagination link relations
836 | # between each other. Old Atom feeds with no changes are marked as archived.
837 | # GENERATE_ATOM = False
838 |
839 | # RSS_LINK is a HTML fragment to link the RSS or Atom feeds. If set to None,
840 | # the base.tmpl will use the feed Nikola generates. However, you may want to
841 | # change it for a FeedBurner feed or something else.
842 | # RSS_LINK = None
843 |
844 | # Show teasers (instead of full posts) in feeds? Defaults to True.
845 | # RSS_TEASERS = True
846 |
847 | # Strip HTML in the RSS feed? Default to False
848 | # RSS_PLAIN = False
849 |
850 | # A search form to search this site, for the sidebar. You can use a Google
851 | # custom search (http://www.google.com/cse/)
852 | # Or a DuckDuckGo search: https://duckduckgo.com/search_box.html
853 | # Default is no search form.
854 | # (translatable)
855 | # SEARCH_FORM = ""
856 | #
857 | # This search form works for any site and looks good in the "site" theme where
858 | # it appears on the navigation bar:
859 | #
860 | # SEARCH_FORM = """
861 | #
862 | #
872 | #
873 | # """ % SITE_URL
874 | #
875 | # If you prefer a Google search form, here's an example that should just work:
876 | # SEARCH_FORM = """
877 | #
878 | #
887 | #
888 | # """ % SITE_URL
889 |
890 | # Use content distribution networks for jQuery, twitter-bootstrap css and js,
891 | # and html5shiv (for older versions of Internet Explorer)
892 | # If this is True, jQuery and html5shiv are served from the Google CDN and
893 | # Bootstrap is served from BootstrapCDN (provided by MaxCDN)
894 | # Set this to False if you want to host your site without requiring access to
895 | # external resources.
896 | # USE_CDN = False
897 |
898 | # Check for USE_CDN compatibility.
899 | # If you are using custom themes, have configured the CSS properly and are
900 | # receiving warnings about incompatibility but believe they are incorrect, you
901 | # can set this to False.
902 | # USE_CDN_WARNING = True
903 |
904 | # Extra things you want in the pages HEAD tag. This will be added right
905 | # before
906 | # (translatable)
907 | # EXTRA_HEAD_DATA = ""
908 | # Google Analytics or whatever else you use. Added to the bottom of
909 | # in the default template (base.tmpl).
910 | # (translatable)
911 | # BODY_END = ""
912 |
913 | # The possibility to extract metadata from the filename by using a
914 | # regular expression.
915 | # To make it work you need to name parts of your regular expression.
916 | # The following names will be used to extract metadata:
917 | # - title
918 | # - slug
919 | # - date
920 | # - tags
921 | # - link
922 | # - description
923 | #
924 | # An example re is the following:
925 | # '(?P\d{4}-\d{2}-\d{2})-(?P.*)-(?P.*)\.md'
926 | # FILE_METADATA_REGEXP = None
927 |
928 | # If you hate "Filenames with Capital Letters and Spaces.md", you should
929 | # set this to true.
930 | UNSLUGIFY_TITLES = True
931 |
932 | # Additional metadata that is added to a post when creating a new_post
933 | # ADDITIONAL_METADATA = {}
934 |
935 | # Nikola supports Open Graph Protocol data for enhancing link sharing and
936 | # discoverability of your site on Facebook, Google+, and other services.
937 | # Open Graph is enabled by default.
938 | # USE_OPEN_GRAPH = True
939 |
940 | # Nikola supports Twitter Card summaries, but they are disabled by default.
941 | # They make it possible for you to attach media to Tweets that link
942 | # to your content.
943 | #
944 | # IMPORTANT:
945 | # Please note, that you need to opt-in for using Twitter Cards!
946 | # To do this please visit https://cards-dev.twitter.com/validator
947 | #
948 | # Uncomment and modify to following lines to match your accounts.
949 | # Images displayed come from the `previewimage` meta tag.
950 | # You can specify the card type by using the `card` parameter in TWITTER_CARD.
951 | # TWITTER_CARD = {
952 | # # 'use_twitter_cards': True, # enable Twitter Cards
953 | # # 'card': 'summary', # Card type, you can also use 'summary_large_image',
954 | # # see https://dev.twitter.com/cards/types
955 | # # 'site': '@website', # twitter nick for the website
956 | # # 'creator': '@username', # Username for the content creator / author.
957 | # }
958 |
959 | # If webassets is installed, bundle JS and CSS into single files to make
960 | # site loading faster in a HTTP/1.1 environment but is not recommended for
961 | # HTTP/2.0 when caching is used. Defaults to True.
962 | # USE_BUNDLES = True
963 |
964 | # Plugins you don't want to use. Be careful :-)
965 | # DISABLED_PLUGINS = ["render_galleries"]
966 |
967 | # Add the absolute paths to directories containing plugins to use them.
968 | # For example, the `plugins` directory of your clone of the Nikola plugins
969 | # repository.
970 | # EXTRA_PLUGINS_DIRS = []
971 |
972 | # List of regular expressions, links matching them will always be considered
973 | # valid by "nikola check -l"
974 | # LINK_CHECK_WHITELIST = []
975 |
976 | # If set to True, enable optional hyphenation in your posts (requires pyphen)
977 | # HYPHENATE = False
978 |
979 | # The tags in HTML generated by certain compilers (reST/Markdown)
980 | # will be demoted by that much (1 → h1 will become h2 and so on)
981 | # This was a hidden feature of the Markdown and reST compilers in the
982 | # past. Useful especially if your post titles are in
tags too, for
983 | # example.
984 | # (defaults to 1.)
985 | # DEMOTE_HEADERS = 1
986 |
987 | # If you don’t like slugified file names ([a-z0-9] and a literal dash),
988 | # and would prefer to use all the characters your file system allows.
989 | # USE WITH CARE! This is also not guaranteed to be perfect, and may
990 | # sometimes crash Nikola, your web server, or eat your cat.
991 | # USE_SLUGIFY = True
992 |
993 | # Templates will use those filters, along with the defaults.
994 | # Consult your engine's documentation on filters if you need help defining
995 | # those.
996 | # TEMPLATE_FILTERS = {}
997 |
998 | # Put in global_context things you want available on all your templates.
999 | # It can be anything, data, functions, modules, etc.
1000 | GLOBAL_CONTEXT = {}
1001 |
1002 | # Add functions here and they will be called with template
1003 | # GLOBAL_CONTEXT as parameter when the template is about to be
1004 | # rendered
1005 | GLOBAL_CONTEXT_FILLER = []
1006 |
--------------------------------------------------------------------------------
/listings/gmaps_get_cid.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Get raw JSON data for a given client Id in Google Places.
3 | # Very hacky, last tested 2019-10-22
4 |
5 | set -e
6 | arg="$1"
7 |
8 | get_gmaps_cid() {
9 | local cid="$1"
10 | local out="${cid}.json"
11 | if [ -s "$out" -a "$OVERWRITE_GMAPS_JSON" != "1" ]; then return; fi
12 | curl -s "https://www.google.com/maps?cid=$((16#$cid))" | \
13 | sed -ne '/^;window.APP_INITIALIZATION_STATE=/,/^;/p' | \
14 | head -n-1 | tail -c+34 | jq > "$out"
15 | echo "$out"
16 | }
17 |
18 | if [ "${#arg}" = 16 ]; then
19 | get_gmaps_cid "$arg"
20 | elif [ "${arg#https://}" != "${arg}" ]; then
21 | get_gmaps_cid "$(echo "$arg" | sed -Ee 's/.*0x\w+:0x(\w+).*/\1/g')"
22 | else
23 | echo >&2 "not recognised: $arg"
24 | exit 1
25 | fi
26 |
--------------------------------------------------------------------------------
/listings/gmaps_getall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Go through a Google Takeout "Saved" data dump and retrieve JSON data for all
3 | # Places URLs listed in all of the CSV files.
4 |
5 | set -e
6 | sdir="$(dirname "$(readlink -f "$0")")"
7 |
8 | mkdir -p json
9 | for i in *.csv; do
10 | mkdir -p "${i%.csv}"
11 | sed -nEe 's,.*(https://.*),\1,gp' "$i" | while read url; do
12 | out="$( cd json && "$sdir/gmaps_get_cid.sh" "$url" )"
13 | if [ -n "$out" ]; then ln -sf "../json/$out" "${i%.csv}/$out"; fi
14 | done
15 | done
16 |
--------------------------------------------------------------------------------
/listings/gpx-update.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | """
3 | Export KDE Marble KML bookmarks to OsmAnd GPX favourites.
4 | Requires: python-lxml, gpsbabel
5 |
6 | Usage: $0 ... < bookmarks.kml > favourites.gpx
7 | $0 -i favourites.gpx
8 |
9 | EXPORT_FOLDER are the bookmark folders (in Marble) to export. The argument can
10 | also be in the following extended syntax:
11 |
12 | EXPORT_FOLDER ::= (folder | gpx_file) "|" colour
13 |
14 | gpx_file is the path to an actual file to splice into the favourites as-is.
15 | colour is what colour to display those places in, in OsmAnd.
16 |
17 | Use the -i option to install the output file into Android.
18 |
19 | TODO: write this up in the main droid-hacks doc. Roughly:
20 | - add this script to a crontab
21 | - expose $(dirname favourites.gpx) via a stealth hidden service
22 | - periodically download it to your phone
23 | - su -c 'ln -sf ../Downloads/favourites.gpx /data/media/0/osmand/'
24 |
25 | """
26 |
27 | from __future__ import print_function
28 |
29 | from lxml import etree
30 |
31 | import copy
32 | import os.path
33 | import subprocess
34 | import sys
35 |
36 | path = os.path.expanduser
37 |
38 | NS = {
39 | "k":"http://www.opengis.net/kml/2.2",
40 | "g":"http://www.topografix.com/GPX/1/0",
41 | }
42 |
43 | def export(*args):
44 | kml_in = etree.parse(sys.stdin)
45 | fav = None
46 | fav_time = None
47 | fav_bounds = None
48 |
49 | for export in args:
50 | parts = export.split("|")
51 | if len(parts) == 2:
52 | folder, colour = parts
53 | elif len(parts) == 1:
54 | folder, colour = parts[0], None
55 | else:
56 | raise ValueError(export)
57 |
58 | if folder.endswith(".gpx"):
59 | fp = open(path(folder))
60 | folder = os.path.basename(folder)
61 | else:
62 | p = subprocess.Popen(["/usr/bin/gpsbabel", "-i", "kml", "-o", "gpx", "-f", "-", "-F", "-"],
63 | stdin=subprocess.PIPE,
64 | stdout=subprocess.PIPE)
65 |
66 | # filter only this folder
67 | r = copy.deepcopy(kml_in)
68 | for e in r.findall("//k:Document/k:Folder", namespaces=NS):
69 | if e.getchildren()[0].text != folder:
70 | r.getroot().getchildren()[0].remove(e)
71 | r.write(p.stdin, encoding="utf-8", xml_declaration=True)
72 | p.stdin.close()
73 | fp = p.stdout
74 |
75 | # write the folder name into the GPX output of gpsbabel, otherwise it's lost
76 | g = etree.parse(fp)
77 | for e in g.findall("//g:wpt", namespaces=NS):
78 | t = e.makeelement('type')
79 | t.tail = e.text # i'm OCD, maintain indentation
80 | t.text = folder
81 | e.insert(0, t)
82 | i2, i1 = e.getchildren()[-2:]
83 | if colour:
84 | x = e.makeelement("extensions")
85 | c = e.makeelement("color")
86 | c.text = colour
87 | x.append(c)
88 | e.append(x)
89 | x.tail = i1.tail # i'm OCD, maintain indentation
90 | i1.tail = i2.tail
91 |
92 | # merge the output into "fav"
93 | if fav is None:
94 | fav = g
95 | fav_time = fav.findall("//g:time", namespaces=NS)[0]
96 | fav_bounds = fav.findall("//g:bounds", namespaces=NS)[0]
97 | else:
98 | g_time = g.findall("//g:time", namespaces=NS)
99 | if g_time:
100 | g_time = g_time[0]
101 | if g_time.text > fav_time.text:
102 | fav_time.text = g_time.text
103 |
104 | g_bounds = g.findall("//g:bounds", namespaces=NS)
105 | if g_bounds:
106 | g_bounds = g_bounds[0]
107 | if float(g_bounds.get("minlon")) < float(fav_bounds.get("minlon")):
108 | fav_bounds.set("minlon", g_bounds.get("minlon"))
109 | if float(g_bounds.get("minlat")) < float(fav_bounds.get("minlat")):
110 | fav_bounds.set("minlat", g_bounds.get("minlat"))
111 | if float(g_bounds.get("maxlon")) > float(fav_bounds.get("maxlon")):
112 | fav_bounds.set("maxlon", g_bounds.get("maxlon"))
113 | if float(g_bounds.get("maxlat")) > float(fav_bounds.get("maxlat")):
114 | fav_bounds.set("maxlat", g_bounds.get("maxlat"))
115 |
116 | for e in g.findall("//g:wpt", namespaces=NS):
117 | fav.getroot().getchildren()[-1].tail = g.getroot().text # i'm OCD, maintain indentation
118 | fav.getroot().append(e)
119 |
120 | fav.write(sys.stdout, encoding="utf-8", xml_declaration=True)
121 | return 0
122 |
123 | def install(f):
124 | subprocess.Popen(["sh"], stdin=subprocess.PIPE).communicate(input="""
125 | pkg=net.osmand.plus
126 | adb shell am force-stop $pkg
127 | adb shell su -c "find /data/data/$pkg/ /sdcard/Android/data/$pkg/ -name 'favourites_*.gpx' -delete"
128 | adb push "%s" /sdcard/Android/data/$pkg/files/favourites.gpx
129 | """ % f)
130 | return 0
131 |
132 | if __name__ == "__main__":
133 | if len(sys.argv) > 1 and sys.argv[1] == "-i":
134 | sys.exit(install(sys.argv[2]))
135 | else:
136 | sys.exit(export(*sys.argv[1:]))
137 |
--------------------------------------------------------------------------------
/listings/pull-apk.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Pull APKs of installed applications from an Android device.
3 | #
4 | # Reads package names on STDIN, then pulls these using adb(1).
5 | # You can see the package name of an app by going to Settings > Apps
6 | #
7 | # $ ./pull-apk.sh <&2 "no version found for: $pkg"
24 | continue
25 | fi
26 | echo "-- $pkg $ver"
27 | i=0
28 | adb shell -n pm path --user "$ANDROID_USER" "$pkg" | sed -nre 's/^package://p' | while read path; do
29 | adb pull "$path" "${pkg}_${ver}-${i}.apk"
30 | echo "${pkg}_${ver}-${i}.apk"
31 | i=$((i + 1))
32 | done
33 | done
34 |
--------------------------------------------------------------------------------
/listings/restore-apk-data.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Run this in /data/data/restore/ after you extract the backup.tar, and after
3 | # opening up the app for the first time (so expected files are created).
4 | set -e
5 | package="$1"
6 |
7 | cd "$(dirname "$(readlink -f "$0")")"
8 |
9 | chmod 771 "$package"
10 | chown -hR "$(stat -c %u:%g ../$package )" "$package"
11 | chcon -hR "$(ls -Zd ../$package | cut '-d ' -f1)" "$package"
12 | if [ -h ../$package/lib ]; then
13 | ln -snf "$(readlink ../$package/lib )" "$package"/lib
14 | chown -h "$(stat -c %u:%g ../$package/lib )" "$package"/lib
15 | chcon -h "$(ls -Zd ../$package/lib | cut '-d ' -f1)" "$package"/lib
16 | fi
17 | chmod 751 "$package"
18 | mv "../$package" "../_old_$package"
19 | mv "$package" ..
20 |
--------------------------------------------------------------------------------
/listings/sanitise-google-calendar.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | """Sanitise Google Calendar exported data.
3 |
4 | Last updated 2014-11; Google may have changed their export format since then.
5 | Furthermore, this script is incomplete and only handles some parts of the
6 | export format. You should manually review the output and decide if it needs
7 | further sanitising. If so, please also send in a pull request.
8 |
9 | Usage: $0
10 | """
11 |
12 | import os
13 | import sys
14 |
15 | from lxml import etree
16 |
17 | dom = etree.XML(open(sys.argv[1]).read())
18 | namespaces = dom.nsmap
19 | namespaces["atom"] = namespaces[None]
20 | entries = dom.findall(".//atom:entry", namespaces)
21 |
22 | cat_by_uid = {}
23 | cat_by_pub = {}
24 |
25 | for entry in entries:
26 | uid = os.path.split(entry.find(".//atom:id", namespaces).text)[1]
27 | published = entry.find(".//atom:published", namespaces).text.strip().replace(":", "").replace("-", "").replace(".000", "")
28 | # Custom event categories written by Mozilla Lightning end up like this in Google Calendar.
29 | # Instead, let's put them in the more standard CATEGORIES field.
30 | category = entry.find(".//gd:extendedProperty[@name='X-MOZ-CATEGORIES']", namespaces)
31 | if category is not None:
32 | cat_by_uid[uid] = category.get("value")
33 | cat_by_pub[published] = category.get("value")
34 |
35 | in_event = False
36 | cur_event_lines = []
37 | cur_uid = None
38 | cur_created = None
39 |
40 | with open(sys.argv[2]) as fp, open(sys.argv[3], "w") as wfp:
41 | for line in fp.readlines():
42 | if line.rstrip("\r\n") == "BEGIN:VEVENT":
43 | in_event = True
44 | cur_event_lines.append(line)
45 | elif line.rstrip("\r\n") == "END:VEVENT":
46 | cur_event_lines.append(line)
47 | wfp.write("".join(cur_event_lines))
48 | in_event = False
49 | cur_event_lines = []
50 | cur_uid = None
51 | cur_created = None
52 | elif not in_event:
53 | wfp.write(line)
54 | else:
55 | if ":" in line:
56 | head, tail = line.split(":", 1)
57 | if head == "UID":
58 | tail = tail.replace("@google.com", "")
59 | cur_uid = tail.strip("\r\n")
60 | #line = "%s:%s" % (head, tail)
61 | elif head == "CREATED":
62 | cur_created = tail.strip("\r\n")
63 | elif head == "CATEGORIES":
64 | if cur_uid in cat_by_uid:
65 | category = cat_by_uid[cur_uid]
66 | elif cur_created in cat_by_pub:
67 | category = cat_by_pub[cur_created]
68 | else:
69 | raise ValueError("not found: %s created %s" % (cur_uid, cur_created))
70 | line = "%s:%s\r\n" % (head, category)
71 | cur_event_lines.append(line)
72 |
73 | #import code; code.interact(local=locals())
74 |
--------------------------------------------------------------------------------
/listings/sanitise-google-contacts.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | """Sanitise Google Contacts exported data.
3 |
4 | Last updated 2014-11; Google may have changed their export format since then.
5 | Furthermore, this script is incomplete and only handles some parts of the
6 | export format. You should manually review the output and decide if it needs
7 | further sanitising. If so, please also send in a pull request.
8 |
9 | Usage: $0
10 | """
11 |
12 | import csv
13 | import logging
14 | import sys
15 |
16 | logging.getLogger().setLevel(logging.INFO)
17 |
18 | keys = []
19 |
20 | def process_entry(k, v, types):
21 | if k.endswith(" - Type"):
22 | head, tail = k[:-7].rsplit(" ", 1)
23 | types[(head, tail)] = v
24 | return
25 | elif k.endswith(" - Value"):
26 | head, tail = k[:-8].rsplit(" ", 1)
27 | k = ":".join((head, types.get((head, tail), ""), tail))
28 | if " ::: " in v:
29 | return k, v.split(" ::: ")
30 | else:
31 | return k, v
32 |
33 | def to_list(l):
34 | return l if type(l) == list else [l]
35 |
36 | def dict_to_vcard(obj):
37 | lines = ["BEGIN:VCARD", "VERSION:2.1"]
38 | lines.append("FN:%s" % obj.pop("Name"))
39 | lines.append("N:%s;%s;%s;%s;%s" % (
40 | obj.pop("Family Name", ""),
41 | obj.pop("Given Name", ""),
42 | obj.pop("Additional Name", ""),
43 | obj.pop("Name Prefix", ""),
44 | obj.pop("Name Suffix", "")
45 | ))
46 | for k in list(obj.keys()):
47 | if ":" not in k:
48 | continue
49 | head, tail, n = k.split(":")
50 | #pref = ";PREF" if n == 1 else ""
51 | vv = to_list(obj[k])
52 |
53 | if head == "Phone":
54 | head = "TEL"
55 | elif head == "E-mail":
56 | head = "EMAIL"
57 | elif head == "Website":
58 | head = "URL"
59 | else:
60 | continue
61 |
62 | tail = ("CELL" if (tail == "Mobile" and head == "TEL") else
63 | "HOME" if tail == "Home" else
64 | "WORK" if tail == "Work" else
65 | "X-%s" % tail if tail else "")
66 | if tail.startswith("X-"):
67 | logging.info("saw unusual %s type: %s", head, tail)
68 |
69 | for v in vv:
70 | if tail:
71 | lines.append("%s;%s:%s" % (head, tail, v))
72 | else:
73 | lines.append("%s:%s" % (head, v))
74 | del obj[k]
75 |
76 | if "Notes" in obj:
77 | lines.append("NOTE:%s" % obj.pop("Notes"))
78 | if "Organization 1 - Name" in obj:
79 | lines.append("ORG:%s" % obj.pop("Organization 1 - Name"))
80 | if "Group Membership" in obj:
81 | lines.append("CATEGORIES:%s" % ",".join(to_list(obj.pop("Group Membership"))))
82 | lines.append("END:VCARD")
83 | if obj:
84 | print("DROPPED data:", obj)
85 | return lines
86 |
87 | with open(sys.argv[1], encoding="utf-16") as fp, open(sys.argv[2], "w") as wfp:
88 | reader = csv.reader(fp.readlines())
89 | for row in reader:
90 | if not keys:
91 | keys = row
92 | continue
93 | types = {}
94 | obj = dict(filter(None, [process_entry(k, v, types) for (k, v) in filter(lambda p: bool(p[1]), zip(keys, row))]))
95 | vcard = dict_to_vcard(obj)
96 | for line in vcard:
97 | print(line, file=wfp)
98 |
--------------------------------------------------------------------------------
/pages/android-overview.rst:
--------------------------------------------------------------------------------
1 | .. title: Overview of an Android phone
2 | .. slug: android-overview
3 | .. date: 2016-01-21
4 | .. tags:
5 | .. category:
6 | .. link:
7 | .. description:
8 | .. type: text
9 |
10 | TODO: explain fastboot, recovery, main system.
11 |
12 | TODO: explain encryption and partition layout.
13 |
14 | TODO: explain unlocking and getting root.
15 |
16 | TODO: explain adb, logcat, SELinux.
17 |
--------------------------------------------------------------------------------
/pages/hw/nospy.rst:
--------------------------------------------------------------------------------
1 | .. title: Remove unnecessary sensor hardware
2 | .. slug: hw/nospy
3 | .. date: 2016-01-20 20:25:39 UTC
4 | .. tags:
5 | .. category:
6 | .. link:
7 | .. description:
8 | .. type: text
9 |
10 | Write your page here.
11 |
--------------------------------------------------------------------------------
/pages/index.rst:
--------------------------------------------------------------------------------
1 | .. title: Android FOSS+H hacks
2 | .. slug: index
3 | .. date: 2016-01-20 20:16:29 UTC
4 | .. tags:
5 | .. category:
6 | .. link:
7 | .. description:
8 | .. type: text
9 |
10 | This is a collection of posts on how to mod Android phones to contain less
11 | non-free and/or privacy-disrespecting software and hardware.
12 |
13 | Background
14 | ----------
15 |
16 | * :doc:`android-overview` (TODO)
17 | * :doc:`setup-enc-cm` (TODO)
18 |
19 | Hardware
20 | --------
21 |
22 | * :doc:`hw/nospy` (TODO)
23 |
24 | Software
25 | --------
26 |
27 | * :doc:`sw/owndata`
28 | * :doc:`sw/location`
29 | * :doc:`sw/firewall`
30 |
31 | Miscellaneous
32 | -------------
33 |
34 | * :doc:`misc/force-adb`
35 | * :doc:`misc/migrate`
36 | * :doc:`misc/screencast`
37 |
--------------------------------------------------------------------------------
/pages/misc/force-adb.rst:
--------------------------------------------------------------------------------
1 | .. title: Enable ADB in highly-constrained situations
2 | .. slug: misc/force-adb
3 | .. date: 2016-01-20
4 | .. tags:
5 | .. category:
6 | .. link:
7 | .. description:
8 | .. type: text
9 |
10 | "Highly-constrained" means that, for whatever reason, you can't go into the
11 | "Developer options" menu to enable "Android debugging" yourself. For example,
12 | your screen is cracked, or you forgot your screen unlock password.
13 |
14 | However, you still need some physical way of communicating with your phone's
15 | ADB service *after* enabling it. These instructions won't work if your USB port
16 | is broken.
17 |
18 | --------------------------------
19 | Nexus 4, CM 12.1 / Android 5.1.1
20 | --------------------------------
21 |
22 | The instructions below assume that your device is encrypted, and that you need
23 | an ``adb shell`` both before and after decryption. If your requirements are
24 | less than this, you should be able to adapt the instructions accordingly.
25 |
26 | They also assume that you've used your computer to debug your phone before -
27 | i.e. that it is already authorized to debug your phone. If you haven't done
28 | this before, don't worry - you can adapt the instructions below to authorize
29 | your computer, but I've forgotten how exactly. TODO: dig this information out
30 | again from the depths of the internet, and add it.
31 |
32 | Prepare
33 | =======
34 |
35 | 0. On your computer, install :doc:`adb and fastboot`,
36 | ``sqlite3`` and ``abootimg``.
37 | 1. On your phone, install :doc:`TWRP `.
38 |
39 | TODO: link to specific sections rather than entire page.
40 |
41 | Enable after decrypt
42 | ====================
43 |
44 | This enables ADB after you decrypt your data partition.
45 |
46 | Boot into TWRP recovery, then from your computer::
47 |
48 | $ adb shell
49 | # twrp decrypt $YOUR_PASSWORD
50 | # mount /system
51 | # echo "persist.service.adb.enable=1" >> /system/build.prop
52 | # echo "persist.service.debuggable=1" >> /system/build.prop
53 | # echo "persist.sys.usb.config=mtp,adb" >> /system/build.prop
54 | # exit
55 | $ echo -n 'mtp,adb' > /data/property/persist.sys.usb.config
56 | $ adb pull "/data/data/com.android.providers.settings/databases/settings.db"
57 | $ sqlite3 settings.db 'update "global" set value=1 where name == "adb_enabled";'
58 | $ sqlite3 settings.db 'update "global" set value=1 where name == "development_settings_enabled";'
59 | $ adb push settings.db "/data/data/com.android.providers.settings/databases/settings.db"
60 | $ adb shell chown system: "/data/data/com.android.providers.settings/databases/settings.db"
61 | $ adb shell chmod 660 "/data/data/com.android.providers.settings/databases/settings.db"
62 |
63 | See also https://android.stackexchange.com/questions/112040/ which gives
64 | details for other android versions.
65 |
66 | Enable before decrypt
67 | =====================
68 |
69 | This enables ADB while Android is still booting up, *before* you decrypt your
70 | data partition.
71 |
72 | Boot into TWRP recovery, then from your computer::
73 |
74 | $ adb shell twrp decrypt
75 | $ adb shell twrp backup B
76 | $ adb pull /sdcard/TWRP/BACKUPS/*/*/boot.emmc.win boot.img
77 | $ mkdir boot && cd boot
78 | boot$ abootimg -x ../boot.img
79 | boot$ mkdir initrd && cd initrd
80 | boot/initrd$ cat ../initrd.img | gunzip | cpio -vid
81 | boot/initrd$ sed -e 's/\(ro.*\.secure\)=1/\1=0/g' -i default.prop
82 | boot/initrd$ find . | cpio --create --format='newc' | gzip > ../initrd-adb.img
83 | boot/initrd$ cd ..
84 | boot$ abootimg --create boot-adb.img -f bootimg.cfg -k zImage -r initrd-adb.img
85 | boot$ adb reboot bootloader
86 | boot$ fastboot boot boot-adb.img
87 |
88 | Or to install this permanently so you don't need to keep running the last
89 | ``fastboot boot`` command::
90 |
91 | boot$ fastboot flash boot boot-adb.img
92 |
93 | Note that any attacker with physical access to the phone can do the equivalent
94 | of above - even though ``twrp backup`` command requires a decrypted ``/data``
95 | to dump the backup into, an attacker could execute the same functionality and
96 | dump it to an unencrypted location.
97 |
98 | This is an inherent danger of rooting your device. But the proper solution to
99 | defend against this, is to develop better security architectures that place
100 | authority (to execute bootstrap code) in the user's hands, instead of in the
101 | hands of a third party then claim it's "for security" (as "locked" phones do).
102 |
--------------------------------------------------------------------------------
/pages/misc/migrate.rst:
--------------------------------------------------------------------------------
1 | .. title: Migrating app data to a new phone
2 | .. slug: misc/migrate
3 | .. date: 2018-12-05
4 | .. tags:
5 | .. category:
6 | .. link:
7 | .. description:
8 | .. type: text
9 |
10 | The below guide was originally written for the Signal Messenger app, and I
11 | haven't had time to generalise it to other apps yet. However I have personally
12 | adapted them to successfully migrate my own data across phones.
13 |
14 | In newer versions of Signal (4.16+) they decided to put user private keys in a
15 | special place in the system (Android KeyStore) that "keeps it safe from
16 | hackers" but unfortunately is totally untransparent to end-users, and can't be
17 | easily backed-up even when you figure out what's going on. So the instructions
18 | below won't actually work for Signal today. However, they can be adapted to
19 | e.g. Silence, or pretty much any other app that doesn't use the KeyStore.
20 |
21 | 0. Download `<../../listings/restore-apk-data.sh.html>`_, have a read through
22 | it and understand what it does. Then push it to your new phone::
23 |
24 | $ adb root
25 | $ adb shell mkdir -p /data/user/0/restore
26 | $ adb push restore-apk-data.sh /data/user/0/restore/ # the slash is important
27 |
28 | 1. On both phones, enable ADB. If you can't, then :doc:`try this `.
29 |
30 | 2. On your new phone, install Signal (or `LibreSignal <#new-way>`_), start it
31 | once, then go to Settings / Apps / Signal then press "Force Stop".
32 |
33 | 3. On your old phone, go to Settings / Apps / Signal then press "Force Stop".
34 | Then, **put your old phone in Airplane Mode**.
35 |
36 | 4. Connect your old phone to your computer, then on the latter::
37 |
38 | $ adb root
39 | $ adb shell tar -C /data/user/0 -czf /data/user/0/signal.tar.gz org.thoughtcrime.securesms
40 | $ adb pull /data/user/0/signal.tar.gz
41 | $ adb shell rm /data/user/0/signal.tar.gz
42 |
43 | 5. Disconnect your old phone from your computer.
44 |
45 | 6. Connect your new phone to your computer, then on the latter::
46 |
47 | $ adb root
48 | $ adb push signal.tar.gz /data/user/0/restore/ # the slash is important
49 | $ adb shell "cd /data/user/0/restore/ && tar -xzf signal.tar.gz"
50 | $ adb shell "cd /data/user/0/restore/ && ./restore-apk-data.sh org.thoughtcrime.securesms"
51 |
52 | 7. Start Signal on your new phone, and check that everything still works. If it
53 | does, you can run::
54 |
55 | $ adb shell rm -rf /data/user/0/_old_signal
56 | $ adb shell rm /data/user/0/signal.tar.gz
57 |
58 | Or you can leave this until later, as a backup.
59 |
60 | 8. Disconnect your new phone from your computer.
61 |
62 | 9. On your old phone, uninstall Signal **before** disabling Airplane Mode. If
63 | you don't do this, *both* of your installations will start to break as the
64 | cryptographic ratchet gets forked.
65 |
--------------------------------------------------------------------------------
/pages/misc/screencast.rst:
--------------------------------------------------------------------------------
1 | .. title: Control your phone via keyboard and mouse
2 | .. slug: misc/screencast
3 | .. date: 2016-01-20
4 | .. tags:
5 | .. category:
6 | .. link:
7 | .. description:
8 | .. type: text
9 |
10 | These instructions let you control your phone via a computer keyboard and
11 | mouse, for example if your touchscreen is cracked.
12 |
13 | This is only necessary on older phones. With more recent phones, you can just
14 | plug in a mouse via a USB OTG cable, or a USB C cable (that entails OTG) for
15 | the newest phones such as the Nexus 5X. (Supposedly you can also do this with
16 | the Nexus 4 after some extra convoluted hacks, but I never managed to get that
17 | working myself.)
18 |
19 | Prepare
20 | =======
21 |
22 | 0. On your computer, install :doc:`adb ` and ``maven``.
23 | 1. On your phone, enable ADB. :doc:`Do this ` if necessary.
24 |
25 | TODO: link to specific sections rather than entire page.
26 |
27 | Run screencast
28 | ==============
29 |
30 | These are the basic core instructions, which may need to be adapted depending
31 | on your system::
32 |
33 | $ git clone https://github.com/xSAVIKx/AndroidScreencast && cd AndroidScreencast
34 | AndroidScreencast$ mvn package
35 | AndroidScreencast$ java -jar target/androidscreencast*.jar
36 |
37 | If you're on Debian, you need to integrate the following:
38 |
39 | - Before ``mvn package``::
40 |
41 | AndroidScreencast$ patch -p1 <
48 |
49 | + local
50 | + file:///usr/share/maven-repo
51 | +
52 | +
53 | + maven-apache
54 | + https://repo.maven.apache.org/maven2/
55 | +
56 | +
57 | onejar-maven-plugin.googlecode.com
58 | - http://onejar-maven-plugin.googlecode.com/svn/mavenrepo
59 | + https://onejar-maven-plugin.googlecode.com/svn/mavenrepo
60 |
61 |
62 | http://xsavikx.github.io/AndroidScreencast
63 | EOF
64 |
65 | - Before ``java -jar [..]``::
66 |
67 | AndroidScreencast$ socat TCP-LISTEN:5037,fork UNIX-CONNECT:/tmp/5037 &
68 |
69 | This is because Debian disables TCP-listen for the adb server by default, for
70 | security. If your non-Debian system does this, you'll need to run this too.
71 |
--------------------------------------------------------------------------------
/pages/setup-enc-cm.rst:
--------------------------------------------------------------------------------
1 | .. title: Basic setup: microG LineageOS with device encryption
2 | .. slug: setup-enc-cm
3 | .. date: 2016-01-21
4 | .. tags:
5 | .. category:
6 | .. link:
7 | .. description:
8 | .. type: text
9 |
10 | TODO: write this up properly, including reasons (e.g. tradeoffs vs AOSP, vs
11 | stock Google, vs Replicant, etc)
12 |
13 | Rough steps
14 | ===========
15 |
16 | 0. Unlock your bootloader.
17 |
18 | 1. Boot into bootloader, then install TWRP recovery. (Ignore official CM
19 | instructions that tell you to install ClockworkMod - TWRP is better.)
20 |
21 | 2. Boot into recovery, then install via ADB sideload.
22 |
23 | 3. Boot into system, then encrypt your phone (Android > 6 doesn't need this,
24 | it's all encrypted by default).
25 |
26 | TODO: how to set a more complex password for the initial bootup decryption?
27 | Previously could be achieved with "Cryptfs Password" but it's no longer
28 | compatible with Android 7+.
29 |
30 | 4. For future upgrades: Boot into recovery, decrypt the phone via ``adb shell
31 | twrp decrypt``, then install via ADB sideload as in (2).
32 |
33 | None of our recommendations require you to install GAPPS. If you are content to
34 | only use our recommendations, you can ignore other online instructions that
35 | tell you to install it.
36 |
37 | Recommended apps
38 | ================
39 |
40 | All F-Droid FOSS
41 |
42 | System, should be already installed as part of microG LineageOS
43 |
44 | * F-Droid, F-Droid Privileged Extension
45 | * microG Services Core, (MozillaNlpBackend), (NominatimNlpBackend)
46 |
47 | Comms and security
48 |
49 | * AFWall+, Orbot
50 | * andOTP, ConnectBot
51 | * Conversations, Silence, Signal
52 | * Orfox
53 | * SnoopSnitch / AIMSICD
54 | * WifiAnalyzer
55 |
56 | Personal data
57 |
58 | * DAVx5, Tasks, see :doc:`sw/owndata`
59 |
60 | Location
61 |
62 | * LocalGSMNlpBackend, LocalWifiNlpBackend, see :doc:`sw/location`
63 | * GPSTest, SatStat
64 | * Mozilla Stumbler
65 | * OsmAnd~
66 | * Sky Map (FOSS by Google)
67 | * Transportr
68 |
69 | Utilities
70 |
71 | * Barcode Scanner
72 | * Equate
73 | * Fennec F-Droid
74 | * Giggity
75 | * Hash Droid
76 | * NewPipe
77 |
78 | Suggested apps
79 | ==============
80 |
81 | F-Droid FOSS
82 | ------------
83 |
84 | * aLogcat ROOT
85 | * AndIodine
86 | * ApkTrack
87 | * Auto Updater for Chromium
88 | * Open Camera
89 | * Plumble
90 |
91 | Not from F-Droid, but microG-compatible
92 | ---------------------------------------
93 |
94 | * Google Gboard (keyboard with swipe)
95 | * Google Maps
96 | * Google Translate
97 | * Revolut
98 | * Songkick
99 | * Soundcloud
100 | * Spotify
101 | * VLC
102 |
103 | Ask a friend to download these from Google Play, then use `pull-apk.sh
104 | <../../listings/pull-apk.sh.html>`_ to grab the APKs from their device, then
105 | install them on your device. Later, most of them are updateable from ApkTrack
106 | so you only have to do this process once.
107 |
108 | Disable stock apps
109 | ==================
110 |
111 | Optionally, disable these stock apps (perhaps after setting the relevant ones
112 | from above to perform the action that they're responsible for):
113 |
114 | * Browser
115 | * Camera
116 | * Email
117 | * Messenger
118 | * Telephone
119 |
120 | If you can't disable them, either leave them as-is or install "/system/app
121 | mover" from F-Droid to forcibly remove them. TODO: test this personally.
122 |
--------------------------------------------------------------------------------
/pages/sw/firewall.rst:
--------------------------------------------------------------------------------
1 | .. title: Enforce internet access through Tor
2 | .. slug: sw/firewall
3 | .. date: 2018-12-05
4 | .. tags:
5 | .. category:
6 | .. link:
7 | .. description:
8 | .. type: text
9 |
10 | Block unwanted internet access system-wide, and force other traffic through
11 | Tor. Of course, one can add exceptions to allow specific applications to access
12 | the internet directly.
13 |
14 | Previously I achieved this through Orwall, but that is no longer maintained and
15 | has several open bugs that need awkward manual work-arounds. The following
16 | approach seems to work better for me, pending clarification of `this issue`_.
17 |
18 | .. _this issue: https://github.com/ukanth/afwall/issues/789
19 |
20 | ----------
21 | Android 10
22 | ----------
23 |
24 | AFWall+
25 |
26 | * Preferences > Rules/Connectivitey > LAN control [check]
27 | * Preferences > Rules/Connectivitey > VPN control [check]
28 | * Mode: Allow selected
29 | * Applications rules:
30 |
31 | ==== ==== ==== ==== ============================== ======================================
32 | LAN WiFi Data VPN Application Reason why it shouldn't go through Tor
33 | ==== ==== ==== ==== ============================== ======================================
34 | . . . Y Any app
35 | Y Y Y Y Orbot Ofc Orbot itself can't go through Tor
36 | Y Y Y Y (any other apps you want to bypass Tor)
37 | ---- ---- ---- ---- ---------------------------------------------------------------------
38 | . . Y Y (root) Mobile internet, need it before Orbot can even access internet
39 | . . Y Y Phone Services, (..) Mobile internet, need it before Orbot can even access internet
40 | . Y Y Y NetworkPermissionConfig, (..) Internet connectivity detection
41 | . Y Y Y (gps) AGPS, Orbot can't intercept this
42 | . Y Y Y (ntp) AGPS, Orbot can't intercept this
43 | Y Y Y Y (tethering) Tethering, Orbot can't intercept this
44 | Y . . Y VLC Chromecast, don't want to put this through Tor
45 | ==== ==== ==== ==== ============================== ======================================
46 |
47 | Orbot
48 |
49 | * Menu > Apps VPN mode [toggle on]
50 | * Apps > select the apps you want to force through Tor, which should at the
51 | very least include:
52 |
53 | * microG Services Core
54 | * Mozilla UnifiedNlp Backend
55 | * Mozilla Stumbler
56 | * Nominatim Geocoder Backend
57 | * GSM Location Service
58 | * SatStat
59 | * Updater -- i.e. LineageOS Updater
60 |
--------------------------------------------------------------------------------
/pages/sw/location.rst:
--------------------------------------------------------------------------------
1 | .. title: Free and privacy-respecting location providers
2 | .. slug: sw/location
3 | .. date: 2018-12-05
4 | .. tags:
5 | .. category:
6 | .. link:
7 | .. description:
8 | .. type: text
9 |
10 | ----------
11 | Background
12 | ----------
13 |
14 | Currently, Android phones determine location by using GPS, as well as from
15 | nearby cell tower and WiFi stations. GPS is accurate and privacy-preserving,
16 | but takes a while to lock onto your position. The other two methods can give
17 | you a rough idea of your location more quickly. However, they involve querying
18 | remote proprietary databases that tell you which cell towers and WiFi stations
19 | are near which locations. This reveals your location to the database provider,
20 | as well as anyone that can read or break this communication.
21 |
22 | In Android, that is handled by **Google Play Services** (GMS). In our
23 | :doc:`basic setup `, we mentioned that we don't need this. It is
24 | proprietary software and makes you economically dependent on a centralized
25 | third party, and getting rid of such software is the aim of our game.
26 |
27 | Instead, we use alternative FOSS to provide the equivalent service. However,
28 | here you download the entire database periodically and do the lookups locally,
29 | so that no remote parties to find out your location. As an added bonus, your
30 | GPS will also be able to get a lock much more quickly, based on this data.
31 |
32 | Yes, your mobile carrier can still determine your location, since they know
33 | which cell towers you're connecting to. Given enough data, this will eventually
34 | de-anonymise you even if you got a burner SIM card. However, the other benefits
35 | mentioned above still remain. Also, it prepares the way (and is a necessary
36 | pre-requisite) for eventually having fully location-private mobile networks in
37 | the future. (More generally, if one needs to do A and B to achieve T, and doing
38 | A is not feasible right now, it does not mean that doing B is pointless.)
39 |
40 | ------------
41 | Instructions
42 | ------------
43 |
44 | The steps are very simple but it makes your phone GPS so much more usable that
45 | it deserves its own page:
46 |
47 | - Follow (1) the installation instructions at `LineagoOS for microG`_ including
48 | (2) install SU for root access and (3) add the `microG Fdroid repo`_. If you
49 | choose to enable the MozillaNlpBackend and NominatimNlpBackend, you should
50 | probably also follow the instructions in :doc:`sw/firewall`.
51 |
52 | - Install `LocalGsmNlpBackend`_, which lets you download global databases for
53 | cell tower locations, and query them later when your phone asks for location.
54 | We also encourage you to install other programs that let you contribute back
55 | to these public and open databases. (TODO: add some specific suggestions)
56 |
57 | - At the time of writing, there are no downloadable global databases for WiFi
58 | station locations, but there is `LocalWifiNlpBackend`_. This remembers the
59 | location of nearby WiFi stations (using GPS) so that it will lock quicker the
60 | next time you're nearby. This can be useful if you go to a new place for a
61 | few days - after the first day or so, your GPS should lock quickly again. But
62 | you should switch it off when you are in a familiar place, to save battery.
63 |
64 | After installing these, restart your phone, then follow further instructions
65 | for *µg UnifiedNlp* which will take you through configuration steps for the
66 | other programs as well.
67 |
68 | Make sure it works
69 | ------------------
70 |
71 | Install "GPSTest", "SatStat", and "HereGPS". Currently it's a bit stupid but
72 | you do need to install all of them for the following functionality:
73 |
74 | - GPSTest has a "clear AGPS" option
75 | - SatStat has a "reload APGS" option
76 | - HereGPS shows you *when* each source (Network, GPS) was last updated.
77 |
78 | Annoyingly, I haven't found an app that contains all three features. >:[
79 |
80 | ``gps.conf`` is the important thing. This file is located in different paths
81 | depending on your phone, use ``adb shell`` and ``find(1)`` to find it yourself.
82 | We are now going to edit it, since the stock one provided by LineageOS is not
83 | very suitable for many phones. You may need to run something like ``mount -o
84 | remount,rw /vendor`` as root in adb if the partition containing ``gps.conf`` is
85 | mounted read-only.
86 |
87 | There is some good information about the file contents here:
88 |
89 | https://rootzwiki.com/topic/28989-the-end-all-be-all-guide-to-your-gps/
90 |
91 | Read the whole thread. Then to get you started, download one of these `custom
92 | gps.conf`_ files. These are pretty good but still contain some mistakes, so
93 | edit it further after downloading it, based on the above thread. For example, I
94 | had to remove extraneous NTP servers and placeholder "FQDN" entries for
95 | ``SUPL_HOST`` and ``SUPL_TLS_HOST``. I also had to download the correct root
96 | cert for the ``SUPL_TLS_HOST``::
97 |
98 | $ openssl s_client -connect $SUPL_TLS_HOST:$SUPL_SECURE_PORT -prexit -showcerts
99 |
100 | It will output a bunch of stuff. Only proceed if near the bottom you see
101 | "Verify return code: 0 (ok)". Then, find the root certificate (probably the
102 | last one that was output), paste it into a new file ``SuplRootCert.pem``, then
103 | run::
104 |
105 | $ openssl x509 -in SuplRootCert.pem -outform DER -out SuplRootCert
106 |
107 | You can then copy ``SuplRootCert`` into your phone. Put it next to ``gps.conf``
108 | and then set the entry for ``SUPL_TLS_CERT`` to point to it.
109 |
110 | When you're all done, restart your phone and go somewhere with good GPS signal
111 | (i.e. outside or near a window) and good cell signal. Then, use the apps I
112 | mentioned above to make sure everything's working correctly. You might have to
113 | dick around a bit, but hopefully the ``gps.conf`` tips helped a lot.
114 |
115 | Next steps
116 | ----------
117 |
118 | To replace **Google Maps**, use `OsmAnd+`_. It has completely offline vector
119 | maps, that are incredibly detailed, with public transport and address data, as
120 | well as offline navigation. The main downside is that redrawing the map when
121 | you move or resize takes about 2-3 seconds, but you get used to it quickly.
122 |
123 | Finally, don't forget to update your data every once in a while. Of the apps
124 | mentioned on this page, that is *LocalGsmNlpBackend* which you access *via* the
125 | *microG Services Core* app, and *OsmAnd~*. At present, neither of these apps
126 | will alert you about out-of-date data, but in practice this hasn't been a
127 | problem for me. Just remember to do it every month, or longer is probably fine
128 | too - and you can even set a :doc:`calendar event ` for that. :)
129 |
130 | .. _LineagoOS for microG: https://lineage.microg.org/
131 | .. _microG Fdroid repo: https://microg.org/fdroid.html
132 | .. _LocalGsmNlpBackend: https://f-droid.org/repository/browse/?fdid=org.fitchfamily.android.gmslocation
133 | .. _LocalWifiNlpBackend: https://f-droid.org/repository/browse/?fdid=org.fitchfamily.android.wifi_backend
134 | .. _OsmAnd+: https://f-droid.org/repository/browse/?fdid=net.osmand.plus
135 | .. _custom gps.conf: https://app.box.com/s/w57s1v1n3hie7l5lk28i
136 |
--------------------------------------------------------------------------------
/pages/sw/owndata.rst:
--------------------------------------------------------------------------------
1 | .. title: Self-hosting your personal data
2 | .. slug: sw/owndata
3 | .. date: 2016-01-21
4 | .. tags:
5 | .. category:
6 | .. link:
7 | .. description:
8 | .. type: text
9 |
10 | Move your data onto infrastructure that you control, then access and work with
11 | it from your phone later.
12 |
13 | ---------------------
14 | Contacts and Calendar
15 | ---------------------
16 |
17 | For now, we discuss Radicale with DAVx5. There are other solutions that are
18 | similarly good, which we may cover in the future or accept contributions to
19 | include. (The previous version of this guide used Apple CalendarServer, but
20 | that has been discontinued. Radicale is much simpler to set up anyway.)
21 |
22 | You need to do the following steps:
23 |
24 | * On your server, configure `Radicale <#configure-radicale>`_ and a
25 | `Tor stealth service <#configure-tor-stealth-service>`_.
26 | * On each client device, configure `tor <#tor-on-a-client>`_ and `DAVx5
27 | <#configure-a-client-device>`_.
28 | * `Sanitise and import your data <#sanitise-and-import-your-old-data>`_.
29 |
30 | Along the way, you'll also configure some other desktop clients to access your
31 | server. This is useful for heavy editing tasks.
32 |
33 | Configure Radicale
34 | ==================
35 |
36 | The Radicale website has `excellent highly-detailed documentation
37 | `_
38 | already. OTOH our instructions below are more concise and fine-tuned for Debian
39 | and Tor Stealth Hidden Services.
40 |
41 | For a Debian server, install::
42 |
43 | # aptitude install radicale python3-passlib
44 |
45 | Configure users::
46 |
47 | $ sudo htpasswd -B -c /etc/radicale/users $USER
48 |
49 | Edit ``/etc/radicale/config`` to contain the following::
50 |
51 | [auth]
52 | type = htpasswd
53 | htpasswd_filename = /etc/radicale/users
54 | htpasswd_encryption = bcrypt
55 |
56 | Use something like ``rsnapshot(1)`` to back up ``/var/lib/radicale/collections``.
57 | This **not optional** - if you don't do this, then none of the rest of the
58 | stuff mentioned on this page will work, and the world will explode. Even worse,
59 | Google SREs will start showing up at your house every day to laugh at you.
60 |
61 | We're ready to start::
62 |
63 | # systemctl enable radicale
64 | # systemctl start radicale
65 |
66 | In your web browser, go to ``localhost:5232``, log in, then create an
67 | "addressbook" and a "calendar, journal and tasks". You will end up with 2
68 | collections each with a URL with the collection UUID at the end, like
69 | ``http://localhost:5232/$YOU/$COLLECTION/``. Note these for later.
70 |
71 | Configure Tor stealth service
72 | =============================
73 |
74 | This allows your client devices to access your server, without exposing it to
75 | the rest of the internet, and without needing complex rules/mechanism to bypass
76 | any NAT/firewall policies.
77 |
78 | Add to ``/etc/tor/torrc``::
79 |
80 | HiddenServiceDir /var/lib/tor/hidden_service/
81 | HiddenServicePort 5232 127.0.0.1:5232
82 | HiddenServiceAuthorizeClient stealth $client_names
83 |
84 | Here, ``$client_names`` is a comma-separated list of arbitrary names (i.e. you
85 | make them up yourself), one for each device that will access your server. You
86 | should make up at least two names - one for your phone, one for the desktop
87 | machine you will use to sanitise and import your data from.
88 |
89 | Restart tor, then find out your client addresses/authcookies::
90 |
91 | # service tor restart
92 | # cat /var/lib/tor/hidden_service/hostname
93 |
94 | Note this information for later - your client devices will need it.
95 |
96 | Configure a client device
97 | =========================
98 |
99 | Tor on a client
100 | ---------------
101 |
102 | Add to ``/etc/tor/torrc``::
103 |
104 | MapAddress $SERVER_HOSTNAME $client_hs_address
105 | HidServAuth $client_hs_address $client_hs_authcookie
106 |
107 | Here, ``$client_hs_address`` is one of the addresses (you pick which one) that
108 | your Tor stealth service generated. ``$SERVER_HOSTNAME`` is something you make
109 | up for your own reference, but it must end in `.onion`; this is a restriction
110 | enforced for non-technical reasons by tor, which might be lifted in the future.
111 |
112 | This also works on Orbot - go into Settings and look for "Torrc Custom Config"
113 | near the bottom. With Android 10+ you also need to disable "Private DNS" in
114 | your system settings, see `guardianproject/orbot#262
115 | `_ for discussion.
116 |
117 | Test web UI
118 | -----------
119 |
120 | Test that you can actually access Radicale via your stealth service by going to
121 | ``http://$SERVER_HOSTNAME:5232`` from a Tor-enabled web browser. For Mozilla
122 | programs such as Firefox or Fennec on Android, you'll need to set
123 | ``network.dns.blockDotOnion`` to false in ``about:config`` (effectively only
124 | after a restart). If you're using Torbirdy, it unfortunately resets the proxy
125 | exception on every restart, so you need to set it manually every time.
126 |
127 | DAVx5
128 | -----
129 |
130 | DAVx5 may be installed from F-Droid. It is not itself a client; rather it is
131 | an accounts provider and data synchronizer. Configure your account:
132 |
133 | * Settings > Accounts > DAVx5 > Login with URL and user name
134 | * Base URL = ``http://$SERVER_HOSTNAME:5232``
135 |
136 | Then, other client programs may access and act on the data in these accounts -
137 | for example, the stock Android Contacts app and the stock Android Calendar app
138 | (*not* Google Calendar).
139 |
140 | For "Contact group method", either choice is fine and totally up to you. I
141 | personally prefer "Groups are per-contact categories" as mine are more fluid
142 | and informal. Since Android 10+ the stock Contacts app can edit these fine.
143 |
144 | If account sync fails, check that your Tor connection is stable by downloading
145 | some large things. Failing that, try to debug via ``adb logcat``.
146 |
147 | Since this is going over Tor, the first sync may take a few minutes. Be patient
148 | and try again several times, if the sync only appears to get part of your data.
149 |
150 | Sanitise and import your old data
151 | =================================
152 |
153 | To begin with, export your data from the Google web interface. This is more
154 | accurate than doing it from client applications - since Google themselves know
155 | what data they hold about you, whereas application authors may have missed some
156 | things. Google offers several formats for export. Do them all, since certain
157 | types of data are present in some formats but not others. For example (during
158 | 2014-11), Google loses event categories and contact groups when exporting VCF
159 | and ICS respectively. You should export to CSV (contacts) and XML (calendar) as
160 | well, and whatever else they've added in the meantime.
161 |
162 | Make sure you sanitise your data - merge duplicate contacts, etc. This is quite
163 | important; Google adds a lot of cruft and non-standard extensions to the data,
164 | which can waste your time if imported as-is into another application. Here are
165 | some sample scripts to help you:
166 |
167 | * `Sanitise Google Contacts exported data <../../listings/sanitise-google-contacts.py.html>`_
168 | * `Sanitise Google Calendar exported data <../../listings/sanitise-google-calendar.py.html>`_
169 |
170 | Note though, that I wrote these quickly for myself - they might not cover all
171 | the features of Google that *you* used, and Google may have changed the export
172 | format since I wrote them. You should manually review the output.
173 |
174 | When you are satisfied with the sanitised data, you can import it into your
175 | server using one of the following clients. (You should first `configure tor
176 | <#tor-on-a-client>`_).
177 |
178 | You can also use these clients to further clean up your data, now or in the
179 | future. I certainly find it much easier to perform mass edits from a desktop
180 | machine than from a phone.
181 |
182 | Import contacts data using Evolution
183 | ------------------------------------
184 |
185 | * File / New / Address Book
186 | * Type = CardDAV
187 | * URL = ``http://$SERVER_HOSTNAME:5232/$YOU/$COLLECTION/``
188 |
189 | Go to Edit > Preferences > Network Preferences, add a new proxy by clicking the
190 | "+" button near the bottom, call it "Tor", and configure its address. Have your
191 | address book use this proxy, in "Apply custom proxy settings to these accounts".
192 |
193 | To import your contacts: File / Import
194 |
195 | Import calendar data using Lightning / Iceowl
196 | ---------------------------------------------
197 |
198 | * File / New / Calendar > On the Network
199 | * Format = CalDAV
200 | * Location = ``http://$SERVER_HOSTNAME:5232/$YOU/$COLLECTION/``
201 | * Check "Offline Support" (optional)
202 |
203 | To import your calendar: Events and Tasks / Import
204 |
--------------------------------------------------------------------------------