├── .gitignore ├── .travis.yml ├── COPYING ├── README.md ├── bin ├── passwd-in-image └── vagrant-package ├── debian ├── changelog ├── clean ├── compat ├── control ├── copyright ├── docs ├── freedom-maker.1 ├── manpages ├── passwd-in-image.1 ├── rules ├── source │ └── format └── vagrant-package.1 ├── doc ├── freedom-maker.xml ├── passwd-in-image.xml └── vagrant-package.xml ├── freedommaker ├── __init__.py ├── __main__.py ├── application.py ├── builder.py ├── freedombox-customize ├── hardware-setup ├── tests │ ├── __init__.py │ ├── test_invocation.py │ ├── test_virtualbox.py │ └── vmdebootstrap-stub ├── vmdb2.py └── vmdebootstrap.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | build 3 | vendor 4 | freedom_maker.egg-info 5 | freedommaker/tests/output 6 | *.pyc 7 | .idea/ 8 | .DS_Store 9 | .pybuild/ 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: generic 4 | services: 5 | - docker 6 | before_install: 7 | - docker pull debian:"${BUILD_DISTRIBUTION:?}" 8 | - docker run -d --name buildhost --privileged -v "${TRAVIS_BUILD_DIR:?}":/tmp/build_dir -w "/tmp/build_dir" debian:"${BUILD_DISTRIBUTION:?}" tail -f /dev/null 9 | install: 10 | - docker exec buildhost apt-get -qq update 11 | - docker exec buildhost apt-get install -y --no-install-recommends ${COMMON_DEPS:?} 12 | - docker exec buildhost apt-get install -y --no-install-recommends ${TARGET_DEPS:?} 13 | script: 14 | - travis_wait 30 docker exec buildhost python3 -m freedommaker "${TARGET:?}" --distribution="${DISTRIBUTION:?}" 15 | - ls -l build 16 | - file build/freedombox-*.img* 17 | - TmpDir="$(mktemp -d)" 18 | - cp -p "$(ls build/freedombox-*.img*)" "${TmpDir:?}" 19 | - ( cd "${TmpDir:?}" && ${DECOMPRESS_AND_DELETE_IMAGE:?} freedombox-*.img*; ) 20 | - file "${TmpDir:?}"/freedombox-*.img 21 | - fdisk -l "${TmpDir:?}"/freedombox-*.img 22 | - rm -r "${TmpDir:?}" 23 | after_failure: 24 | - docker exec buildhost /bin/sh -c 'for F in $(find . -name "*.log"); do echo ${F:?}; tail -n 1000 ${F:?}; done' 25 | env: 26 | global: 27 | - BUILD_DISTRIBUTION=stretch 28 | - COMMON_DEPS="python3-minimal libpython3-stdlib sudo vmdebootstrap" 29 | - DISTRIBUTION=stable 30 | matrix: 31 | - TARGET=raspberry TARGET_DEPS="dosfstools qemu-user-static binfmt-support pxz" DECOMPRESS_AND_DELETE_IMAGE="xz -d" 32 | 33 | notifications: 34 | email: 35 | on_success: change 36 | on_failure: always 37 | irc: 38 | channels: 39 | - "irc.oftc.net#freedombox-ci" 40 | on_success: always 41 | on_failure: always 42 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Freedom-Maker: The FreedomBox image builder 2 | 3 | These scripts build images for FreedomBox for various support hardware 4 | that can then be copied to SD card, USB stick or Hard Disk drive to 5 | boot into FreedomBox. 6 | 7 | These scripts are meant for developers of FreedomBox to build images 8 | during releases and for advanced users who intend to build their own 9 | images. Regular users who wish to turn their devices into 10 | FreedomBoxes should instead download the pre-built images. 11 | 12 | Get a pre-built image via https://wiki.debian.org/FreedomBox . There 13 | are images available for all supported target devices. You also find 14 | the setup instructions on the Wiki. 15 | 16 | If you wish to create your own FreedomBox image, perhaps with some 17 | tweaks, see the *Build Images* section below. 18 | 19 | # Build Images 20 | 21 | ## Supported Targets 22 | 23 | Freedom-maker supports building for the following targets: 24 | 25 | - *beaglebone*: BeagleBone Black's SD card 26 | - *cubieboard2*: Cubieboard2's SD card 27 | - *cubietruck*: Cubietruck's SD card 28 | - *a20-olinuxino-lime*: A20 OLinuXino Lime's SD card 29 | - *a20-olinuxino-lime2*: A20 OLinuXino Lime2's SD card 30 | - *a20-olinuxino-micro*: A20 OLinuXino MICRO's SD card 31 | - *banana-pro*: Banana Pro's SD card 32 | - *dreamplug*: DreamPlug's internal SD card 33 | - *raspberry*: RasbperryPi's SD card. 34 | - *raspberry2*: RasbperryPi 2's SD card. 35 | - *raspberry3*: RasbperryPi 3's SD card. 36 | - *i386*: Disk image for any machine with i386 architecture 37 | - *amd64*: Disk image for any machine with amd64 architecture. 38 | - *virtualbox-i386*: 32-bit image for the VirtualBox virtualization tool 39 | - *virtualbox-amd64*: 64-bit image for the VirtualBox virtualization tool 40 | - *qemu-i386*: 32-bit image for the Qemu virtualization tool 41 | - *qemu-amd64*: 64-bit image for the Qemu virtualization tool 42 | - *pcDuino3*: pcDuino3 43 | - *test*: build virtualbox i386 image and run diagnostics tests on it 44 | 45 | ## Install dependencies 46 | 47 | To build an image, first install the required dependencies for the 48 | image as follows: 49 | 50 | For all dependencies: 51 | ```shell 52 | $ sudo apt-get -y install git \ 53 | sudo \ 54 | vmdebootstrap \ 55 | dosfstools \ 56 | btrfs-progs \ 57 | pxz \ 58 | virtualbox \ 59 | qemu-utils \ 60 | qemu-user-static \ 61 | binfmt-support \ 62 | u-boot-tools \ 63 | sshpass 64 | ``` 65 | 66 | For VirtualBox: 67 | ``` 68 | $ sudo apt-get -y install virtualbox 69 | ``` 70 | 71 | For Qemu: 72 | ``` 73 | $ sudo apt-get -y install qemu-utils 74 | ``` 75 | 76 | For RaspberryPi: 77 | ``` 78 | $ sudo apt-get -y install qemu-user-static binfmt-support 79 | ``` 80 | 81 | For DreamPlug: 82 | ``` 83 | $ sudo apt-get -y install qemu-user-static binfmt-support u-boot-tools 84 | ``` 85 | 86 | For Testing: 87 | ``` 88 | $ sudo apt-get install virtualbox sshpass 89 | ``` 90 | 91 | ## Running Build 92 | 93 | 1. Fetch the git source of freedom-maker: 94 | ``` 95 | $ git clone https://alioth.debian.org/anonscm/git/freedombox/freedom-maker.git freedom-maker 96 | ``` 97 | 98 | 2. Build all images: 99 | ``` 100 | $ python3 -m freedommaker dreamplug raspberry raspberry2 raspberry3 \ 101 | beaglebone cubieboard2 cubietruck a20-olinuxino-lime a20-olinuxino-lime2 \ 102 | a20-olinuxino-micro i386 amd64 virtualbox-i386 virtualbox-amd64 \ 103 | qemu-i386 qemu-amd64 banana-pro 104 | ``` 105 | 106 | or to build just a single image: 107 | ``` 108 | $ python3 -m freedommaker beaglebone 109 | ``` 110 | 111 | or to see the full list of options you can pass to the build proces: 112 | ``` 113 | $ python3 -m freedommaker --help 114 | ``` 115 | 116 | The images will show up in *freedom-maker/build/*. Copy the image to 117 | target disk following the instructions in *Use Images* section. 118 | 119 | # Use Images 120 | 121 | You'll need to copy the image to the memory card or USB stick: 122 | 123 | 1. Figure out which device your card actually is. 124 | 125 | A. Unplug your card. 126 | 127 | B. Run "dmesg -w" to show and follow the kernel messages. 128 | 129 | C. Plug your card in. You will see message such as following: 130 | 131 | [33299.023096] usb 4-6: new high-speed USB device number 12 using ehci-pci 132 | [33299.157160] usb 4-6: New USB device found, idVendor=058f, idProduct=6361 133 | [33299.157162] usb 4-6: New USB device strings: Mfr=1, Product=2, SerialNumber=3 134 | [33299.157164] usb 4-6: Product: Mass Storage Device 135 | [33299.157165] usb 4-6: Manufacturer: Generic 136 | [33299.157167] usb 4-6: SerialNumber: XXXXXXXXXXXX 137 | [33299.157452] usb-storage 4-6:1.0: USB Mass Storage device detected 138 | [33299.157683] scsi host13: usb-storage 4-6:1.0 139 | [33300.155626] scsi 13:0:0:0: Direct-Access Generic- Compact Flash 1.01 PQ: 0 ANSI: 0 140 | [33300.156223] scsi 13:0:0:1: Direct-Access Multiple Flash Reader 1.05 PQ: 0 ANSI: 0 141 | [33300.157059] sd 13:0:0:0: Attached scsi generic sg4 type 0 142 | [33300.157462] sd 13:0:0:1: Attached scsi generic sg5 type 0 143 | [33300.462115] sd 13:0:0:1: [sdg] 30367744 512-byte logical blocks: (15.5 GB/14.4 GiB) 144 | [33300.464144] sd 13:0:0:1: [sdg] Write Protect is off 145 | [33300.464159] sd 13:0:0:1: [sdg] Mode Sense: 03 00 00 00 146 | [33300.465896] sd 13:0:0:1: [sdg] No Caching mode page found 147 | [33300.465912] sd 13:0:0:1: [sdg] Assuming drive cache: write through 148 | [33300.470489] sd 13:0:0:0: [sdf] Attached SCSI removable disk 149 | [33300.479493] sdg: sdg1 150 | [33300.483566] sd 13:0:0:1: [sdg] Attached SCSI removable disk 151 | 152 | D. In the above case, the disk that is newly inserted is available 153 | as */dev/sdg*. Very carefully note this and use it in the 154 | copying step below. 155 | 156 | 2. Decompress the built image using tar: 157 | ``` 158 | $ cd freedom-maker/build 159 | $ tar -xvf freedombox-unstable_2015-08-06_beaglebone-armhf-card.tar.xz 160 | ``` 161 | 162 | The above command is an example for the beaglebone image built on 163 | 2015-08-06. Your tar file name will be different. 164 | 165 | 3. Copy the image to your card. Double check and make sure you don't 166 | write to your computer's main storage (such as /dev/sda). Also 167 | make sure that you don't run this step as root to avoid potentially 168 | overriding data on your hard drive due to a mistake in identifying 169 | the device or errors while typing the command. USB disks and SD 170 | cards inserted into the system should typically be write accessible 171 | to normal users. If you don't have permission to write to your SD 172 | card as a user, you may need to run this command as root. In this 173 | case triple check everything before you run the command. Another 174 | safety precaution is to unplug all external disks except the SD 175 | card before running the command. 176 | 177 | For example, if your SD card is /dev/sdf as noted in the first step 178 | above, then to copy the image, run: 179 | ``` 180 | $ cd build 181 | $ dd bs=1M if=freedombox-unstable_2015-08-06_beaglebone-armhf-card.img of=/dev/sdf conv=fdatasync 182 | ``` 183 | 184 | The above command is an example for the beaglebone image built on 185 | 2015-08-06. Your image file name will be different. 186 | 187 | When picking a device, use the drive-letter destination, like 188 | /dev/sdf, not a numbered destination, like /dev/sdf1. The device 189 | without a number refers to the entire device, while the device with 190 | anumber refers to a specific partition. We want to use the whole 191 | device. Downloaded images contain complete information about how 192 | many partitions there should be, their sizes and types. You don't 193 | have to format your SD card or create partitions. All the data on 194 | the SD card will be wiped off during the write process. 195 | 196 | 4. Use the image by inserting the SD card or USB disk into the target 197 | device and booting from it. Also see hardware specific 198 | instructions on how to prepare your device at 199 | https://wiki.debian.org/FreedomBox/Hardware 200 | -------------------------------------------------------------------------------- /bin/passwd-in-image: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # This file is part of Freedom Maker. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | """ 20 | Utility for setting passwords inside disk/VM images. 21 | 22 | Written for FreedomBox images. Works on Qemu, VirtualBox and raw disk 23 | images. 24 | """ 25 | 26 | import argparse 27 | import getpass 28 | import logging 29 | import os 30 | import subprocess 31 | import sys 32 | import tempfile 33 | 34 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 35 | 36 | 37 | def main(): 38 | """The main entry point.""" 39 | logging.basicConfig(level=logging.INFO) 40 | 41 | parser = argparse.ArgumentParser( 42 | description='Change password of a user inside a disk image file') 43 | parser.add_argument( 44 | 'image', 45 | help='Disk image file (.img or .vdi) inside which user manipulation ' 46 | 'is sought') 47 | parser.add_argument('user', 48 | help='User account to change password for') 49 | parser.add_argument('--password', help='New password for user') 50 | 51 | arguments = parser.parse_args() 52 | 53 | image_type = get_image_type(arguments) 54 | 55 | check_requirements(image_type) 56 | 57 | try: 58 | password = arguments.password or take_password() 59 | perform_operations(arguments, password, image_type) 60 | except subprocess.CalledProcessError as exception: 61 | logger.error('Error running command: %s', exception.cmd) 62 | if exception.output: 63 | logger.error('Error output - %s', exception.output.decode()) 64 | sys.exit(1) 65 | except KeyboardInterrupt: 66 | logger.error('Command terminated by user action') 67 | sys.exit(2) 68 | except Exception as exception: # pylint: disable=broad-except 69 | logger.exception('Unexpected error: %s', exception) 70 | sys.exit(3) 71 | 72 | 73 | def check_requirements(image_type): 74 | """Check that the necessary requirements are available.""" 75 | logger.info('Checking for necessary dependencies') 76 | if os.geteuid() != 0: 77 | logger.error('Due to limitations of the tools invovled, you need to ' 78 | 'run this command as "root" user or using the "sudo" ' 79 | 'command.') 80 | sys.exit(-1) 81 | 82 | if image_type == 'raw': 83 | try: 84 | subprocess.check_output(['which', 'kpartx']) 85 | except subprocess.CalledProcessError: 86 | logger.error('"kpartx" command not found. On Debian based ' 87 | 'systems it is provided by the package "kpartx".') 88 | sys.exit(-1) 89 | 90 | if image_type == 'vm': 91 | try: 92 | subprocess.check_output(['which', 'qemu-nbd']) 93 | except subprocess.CalledProcessError: 94 | logger.error('"qemu-nbd" command not found. On Debian based ' 95 | 'systems it is provided by the package "qemu-utils".') 96 | sys.exit(-1) 97 | 98 | found = False 99 | with open('/proc/filesystems') as file_handle: 100 | for line in file_handle: 101 | if 'btrfs' in line.split(): 102 | found = True 103 | 104 | if not found: 105 | logger.error('Your kernel does not support Btrfs filesystem.') 106 | sys.exit(-1) 107 | 108 | 109 | def get_image_type(arguments): 110 | """Return the type of the disk image: raw/vm.""" 111 | if arguments.image.split('.')[-1] in ('vdi', 'qcow2'): 112 | return 'vm' 113 | 114 | return 'raw' 115 | 116 | 117 | def take_password(): 118 | """Prompt for new password to be set.""" 119 | while True: 120 | password1 = getpass.getpass('Enter new password: ') 121 | password2 = getpass.getpass('Re-enter new password: ') 122 | if password1 == password2: 123 | return password1 124 | 125 | logger.error('Passwords do not match\n') 126 | 127 | 128 | def perform_operations(arguments, password, image_type): 129 | """Map/mount image and change password.""" 130 | map_info = map_disk_image(arguments.image, image_type) 131 | 132 | logger.info('Root device is - %s', map_info['root_device']) 133 | 134 | try: 135 | mount_info = mount_disk_image(map_info['root_device']) 136 | 137 | try: 138 | change_password(mount_info['root_path'], arguments.user, password) 139 | finally: 140 | unmount_disk_image(mount_info) 141 | finally: 142 | unmap_disk_image(map_info) 143 | 144 | 145 | def map_disk_image(disk_image, image_type): 146 | """Map the partitions inside disk image as block devices.""" 147 | if image_type == 'vm': 148 | return map_vm_disk_image(disk_image) 149 | 150 | return map_raw_disk_image(disk_image) 151 | 152 | 153 | def map_vm_disk_image(disk_image): 154 | """Map the partitions inside a VM disk image as block devices.""" 155 | logger.info('Adding partition mappings for VM disk image - %s', disk_image) 156 | device = '/dev/nbd7' 157 | subprocess.check_call(['modprobe', 'nbd', 'max_part=64']) 158 | subprocess.check_call(['qemu-nbd', '--connect=' + device, disk_image]) 159 | subprocess.check_call(['partprobe', device]) 160 | output = subprocess.check_output(['fdisk', '-o', 'Device', '-l', device]) 161 | root_device = output.decode().split('\n')[-2] 162 | 163 | return {'root_device': root_device, 164 | 'image_type': 'vm', 165 | 'mapped_device': device} 166 | 167 | 168 | def map_raw_disk_image(disk_image): 169 | """Map the partitions inside a raw disk image as block devices.""" 170 | logger.info('Adding partition mappings for raw disk image - %s', 171 | disk_image) 172 | output = subprocess.check_output(['kpartx', '-a', '-v', '-s', disk_image]) 173 | output = output.decode() 174 | 175 | devices = [] 176 | for line in output.split('\n'): 177 | if line: 178 | devices.append(line.split(' ')[2]) 179 | 180 | root_device = '/dev/mapper/' + devices[-1] 181 | return {'root_device': root_device, 182 | 'image_type': 'raw', 183 | 'mapped_image': disk_image} 184 | 185 | 186 | def mount_disk_image(root_device): 187 | """Mount the root device into a temporary directory and return the path.""" 188 | mount_path = tempfile.mkdtemp() 189 | 190 | logger.info('Mounting %s on %s', root_device, mount_path) 191 | subprocess.check_call(['mount', root_device, mount_path]) 192 | 193 | mount_info = {'mount_path': mount_path, 'root_path': mount_path} 194 | 195 | # XXX: Assumption that if root/@ exists, it is btrfs and that we 196 | # are going use that snapshot to work on it. 197 | if os.path.isdir(os.path.join(mount_path, '@')): 198 | mount_info['root_path'] = os.path.join(mount_path, '@') 199 | 200 | return mount_info 201 | 202 | 203 | def change_password(root_path, user, password): 204 | """Change a user's password inside chroot directory.""" 205 | logger.info('Changing password for %s inside %s', user, root_path) 206 | chpasswd_input = '{0}:{1}'.format(user, password) 207 | 208 | # XXX: Providing crypt method is not recommended. However, without crypt 209 | # method, the passwd encryption happens using PAM and that does not seem to 210 | # be working in a chroot. 211 | subprocess.check_output(['chpasswd', '--root', root_path, '--crypt-method', 212 | 'SHA512'], input=chpasswd_input.encode()) 213 | 214 | 215 | def unmount_disk_image(mount_info): 216 | """Unmount the root device.""" 217 | mount_path = mount_info['mount_path'] 218 | 219 | logger.info('Unmounting %s', mount_path) 220 | subprocess.check_call(['umount', mount_path]) 221 | 222 | os.rmdir(mount_path) 223 | 224 | return mount_path 225 | 226 | 227 | def unmap_disk_image(map_info): 228 | """Ummap the disk image partitions.""" 229 | if map_info['image_type'] == 'vm': 230 | unmap_vm_disk_image(map_info) 231 | else: 232 | unmap_raw_disk_image(map_info) 233 | 234 | 235 | def unmap_vm_disk_image(map_info): 236 | """Ummap the VM disk image partitions.""" 237 | device = map_info['mapped_device'] 238 | logger.info('Removing partition mappings from VM device - %s', device) 239 | subprocess.check_output(['qemu-nbd', '--disconnect', device]) 240 | 241 | 242 | def unmap_raw_disk_image(map_info): 243 | """Ummap the raw disk image partitions.""" 244 | disk_image = map_info['mapped_image'] 245 | logger.info('Removing partition mappings from raw disk - %s', disk_image) 246 | subprocess.check_output(['kpartx', '-d', disk_image]) 247 | 248 | 249 | if __name__ == '__main__': 250 | main() 251 | -------------------------------------------------------------------------------- /bin/vagrant-package: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # This file is part of Freedom Maker. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | """ 20 | Utility for converting VirtualBox disk images into Vagrant boxes. 21 | """ 22 | 23 | import argparse 24 | import logging 25 | import os 26 | import random 27 | import shutil 28 | import string 29 | import subprocess 30 | import sys 31 | import time 32 | 33 | vm_name = 'freedom-maker-vagrant-package' 34 | 35 | password = ''.join(random.SystemRandom().choice( 36 | string.ascii_letters + string.digits) for x in range(20)) 37 | 38 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 39 | 40 | 41 | def main(): 42 | """The main entry point.""" 43 | logging.basicConfig(level=logging.INFO) 44 | 45 | parser = argparse.ArgumentParser( 46 | description='Convert VirtualBox disk image into Vagrant box') 47 | parser.add_argument( 48 | 'image', 49 | help='Disk image file (.vdi) to be converted') 50 | parser.add_argument( 51 | '--output', default='package.box', 52 | help='Path of the output vagrant box file (default: package.box)') 53 | 54 | arguments = parser.parse_args() 55 | 56 | check_requirements() 57 | 58 | set_fbx_user_password(arguments) 59 | 60 | setup_vm(arguments) 61 | start_vm() 62 | 63 | create_vagrant_user() 64 | set_ssh_key() 65 | setup_sudo() 66 | install_guest_additions() 67 | install_dev_packages() 68 | 69 | stop_vm() 70 | package_vm(arguments) 71 | delete_vm() 72 | 73 | 74 | def check_requirements(): 75 | """Check that the necessary requirements are available.""" 76 | if os.geteuid() != 0: 77 | logger.error('Due to limitations of the tools invovled, you need to ' 78 | 'run this command as "root" user or using the "sudo" ' 79 | 'command.') 80 | sys.exit(-1) 81 | 82 | if not shutil.which('VBoxManage'): 83 | logger.error('"VBoxManage" command not found. It is provided by the ' 84 | 'package "virtualbox" in the "contrib" section of the ' 85 | 'Debian repository.') 86 | sys.exit(-1) 87 | 88 | if not shutil.which('vagrant'): 89 | logger.error('"vagrant" command not found. On Debian based ' 90 | 'systems it is provided by the package "vagrant".') 91 | sys.exit(-1) 92 | 93 | 94 | def set_fbx_user_password(arguments): 95 | """Set password for 'fbx' user using passwd-in-image script.""" 96 | passwd_tool = os.path.join(os.path.dirname(__file__), 'passwd-in-image') 97 | subprocess.run([ 98 | 'sudo', 'python3', passwd_tool, arguments.image, 'fbx', 99 | '--password', password], check=True) 100 | 101 | 102 | def setup_vm(arguments): 103 | """Create and configure VirtualBox VM.""" 104 | subprocess.run([ 105 | 'VBoxManage', 'createvm', '--name', vm_name, '--ostype', 'Debian_64', 106 | '--register'], check=True) 107 | subprocess.run([ 108 | 'VBoxManage', 'storagectl', vm_name, '--name', 'SATA Controller', 109 | '--add', 'sata', '--controller', 'IntelAHCI'], check=True) 110 | subprocess.run([ 111 | 'VBoxManage', 'storageattach', vm_name, 112 | '--storagectl', 'SATA Controller', '--port', '0', '--device', '0', 113 | '--type', 'hdd', '--medium', arguments.image], check=True) 114 | subprocess.run([ 115 | 'VBoxManage', 'modifyvm', vm_name, '--pae', 'on', '--memory', '1024', 116 | '--vram', '128', '--nic1', 'nat', '--natpf1', ',tcp,,2222,,22' 117 | ], check=True) 118 | 119 | 120 | def start_vm(): 121 | """Start the VM.""" 122 | subprocess.run([ 123 | 'VBoxManage', 'startvm', vm_name, '--type', 'headless'], check=True) 124 | time.sleep(180) 125 | 126 | 127 | def create_vagrant_user(): 128 | """Create vagrant user.""" 129 | run_vm_command('sudo adduser --disabled-password --gecos "" vagrant') 130 | run_vm_command( 131 | 'sudo sed -i "s/fbx/fbx vagrant/g" /etc/security/access.conf') 132 | 133 | 134 | def set_ssh_key(): 135 | """Install insecure public key for vagrant user. 136 | 137 | This will be replaced by Vagrant during first boot. 138 | """ 139 | run_vm_command('sudo mkdir /home/vagrant/.ssh') 140 | run_vm_command( 141 | 'sudo wget -O /home/vagrant/.ssh/authorized_keys ' 142 | 'https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/' 143 | 'vagrant.pub') 144 | run_vm_command('sudo chown -R vagrant:vagrant /home/vagrant/.ssh') 145 | run_vm_command('sudo chmod 0700 /home/vagrant/.ssh') 146 | run_vm_command('sudo chmod 0600 /home/vagrant/.ssh/authorized_keys') 147 | 148 | 149 | def setup_sudo(): 150 | """Setup password-less sudo for vagrant user.""" 151 | run_vm_command('sudo usermod -a -G sudo vagrant') 152 | run_vm_command( 153 | 'sudo su -c "echo \'vagrant ALL=(ALL) NOPASSWD: ALL\' ' 154 | '>/etc/sudoers.d/vagrant"') 155 | 156 | 157 | def install_guest_additions(): 158 | """Install VirtualBox Guest Additions into the VM.""" 159 | run_vm_command( 160 | 'sudo sed -i "s/main/main contrib/g" /etc/apt/sources.list') 161 | run_vm_command('sudo apt-get update') 162 | run_vm_command( 163 | 'sudo DEBIAN_FRONTEND=noninteractive apt install -y ' 164 | 'linux-headers-$(uname -r) virtualbox-guest-dkms ' 165 | 'virtualbox-guest-utils') 166 | 167 | 168 | def install_dev_packages(): 169 | """Install build deps and other useful packages for development.""" 170 | run_vm_command('sudo apt build-dep -y plinth freedombox-setup') 171 | run_vm_command('sudo apt install -y python3-dev') 172 | 173 | 174 | def stop_vm(): 175 | """Shutdown the VM.""" 176 | run_vm_command('sudo shutdown now') 177 | time.sleep(30) 178 | 179 | 180 | def package_vm(arguments): 181 | """Convert the VM into a Vagrant box.""" 182 | subprocess.run(['vagrant', 'package', '--base', vm_name, '--output', 183 | arguments.output], check=True) 184 | 185 | 186 | def delete_vm(): 187 | """Delete the VM.""" 188 | subprocess.run(['VBoxManage', 'modifyvm', vm_name, '--hda', 'none']) 189 | subprocess.run(['VBoxManage', 'unregistervm', vm_name, '--delete']) 190 | 191 | 192 | def run_vm_command(command): 193 | """Send a command to the VM through SSH.""" 194 | echo = subprocess.Popen(['echo', password], stdout=subprocess.PIPE) 195 | process = subprocess.Popen([ 196 | 'sshpass', '-p', password, 'ssh', 197 | '-o', 'UserKnownHostsFile=/dev/null', 198 | '-o', 'StrictHostKeyChecking=no', 199 | '-t', '-t', '-p', '2222', 'fbx@127.0.0.1', 200 | command], stdin=echo.stdout) 201 | process.communicate() 202 | 203 | 204 | if __name__ == '__main__': 205 | main() 206 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | freedom-maker (0.9) unstable; urgency=medium 2 | 3 | [ Sunil Mohan Adapa ] 4 | * Remove dependency on extlinux. (Closes: #869203) 5 | * Add myself to list of uploaders. 6 | * Depend on btrfs-progs instead of btrfs-tools. (Closes: #869223) 7 | 8 | [ James Valleroy ] 9 | * Add myself to list of uploaders. 10 | * Bump standards version. 11 | * Replace priority extra with priority optional. 12 | 13 | -- James Valleroy Tue, 02 Jan 2018 19:30:22 -0500 14 | 15 | freedom-maker (0.8) unstable; urgency=low 16 | 17 | [ Joseph Nuthalapati ] 18 | * Initial release. (Closes: #864764) 19 | 20 | -- Federico Ceratto Sun, 18 Jun 2017 16:00:03 +0100 21 | -------------------------------------------------------------------------------- /debian/clean: -------------------------------------------------------------------------------- 1 | **/*.pyc 2 | *.egg-info/* 3 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: freedom-maker 2 | Maintainer: FreedomBox Packaging Team 3 | Uploaders: 4 | Joseph Nuthalapati , 5 | Federico Ceratto , 6 | Sunil Mohan Adapa , 7 | James Valleroy , 8 | Section: utils 9 | Priority: optional 10 | Build-Depends: 11 | debhelper (>= 9), 12 | dh-python, 13 | python3-all, 14 | python3-setuptools, 15 | Standards-Version: 4.1.3 16 | Homepage: https://freedombox.org 17 | Vcs-Browser: https://anonscm.debian.org/git/freedombox/freedom-maker.git 18 | Vcs-Git: https://anonscm.debian.org/git/freedombox/freedom-maker.git 19 | 20 | Package: freedom-maker 21 | Architecture: all 22 | Depends: 23 | ${misc:Depends}, 24 | ${python3:Depends}, 25 | binfmt-support, 26 | btrfs-progs, 27 | dosfstools, 28 | git, 29 | pxz, 30 | qemu-user-static, 31 | qemu-utils, 32 | sshpass, 33 | sudo, 34 | u-boot-tools, 35 | vmdebootstrap 36 | Suggests: 37 | virtualbox 38 | Description: FreedomBox image builder 39 | FreedomBox is a personal cloud server which can be installed on single board 40 | computers and Debian machines. 41 | . 42 | Freedom-Maker is a tool to build images for FreedomBox for various supported 43 | hardware that can then be copied to SD card, USB stick or Hard Disk drive to 44 | boot into FreedomBox. 45 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Source: https://anonscm.debian.org/git/freedombox/freedom-maker.git 3 | 4 | Files: * 5 | Copyright: 2011 - 2017 FreedomBox Developers 6 | License: GPL-3.0+ 7 | 8 | License: GPL-3.0+ 9 | This package is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | . 14 | This package is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | . 19 | You should have received a copy of the GNU General Public License 20 | along with this program. If not, see 21 | . 22 | On Debian systems, the complete text of the GNU General 23 | Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". 24 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /debian/freedom-maker.1: -------------------------------------------------------------------------------- 1 | '\" t 2 | .\" Title: freedom-maker 3 | .\" Author: [see the "Author" section] 4 | .\" Generator: DocBook XSL Stylesheets v1.79.1 5 | .\" Date: 06/15/2017 6 | .\" Manual: FreedomBox Manual 7 | .\" Source: 0.8 8 | .\" Language: English 9 | .\" 10 | .TH "FREEDOM\-MAKER" "1" "06/15/2017" "0\&.8" "FreedomBox Manual" 11 | .\" ----------------------------------------------------------------- 12 | .\" * Define some portability stuff 13 | .\" ----------------------------------------------------------------- 14 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 | .\" http://bugs.debian.org/507673 16 | .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html 17 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | .ie \n(.g .ds Aq \(aq 19 | .el .ds Aq ' 20 | .\" ----------------------------------------------------------------- 21 | .\" * set default formatting 22 | .\" ----------------------------------------------------------------- 23 | .\" disable hyphenation 24 | .nh 25 | .\" disable justification (adjust text to left margin only) 26 | .ad l 27 | .\" ----------------------------------------------------------------- 28 | .\" * MAIN CONTENT STARTS HERE * 29 | .\" ----------------------------------------------------------------- 30 | .SH "NAME" 31 | freedom-maker \- image builder for FreedomBox 32 | .SH "SYNOPSIS" 33 | .HP \w'\fBfreedom\-maker\fR\ 'u 34 | \fBfreedom\-maker\fR [\fB\-\-vmdebootstrap\fR] [\fB\-\-build\-stamp\fR] [\fB\-\-image\-size\fR] [\fB\-\-build\-mirror\fR] [\fB\-\-mirror\fR] [\fB\-\-distribution\fR] [\fB\-\-include\-source\fR] [\fB\-\-package\fR] [\fB\-\-custom\-package\fR] [\fB\-\-build\-dir\fR] [\fB\-\-log\-level\*(Aq\fR] [\fB\-\-hostname\fR] [\fB\-\-sign\fR] [\fB\-\-force\fR] [\fBtargets\fR] [\fB\-h,\ \fR\fB\-\-help\fR] 35 | .SH "DESCRIPTION" 36 | .PP 37 | FreedomBox is a community project to develop, design and promote personal servers running free software for private, personal communications\&. It is a networking appliance designed to allow interfacing with the rest of the Internet under conditions of protected privacy and data security\&. It hosts applications such as blog, wiki, website, social network, email, web proxy and a Tor relay on a device that can replace a wireless router so that data stays with the users\&. 38 | .PP 39 | freedom\-maker is a tool to build FreedomBox images for various single board computers, virtual machines and general purpose computers\&. 40 | .SH "OPTIONS" 41 | .PP 42 | \fB\-\-vmdebootstrap\fR 43 | .RS 4 44 | Path to vmdebootstrap executable 45 | .RE 46 | .PP 47 | \fB\-\-build\-stamp\fR 48 | .RS 4 49 | Build stamp to use on image file names 50 | .RE 51 | .PP 52 | \fB\-\-image\-size\fR 53 | .RS 4 54 | Size of the image to build 55 | .RE 56 | .PP 57 | \fB\-\-build\-mirror\fR 58 | .RS 4 59 | Debian mirror to use for building 60 | .RE 61 | .PP 62 | \fB\-\-mirror\fR 63 | .RS 4 64 | Debian mirror to use in built image 65 | .RE 66 | .PP 67 | \fB\-\-distribution\fR 68 | .RS 4 69 | Debian release to use in built image 70 | .RE 71 | .PP 72 | \fB\-\-include\-source\fR 73 | .RS 4 74 | Whether to include source in build image, False by default 75 | .RE 76 | .PP 77 | \fB\-\-package\fR 78 | .RS 4 79 | Install additional packages in the image 80 | .RE 81 | .PP 82 | \fB\-\-custom\-package\fR 83 | .RS 4 84 | Install package from DEB file into the image 85 | .RE 86 | .PP 87 | \fB\-\-build\-dir\fR 88 | .RS 4 89 | Directory to build images and create log file 90 | .RE 91 | .PP 92 | \fB\-\-log\-level\*(Aq\fR 93 | .RS 4 94 | The logging level \- choose one of (\*(Aqcritical\*(Aq, \*(Aqerror\*(Aq, \*(Aqwarn\*(Aq, \*(Aqinfo\*(Aq, \*(Aqdebug\*(Aq)\&. Default log level is \*(Aqdebug\*(Aq\&. 95 | .RE 96 | .PP 97 | \fB\-\-hostname\fR 98 | .RS 4 99 | Hostname to set inside the built images 100 | .RE 101 | .PP 102 | \fB\-\-sign\fR 103 | .RS 4 104 | Sign the images with default GPG key after building 105 | .RE 106 | .PP 107 | \fB\-\-force\fR 108 | .RS 4 109 | Force rebuild of images even when required image exists 110 | .RE 111 | .PP 112 | \fBtargets\fR 113 | .RS 4 114 | Image targets to build\&. Choose one or more of freedommaker, dreamplug, raspberry, raspberry2, beaglebone, cubieboard2, cubietruck, a20\-olinuxino\-lime, a20\-olinuxino\-lime2, a20\-olinuxino\-micro, i386, amd64, virtualbox\-i386, virtualbox\-amd64, qemu\-i386, qemu\-amd64, pcDuino3 115 | .RE 116 | .SH "EXAMPLES" 117 | .PP 118 | \fBExample\ \&1.\ \&Build BeagleBone image\fR 119 | .sp 120 | .if n \{\ 121 | .RS 4 122 | .\} 123 | .nf 124 | $ freedom\-maker beaglebone 125 | .fi 126 | .if n \{\ 127 | .RE 128 | .\} 129 | .PP 130 | Build a FreedomBox image for the BeagleBone Single Board Computer\&. 131 | .PP 132 | \fBExample\ \&2.\ \&Build all images\fR 133 | .sp 134 | .if n \{\ 135 | .RS 4 136 | .\} 137 | .nf 138 | $ python3 \-m freedommaker dreamplug raspberry raspberry2 139 | beaglebone cubieboard2 cubietruck a20\-olinuxino\-lime a20\-olinuxino\-lime2 140 | a20\-olinuxino\-micro i386 amd64 virtualbox\-i386 virtualbox\-amd64 141 | qemu\-i386 qemu\-amd64 pcDuino3 142 | .fi 143 | .if n \{\ 144 | .RE 145 | .\} 146 | .PP 147 | Build all the available FreedomBox images using freedom\-maker\&. 148 | .SH "BUGS" 149 | .PP 150 | See the 151 | \m[blue]\fBfreedom\-maker issue tracker\fR\m[]\&\s-2\u[1]\d\s+2 152 | for a full list of known issues and TODO items\&. 153 | .SH "AUTHOR" 154 | .PP 155 | FreedomBox Developers 156 | .SH "NOTES" 157 | .IP " 1." 4 158 | freedom-maker issue tracker 159 | .RS 4 160 | \%https://github.com/freedombox/freedom-maker/issues 161 | .RE 162 | -------------------------------------------------------------------------------- /debian/manpages: -------------------------------------------------------------------------------- 1 | debian/freedom-maker.1 2 | debian/passwd-in-image.1 3 | debian/vagrant-package.1 4 | -------------------------------------------------------------------------------- /debian/passwd-in-image.1: -------------------------------------------------------------------------------- 1 | '\" t 2 | .\" Title: passwd-in-image 3 | .\" Author: [see the "Author" section] 4 | .\" Generator: DocBook XSL Stylesheets v1.79.1 5 | .\" Date: 06/15/2017 6 | .\" Manual: FreedomBox Manual 7 | .\" Source: 0.8 8 | .\" Language: English 9 | .\" 10 | .TH "PASSWD\-IN\-IMAGE" "1" "06/15/2017" "0\&.8" "FreedomBox Manual" 11 | .\" ----------------------------------------------------------------- 12 | .\" * Define some portability stuff 13 | .\" ----------------------------------------------------------------- 14 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 | .\" http://bugs.debian.org/507673 16 | .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html 17 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | .ie \n(.g .ds Aq \(aq 19 | .el .ds Aq ' 20 | .\" ----------------------------------------------------------------- 21 | .\" * set default formatting 22 | .\" ----------------------------------------------------------------- 23 | .\" disable hyphenation 24 | .nh 25 | .\" disable justification (adjust text to left margin only) 26 | .ad l 27 | .\" ----------------------------------------------------------------- 28 | .\" * MAIN CONTENT STARTS HERE * 29 | .\" ----------------------------------------------------------------- 30 | .SH "NAME" 31 | passwd-in-image \- change password of a user inside a disk image file 32 | .SH "SYNOPSIS" 33 | .HP \w'\fBpasswd\-in\-image\fR\ 'u 34 | \fBpasswd\-in\-image\fR [\fBimage\fR] [\fBuser\fR] [\fB\-\-password\fR\ {PASSWORD}] [\fB\-h,\ \fR\fB\-\-help\fR] 35 | .SH "DESCRIPTION" 36 | .PP 37 | FreedomBox is a community project to develop, design and promote personal servers running free software for private, personal communications\&. It is a networking appliance designed to allow interfacing with the rest of the Internet under conditions of protected privacy and data security\&. It hosts applications such as blog, wiki, website, social network, email, web proxy and a Tor relay on a device that can replace a wireless router so that data stays with the users\&. 38 | .PP 39 | freedom\-maker is a tool to build FreedomBox images for various single board computers, virtual machines and general purpose computers\&. 40 | .PP 41 | Password in image script lets you change the password of a user in an image file generated by freedom\-maker\&. 42 | .SH "OPTIONS" 43 | .PP 44 | \fBimage\fR 45 | .RS 4 46 | Disk image file (\&.img or \&.vdi) inside which user manipulation is sought\&. 47 | .RE 48 | .PP 49 | \fBuser\fR 50 | .RS 4 51 | User account to change password for\&. 52 | .RE 53 | .PP 54 | \fB\-\-password PASSWORD\fR 55 | .RS 4 56 | New password for the user\&. 57 | .RE 58 | .SH "EXAMPLES" 59 | .PP 60 | \fBExample\ \&1.\ \&Change password for fbx default user in BeagleBone image\fR 61 | .sp 62 | .if n \{\ 63 | .RS 4 64 | .\} 65 | .nf 66 | $ passwd\-in\-image freedom\-maker\-beaglebone\&.img fbx 67 | \-\-password somepassword 68 | .fi 69 | .if n \{\ 70 | .RE 71 | .\} 72 | .PP 73 | Change the password of the user with username someuser to somepassword\&. 74 | .SH "BUGS" 75 | .PP 76 | See the 77 | \m[blue]\fBfreedom\-maker issue tracker\fR\m[]\&\s-2\u[1]\d\s+2 78 | for a full list of known issues and TODO items\&. 79 | .SH "AUTHOR" 80 | .PP 81 | FreedomBox Developers 82 | .SH "NOTES" 83 | .IP " 1." 4 84 | freedom-maker issue tracker 85 | .RS 4 86 | \%https://github.com/freedombox/freedom-maker/issues 87 | .RE 88 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | export DH_VERBOSE=1 4 | export PYBUILD_NAME=freedom-maker 5 | 6 | %: 7 | dh $@ --with python3 --buildsystem=pybuild 8 | 9 | # Skipping tests due to https://github.com/freedombox/freedom-maker/issues/102 10 | override_dh_auto_test: 11 | true 12 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/vagrant-package.1: -------------------------------------------------------------------------------- 1 | '\" t 2 | .\" Title: vagrant-package 3 | .\" Author: [see the "Author" section] 4 | .\" Generator: DocBook XSL Stylesheets v1.79.1 5 | .\" Date: 06/15/2017 6 | .\" Manual: FreedomBox Manual 7 | .\" Source: 0.8 8 | .\" Language: English 9 | .\" 10 | .TH "VAGRANT\-PACKAGE" "1" "06/15/2017" "0\&.8" "FreedomBox Manual" 11 | .\" ----------------------------------------------------------------- 12 | .\" * Define some portability stuff 13 | .\" ----------------------------------------------------------------- 14 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 | .\" http://bugs.debian.org/507673 16 | .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html 17 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | .ie \n(.g .ds Aq \(aq 19 | .el .ds Aq ' 20 | .\" ----------------------------------------------------------------- 21 | .\" * set default formatting 22 | .\" ----------------------------------------------------------------- 23 | .\" disable hyphenation 24 | .nh 25 | .\" disable justification (adjust text to left margin only) 26 | .ad l 27 | .\" ----------------------------------------------------------------- 28 | .\" * MAIN CONTENT STARTS HERE * 29 | .\" ----------------------------------------------------------------- 30 | .SH "NAME" 31 | vagrant-package \- convert VirtualBox disk image into Vagrant box 32 | .SH "SYNOPSIS" 33 | .HP \w'\fBvagrant\-package\fR\ 'u 34 | \fBvagrant\-package\fR [\fBimage\fR] [\fB\-\-output\fR\ {OUTPUT}] [\fB\-h,\ \fR\fB\-\-help\fR] 35 | .SH "DESCRIPTION" 36 | .PP 37 | FreedomBox is a community project to develop, design and promote personal servers running free software for private, personal communications\&. It is a networking appliance designed to allow interfacing with the rest of the Internet under conditions of protected privacy and data security\&. It hosts applications such as blog, wiki, website, social network, email, web proxy and a Tor relay on a device that can replace a wireless router so that data stays with the users\&. 38 | .PP 39 | freedom\-maker is a tool to build FreedomBox images for various single board computers, virtual machines and general purpose computers\&. 40 | .PP 41 | Vagrant Package script lets you convert a Virtualbox disk image into a Vagrant box\&. 42 | .SH "OPTIONS" 43 | .PP 44 | \fBimage\fR 45 | .RS 4 46 | Disk image file (\&.vdi) to be converted\&. 47 | .RE 48 | .PP 49 | \fB\-\-output OUTPUT\fR 50 | .RS 4 51 | Path of the output vagrant box file (default: package\&.box)\&. 52 | .RE 53 | .SH "EXAMPLES" 54 | .PP 55 | \fBExample\ \&1.\ \&Convert amd64 VirtualBox disk image into Vagrant box\fR 56 | .sp 57 | .if n \{\ 58 | .RS 4 59 | .\} 60 | .nf 61 | $ vagrant\-package freedom\-maker\-amd64\&.vdi \-\-output 62 | freedom\-maker\-amd64\&.box 63 | .fi 64 | .if n \{\ 65 | .RE 66 | .\} 67 | .SH "BUGS" 68 | .PP 69 | See the 70 | \m[blue]\fBfreedom\-maker issue tracker\fR\m[]\&\s-2\u[1]\d\s+2 71 | for a full list of known issues and TODO items\&. 72 | .SH "AUTHOR" 73 | .PP 74 | FreedomBox Developers 75 | .SH "NOTES" 76 | .IP " 1." 4 77 | freedom-maker issue tracker 78 | .RS 4 79 | \%https://github.com/freedombox/freedom-maker/issues 80 | .RE 81 | -------------------------------------------------------------------------------- /doc/freedom-maker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | freedom-maker 24 | 1 25 | FreedomBox Manual 26 | 0.8 27 | 28 | 29 | 30 | freedom-maker 31 | 32 | image builder for FreedomBox 33 | 34 | 35 | 36 | 37 | 38 | freedom-maker 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Description 60 | 61 | FreedomBox is a community project to develop, design and promote 62 | personal servers running free software for private, personal 63 | communications. It is a networking appliance designed to allow 64 | interfacing with the rest of the Internet under conditions of 65 | protected privacy and data security. It hosts applications such 66 | as blog, wiki, website, social network, email, web proxy and a 67 | Tor relay on a device that can replace a wireless router so that 68 | data stays with the users. 69 | 70 | 71 | freedom-maker is a tool to build FreedomBox images for various single 72 | board computers, virtual machines and general purpose computers. 73 | 74 | 75 | 76 | 77 | Options 78 | 79 | 80 | 81 | 82 | 83 | Path to vmdebootstrap executable 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | Build stamp to use on image file names 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | Size of the image to build 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | Debian mirror to use for building 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | Debian mirror to use in built image 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | Debian release to use in built image 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | Whether to include source in build image, 132 | False by default 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | Install additional packages in the image 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | Install package from DEB file into the image 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | Directory to build images and create log file 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | The logging level - choose one of ('critical', 165 | 'error', 'warn', 'info', 'debug'). Default log level 166 | is 'debug'. 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | Hostname to set inside the built images 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | Sign the images with default GPG key after building 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | Force rebuild of images even when required image exists 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | Image targets to build. Choose one or more of freedommaker, 199 | dreamplug, raspberry, raspberry2, raspberry3, beaglebone, 200 | cubieboard2, cubietruck, a20-olinuxino-lime, a20-olinuxino-lime2, 201 | a20-olinuxino-micro, i386, amd64, virtualbox-i386, virtualbox-amd64, 202 | qemu-i386, qemu-amd64, pcduino3, banana-pro 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | Examples 211 | 212 | 213 | Build BeagleBone image 214 | $ freedom-maker beaglebone 215 | 216 | Build a FreedomBox image for the BeagleBone Single Board Computer. 217 | 218 | 219 | 220 | 221 | Build all images 222 | $ python3 -m freedommaker dreamplug raspberry raspberry2 223 | raspberry3 beaglebone cubieboard2 cubietruck a20-olinuxino-lime 224 | a20-olinuxino-lime2 a20-olinuxino-micro i386 amd64 virtualbox-i386 225 | virtualbox-amd64 qemu-i386 qemu-amd64 pcduino3 banana-pro 226 | 227 | Build all the available FreedomBox images using freedom-maker. 228 | 229 | 230 | 231 | 232 | 233 | Bugs 234 | 235 | See the freedom-maker 236 | issue tracker for a full list of known issues and TODO items. 237 | 238 | 239 | 240 | 241 | Author 242 | 243 | 244 | FreedomBox Developers 245 | Original author 246 | 247 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /doc/passwd-in-image.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | passwd-in-image 24 | 1 25 | FreedomBox Manual 26 | 0.8 27 | 28 | 29 | 30 | passwd-in-image 31 | 32 | change password of a user inside a disk image file 33 | 34 | 35 | 36 | 37 | 38 | passwd-in-image 39 | 40 | 41 | PASSWORD 42 | 43 | 44 | 45 | 46 | 47 | Description 48 | 49 | FreedomBox is a community project to develop, design and promote 50 | personal servers running free software for private, personal 51 | communications. It is a networking appliance designed to allow 52 | interfacing with the rest of the Internet under conditions of 53 | protected privacy and data security. It hosts applications such 54 | as blog, wiki, website, social network, email, web proxy and a 55 | Tor relay on a device that can replace a wireless router so that 56 | data stays with the users. 57 | 58 | 59 | freedom-maker is a tool to build FreedomBox images for various single 60 | board computers, virtual machines and general purpose computers. 61 | 62 | 63 | Password in image script lets you change the password of a user in an 64 | image file generated by freedom-maker. 65 | 66 | 67 | 68 | 69 | Options 70 | 71 | 72 | 73 | 74 | 75 | Disk image file (.img or .vdi) inside which user manipulation is 76 | sought. 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | User account to change password for. 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | New password for the user. 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Examples 101 | 102 | 103 | Change password for fbx default user in BeagleBone image 104 | $ passwd-in-image freedom-maker-beaglebone.img fbx 105 | --password somepassword 106 | 107 | Change the password of the user with username someuser to somepassword. 108 | 109 | 110 | 111 | 112 | 113 | Bugs 114 | 115 | See the freedom-maker 117 | issue tracker for a full list of known issues and TODO items. 118 | 119 | 120 | 121 | 122 | Author 123 | 124 | 125 | FreedomBox Developers 126 | Original author 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /doc/vagrant-package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | vagrant-package 24 | 1 25 | FreedomBox Manual 26 | 0.8 27 | 28 | 29 | 30 | vagrant-package 31 | 32 | convert VirtualBox disk image into Vagrant box 33 | 34 | 35 | 36 | 37 | 38 | vagrant-package 39 | 40 | OUTPUT 41 | 42 | 43 | 44 | 45 | 46 | Description 47 | 48 | FreedomBox is a community project to develop, design and promote 49 | personal servers running free software for private, personal 50 | communications. It is a networking appliance designed to allow 51 | interfacing with the rest of the Internet under conditions of 52 | protected privacy and data security. It hosts applications such 53 | as blog, wiki, website, social network, email, web proxy and a 54 | Tor relay on a device that can replace a wireless router so that 55 | data stays with the users. 56 | 57 | 58 | freedom-maker is a tool to build FreedomBox images for various single 59 | board computers, virtual machines and general purpose computers. 60 | 61 | 62 | Vagrant Package script lets you convert a Virtualbox disk image into a 63 | Vagrant box. 64 | 65 | 66 | 67 | 68 | Options 69 | 70 | 71 | 72 | 73 | 74 | Disk image file (.vdi) to be converted. 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Path of the output vagrant box file (default: package.box). 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | Examples 91 | 92 | 93 | Convert amd64 VirtualBox disk image into Vagrant box 94 | $ vagrant-package freedom-maker-amd64.vdi --output 95 | freedom-maker-amd64.box 96 | 97 | 98 | 99 | 100 | Bugs 101 | 102 | See the freedom-maker 104 | issue tracker for a full list of known issues and TODO items. 105 | 106 | 107 | 108 | 109 | Author 110 | 111 | 112 | FreedomBox Developers 113 | Original author 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /freedommaker/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # This file is part of Freedom Maker. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | """ 20 | Tool to build FreedomBox images for various targets. 21 | """ 22 | 23 | from .application import Application 24 | 25 | __version__ = '0.9' 26 | 27 | __all__ = [ 28 | 'Application', 29 | 'main', 30 | ] 31 | 32 | def main(): 33 | Application().run() 34 | -------------------------------------------------------------------------------- /freedommaker/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # This file is part of Freedom Maker. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | """ 20 | Module to provide ability to run this freedommaker package. 21 | 22 | Run the package as: 23 | python3 -m freedommaker 24 | """ 25 | 26 | from .application import Application 27 | 28 | 29 | if __name__ == '__main__': 30 | Application().run() 31 | -------------------------------------------------------------------------------- /freedommaker/application.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # This file is part of Freedom Maker. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | """ 20 | Command line application wrapper over image builder. 21 | """ 22 | 23 | import argparse 24 | import datetime 25 | import logging 26 | import logging.config 27 | import os 28 | 29 | from .builder import ImageBuilder 30 | import freedommaker 31 | 32 | IMAGE_SIZE = '3800M' 33 | BUILD_MIRROR = 'http://deb.debian.org/debian' 34 | MIRROR = 'http://deb.debian.org/debian' 35 | DISTRIBUTION = 'unstable' 36 | DOWNLOAD_SOURCE = False 37 | INCLUDE_SOURCE = False 38 | BUILD_DIR = 'build' 39 | LOG_LEVEL = 'debug' 40 | HOSTNAME = 'freedombox' 41 | 42 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 43 | 44 | 45 | class Application(object): 46 | """Command line application to build FreedomBox images.""" 47 | def __init__(self): 48 | """Initialize object.""" 49 | self.arguments = None 50 | 51 | def run(self): 52 | """Parse the command line args and execute the command.""" 53 | self.parse_arguments() 54 | 55 | self.setup_logging() 56 | logger.info('Freedom Maker version - %s', freedommaker.__version__) 57 | 58 | try: 59 | logger.info('Creating directory - %s', self.arguments.build_dir) 60 | os.makedirs(self.arguments.build_dir) 61 | except os.error: 62 | pass 63 | 64 | for target in self.arguments.targets: 65 | logger.info('Building target - %s', target) 66 | 67 | cls = ImageBuilder.get_builder_class(target) 68 | if not cls: 69 | logger.warn('Unknown target - %s', target) 70 | continue 71 | 72 | builder = cls(self.arguments) 73 | self.setup_logging(builder.log_file) 74 | try: 75 | builder.build() 76 | logger.info('Target complete - %s', target) 77 | except: 78 | logger.error('Target failed - %s', target) 79 | raise 80 | finally: 81 | builder.cleanup() 82 | 83 | def parse_arguments(self): 84 | """Parse command line arguments.""" 85 | build_stamp = datetime.datetime.today().strftime('%Y-%m-%d') 86 | 87 | parser = argparse.ArgumentParser( 88 | description='FreedomMaker - Script to build FreedomBox images', 89 | formatter_class=argparse.ArgumentDefaultsHelpFormatter 90 | ) 91 | parser.add_argument( 92 | '--vmdebootstrap', default='vmdebootstrap', 93 | help='Path to vmdebootstrap executable') 94 | parser.add_argument( 95 | '--build-stamp', default=build_stamp, 96 | help='Build stamp to use on image file names') 97 | parser.add_argument( 98 | '--image-size', default=IMAGE_SIZE, 99 | help='Size of the image to build') 100 | parser.add_argument( 101 | '--build-mirror', default=BUILD_MIRROR, 102 | help='Debian mirror to use for building') 103 | parser.add_argument( 104 | '--mirror', default=MIRROR, 105 | help='Debian mirror to use in built image') 106 | parser.add_argument( 107 | '--distribution', default=DISTRIBUTION, 108 | help='Debian release to use in built image') 109 | parser.add_argument( 110 | '--download-source', action='store_true', default=DOWNLOAD_SOURCE, 111 | help='Whether to download source packages') 112 | parser.add_argument( 113 | '--include-source', action='store_true', default=INCLUDE_SOURCE, 114 | help='Whether to include source in build image') 115 | parser.add_argument( 116 | '--package', action='append', 117 | help='Install additional packages in the image') 118 | parser.add_argument( 119 | '--custom-package', action='append', 120 | help='Install package from DEB file into the image') 121 | parser.add_argument( 122 | '--build-dir', default=BUILD_DIR, 123 | help='Diretory to build images and create log file') 124 | parser.add_argument( 125 | '--log-level', default=LOG_LEVEL, help='Log level', 126 | choices=('critical', 'error', 'warn', 'info', 'debug')) 127 | parser.add_argument( 128 | '--hostname', default=HOSTNAME, 129 | help='Hostname to set inside the built images') 130 | parser.add_argument( 131 | '--sign', action='store_true', 132 | help='Sign the images with default GPG key after building') 133 | parser.add_argument( 134 | '--force', action='store_true', 135 | help='Force rebuild of images even when required image exists') 136 | parser.add_argument( 137 | '--build-in-ram', action='store_true', 138 | help='Build the image in RAM so that it is faster, requires ' 139 | 'free RAM about the size of disk image') 140 | parser.add_argument( 141 | 'targets', nargs='+', help='Image targets to build') 142 | 143 | self.arguments = parser.parse_args() 144 | 145 | def setup_logging(self, log_file=None): 146 | """Setup logging.""" 147 | config = { 148 | 'version': 1, 149 | 'formatters': { 150 | 'date': { 151 | 'format': '%(asctime)s - %(levelname)s - %(message)s' 152 | } 153 | }, 154 | 'handlers': { 155 | 'console': { 156 | 'class': 'logging.StreamHandler', 157 | 'formatter': 'date', 158 | }, 159 | }, 160 | 'root': { 161 | 'level': self.arguments.log_level.upper(), 162 | 'handlers': ['console'], 163 | }, 164 | 'disable_existing_loggers': False 165 | } 166 | logging.config.dictConfig(config) 167 | -------------------------------------------------------------------------------- /freedommaker/builder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # This file is part of Freedom Maker. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | """ 20 | Worker class to run various command build the image. 21 | """ 22 | 23 | import logging 24 | import os 25 | import shutil 26 | import subprocess 27 | import tempfile 28 | 29 | from . import vmdb2 30 | from . import vmdebootstrap 31 | 32 | BASE_PACKAGES = [ 33 | 'initramfs-tools', 34 | ] 35 | 36 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 37 | 38 | 39 | class ImageBuilder(object): # pylint: disable=too-many-instance-attributes 40 | """Base for all image builders.""" 41 | architecture = None 42 | machine = 'all' 43 | free = True 44 | 45 | builder_backend = 'vmdebootstrap' 46 | root_filesystem_type = 'btrfs' 47 | boot_filesystem_type = None 48 | boot_size = None 49 | boot_offset = None 50 | kernel_flavor = 'default' 51 | debootstrap_variant = None 52 | 53 | @classmethod 54 | def get_target_name(cls): 55 | """Return the command line name of target for this builder.""" 56 | return None 57 | 58 | @classmethod 59 | def get_builder_class(cls, target): 60 | """Return an builder class given target name.""" 61 | for cls in cls.get_subclasses(): 62 | if cls.get_target_name() == target: 63 | return cls 64 | 65 | @classmethod 66 | def get_subclasses(cls): 67 | """Iterate through the subclasses of this class.""" 68 | for subclass in cls.__subclasses__(): 69 | yield subclass 70 | yield from subclass.get_subclasses() 71 | 72 | def __init__(self, arguments): 73 | """Initialize object.""" 74 | self.arguments = arguments 75 | self.packages = BASE_PACKAGES 76 | 77 | self.ram_directory = None 78 | 79 | self.builder_backends = {} 80 | self.builder_backends['vmdebootstrap'] = \ 81 | vmdebootstrap.VmdebootstrapBuilderBackend(self) 82 | self.builder_backends['vmdb2'] = vmdb2.Vmdb2BuilderBackend(self) 83 | 84 | self.image_file = os.path.join( 85 | self.arguments.build_dir, self._get_image_base_name() + '.img') 86 | self.log_file = os.path.join( 87 | self.arguments.build_dir, self._get_image_base_name() + '.log') 88 | 89 | # Setup logging 90 | formatter = logging.root.handlers[0].formatter 91 | self.log_handler = logging.FileHandler( 92 | filename=self.log_file, mode='a') 93 | self.log_handler.setFormatter(formatter) 94 | logger.addHandler(self.log_handler) 95 | 96 | self.customization_script = os.path.join( 97 | os.path.dirname(__file__), 'freedombox-customize') 98 | 99 | def cleanup(self): 100 | """Finalize tasks.""" 101 | logger.info('Cleaning up') 102 | if self.ram_directory: 103 | self._run(['sudo', 'umount', self.ram_directory.name]) 104 | self.ram_directory.cleanup() 105 | self.ram_directory = None 106 | 107 | logger.removeHandler(self.log_handler) 108 | 109 | def build(self): 110 | """Run the image building process.""" 111 | # Create empty log file owned by process runner 112 | open(self.log_file, 'w').close() 113 | 114 | archive_file = self.image_file + '.xz' 115 | if not self.should_skip_step(archive_file): 116 | self.make_image() 117 | self.compress(archive_file, self.image_file) 118 | else: 119 | logger.info('Compressed image exists, skipping') 120 | 121 | self.sign(archive_file) 122 | 123 | def make_image(self): 124 | """Call a builder backend to create basic image.""" 125 | self.builder_backends[self.builder_backend].make_image() 126 | 127 | def _get_image_base_name(self): 128 | """Return the base file name of the final image.""" 129 | free_tag = 'free' if self.free else 'nonfree' 130 | 131 | return 'freedombox-{distribution}-{free_tag}_{build_stamp}_{machine}' \ 132 | '-{architecture}'.format( 133 | distribution=self.arguments.distribution, free_tag=free_tag, 134 | build_stamp=self.arguments.build_stamp, machine=self.machine, 135 | architecture=self.architecture) 136 | 137 | def get_temp_image_file(self): 138 | """Get the temporary path to where the image should be built. 139 | 140 | If building to RAM is enabled, create a temporary directory, mount 141 | tmpfs in it and return a path in that directory. This is so that builds 142 | that happen in RAM will be faster. 143 | 144 | If building to RAM is disabled, append .temp to the final file name and 145 | return it. 146 | 147 | """ 148 | if not self.arguments.build_in_ram: 149 | return self.image_file + '.temp' 150 | 151 | self.ram_directory = tempfile.TemporaryDirectory() 152 | self._run( 153 | ['sudo', 'mount', '-t', 'tmpfs', 'tmpfs', self.ram_directory.name]) 154 | return os.path.join(self.ram_directory.name, 155 | os.path.basename(self.image_file)) 156 | 157 | def compress(self, archive_file, image_file): 158 | """Compress the generate image.""" 159 | if self.should_skip_step(archive_file, [image_file]): 160 | logger.info('Compressed image exists, skipping compression - %s', 161 | archive_file) 162 | return 163 | 164 | command = ['xz', '--no-warn', '--best', '--force'] 165 | if shutil.which('pxz'): 166 | command = ['pxz', '-9', '--force'] 167 | 168 | self._run(command + [image_file]) 169 | 170 | def sign(self, archive): 171 | """Signed the final output image.""" 172 | if not self.arguments.sign: 173 | return 174 | 175 | signature = archive + '.sig' 176 | 177 | if self.should_skip_step(signature, [archive]): 178 | logger.info('Signature file up-to-date, skipping - %s', signature) 179 | return 180 | 181 | try: 182 | os.remove(signature) 183 | except FileNotFoundError: 184 | pass 185 | 186 | self._run(['gpg', '--output', signature, '--detach-sig', archive]) 187 | 188 | def should_skip_step(self, target, dependencies=None): 189 | """Check whether a given build step may be skipped.""" 190 | # Check forced rebuild 191 | if self.arguments.force: 192 | return False 193 | 194 | # Check if target exists 195 | if not os.path.isfile(target): 196 | return False 197 | 198 | # Check if a dependency is newer than the target 199 | for dependency in (dependencies or []): 200 | if os.path.getmtime(dependency) > os.path.getmtime(target): 201 | return False 202 | 203 | return True 204 | 205 | @staticmethod 206 | def _replace_extension(file_name, new_extension): 207 | """Replace a file's extension with a new extention.""" 208 | return file_name.rsplit('.', maxsplit=1)[0] + new_extension 209 | 210 | def _run(self, *args, **kwargs): 211 | """Execute a program and log output to log file.""" 212 | logger.info('Executing command - %s', args) 213 | with open(self.log_file, 'a') as file_handle: 214 | subprocess.check_call(*args, stdout=file_handle, 215 | stderr=file_handle, **kwargs) 216 | 217 | 218 | class AMDIntelImageBuilder(ImageBuilder): 219 | """Base image build for all Intel/AMD targets.""" 220 | boot_loader = 'grub' 221 | 222 | @classmethod 223 | def get_target_name(cls): 224 | """Return the name of the target for an image builder.""" 225 | return getattr(cls, 'architecture', None) 226 | 227 | 228 | class AMD64ImageBuilder(AMDIntelImageBuilder): 229 | """Image builder for all amd64 targets.""" 230 | architecture = 'amd64' 231 | 232 | 233 | class I386ImageBuilder(AMDIntelImageBuilder): 234 | """Image builder for all i386 targets.""" 235 | architecture = 'i386' 236 | kernel_flavor = '686' 237 | 238 | 239 | class VMImageBuilder(AMDIntelImageBuilder): 240 | """Base image builder for all virtual machine targets.""" 241 | vm_image_extension = None 242 | 243 | def build(self): 244 | """Run the image building process.""" 245 | archive_file = self.image_file + '.xz' 246 | vm_file = self._replace_extension( 247 | self.image_file, self.vm_image_extension) 248 | vm_archive_file = vm_file + '.xz' 249 | 250 | # Create empty log file owned by process runner 251 | open(self.log_file, 'w').close() 252 | 253 | if not self.should_skip_step(vm_archive_file): 254 | if not self.should_skip_step(self.image_file): 255 | if self.should_skip_step(archive_file): 256 | logger.info('Compressed image exists, uncompressing - %s', 257 | archive_file) 258 | self._run(['unxz', '--keep', archive_file]) 259 | else: 260 | self.make_image() 261 | else: 262 | logger.info('Pre-built image exists, skipping build - %s', 263 | self.image_file) 264 | 265 | self.create_vm_file(self.image_file, vm_file) 266 | os.remove(self.image_file) 267 | self.compress(vm_archive_file, vm_file) 268 | else: 269 | logger.info('Compressed VM image exists, skipping - %s', 270 | vm_archive_file) 271 | 272 | self.sign(vm_archive_file) 273 | 274 | def create_vm_file(self, image_file, vm_file): 275 | """Create a VM image from image file.""" 276 | raise Exception('Not reached') 277 | 278 | 279 | class VirtualBoxImageBuilder(VMImageBuilder): 280 | """Base image builder for all VirutalBox targets.""" 281 | vm_image_extension = '.vdi' 282 | 283 | @classmethod 284 | def get_target_name(cls): 285 | """Return the name of the target for an image builder.""" 286 | if getattr(cls, 'architecture', None): 287 | return 'virtualbox-' + cls.architecture 288 | 289 | def create_vm_file(self, image_file, vm_file): 290 | """Create a VM file from image file.""" 291 | if self.should_skip_step(vm_file, [image_file]): 292 | logger.info('VM file exists, skipping conversion - %s', vm_file) 293 | return 294 | 295 | self._run(['VBoxManage', 'convertdd', image_file, vm_file]) 296 | 297 | 298 | class VirtualBoxAmd64ImageBuilder(VirtualBoxImageBuilder): 299 | """Image builder for all VirutalBox amd64 targets.""" 300 | architecture = 'amd64' 301 | 302 | 303 | class VirtualBoxI386ImageBuilder(VirtualBoxImageBuilder): 304 | """Image builder for all VirutalBox i386 targets.""" 305 | architecture = 'i386' 306 | kernel_flavor = '686' 307 | 308 | 309 | class VagrantImageBuilder(VirtualBoxAmd64ImageBuilder): 310 | """Image builder for Vagrant package.""" 311 | vagrant_extension = '.box' 312 | 313 | @classmethod 314 | def get_target_name(cls): 315 | """Return the name of the target for an image builder.""" 316 | return 'vagrant' 317 | 318 | def build(self): 319 | """Run the image building process.""" 320 | archive_file = self.image_file + '.xz' 321 | vm_file = self._replace_extension( 322 | self.image_file, self.vm_image_extension) 323 | vm_archive_file = vm_file + '.xz' 324 | vagrant_file = self._replace_extension( 325 | self.image_file, self.vagrant_extension) 326 | 327 | # Create empty log file owned by process runner 328 | open(self.log_file, 'w').close() 329 | 330 | if self.should_skip_step(vagrant_file): 331 | logger.info('Vagrant package exists, skipping - %s', 332 | vagrant_file) 333 | return 334 | 335 | if self.should_skip_step(vm_file): 336 | logger.info('VM image exists, skipping - %s', vm_archive_file) 337 | self.vagrant_package(vm_file, vagrant_file) 338 | return 339 | 340 | if self.should_skip_step(vm_archive_file): 341 | logger.info('Compressed VM image exists, skipping - %s', 342 | vm_archive_file) 343 | self._run(['unxz', '--keep', vm_archive_file]) 344 | self.vagrant_package(vm_file, vagrant_file) 345 | return 346 | 347 | if self.should_skip_step(self.image_file): 348 | logger.info( 349 | 'Pre-built image exists, skipping build - %s', self.image_file) 350 | self.create_vm_file(self.image_file, vm_file) 351 | self.vagrant_package(vm_file, vagrant_file) 352 | return 353 | 354 | if self.should_skip_step(archive_file): 355 | logger.info( 356 | 'Compressed image exists, uncompressing - %s', archive_file) 357 | self._run(['unxz', '--keep', archive_file]) 358 | self.create_vm_file(self.image_file, vm_file) 359 | os.remove(self.image_file) 360 | self.vagrant_package(vm_file, vagrant_file) 361 | return 362 | 363 | self.make_image() 364 | self.create_vm_file(self.image_file, vm_file) 365 | os.remove(self.image_file) 366 | self.vagrant_package(vm_file, vagrant_file) 367 | 368 | def vagrant_package(self, vm_file, vagrant_file): 369 | """Create a vagrant package from VM file.""" 370 | self._run(['sudo', 'bin/vagrant-package', '--output', vagrant_file, 371 | vm_file]) 372 | 373 | 374 | class QemuImageBuilder(VMImageBuilder): 375 | """Base image builder for all Qemu targets.""" 376 | vm_image_extension = '.qcow2' 377 | 378 | @classmethod 379 | def get_target_name(cls): 380 | """Return the name of the target for an image builder.""" 381 | if getattr(cls, 'architecture', None): 382 | return 'qemu-' + cls.architecture 383 | 384 | def create_vm_file(self, image_file, vm_file): 385 | """Create a VM image file from image file.""" 386 | if self.should_skip_step(vm_file, [image_file]): 387 | logger.info('VM file exists, skipping conversion - %s', vm_file) 388 | return 389 | 390 | self._run(['qemu-img', 'convert', '-O', 'qcow2', image_file, vm_file]) 391 | 392 | 393 | class QemuAmd64ImageBuilder(QemuImageBuilder): 394 | """Image builder for all Qemu amd64 targets.""" 395 | architecture = 'amd64' 396 | 397 | 398 | class QemuI386ImageBuilder(QemuImageBuilder): 399 | """Image builder for all Qemu i386 targets.""" 400 | architecture = 'i386' 401 | kernel_flavor = '686' 402 | 403 | 404 | class ARMImageBuilder(ImageBuilder): 405 | """Base image builder for all ARM targets.""" 406 | boot_loader = 'u-boot' 407 | boot_filesystem_type = 'ext2' 408 | boot_size = '128M' 409 | 410 | @classmethod 411 | def get_target_name(cls): 412 | """Return the name of the target for an image builder.""" 413 | return getattr(cls, 'machine', None) 414 | 415 | 416 | class BeagleBoneImageBuilder(ARMImageBuilder): 417 | """Image builder for BeagleBone target.""" 418 | architecture = 'armhf' 419 | machine = 'beaglebone' 420 | kernel_flavor = 'armmp' 421 | boot_offset = '2mib' 422 | 423 | 424 | class A20ImageBuilder(ARMImageBuilder): 425 | """Base image builder for all Allwinner A20 board based targets.""" 426 | architecture = 'armhf' 427 | kernel_flavor = 'armmp-lpae' 428 | boot_offset = '1mib' 429 | 430 | 431 | class A20OLinuXinoLimeImageBuilder(A20ImageBuilder): 432 | """Image builder for A20 OLinuXino Lime targets.""" 433 | machine = 'a20-olinuxino-lime' 434 | 435 | 436 | class A20OLinuXinoLime2ImageBuilder(A20ImageBuilder): 437 | """Image builder for A20 OLinuXino Lime2 targets.""" 438 | machine = 'a20-olinuxino-lime2' 439 | 440 | 441 | class A20OLinuXinoMicroImageBuilder(A20ImageBuilder): 442 | """Image builder for A20 OLinuXino Micro targets.""" 443 | machine = 'a20-olinuxino-micro' 444 | 445 | 446 | class BananaProImageBuilder(A20ImageBuilder): 447 | """Image builder for Banana Pro target.""" 448 | machine = 'banana-pro' 449 | 450 | 451 | class Cubieboard2ImageBuilder(A20ImageBuilder): 452 | """Image builder for Cubieboard 2 target.""" 453 | machine = 'cubieboard2' 454 | 455 | 456 | class CubietruckImageBuilder(A20ImageBuilder): 457 | """Image builder for Cubietruck (Cubieboard 3) target.""" 458 | machine = 'cubietruck' 459 | 460 | 461 | class PCDuino3ImageBuilder(A20ImageBuilder): 462 | """Image builder for PCDuino3 target.""" 463 | machine = 'pcduino3' 464 | 465 | 466 | class DreamPlugImageBuilder(ARMImageBuilder): 467 | """Image builder for DreamPlug target.""" 468 | architecture = 'armel' 469 | machine = 'dreamplug' 470 | kernel_flavor = 'marvell' 471 | boot_filesystem_type = 'vfat' 472 | 473 | 474 | class RaspberryPiImageBuilder(ARMImageBuilder): 475 | """Image builder for Raspberry Pi target.""" 476 | architecture = 'armel' 477 | machine = 'raspberry' 478 | free = False 479 | boot_loader = None 480 | root_filesystem_type = 'ext4' 481 | boot_filesystem_type = 'vfat' 482 | kernel_flavor = None 483 | 484 | 485 | class RaspberryPi2ImageBuilder(ARMImageBuilder): 486 | """Image builder for Raspberry Pi 2 target.""" 487 | architecture = 'armhf' 488 | machine = 'raspberry2' 489 | free = False 490 | boot_offset = '64mib' 491 | kernel_flavor = 'armmp' 492 | 493 | 494 | class RaspberryPi3ImageBuilder(ARMImageBuilder): 495 | """Image builder for Raspberry Pi 3 target.""" 496 | architecture = 'armhf' 497 | machine = 'raspberry3' 498 | free = False 499 | boot_offset = '64mib' 500 | kernel_flavor = 'armmp' 501 | -------------------------------------------------------------------------------- /freedommaker/freedombox-customize: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | set -e 17 | set -x 18 | set -o pipefail 19 | 20 | set_apt_sources() { 21 | NEW_MIRROR="$1" 22 | COMPONENTS="main" 23 | if [ "$ENABLE_NONFREE" = "yes" ] 24 | then 25 | COMPONENTS="main contrib non-free" 26 | fi 27 | 28 | cat < etc/apt/sources.list 29 | deb $NEW_MIRROR $SUITE $COMPONENTS 30 | deb-src $NEW_MIRROR $SUITE $COMPONENTS 31 | EOF 32 | 33 | if [ "$SUITE" != "unstable" ] && [ "$SUITE" != "sid" ]; then 34 | cat <> etc/apt/sources.list 35 | 36 | deb http://security.debian.org/ $SUITE/updates $COMPONENTS 37 | deb-src http://security.debian.org/ $SUITE/updates $COMPONENTS 38 | 39 | deb $NEW_MIRROR $SUITE-updates $COMPONENTS 40 | deb-src $NEW_MIRROR $SUITE-updates $COMPONENTS 41 | EOF 42 | fi 43 | } 44 | 45 | atheros_wifi() { 46 | # Fetch and install free software firmware for a couple USB Atheros 47 | # Wi-Fi devices from Trisqel repository. 48 | firmware_filename="open-ath9k-htc-firmware_1.3-1_all.deb" 49 | firmware_hash='5fea58ffefdf0ef15b504db7fbe3bc078c03e0d927bba64085e4b6f2546102f5' 50 | 51 | firmware_url="http://us.archive.trisquel.info/trisquel/pool/main/o/open-ath9k-htc-firmware/$firmware_filename" 52 | firmware_tempfile="/tmp/$firmware_filename" 53 | wget "$firmware_url" -O "$rootdir$firmware_tempfile" 54 | downloaded_firmware_hash=$(sha256sum "$rootdir$firmware_tempfile" | awk -F ' ' '{print $1}') 55 | if [ "$downloaded_firmware_hash" = "$firmware_hash" ]; then 56 | chroot "$rootdir" dpkg -i "$firmware_tempfile" 57 | return 58 | fi 59 | echo 'Atheros wifi firmware download verification failed' 60 | fuser -mvk $rootdir/. || true 61 | exit 1 62 | } 63 | 64 | mount_file_systems() { 65 | mount /dev -t devfs -o bind "$rootdir/dev" 66 | mount /dev/pts -t devpts -o bind "$rootdir/dev/pts" 67 | mount /proc -t proc -o bind "$rootdir/proc" 68 | mount /run -t run -o bind "$rootdir/run" 69 | mount /sys -t sys -o bind "$rootdir/sys" 70 | } 71 | 72 | unmount_file_systems() { 73 | # XXX: Should be doing strict checks but /dev/pts seems to be 74 | # prematurely unmounted for some reason perhaps due to issues in 75 | # parallel builds. Workaround that. 76 | umount "$rootdir/dev/pts" || true 77 | umount "$rootdir/dev" || true 78 | umount "$rootdir/proc" || true 79 | umount "$rootdir/run" || true 80 | umount "$rootdir/sys" || true 81 | 82 | case "$MACHINE" in 83 | raspberry2 | raspberry3) 84 | umount "$rootdir/boot/firmware" || true 85 | ;; 86 | esac 87 | } 88 | 89 | make_source_tarball() { 90 | # Make source packages available outside of image. 91 | ( 92 | cd "$rootdir/usr/src/packages" 93 | tar -czvf "$1" ./* 94 | ) 95 | } 96 | 97 | rootdir="$1" 98 | image="$(cd "$(dirname "$2")"; pwd)/$(basename "$2")" 99 | 100 | # Create vfat /boot/firmware partition for devices that need it. 101 | case "$MACHINE" in 102 | raspberry2 | raspberry3) 103 | umount "$rootdir/boot" 104 | umount "$rootdir" 105 | kpartx -dvs "$image" 106 | 107 | parted -s "$image" mkpart primary 0% 60MiB 108 | 109 | # Reorder partitions by start offset 110 | sfdisk --reorder "$image" 111 | 112 | device=/dev/mapper/$(kpartx -avs "$image" \ 113 | | awk '/^add map / {print $3; exit}') 114 | mkfs -t vfat "$device" 115 | parted -s "$image" set 1 lba on 116 | 117 | mount -t btrfs "${device%?}3" "$rootdir" 118 | mount -t ext2 "${device%?}2" "$rootdir"/boot 119 | mkdir "$rootdir/boot/firmware" 120 | mount -t vfat "$device" "$rootdir/boot/firmware" 121 | 122 | fs_uuid=$(blkid -c /dev/null -o value -s UUID "$device") 123 | echo "UUID=$fs_uuid /boot/firmware vfat errors=remount-ro 0 3" \ 124 | >>"$rootdir"/etc/fstab 125 | ;; 126 | esac 127 | 128 | mount_file_systems 129 | trap unmount_file_systems EXIT 130 | 131 | cd "$rootdir" 132 | 133 | echo info: building $MACHINE 134 | 135 | export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true 136 | export LC_ALL=C LANGUAGE=C LANG=C 137 | 138 | # Override libpam-tmpdir setting during build, as the directories 139 | # are not created yet. 140 | export TMP=/tmp/ TMPDIR=/tmp/ 141 | 142 | username=fbx 143 | echo "info: creating initial user $username with disabled password!" 144 | chroot $rootdir adduser --gecos $username --disabled-password $username 145 | chroot $rootdir adduser $username sudo 146 | 147 | case "$MACHINE" in 148 | virtualbox) 149 | # hide irrelevant console keyboard messages. 150 | echo "echo \"4 4 1 7\" > /proc/sys/kernel/printk" \ 151 | >> /etc/init.d/rc.local 152 | ;; 153 | esac 154 | 155 | set_apt_sources $BUILD_MIRROR 156 | chroot $rootdir apt-get update 157 | 158 | # Set a flag to indicate that this is a FreedomBox image 159 | # and FreedomBox is not installed using a Debian package 160 | mkdir -p $rootdir/var/lib/freedombox 161 | touch $rootdir/var/lib/freedombox/is-freedombox-disk-image 162 | 163 | cat > $rootdir/usr/sbin/policy-rc.d <&1 | \ 188 | tee $rootdir/var/log/hardware-setup.log 189 | 190 | rm $rootdir/usr/sbin/policy-rc.d 191 | 192 | # freedombox-setup up to version 0.10 had setup steps. 193 | # Setup is delegated to Plinth in later versions. 194 | if [ -f $rootdir/usr/lib/freedombox/setup ]; then 195 | chroot $rootdir /usr/lib/freedombox/setup 2>&1 | \ 196 | tee $rootdir/var/log/freedombox-setup.log 197 | fi 198 | 199 | if [ 'true' = "$SOURCE" ] ; then 200 | make_source_tarball "${image%.img.temp}"-source.tar.gz 201 | 202 | if [ 'false' = "$SOURCE_IN_IMAGE" ] ; then 203 | # Remove source packages from image. 204 | rm -rf "$rootdir/usr/src/packages" 205 | fi 206 | fi 207 | 208 | # Remove SSH keys from the image, freedomxbox-setup does not do that 209 | # anymore. 210 | rm $rootdir/etc/ssh/ssh_host_* || true 211 | 212 | # Move hostname to 127.0.1.1 line of /etc/hosts. 213 | # TODO: Can this be changed in vmdebootstrap? 214 | sed -i "s/127.0.0.1.*/127.0.0.1 localhost/" "$rootdir"/etc/hosts 215 | if ! grep -q 127.0.1.1 "$rootdir"/etc/hosts ; then 216 | sed -i "/127.0.0.1.*/a \ 217 | 127.0.1.1 freedombox" "$rootdir"/etc/hosts 218 | fi 219 | 220 | # copy u-boot to beginning of image 221 | case "$MACHINE" in 222 | beaglebone) 223 | dd if=$rootdir/usr/lib/u-boot/am335x_boneblack/MLO of="$image" \ 224 | count=1 seek=1 conv=notrunc bs=128k 225 | dd if=$rootdir/usr/lib/u-boot/am335x_boneblack/u-boot.img of="$image" \ 226 | count=2 seek=1 conv=notrunc bs=384k 227 | ;; 228 | cubietruck) 229 | dd if=$rootdir/usr/lib/u-boot/Cubietruck/u-boot-sunxi-with-spl.bin of="$image" \ 230 | seek=8 conv=notrunc bs=1k 231 | ;; 232 | a20-olinuxino-lime) 233 | dd if=$rootdir/usr/lib/u-boot/A20-OLinuXino-Lime/u-boot-sunxi-with-spl.bin \ 234 | of="$image" seek=8 conv=notrunc bs=1k 235 | ;; 236 | a20-olinuxino-lime2) 237 | dd if=$rootdir/usr/lib/u-boot/A20-OLinuXino-Lime2/u-boot-sunxi-with-spl.bin \ 238 | of="$image" seek=8 conv=notrunc bs=1k 239 | ;; 240 | a20-olinuxino-micro) 241 | dd if=$rootdir/usr/lib/u-boot/A20-OLinuXino_MICRO/u-boot-sunxi-with-spl.bin \ 242 | of="$image" seek=8 conv=notrunc bs=1k 243 | ;; 244 | banana-pro) 245 | dd if=$rootdir/usr/lib/u-boot/Bananapro/u-boot-sunxi-with-spl.bin \ 246 | of="$image" seek=8 conv=notrunc bs=1k 247 | ;; 248 | cubieboard2) 249 | dd if=$rootdir/usr/lib/u-boot/Cubieboard2/u-boot-sunxi-with-spl.bin of="$image" \ 250 | seek=8 conv=notrunc bs=1k 251 | ;; 252 | pcduino3) 253 | dd if=$rootdir/usr/lib/u-boot/Linksprite_pcDuino3/u-boot-sunxi-with-spl.bin of="$image" \ 254 | seek=8 conv=notrunc bs=1k 255 | ;; 256 | esac 257 | 258 | set_apt_sources $MIRROR 259 | chroot $rootdir apt-get update 260 | 261 | cd / 262 | echo "info: killing leftover processes in chroot" 263 | # 2014-11-04 this killed /usr/lib/erlang/erts-6.2/bin/epmd, see 264 | # to learn more. 265 | fuser -mvk $rootdir/. || true 266 | -------------------------------------------------------------------------------- /freedommaker/hardware-setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | set -e 17 | set -x 18 | set -o pipefail 19 | 20 | # Raspberry Pi blob repo 21 | rpi_blob_repo='https://github.com/Hexxeh/rpi-update' 22 | rpi_blob_commit='31615deb9406ffc3ab823e76d12dedf373c8e087' 23 | 24 | # Expected sha256 hash for rpi-update 25 | rpi_blob_hash='9868671978541ae6efa692d087028ee5cc5019c340296fdd17793160b6cf403f' 26 | 27 | enable_serial_console() { 28 | # By default, spawn a console on the serial port 29 | device="$1" 30 | echo "Adding a getty on the serial port" 31 | echo "T0:12345:respawn:/sbin/getty -L $device 115200 vt100" >> /etc/inittab 32 | } 33 | 34 | dreamplug_flash() { 35 | # allow flash-kernel to work without valid /proc contents 36 | # ** this doesn't *really* work, since there are too many checks 37 | # that fail in an emulated environment! We'll have to do it by 38 | # hand below anyway... 39 | export FK_MACHINE="Globalscale Technologies Dreamplug" 40 | apt-get install -y flash-kernel 41 | } 42 | 43 | dreamplug_repack_kernel() { 44 | # process installed kernel to create uImage, uInitrd, dtb 45 | # using flash-kernel would be a good approach, except it fails in the 46 | # cross build environment due to too many environment checks... 47 | #FK_MACHINE="Globalscale Technologies Dreamplug" flash-kernel 48 | # so, let's do it manually... 49 | 50 | # flash-kernel's hook-functions provided to mkinitramfs have the 51 | # unfortunate side-effect of creating /conf/param.conf in the initrd 52 | # when run from our emulated chroot environment, which means our root= 53 | # on the kernel command line is completely ignored! repack the initrd 54 | # to remove this evil... 55 | 56 | echo "info: repacking dreamplug kernel and initrd" 57 | 58 | kernelVersion=$(ls /usr/lib/*/kirkwood-dreamplug.dtb | head -1 | cut -d/ -f4) 59 | version=$(echo $kernelVersion | sed 's/linux-image-\(.*\)/\1/') 60 | initRd=initrd.img-$version 61 | vmlinuz=vmlinuz-$version 62 | 63 | mkdir /tmp/initrd-repack 64 | 65 | (cd /tmp/initrd-repack ; \ 66 | zcat /boot/$initRd | cpio -i ; \ 67 | rm -f conf/param.conf ; \ 68 | find . | cpio --quiet -o -H newc | \ 69 | gzip -9 > /boot/$initRd ) 70 | 71 | rm -rf /tmp/initrd-repack 72 | 73 | (cd /boot ; \ 74 | cp /usr/lib/$kernelVersion/kirkwood-dreamplug.dtb dtb ; \ 75 | cat $vmlinuz dtb >> temp-kernel ; \ 76 | mkimage -A arm -O linux -T kernel -n "Debian kernel ${version}" \ 77 | -C none -a 0x8000 -e 0x8000 -d temp-kernel uImage ; \ 78 | rm -f temp-kernel ; \ 79 | mkimage -A arm -O linux -T ramdisk -C gzip -a 0x0 -e 0x0 \ 80 | -n "Debian ramdisk ${version}" \ 81 | -d $initRd uInitrd ) 82 | } 83 | 84 | # Install binary blob and kernel needed to boot on the Raspberry Pi. 85 | raspberry_setup_boot() { 86 | # Packages used by rpi-update to make Raspberry Pi bootable 87 | apt-get install -y git-core binutils ca-certificates wget kmod 88 | 89 | rpi_tempdir=/tmp/fbx-rpi-update 90 | if [ -d $rpi_tempdir ]; then 91 | rm -rf $rpi_tempdir 92 | fi 93 | git clone $rpi_blob_repo $rpi_tempdir 94 | cd $rpi_tempdir 95 | git checkout $rpi_blob_commit -b $rpi_blob_commit 96 | 97 | downloaded_rpi_blob_hash=$(sha256sum $rpi_tempdir/rpi-update | awk -F ' ' '{print $1}') 98 | if [ "$downloaded_rpi_blob_hash" != "$rpi_blob_hash" ]; then 99 | echo 'WARNING: Unable to verify Raspberry Pi boot blob' 100 | return 101 | fi 102 | 103 | cp $rpi_tempdir/rpi-update /usr/bin/rpi-update 104 | 105 | chmod a+x /usr/bin/rpi-update 106 | mkdir -p /lib/modules 107 | touch /boot/start.elf 108 | SKIP_BACKUP=1 SKIP_WARNING=1 rpi-update | tee /root/rpi-update.log 109 | } 110 | 111 | # Install binary blob and u-boot needed to boot on the Raspberry Pi 2. 112 | raspberry2or3_setup_boot() { 113 | pi_version="$1" 114 | 115 | # install boot firmware 116 | apt-get install --no-install-recommends -y dpkg-dev 117 | cd /tmp 118 | apt-get source raspi3-firmware 119 | cp raspi3-firmware*/boot/* /boot/firmware 120 | rm -rf raspi3-firmware* 121 | cd / 122 | 123 | # remove unneeded firmware files 124 | rm -f /boot/firmware/fixup_* 125 | rm -f /boot/firmware/start_* 126 | 127 | # u-boot setup 128 | apt-get install -y u-boot-rpi 129 | case "$pi_version" in 130 | raspberry2) 131 | cp /usr/lib/u-boot/rpi_2/u-boot.bin /boot/firmware/kernel.img 132 | ;; 133 | raspberry3) 134 | cp /usr/lib/u-boot/rpi_3_32b/u-boot.bin /boot/firmware/kernel.img 135 | ;; 136 | esac 137 | } 138 | 139 | 140 | setup_flash_kernel() { 141 | if [ ! -d /etc/flash-kernel ] ; then 142 | mkdir /etc/flash-kernel 143 | fi 144 | full_machine_name="$1" 145 | echo -n "$full_machine_name" > /etc/flash-kernel/machine 146 | 147 | # Raspberry Pi 3 Model B can also work with armmp kernel, so add it to the 148 | # flash-kernel database in addition to the existing arm64. XXX: Remove this 149 | # override when flash-kernel is updated. 150 | case "$full_machine_name" in 151 | "Raspberry Pi 3 Model B") 152 | mkdir -p /usr/share/flash-kernel/db/ 153 | cat >/usr/share/flash-kernel/db/01-raspberrypi3-freedombox.db </etc/NetworkManager/conf.d/30-stable-mac.conf <. 16 | # 17 | 18 | """ 19 | Tests for Freedom Maker. 20 | """ 21 | -------------------------------------------------------------------------------- /freedommaker/tests/test_invocation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # This file is part of Freedom Maker. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | """ 20 | Tests for checking Freedom Maker's invocation of vmdeboostrap. 21 | 22 | Also test: 23 | - Arguments given to Freedom Maker. 24 | - Other basic behavior 25 | """ 26 | 27 | import json 28 | import logging 29 | import os 30 | import random 31 | import string 32 | import subprocess 33 | import time 34 | import unittest 35 | 36 | logger = logging.getLogger(__name__) 37 | 38 | 39 | ARCHITECTURES = { 40 | 'amd64': 'amd64', 41 | 'i386': 'i386', 42 | 'virtualbox-amd64': 'amd64', 43 | 'virtualbox-i386': 'i386', 44 | 'qemu-amd64': 'amd64', 45 | 'qemu-i386': 'i386', 46 | 'beaglebone': 'armhf', 47 | 'a20-olinuxino-lime': 'armhf', 48 | 'a20-olinuxino-lime2': 'armhf', 49 | 'a20-olinuxino-micro': 'armhf', 50 | 'banana-pro': 'armhf', 51 | 'cubieboard2': 'armhf', 52 | 'cubietruck': 'armhf', 53 | 'dreamplug': 'armel', 54 | 'raspberry': 'armel', 55 | 'raspberry2': 'armhf', 56 | 'raspberry3': 'armhf', 57 | } 58 | 59 | 60 | class TestInvocation(unittest.TestCase): 61 | """Tests for Freedom Maker's invocation of vmdeboostrap.""" 62 | 63 | def setUp(self): 64 | """Setup test case.""" 65 | self.path = os.path.dirname(__file__) 66 | self.binary = 'freedommaker' 67 | self.output_dir = os.path.join(self.path, 'output') 68 | 69 | self.build_stamp = self.random_string() 70 | self.current_target = 'amd64' 71 | 72 | def random_string(self): 73 | """Generate a random string.""" 74 | return ''.join([random.choice(string.ascii_lowercase) 75 | for _ in range(8)]) 76 | 77 | def invoke(self, targets=None, **kwargs): 78 | """Invoke Freedom Maker.""" 79 | if targets: 80 | if len(targets) == 1: 81 | self.current_target = targets[0] 82 | else: 83 | targets = [self.current_target] 84 | 85 | parameters = [ 86 | '--vmdebootstrap', os.path.join(self.path, 'vmdebootstrap-stub'), 87 | '--build-dir', self.output_dir 88 | ] 89 | 90 | if 'build_stamp' not in kwargs: 91 | parameters += ['--build-stamp', self.build_stamp] 92 | 93 | for parameter, value in kwargs.items(): 94 | parameter = '--' + parameter.replace('_', '-') 95 | 96 | if isinstance(value, bool): 97 | if value: 98 | parameters += [parameter] 99 | else: 100 | parameters += [parameter, value] 101 | 102 | command = ['python3', '-m', self.binary] + parameters + targets 103 | subprocess.check_call(command) 104 | 105 | def get_built_file(self, target=None, distribution=None): 106 | """Return the path of the expected built file. 107 | 108 | Also tests: 109 | - free flag 110 | """ 111 | target = target or self.current_target 112 | extra_map = { 113 | 'amd64': 'all-amd64.img', 114 | 'i386': 'all-i386.img', 115 | 'virtualbox-amd64': 'all-amd64.vdi', 116 | 'virtualbox-i386': 'all-i386.vdi', 117 | 'qemu-amd64': 'all-amd64.qcow2', 118 | 'qemu-i386': 'all-i386.qcow2', 119 | 'beaglebone': 'beaglebone-armhf.img', 120 | 'a20-olinuxino-lime': 'a20-olinuxino-lime-armhf.img', 121 | 'a20-olinuxino-lime2': 'a20-olinuxino-lime2-armhf.img', 122 | 'a20-olinuxino-micro': 'a20-olinuxino-micro-armhf.img', 123 | 'banana-pro': 'banana-pro-armhf.img', 124 | 'cubieboard2': 'cubieboard2-armhf.img', 125 | 'cubietruck': 'cubietruck-armhf.img', 126 | 'dreamplug': 'dreamplug-armel.img', 127 | 'raspberry': 'raspberry-armel.img', 128 | 'raspberry2': 'raspberry2-armhf.img', 129 | 'raspberry3': 'raspberry3-armhf.img', 130 | } 131 | 132 | distribution = distribution or 'unstable' 133 | 134 | free_tag = 'free' 135 | if target in ('raspberry', 'raspberry2', 'raspberry3'): 136 | free_tag = 'nonfree' 137 | 138 | file_name = 'freedombox-{distribution}-{free_tag}_{build_stamp}_' \ 139 | '{extra}.xz' \ 140 | .format(distribution=distribution, build_stamp=self.build_stamp, 141 | extra=extra_map[target], free_tag=free_tag) 142 | 143 | return os.path.join(self.output_dir, file_name) 144 | 145 | def get_parameters_passed(self, distribution=None): 146 | """Return parameters passed to vmdeboostrap during invocation.""" 147 | compressed_built_file = self.get_built_file(distribution=distribution) 148 | 149 | built_file = compressed_built_file.rsplit('.', maxsplit=1)[0] 150 | 151 | if not os.path.isfile(built_file): 152 | subprocess.check_call(['unxz', '--keep', '--force', 153 | compressed_built_file]) 154 | 155 | if built_file.endswith('.qcow2'): 156 | built_file = self.convert_qcow2_to_raw(built_file) 157 | elif built_file.endswith('.vdi'): 158 | built_file = self.convert_vdi_to_raw(built_file) 159 | 160 | with open(built_file, 'r') as file_handle: 161 | return json.loads(file_handle.read().strip('\0')) 162 | 163 | def convert_qcow2_to_raw(self, built_file): 164 | """Convert back a QCOW2 image to raw image file.""" 165 | new_file = built_file.rstrip('.qcow2') + '.img' 166 | subprocess.check_call(['qemu-img', 'convert', '-O', 'raw', built_file, 167 | new_file]) 168 | return new_file 169 | 170 | def convert_vdi_to_raw(self, built_file): 171 | """Convert back a VDI image to raw image file.""" 172 | new_file = built_file.rstrip('.vdi') + '.img' 173 | subprocess.check_call(['qemu-img', 'convert', '-O', 'raw', built_file, 174 | new_file]) 175 | return new_file 176 | 177 | def assert_file_exists(self, file_name): 178 | """Check that a file exists in build directory.""" 179 | self.assertTrue(os.path.isfile( 180 | os.path.join(self.output_dir, file_name))) 181 | 182 | def assert_arguments_passed(self, expected_arguments, distribution=None): 183 | """Check that a sequence of arguments are passed to vmdeboostrap.""" 184 | arguments = self.get_parameters_passed( 185 | distribution=distribution)['arguments'] 186 | arguments = '@'.join(arguments) 187 | expected_arguments = '@'.join(expected_arguments) 188 | self.assertIn(expected_arguments, arguments) 189 | 190 | def assert_arguments_not_passed(self, expected_arguments): 191 | """Check that a sequence of arguments are passed to vmdeboostrap.""" 192 | arguments = self.get_parameters_passed()['arguments'] 193 | arguments = '@'.join(arguments) 194 | expected_arguments = '@'.join(expected_arguments) 195 | self.assertNotIn(expected_arguments, arguments) 196 | 197 | def assert_environment_passed(self, expected_environment, 198 | distribution=None): 199 | """Check that expected environment is passed to vmdeboostrap.""" 200 | environment = self.get_parameters_passed( 201 | distribution=distribution)['environment'] 202 | for key, value in expected_environment.items(): 203 | self.assertEqual(environment[key], value) 204 | 205 | def test_basic_build(self): 206 | """Test whether building works. 207 | 208 | Tests the following parameters: 209 | - build-stamp 210 | - build-dir 211 | - vmdebootstrap 212 | """ 213 | self.invoke() 214 | self.assert_file_exists(self.get_built_file()) 215 | 216 | def test_image_size(self): 217 | """Test that image size parameter works.""" 218 | size = str(random.randint(2, 1024)) + 'G' 219 | self.invoke(image_size=size) 220 | self.assert_arguments_passed(['--size', size]) 221 | 222 | def test_build_mirror(self): 223 | """Test that build-mirror parameter works.""" 224 | mirror = 'http://' + self.random_string() + '/debian/' 225 | self.invoke(build_mirror=mirror) 226 | self.assert_arguments_passed(['--mirror', mirror]) 227 | self.assert_environment_passed({'BUILD_MIRROR': mirror}) 228 | 229 | def test_mirror(self): 230 | """Test that mirror parameter works.""" 231 | mirror = 'http://' + self.random_string() + '/debian/' 232 | self.invoke(mirror=mirror) 233 | self.assert_environment_passed({'MIRROR': mirror}) 234 | 235 | def test_distribution(self): 236 | """Test that distribution parameter works.""" 237 | distribution = self.random_string() 238 | self.invoke(distribution=distribution) 239 | self.assert_arguments_passed(['--distribution', distribution], 240 | distribution=distribution) 241 | self.assert_environment_passed({'SUITE': distribution}, 242 | distribution=distribution) 243 | 244 | def test_download_source(self): 245 | """Test that download-source parameter works.""" 246 | self.invoke() 247 | self.assert_environment_passed({'SOURCE': 'false'}) 248 | 249 | self.invoke(download_source=True, force=True) 250 | self.assert_environment_passed({'SOURCE': 'true'}) 251 | 252 | def test_include_source(self): 253 | """Test that include-source parameter works.""" 254 | self.invoke() 255 | self.assert_environment_passed({'SOURCE_IN_IMAGE': 'false'}) 256 | 257 | self.invoke(include_source=True, force=True) 258 | self.assert_environment_passed({'SOURCE_IN_IMAGE': 'true'}) 259 | 260 | def test_package(self): 261 | """Test that package parameter works.""" 262 | package = self.random_string() 263 | self.invoke(package=package) 264 | self.assert_arguments_passed(['--package', package]) 265 | 266 | def test_custom_package(self): 267 | """Test that custom-package parameter works.""" 268 | custom_package = self.random_string() 269 | self.invoke(custom_package=custom_package) 270 | self.assert_arguments_passed(['--custom-package', custom_package]) 271 | 272 | def test_custom_package_plinth(self): 273 | """Test that custom-package parameter works for plinth.""" 274 | custom_package = self.random_string() + '/plinth_0.7-1_all.deb' 275 | self.invoke(custom_package=custom_package) 276 | self.assert_environment_passed({'CUSTOM_PLINTH': custom_package}) 277 | 278 | def test_custom_package_setup(self): 279 | """Test that custom-package parameter works for freedombox-setup.""" 280 | custom_package = \ 281 | self.random_string() + '/freedombox-setup_0.7_all.deb' 282 | self.invoke(custom_package=custom_package) 283 | self.assert_environment_passed({'CUSTOM_SETUP': custom_package}) 284 | 285 | def test_hostname(self): 286 | """Test that hostname parameter works.""" 287 | hostname = self.random_string() 288 | self.invoke(hostname=hostname) 289 | self.assert_arguments_passed(['--hostname', hostname]) 290 | 291 | def test_no_force(self): 292 | """Test that not giving force parameter works.""" 293 | self.invoke() 294 | mtime1 = os.path.getmtime(self.get_built_file()) 295 | time.sleep(2) 296 | self.invoke() 297 | mtime2 = os.path.getmtime(self.get_built_file()) 298 | self.assertEqual(mtime1, mtime2) 299 | 300 | def test_force(self): 301 | """Test that force parameter works.""" 302 | self.invoke() 303 | mtime1 = os.path.getmtime(self.get_built_file()) 304 | time.sleep(2) 305 | self.invoke(force=True) 306 | mtime2 = os.path.getmtime(self.get_built_file()) 307 | self.assertNotEqual(mtime1, mtime2) 308 | 309 | def test_sign(self): 310 | """Test that sign parameter works.""" 311 | # XXX: Implement 312 | 313 | def test_multiple_targets(self): 314 | """Test that passing multiple targets works.""" 315 | self.invoke(['amd64', 'i386']) 316 | self.assert_file_exists(self.get_built_file(target='amd64')) 317 | self.assert_file_exists(self.get_built_file(target='i386')) 318 | 319 | def test_all_targets(self): 320 | """Test that each target works. 321 | 322 | Also tests: 323 | - machine names are correct 324 | """ 325 | for target in ARCHITECTURES: 326 | self.build_stamp = self.random_string() 327 | self.invoke([target]) 328 | self.assert_file_exists(self.get_built_file(target=target)) 329 | 330 | def test_architecture(self): 331 | """Test that architecture is properly choosen.""" 332 | for target, architecture in ARCHITECTURES.items(): 333 | self.build_stamp = self.random_string() 334 | self.invoke([target]) 335 | self.assert_arguments_passed(['--arch', architecture]) 336 | 337 | def test_lock_root_password(self): 338 | """Test that root password is locked.""" 339 | self.invoke() 340 | self.assert_arguments_passed(['--lock-root-password']) 341 | 342 | def test_log_level(self): 343 | """Test that log level is set properly. 344 | 345 | Also tests: 346 | - verbose flag 347 | - log file path 348 | """ 349 | self.invoke(log_level='debug') 350 | self.assert_arguments_passed(['--verbose']) 351 | self.assert_arguments_passed(['--log-level', 'debug']) 352 | log_file = self.get_built_file().rstrip('.img.xz') + '.log' 353 | self.assert_file_exists(log_file) 354 | 355 | self.invoke(log_level='info', force=True) 356 | self.assert_arguments_passed(['--log-level', 'info']) 357 | 358 | def test_base_packages(self): 359 | """Test that base packages are availble.""" 360 | self.invoke() 361 | for package in ['initramfs-tools']: 362 | self.assert_arguments_passed(['--package', package]) 363 | 364 | def test_foreign_architecture(self): 365 | """Test that foreign arch parameter is passed.""" 366 | for target, architecture in ARCHITECTURES.items(): 367 | self.build_stamp = self.random_string() 368 | self.invoke([target]) 369 | if architecture in ('i386', 'amd64'): 370 | self.assert_arguments_not_passed( 371 | ['--foreign', '/usr/bin/qemu-arm-static']) 372 | else: 373 | self.assert_arguments_passed( 374 | ['--foreign', '/usr/bin/qemu-arm-static']) 375 | 376 | def test_boot_loader(self): 377 | """Test proper boot loader arguments.""" 378 | for target, architecture in ARCHITECTURES.items(): 379 | self.build_stamp = self.random_string() 380 | self.invoke([target]) 381 | if architecture in ('i386', 'amd64'): 382 | self.assert_arguments_passed(['--grub']) 383 | self.assert_arguments_not_passed(['--package', 'u-boot']) 384 | self.assert_arguments_not_passed(['--package', 'u-boot-tools']) 385 | self.assert_arguments_not_passed(['--no-extlinux']) 386 | elif target in ('raspberry'): 387 | self.assert_arguments_not_passed(['--grub']) 388 | self.assert_arguments_not_passed(['--package', 'u-boot']) 389 | self.assert_arguments_not_passed(['--package', 'u-boot-tools']) 390 | self.assert_arguments_passed(['--no-extlinux']) 391 | elif target in ('raspberry2', 'raspberry3'): 392 | self.assert_arguments_not_passed(['--grub']) 393 | self.assert_arguments_passed(['--package', 'u-boot']) 394 | self.assert_arguments_passed(['--package', 'u-boot-tools']) 395 | self.assert_arguments_passed(['--no-extlinux']) 396 | else: 397 | self.assert_arguments_not_passed(['--grub']) 398 | self.assert_arguments_passed(['--package', 'u-boot']) 399 | self.assert_arguments_passed(['--package', 'u-boot-tools']) 400 | self.assert_arguments_passed(['--no-extlinux']) 401 | 402 | def test_filesystems(self): 403 | """Test proper filesystem arguments. 404 | 405 | Also tests: 406 | - boot size 407 | """ 408 | for target, architecture in ARCHITECTURES.items(): 409 | self.build_stamp = self.random_string() 410 | self.invoke([target]) 411 | if architecture in ('i386', 'amd64'): 412 | self.assert_arguments_passed(['--roottype', 'btrfs']) 413 | self.assert_arguments_not_passed(['--boottype']) 414 | self.assert_arguments_passed(['--package', 'btrfs-progs']) 415 | self.assert_arguments_not_passed(['--bootsize', '128M']) 416 | elif target in ('raspberry'): 417 | self.assert_arguments_passed(['--roottype', 'ext4']) 418 | self.assert_arguments_passed(['--boottype', 'vfat']) 419 | self.assert_arguments_not_passed(['--package', 'btrfs-progs']) 420 | self.assert_arguments_passed(['--bootsize', '128M']) 421 | elif target in ('raspberry2', 'raspberry3'): 422 | self.assert_arguments_passed(['--roottype', 'btrfs']) 423 | self.assert_arguments_passed(['--boottype', 'ext2']) 424 | self.assert_arguments_passed(['--package', 'btrfs-progs']) 425 | self.assert_arguments_passed(['--bootsize', '128M']) 426 | elif target in ('dreamplug'): 427 | self.assert_arguments_passed(['--roottype', 'btrfs']) 428 | self.assert_arguments_passed(['--boottype', 'vfat']) 429 | self.assert_arguments_passed(['--package', 'btrfs-progs']) 430 | self.assert_arguments_passed(['--bootsize', '128M']) 431 | else: 432 | self.assert_arguments_passed(['--roottype', 'btrfs']) 433 | self.assert_arguments_passed(['--boottype', 'ext2']) 434 | self.assert_arguments_passed(['--package', 'btrfs-progs']) 435 | self.assert_arguments_passed(['--bootsize', '128M']) 436 | 437 | def test_boot_offset(self): 438 | """Test proper boot offset arguments.""" 439 | for target, architecture in ARCHITECTURES.items(): 440 | self.build_stamp = self.random_string() 441 | self.invoke([target]) 442 | if target in ('raspberry', 'dreamplug') or \ 443 | architecture in ('i386', 'amd64'): 444 | self.assert_arguments_not_passed(['--bootoffset']) 445 | elif target in ('raspberry2', 'raspberry3'): 446 | self.assert_arguments_passed(['--bootoffset', '64mib']) 447 | elif target in ('beaglebone'): 448 | self.assert_arguments_passed(['--bootoffset', '2mib']) 449 | else: 450 | self.assert_arguments_passed(['--bootoffset', '1mib']) 451 | 452 | def test_kernel_flavor(self): 453 | """Test proper kernel flavor arguments.""" 454 | for target, architecture in ARCHITECTURES.items(): 455 | self.build_stamp = self.random_string() 456 | self.invoke([target]) 457 | if architecture in ('amd64'): 458 | self.assert_arguments_not_passed(['--no-kernel']) 459 | self.assert_arguments_not_passed(['--kernel-package']) 460 | elif architecture in ('i386'): 461 | self.assert_arguments_not_passed(['--no-kernel']) 462 | self.assert_arguments_passed( 463 | ['--kernel-package', 'linux-image-686']) 464 | elif target in ('beaglebone'): 465 | self.assert_arguments_not_passed(['--no-kernel']) 466 | self.assert_arguments_passed( 467 | ['--kernel-package', 'linux-image-armmp']) 468 | elif target in ('raspberry'): 469 | self.assert_arguments_passed(['--no-kernel']) 470 | self.assert_arguments_not_passed(['--kernel-package']) 471 | elif target in ('raspberry2', 'raspberry3'): 472 | self.assert_arguments_passed( 473 | ['--kernel-package', 'linux-image-armmp']) 474 | elif target in ('dreamplug'): 475 | self.assert_arguments_not_passed(['--no-kernel']) 476 | self.assert_arguments_passed( 477 | ['--kernel-package', 'linux-image-marvell']) 478 | else: 479 | self.assert_arguments_not_passed(['--no-kernel']) 480 | self.assert_arguments_passed( 481 | ['--kernel-package', 'linux-image-armmp-lpae']) 482 | 483 | def test_variant(self): 484 | """Test that variant targets works.""" 485 | for target, architecture in ARCHITECTURES.items(): 486 | self.build_stamp = self.random_string() 487 | self.invoke([target]) 488 | self.assert_arguments_not_passed(['--debootstrapopts']) 489 | -------------------------------------------------------------------------------- /freedommaker/tests/test_virtualbox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # This file is part of Freedom Maker. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | """ 20 | Tests for checking built image with VirtualBox. 21 | """ 22 | 23 | import logging 24 | import os 25 | import random 26 | import string 27 | import subprocess 28 | import time 29 | import unittest 30 | 31 | logger = logging.getLogger(__name__) 32 | 33 | 34 | class TestVirtualBox(unittest.TestCase): 35 | """Tests for checking built image with VirtualBox.""" 36 | 37 | def setUp(self): 38 | """Setup test case.""" 39 | self.path = os.path.dirname(__file__) 40 | self.binary = 'freedommaker' 41 | self.output_dir = os.path.join(self.path, 'output') 42 | 43 | self.build_stamp = self.random_string() 44 | self.current_target = 'virtualbox-i386' 45 | 46 | def random_string(self): 47 | """Generate a random string.""" 48 | return ''.join([random.choice(string.ascii_lowercase) 49 | for _ in range(8)]) 50 | 51 | def invoke(self, targets=None, **kwargs): 52 | """Invoke Freedom Maker.""" 53 | parameters = ['--build-dir', self.output_dir] 54 | 55 | if 'build_stamp' not in kwargs: 56 | parameters += ['--build-stamp', self.build_stamp] 57 | 58 | command = ['python3', '-m', self.binary] + parameters + targets 59 | subprocess.check_call(command) 60 | 61 | def get_built_file(self): 62 | """Return the path of the expected built file.""" 63 | target = self.current_target 64 | extra_map = { 65 | 'virtualbox-amd64': 'all-amd64.vdi', 66 | 'virtualbox-i386': 'all-i386.vdi', 67 | } 68 | 69 | file_name = 'freedombox-unstable-free_{build_stamp}_{extra}.xz' \ 70 | .format(build_stamp=self.build_stamp, extra=extra_map[target]) 71 | 72 | return os.path.join(self.output_dir, file_name) 73 | 74 | def _run(self, *args): 75 | """Execute a command.""" 76 | subprocess.check_call(*args) 77 | 78 | @unittest.skipUnless(os.environ.get('FM_RUN_VM_TESTS') == 'true', 79 | 'Not requested') 80 | def test_basic_build(self): 81 | """Test booting and opening SSH shell. 82 | 83 | Also: 84 | - Output the plinth diagnostic log. 85 | """ 86 | self.invoke(['virtualbox-i386']) 87 | 88 | vm_name = 'freedom-maker-test' 89 | test_ssh_port = 2222 90 | first_run_wait_time = 120 91 | 92 | compressed_built_file = self.get_built_file() 93 | built_file = compressed_built_file.rsplit('.', maxsplit=1)[0] 94 | 95 | if not os.path.isfile(built_file): 96 | subprocess.check_call(['unxz', '--keep', '--force', 97 | compressed_built_file]) 98 | 99 | passwd_tool = os.path.join(self.path, '..', '..', 'bin', 100 | 'passwd-in-image') 101 | self._run( 102 | ['sudo', 'python3', passwd_tool, built_file, 'fbx', '--password', 103 | 'frdm']) 104 | try: 105 | self._run( 106 | ['VBoxManage', 'createvm', '--name', vm_name, '--ostype', 107 | 'Debian', '--register']) 108 | self._run( 109 | ['VBoxManage', 'storagectl', vm_name, '--name', 110 | 'SATA Controller', '--add', 'sata', '--controller', 111 | 'IntelAHCI']) 112 | self._run( 113 | ['VBoxManage', 'storageattach', vm_name, '--storagectl', 114 | 'SATA Controller', '--port', '0', '--device', '0', '--type', 115 | 'hdd', '--medium', built_file]) 116 | self._run( 117 | ['VBoxManage', 'modifyvm', vm_name, '--pae', 'on', '--memory', 118 | '1024', '--vram', '128', '--nic1', 'nat', '--natpf1', 119 | ',tcp,,{port},,22'.format(port=test_ssh_port)]) 120 | self._run( 121 | ['VBoxManage', 'startvm', vm_name, '--type', 'headless']) 122 | time.sleep(first_run_wait_time) 123 | 124 | echo = subprocess.Popen(['echo', 'frdm'], stdout=subprocess.PIPE) 125 | process = subprocess.Popen( 126 | ['sshpass', '-p', 'frdm', 'ssh', 127 | '-o', 'UserKnownHostsFile=/dev/null', 128 | '-o', 'StrictHostKeyChecking=no', 129 | '-t', '-t', '-p', str(test_ssh_port), 'fbx@127.0.0.1', 130 | 'sudo plinth --diagnose'], stdin=echo.stdout) 131 | process.communicate() 132 | finally: 133 | self._run(['VBoxManage', 'controlvm', vm_name, 'poweroff']) 134 | self._run(['VBoxManage', 'modifyvm', vm_name, '--hda', 'none']) 135 | self._run(['VBoxManage', 'unregistervm', vm_name]) 136 | -------------------------------------------------------------------------------- /freedommaker/tests/vmdebootstrap-stub: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # This file is part of Freedom Maker. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | """ 20 | Stub for vmdeboostrap to test freedom-maker 21 | """ 22 | 23 | import argparse 24 | import json 25 | import os 26 | import sys 27 | 28 | 29 | def main(): 30 | """Save the environement and arguments.""" 31 | print('Running vmdebootstrap stub') 32 | 33 | parser = argparse.ArgumentParser() 34 | parser.add_argument('--image') 35 | parser.add_argument('--arch') 36 | 37 | output = { 38 | 'arguments': sys.argv, 39 | 'environment': dict(os.environ), 40 | 'padding': '@' * 1024 * 1024, 41 | } 42 | 43 | arguments, unknown = parser.parse_known_args() 44 | with open(arguments.image, 'w') as file_handle: 45 | output = json.dumps(output) 46 | # Make the output a multiple of 512 bytes so it be treated as disk 47 | # image 48 | output += ' ' * (((512 - (len(output) % 512)) % 512) - 1) 49 | print(output, file=file_handle) 50 | 51 | 52 | if __name__ == '__main__': 53 | main() 54 | -------------------------------------------------------------------------------- /freedommaker/vmdb2.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of Freedom Maker. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | # 17 | """ 18 | Basic image builder using vmdb2. 19 | """ 20 | 21 | import logging 22 | 23 | logger = logging.getLogger(__name__) 24 | 25 | 26 | class Vmdb2BuilderBackend(): 27 | """Build an image using vmdb2 tool.""" 28 | 29 | def __init__(self, builder): 30 | """Initialize the builder.""" 31 | self.builder = builder 32 | 33 | def make_image(self): 34 | """Create a disk image.""" 35 | raise Exception('Not implemented yet.') 36 | -------------------------------------------------------------------------------- /freedommaker/vmdebootstrap.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of Freedom Maker. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | # 17 | """ 18 | Basic image builder using vmdebootstrap. 19 | """ 20 | 21 | import json 22 | import logging 23 | import shutil 24 | import subprocess 25 | 26 | logger = logging.getLogger(__name__) 27 | 28 | 29 | class VmdebootstrapBuilderBackend(): 30 | """Build an image using vmdebootstrap tool.""" 31 | 32 | def __init__(self, builder): 33 | """Initialize the builder.""" 34 | self.builder = builder 35 | self.parameters = [] 36 | self.environment = [] 37 | self.execution_wrapper = [] 38 | 39 | def make_image(self): 40 | """Create a disk image.""" 41 | if self.builder.should_skip_step(self.builder.image_file): 42 | logger.info('Image exists, skipping build - %s', 43 | self.builder.image_file) 44 | return 45 | 46 | temp_image_file = self.builder.get_temp_image_file() 47 | logger.info('Building image in temporary file - %s', temp_image_file) 48 | self.execution_wrapper = ['sudo', '-H'] 49 | self.parameters = [ 50 | '--hostname', 51 | self.builder.arguments.hostname, 52 | '--image', 53 | temp_image_file, 54 | '--size', 55 | self.builder.arguments.image_size, 56 | '--mirror', 57 | self.builder.arguments.build_mirror, 58 | '--distribution', 59 | self.builder.arguments.distribution, 60 | '--arch', 61 | self.builder.architecture, 62 | '--lock-root-password', 63 | '--log', 64 | self.builder.log_file, 65 | '--log-level', 66 | self.builder.arguments.log_level, 67 | '--verbose', 68 | '--customize', 69 | self.builder.customization_script, 70 | ] 71 | self.environment = { 72 | 'MIRROR': self.builder.arguments.mirror, 73 | 'BUILD_MIRROR': self.builder.arguments.build_mirror, 74 | 'MACHINE': self.builder.machine, 75 | 'SOURCE': 'true' 76 | if self.builder.arguments.download_source else 'false', 77 | 'SOURCE_IN_IMAGE': 'true' 78 | if self.builder.arguments.include_source else 'false', 79 | 'SUITE': self.builder.arguments.distribution, 80 | 'ENABLE_NONFREE': 'no' if self.builder.free else 'yes', 81 | } 82 | self.process_variant() 83 | self.process_architecture() 84 | self.process_boot_loader() 85 | self.process_kernel_flavor() 86 | self.process_filesystems() 87 | self.process_packages() 88 | self.process_custom_packages() 89 | self.process_environment() 90 | 91 | command = self.execution_wrapper + [ 92 | self.builder.arguments.vmdebootstrap 93 | ] + self.parameters 94 | 95 | try: 96 | self.builder._run(command) 97 | finally: 98 | self._cleanup_vmdebootstrap(temp_image_file) 99 | 100 | logger.info('Moving file: %s -> %s', temp_image_file, 101 | self.builder.image_file) 102 | shutil.move(temp_image_file, self.builder.image_file) 103 | 104 | def _cleanup_vmdebootstrap(self, image_file): 105 | """Cleanup those that vmdebootstrap is supposed to have cleaned up.""" 106 | # XXX: Remove this when vmdebootstrap removes kpartx mappings properly 107 | # after a successful build. 108 | process = subprocess.run(['losetup', '--json'], 109 | stdout=subprocess.PIPE, 110 | check=True) 111 | output = process.stdout.decode() 112 | if not output: 113 | return 114 | 115 | loop_data = json.loads(output) 116 | loop_device = None 117 | for device_data in loop_data['loopdevices']: 118 | if image_file == device_data['back-file']: 119 | loop_device = device_data['name'] 120 | break 121 | 122 | if not loop_device: 123 | return 124 | 125 | partition_devices = [ 126 | '/dev/mapper/' + loop_device.split('/')[-1] + 'p' + str(number) 127 | for number in range(1, 4) 128 | ] 129 | # Don't log command, ignore errors, force 130 | for device in partition_devices: 131 | subprocess.run(['dmsetup', 'remove', '-f', device], 132 | stdout=subprocess.DEVNULL, 133 | stderr=subprocess.DEVNULL) 134 | 135 | subprocess.run(['losetup', '-d', loop_device], 136 | stdout=subprocess.DEVNULL, 137 | stderr=subprocess.DEVNULL) 138 | 139 | def process_variant(self): 140 | """Add paramaters for deboostrap variant.""" 141 | if self.builder.debootstrap_variant: 142 | self.parameters += [ 143 | '--debootstrapopts', 144 | 'variant=' + self.builder.debootstrap_variant 145 | ] 146 | 147 | def process_architecture(self): 148 | """Add parameters specific to the architecture.""" 149 | if self.builder.architecture not in ('i386', 'amd64'): 150 | self.parameters += ['--foreign', '/usr/bin/qemu-arm-static'] 151 | 152 | # Using taskset to pin build process to single core. This 153 | # is a workaround for a qemu-user-static issue that causes 154 | # builds to hang. (See Debian bug #769983 for details.) 155 | self.execution_wrapper = \ 156 | ['taskset', '0x01'] + self.execution_wrapper 157 | 158 | def process_boot_loader(self): 159 | """Add parameters related to boot loader.""" 160 | option_map = { 161 | 'grub': ['--grub'], 162 | 'u-boot': ['--no-extlinux'], 163 | None: ['--no-extlinux'] 164 | } 165 | self.parameters += option_map[self.builder.boot_loader] 166 | 167 | if self.builder.boot_loader == 'u-boot': 168 | self.parameters += [ 169 | '--package', 'u-boot-tools', '--package', 'u-boot' 170 | ] 171 | 172 | if self.builder.boot_size: 173 | self.parameters += ['--bootsize', self.builder.boot_size] 174 | 175 | if self.builder.boot_offset: 176 | self.parameters += ['--bootoffset', self.builder.boot_offset] 177 | 178 | def process_kernel_flavor(self): 179 | """Add parameters for kernel flavor.""" 180 | if self.builder.kernel_flavor == 'default': 181 | return 182 | 183 | if self.builder.kernel_flavor is None: 184 | self.parameters += ['--no-kernel'] 185 | return 186 | 187 | self.parameters += [ 188 | '--kernel-package', 'linux-image-' + self.builder.kernel_flavor 189 | ] 190 | 191 | def process_filesystems(self): 192 | """Add parameters necessary for file systems.""" 193 | self.parameters += ['--roottype', self.builder.root_filesystem_type] 194 | if self.builder.boot_filesystem_type: 195 | self.parameters += [ 196 | '--boottype', self.builder.boot_filesystem_type 197 | ] 198 | 199 | if 'btrfs' in [ 200 | self.builder.root_filesystem_type, 201 | self.builder.boot_filesystem_type 202 | ]: 203 | self.builder.packages += ['btrfs-progs'] 204 | 205 | def process_packages(self): 206 | """Add parameters for additional packages to install in image.""" 207 | for package in self.builder.packages + (self.builder.arguments.package 208 | or []): 209 | self.parameters += ['--package', package] 210 | 211 | def process_custom_packages(self): 212 | """Add parameters for custom DEB packages to install in image.""" 213 | for package in (self.builder.arguments.custom_package or []): 214 | if 'plinth_' in package: 215 | self.environment['CUSTOM_PLINTH'] = package 216 | elif 'freedombox-setup_' in package: 217 | self.environment['CUSTOM_SETUP'] = package 218 | else: 219 | self.parameters += ['--custom-package', package] 220 | 221 | def process_environment(self): 222 | """Add environment we wish to pass to the command wrapper: sudo.""" 223 | for key, value in self.environment.items(): 224 | self.execution_wrapper += [key + '=' + value] 225 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # This file is part of Freedom Maker. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | """ 20 | Freedom Maker setup file 21 | """ 22 | 23 | import setuptools 24 | 25 | from freedommaker import __version__ 26 | 27 | 28 | setuptools.setup( 29 | name='freedom-maker', 30 | version=__version__, 31 | description='The FreedomBox image builder', 32 | author='FreedomBox Authors', 33 | author_email='freedombox-discuss@lists.alioth.debian.org', 34 | url='http://freedomboxfoundation.org', 35 | packages=setuptools.find_packages(), 36 | scripts=['bin/passwd-in-image','bin/vagrant-package'], 37 | entry_points={ 38 | 'console_scripts': [ 39 | 'freedom-maker = freedommaker:main' 40 | ] 41 | }, 42 | test_suite='freedommaker.tests', 43 | license='COPYING', 44 | classifiers=[ 45 | 'Development Status :: 4 - Beta', 46 | 'Environment :: Console', 47 | 'Intended Audience :: End Users/Desktop', 48 | 'License :: DFSG approved', 49 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 50 | 'Natural Language :: English', 51 | 'Operating System :: POSIX :: Linux', 52 | 'Programming Language :: Python', 53 | 'Programming Language :: Python :: 3', 54 | 'Programming Language :: Unix Shell', 55 | 'Topic :: System :: Software Distribution', 56 | ], 57 | include_package_data=True, 58 | ) 59 | --------------------------------------------------------------------------------