├── .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 = '

{read_more}…

' 629 | # 'Read more...' for the RSS_FEED, if RSS_TEASERS is True (translatable) 630 | RSS_READ_MORE_LINK = '

{read_more}… ({min_remaining_read})

' 631 | 632 | # Append a URL query to the RSS_READ_MORE_LINK in Atom and RSS feeds. Advanced 633 | # option used for traffic source tracking. 634 | # Minimum example for use with Piwik: "pk_campaign=feed" 635 | # The following tags exist and are replaced for you: 636 | # {feedRelUri} A relative link to the feed. 637 | # {feedFormat} The name of the syndication format. 638 | # Example using replacement for use with Google Analytics: 639 | # "utm_source={feedRelUri}&utm_medium=nikola_feed&utm_campaign={feedFormat}_feed" 640 | RSS_LINKS_APPEND_QUERY = False 641 | 642 | # A HTML fragment describing the license, for the sidebar. 643 | # (translatable) 644 | LICENSE = "" 645 | # I recommend using the Creative Commons' wizard: 646 | # http://creativecommons.org/choose/ 647 | # LICENSE = """ 648 | # 649 | # Creative Commons License BY-NC-SA""" 652 | 653 | # A small copyright notice for the page footer (in HTML). 654 | # (translatable) 655 | CONTENT_FOOTER = 'Contents © {date} {author} - Powered by Nikola {license}' 656 | 657 | # Things that will be passed to CONTENT_FOOTER.format(). This is done 658 | # for translatability, as dicts are not formattable. Nikola will 659 | # intelligently format the setting properly. 660 | # The setting takes a dict. The keys are languages. The values are 661 | # tuples of tuples of positional arguments and dicts of keyword arguments 662 | # to format(). For example, {'en': (('Hello'), {'target': 'World'})} 663 | # results in CONTENT_FOOTER['en'].format('Hello', target='World'). 664 | # WARNING: If you do not use multiple languages with CONTENT_FOOTER, this 665 | # still needs to be a dict of this format. (it can be empty if you 666 | # do not need formatting) 667 | # (translatable) 668 | CONTENT_FOOTER_FORMATS = { 669 | DEFAULT_LANG: ( 670 | (), 671 | { 672 | "email": BLOG_EMAIL, 673 | "author": BLOG_AUTHOR, 674 | "date": time.gmtime().tm_year, 675 | "license": LICENSE 676 | } 677 | ) 678 | } 679 | 680 | # To use comments, you can choose between different third party comment 681 | # systems. The following comment systems are supported by Nikola: 682 | # disqus, facebook, googleplus, intensedebate, isso, livefyre, muut 683 | # You can leave this option blank to disable comments. 684 | COMMENT_SYSTEM = "" 685 | # And you also need to add your COMMENT_SYSTEM_ID which 686 | # depends on what comment system you use. The default is 687 | # "nikolademo" which is a test account for Disqus. More information 688 | # is in the manual. 689 | COMMENT_SYSTEM_ID = "" 690 | 691 | # Enable annotations using annotateit.org? 692 | # If set to False, you can still enable them for individual posts and pages 693 | # setting the "annotations" metadata. 694 | # If set to True, you can disable them for individual posts and pages using 695 | # the "noannotations" metadata. 696 | # ANNOTATIONS = False 697 | 698 | # Create index.html for page (story) folders? 699 | # WARNING: if a page would conflict with the index file (usually 700 | # caused by setting slug to `index`), the STORY_INDEX 701 | # will not be generated for that directory. 702 | # STORY_INDEX = False 703 | # Enable comments on story pages? 704 | # COMMENTS_IN_STORIES = False 705 | # Enable comments on picture gallery pages? 706 | # COMMENTS_IN_GALLERIES = False 707 | 708 | # What file should be used for directory indexes? 709 | # Defaults to index.html 710 | # Common other alternatives: default.html for IIS, index.php 711 | # INDEX_FILE = "index.html" 712 | 713 | # If a link ends in /index.html, drop the index.html part. 714 | # http://mysite/foo/bar/index.html => http://mysite/foo/bar/ 715 | # (Uses the INDEX_FILE setting, so if that is, say, default.html, 716 | # it will instead /foo/default.html => /foo) 717 | # (Note: This was briefly STRIP_INDEX_HTML in v 5.4.3 and 5.4.4) 718 | STRIP_INDEXES = True 719 | 720 | # Should the sitemap list directories which only include other directories 721 | # and no files. 722 | # Default to True 723 | # If this is False 724 | # e.g. /2012 includes only /01, /02, /03, /04, ...: don't add it to the sitemap 725 | # if /2012 includes any files (including index.html)... add it to the sitemap 726 | # SITEMAP_INCLUDE_FILELESS_DIRS = True 727 | 728 | # List of files relative to the server root (!) that will be asked to be excluded 729 | # from indexing and other robotic spidering. * is supported. Will only be effective 730 | # if SITE_URL points to server root. The list is used to exclude resources from 731 | # /robots.txt and /sitemap.xml, and to inform search engines about /sitemapindex.xml. 732 | # ROBOTS_EXCLUSIONS = ["/archive.html", "/category/*.html"] 733 | 734 | # Instead of putting files in .html, put them in /index.html. 735 | # No web server configuration is required. Also enables STRIP_INDEXES. 736 | # This can be disabled on a per-page/post basis by adding 737 | # .. pretty_url: False 738 | # to the metadata. 739 | PRETTY_URLS = True 740 | 741 | # If True, publish future dated posts right away instead of scheduling them. 742 | # Defaults to False. 743 | # FUTURE_IS_NOW = False 744 | 745 | # If True, future dated posts are allowed in deployed output 746 | # Only the individual posts are published/deployed; not in indexes/sitemap 747 | # Generally, you want FUTURE_IS_NOW and DEPLOY_FUTURE to be the same value. 748 | # DEPLOY_FUTURE = False 749 | # If False, draft posts will not be deployed 750 | # DEPLOY_DRAFTS = True 751 | 752 | # Allows scheduling of posts using the rule specified here (new_post -s) 753 | # Specify an iCal Recurrence Rule: http://www.kanzaki.com/docs/ical/rrule.html 754 | # SCHEDULE_RULE = '' 755 | # If True, use the scheduling rule to all posts by default 756 | # SCHEDULE_ALL = False 757 | 758 | # Do you want a add a Mathjax config file? 759 | # MATHJAX_CONFIG = "" 760 | 761 | # If you are using the compile-ipynb plugin, just add this one: 762 | # MATHJAX_CONFIG = """ 763 | # 776 | # """ 777 | 778 | # Do you want to customize the nbconversion of your IPython notebook? 779 | # IPYNB_CONFIG = {} 780 | # With the following example configuration you can use a custom jinja template 781 | # called `toggle.tpl` which has to be located in your site/blog main folder: 782 | # IPYNB_CONFIG = {'Exporter':{'template_file': 'toggle'}} 783 | 784 | # What Markdown extensions to enable? 785 | # You will also get gist, nikola and podcast because those are 786 | # done in the code, hope you don't mind ;-) 787 | # Note: most Nikola-specific extensions are done via the Nikola plugin system, 788 | # with the MarkdownExtension class and should not be added here. 789 | # The default is ['fenced_code', 'codehilite'] 790 | MARKDOWN_EXTENSIONS = ['fenced_code', 'codehilite', 'extra'] 791 | 792 | # Extra options to pass to the pandoc comand. 793 | # by default, it's empty, is a list of strings, for example 794 | # ['-F', 'pandoc-citeproc', '--bibliography=/Users/foo/references.bib'] 795 | # PANDOC_OPTIONS = [] 796 | 797 | # Social buttons. This is sample code for AddThis (which was the default for a 798 | # long time). Insert anything you want here, or even make it empty (which is 799 | # the default right now) 800 | # (translatable) 801 | # SOCIAL_BUTTONS_CODE = """ 802 | # 803 | #
804 | # Share 805 | #
  • 806 | #
  • 807 | #
  • 808 | #
  • 809 | #
810 | #
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 <hN> 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 <h1> 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 <EXPORT_FOLDER> ... < 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 <<EOF 8 | # com.google.android.gms 9 | # EOF 10 | # 11 | # You can then install these on another device with: 12 | # 13 | # $ adb install com.aurora.store_45-0.apk # install new 14 | # $ adb install -r com.aurora.store_45-0.apk # upgrade existing 15 | # $ adb install-multiple com.twitter.android_VERSION-*.apk # install multi-apk package 16 | # 17 | set -e 18 | ANDROID_USER="${ANDROID_USER:-0}" 19 | 20 | while read pkg; do 21 | ver="$(adb shell -n pm list packages --show-versioncode "$pkg" | sed -nre 's/.* versionCode://p' || true)" 22 | if [ -z "$ver" ]; then 23 | echo >&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 <exported XML> <exported ICS> <sanitised ICS> 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 <CSV export> <sanitised VCF> 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<android-overview>`, 36 | ``sqlite3`` and ``abootimg``. 37 | 1. On your phone, install :doc:`TWRP <setup-enc-cm>`. 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 <misc/force-adb>`. 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 <android-overview>` and ``maven``. 23 | 1. On your phone, enable ADB. :doc:`Do this <misc/force-adb>` 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 <<EOF 42 | diff --git a/pom.xml b/pom.xml 43 | index 1dc8e0a..40012d8 100644 44 | --- a/pom.xml 45 | +++ b/pom.xml 46 | @@ -89,7 +89,15 @@ 47 | <pluginRepositories> 48 | <pluginRepository> 49 | + <id>local</id> 50 | + <url>file:///usr/share/maven-repo</url> 51 | + </pluginRepository> 52 | + <pluginRepository> 53 | + <id>maven-apache</id> 54 | + <url>https://repo.maven.apache.org/maven2/</url> 55 | + </pluginRepository> 56 | + <pluginRepository> 57 | <id>onejar-maven-plugin.googlecode.com</id> 58 | - <url>http://onejar-maven-plugin.googlecode.com/svn/mavenrepo</url> 59 | + <url>https://onejar-maven-plugin.googlecode.com/svn/mavenrepo</url> 60 | </pluginRepository> 61 | </pluginRepositories> 62 | <url>http://xsavikx.github.io/AndroidScreencast</url> 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 <setup-enc-cm>`, 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 <sw/owndata>` 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 | <https://radicale.org/3.0.html#tutorials/basic-configuration/storage>`_ 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 | <https://github.com/guardianproject/orbot/issues/262>`_ 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 | --------------------------------------------------------------------------------