├── .gitignore ├── AUTHORS ├── INSTALL ├── LICENSE ├── README.md ├── bin ├── pgbackman ├── pgbackman-bulk-update ├── pgbackman_alerts ├── pgbackman_control ├── pgbackman_dump ├── pgbackman_maintenance ├── pgbackman_restore ├── pgbackman_status_info └── pgbackman_zabbix_autodiscovery ├── debian ├── changelog ├── compat ├── control ├── copyright ├── postinst ├── rules └── source │ └── format ├── docs ├── Makefile ├── images │ ├── architecture.graphml │ ├── architecture.jpg │ ├── components.graphml │ ├── components.jpg │ ├── main.dia │ ├── register_restore.graphml │ └── register_restore.jpg ├── manual.rst ├── manual_es.rst ├── release-notes.rst └── style.css ├── etc ├── pgbackman-alerts.service ├── pgbackman-control.service ├── pgbackman-maintenance.service ├── pgbackman.conf ├── pgbackman.log ├── pgbackman.logrotate ├── pgbackman_alerts.template ├── pgbackman_init_debian.sh └── pgbackman_init_rh.sh ├── pgbackman ├── __init__.py ├── cli.py ├── config.py ├── database.py ├── logs.py ├── ordereddict.py ├── prettytable.py └── version.py ├── rpm └── pgbackman.spec ├── setup.py ├── setup2.py ├── sql ├── pgbackman.sql ├── pgbackman_2.sql ├── pgbackman_3.sql └── pgbackman_4.sql └── vagrant ├── Vagrantfile ├── Vagrantfile_orig ├── bootstrap.sh ├── bootstrap2.sh ├── bootstrap3.sh ├── bootstrap_centos.sh └── bootstrap_debian.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | *.pyc 10 | 11 | # Packages # 12 | ############ 13 | # it's better to unpack these files and commit the raw source 14 | # git has its own built in compression methods 15 | *.7z 16 | *.dmg 17 | *.gz 18 | *.iso 19 | *.jar 20 | *.rar 21 | *.tar 22 | *.zip 23 | 24 | # Logs and databases # 25 | ###################### 26 | *.log 27 | 28 | # OS generated files # 29 | ###################### 30 | .DS_Store 31 | .DS_Store? 32 | ._* 33 | .Spotlight-V100 34 | .Trashes 35 | ehthumbs.db 36 | Thumbs.db -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Rafael Martinez Guerrero - PostgreSQL-es 2 | 3 | rafael@postgresql.org.es 4 | http://www.postgresql.org.es/ 5 | https://github.com/rafaelma/pgbackman 6 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Check the PgBackMan documentation: 2 | http://www.pgbackman.org/documentation.html 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 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 | {project} Copyright (C) {year} {fullname} 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 | PgBackMan 2 | ========= 3 | 4 | PostgreSQL backup manager 5 | 6 | *NOTE: This project is no longer maintained due to lack of free time to do so. 7 | The source code is still available and it was supported up to PostgreSQL 9.6.* 8 | 9 | PgBackMan is a tool for managing PostgreSQL logical backups created 10 | with ``pg_dump`` and ``pg_dumpall``. 11 | 12 | It is designed to manage backups from thousands of databases running 13 | in multiple PostgreSQL nodes, and it supports a multiple backup server 14 | topology. 15 | 16 | PgBackMan is not a tool for managing PITR (Point in time recovery) 17 | backups. There are several other solutions that can be use for 18 | managing PITR backups, such as PITRTools, OmniPITR, and Barman. 19 | 20 | The PgBackMan code is distributed under the GNU General Public License 21 | and it is written in Python and PL/PgSQL. It has been developed and 22 | tested by members of the Database Operations Group at the Center for 23 | Information Technology at the University of Oslo. 24 | 25 | -------------------------------------------------------------------------------- /bin/pgbackman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2013-2015 Rafael Martinez Guerrero / PostgreSQL-es 4 | # rafael@postgresql.org.es / http://www.postgresql.org.es/ 5 | # 6 | # Copyright (c) 2015 USIT-University of Oslo 7 | # 8 | # This file is part of Pgbackman 9 | # https://github.com/rafaelma/pgbackman 10 | # 11 | # PgBackMan is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # PgBackMan is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with Pgbackman. If not, see . 23 | 24 | import argparse 25 | from pgbackman.cli import * 26 | 27 | if __name__ == '__main__': 28 | 29 | try: 30 | 31 | # 32 | # Processing command line parameters 33 | # 34 | 35 | output_format = '' 36 | pgbackman_command = '' 37 | 38 | parser = argparse.ArgumentParser(prog=sys.argv[0], description='zabbix-cli - Zabbix client') 39 | 40 | parser.add_argument('--output', '-o', metavar='[csv|json]', choices=['csv', 'json'], 41 | required=False, dest='output_format') 42 | 43 | parser.add_argument('--command', '-C', metavar='', required=False, dest='pgbackman_command') 44 | 45 | args = parser.parse_args() 46 | 47 | if args.output_format: 48 | output_format = args.output_format 49 | 50 | if args.pgbackman_command: 51 | pgbackman_command = args.pgbackman_command 52 | 53 | # 54 | # pgbackman cli initialization 55 | # 56 | 57 | cli = PgbackmanCli() 58 | 59 | # 60 | # Processing output format 61 | # 62 | 63 | if output_format == 'csv': 64 | cli.output_format = 'csv' 65 | 66 | elif output_format == 'json': 67 | cli.output_format = 'json' 68 | 69 | else: 70 | cli.output_format = 'table' 71 | 72 | # 73 | # PgBackMan in non-interactive modus 74 | # 75 | 76 | if pgbackman_command != '': 77 | 78 | cli.execution_modus = 'non-interactive' 79 | cli.onecmd(pgbackman_command) 80 | 81 | # 82 | # PgBackMan in interactive modus (pgbackman-shell) 83 | # 84 | 85 | elif pgbackman_command == '': 86 | os.system('clear') 87 | 88 | cli.execution_modus = 'interactive' 89 | 90 | cli.check_pgbackman_database_version() 91 | cli.cmdloop() 92 | 93 | else: 94 | raise NotImplementedError 95 | 96 | except KeyboardInterrupt: 97 | print 98 | print "\nDone, thank you for using PgBackMan" 99 | 100 | sys.exit(0) 101 | -------------------------------------------------------------------------------- /bin/pgbackman-bulk-update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2013-2015 Rafael Martinez Guerrero / PostgreSQL-es 4 | # rafael@postgresql.org.es / http://www.postgresql.org.es/ 5 | # 6 | # Copyright (c) 2015 USIT-University of Oslo 7 | # 8 | # This file is part of Pgbackman 9 | # https://github.com/rafaelma/pgbackman 10 | # 11 | # PgBackMan is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # PgBackMan is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with Pgbackman. If not, see . 23 | 24 | import sys 25 | import os 26 | import argparse 27 | import subprocess 28 | 29 | from pgbackman.config import * 30 | from pgbackman.logs import * 31 | 32 | if __name__ == '__main__': 33 | 34 | try: 35 | 36 | intro = '\n####################################################################\n' + \ 37 | 'PgBackMan bulk update \n' + \ 38 | '####################################################################\n' 39 | 40 | print intro 41 | 42 | # 43 | # Process command line parameters 44 | # 45 | 46 | input_file = '' 47 | 48 | ok_count = 0 49 | error_count = 0 50 | not_supported_count = 0 51 | 52 | parser = argparse.ArgumentParser(prog=sys.argv[0]) 53 | parser.add_argument('--input-file', '-f', metavar='[Filename]', required=True, help='Input file', dest='input_file') 54 | 55 | args = parser.parse_args() 56 | 57 | if args.input_file: 58 | input_file = args.input_file 59 | 60 | 61 | logs = PgbackmanLogs("pgbackman-bulk-update", "", "") 62 | 63 | logs.logger.debug('**** pgbackman-bulk-update startet. ****') 64 | 65 | # Normalized absolutized version of the pathname if 66 | # files does not include an absolute path 67 | 68 | if os.path.isabs(input_file) == False: 69 | input_file = os.path.abspath(input_file) 70 | 71 | if os.path.exists(input_file): 72 | 73 | logs.logger.info('File [%s] exists. Bulk execution of commands defined in this file started.',input_file) 74 | print '[OK] File [' + input_file + '] exists. Bulk execution of commands defined in this file started.\n' 75 | 76 | # 77 | # Processing pgbackman commands in file 78 | # 79 | 80 | try: 81 | with open(input_file,'r') as file: 82 | for line in file: 83 | 84 | line = line.strip() 85 | 86 | if line.find('#',0) == -1 and line != '': 87 | 88 | pgbackman_command = line 89 | command = 'pgbackman -o json -C "' + pgbackman_command + '"' 90 | 91 | if 'delete_' in line.lower() or \ 92 | 'register_' in line.lower() or \ 93 | 'update_' in line.lower(): 94 | 95 | DEVNULL = open(os.devnull, 'w') 96 | proc = subprocess.Popen([command],stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True) 97 | proc.wait() 98 | 99 | stdout, stderr = proc.communicate() 100 | 101 | if proc.returncode == 0: 102 | logs.logger.info('PgBackMan command [%s] executed',command) 103 | print '[OK]\tPgBackMan command [' + command + '] executed' 104 | 105 | ok_count += 1 106 | 107 | else: 108 | logs.logger.error('PgBackMan command [%s] could not be executed - %s',command,str(stdout)) 109 | print '[ERROR]\tPgBackMan command [' + command + '] could not be executed' 110 | 111 | error_count += 1 112 | else: 113 | 114 | logs.logger.info('PgBackMan command [%s] is not supported by pgbackman-bulk-update.',command) 115 | print '[INFO]\tPgBackMan command [' + command + '] is not supported by pgbackman-bulk-update.' 116 | 117 | not_supported_count += 1 118 | 119 | footer = '\n####################################################################\n' + \ 120 | 'Total ok: ' + str(ok_count) + '\n' + \ 121 | 'Total error: ' + str(error_count) + '\n' + \ 122 | 'Total not supported: ' + str(not_supported_count) + '\n' + \ 123 | '####################################################################' 124 | 125 | print footer 126 | 127 | if error_count > 0: 128 | print "WARNING: Check PgBackMan log file for error information" 129 | print 130 | 131 | except Exception as e: 132 | 133 | logs.logger.error('Problems using file [%s] - %s',input_file,e) 134 | print '[ERROR]\tProblems using file [' + input_file + '] - ' + str(e) 135 | sys.exit(1) 136 | 137 | else: 138 | logs.logger.info('File [%s] does not exist. Bulk execution of commands aborted.',input_file) 139 | print '[ERROR]\tFile [' + input_file + '] does not exist. Bulk execution of commands aborted' 140 | 141 | logs.logger.debug('**** pgbackman-bulk-update finished. ****') 142 | 143 | except Exception as e: 144 | print '\n[ERROR]: %s\n',e 145 | 146 | logs.logger.error('Problems running pgbackman-bulk-update - %s',e) 147 | print 'Problems running pgbackman-bulk-update - ' + str(e) 148 | 149 | sys.exit(1) 150 | -------------------------------------------------------------------------------- /bin/pgbackman_alerts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2013-2015 Rafael Martinez Guerrero / PostgreSQL-es 4 | # rafael@postgresql.org.es / http://www.postgresql.org.es/ 5 | # 6 | # Copyright (c) 2014-2015 USIT-University of Oslo 7 | # 8 | # This file is part of PgBackMan 9 | # https://github.com/rafaelma/pgbackman 10 | # 11 | # PgBackMan is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # PgBackMan is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with Pgbackman. If not, see . 23 | 24 | import datetime 25 | import sys 26 | import os 27 | import time 28 | import socket 29 | import signal 30 | import errno 31 | import smtplib 32 | 33 | from string import Template 34 | from pgbackman.logs import * 35 | from pgbackman.database import * 36 | from pgbackman.config import * 37 | import pgbackman.version 38 | 39 | 40 | # ############################################ 41 | # Function get_alerts() 42 | # ############################################ 43 | 44 | def get_alerts(conf,db,backup_server_id): 45 | """ 46 | Getting alerts from this backup server 47 | """ 48 | 49 | try: 50 | for alert in db.get_alerts(backup_server_id): 51 | send_alert(conf,db,alert) 52 | 53 | except psycopg2.OperationalError as e: 54 | raise e 55 | except Exception as e: 56 | logs.logger.error('Could not get alerts for this backup server - %s.',e) 57 | 58 | 59 | # ############################################ 60 | # Function send_alert() 61 | # ############################################ 62 | 63 | def send_alert(conf,db,alert_data): 64 | """ 65 | Sending alert from this backup server 66 | """ 67 | 68 | try: 69 | 70 | variables = {} 71 | 72 | variables['alert_id'] = alert_data[0] 73 | variables['registered'] = alert_data[1] 74 | variables['alert_type'] = alert_data[2] 75 | variables['ref_id'] = alert_data[3] 76 | variables['bck_id'] = alert_data[4] 77 | variables['backup_server_id'] = alert_data[5] 78 | variables['backup_server_fqdn'] = db.get_backup_server_fqdn(variables['backup_server_id']) 79 | variables['pgsql_node_id'] = alert_data[6] 80 | variables['pgsql_node_fqdn'] = db.get_pgsql_node_fqdn(variables['pgsql_node_id']) 81 | variables['dbname'] = alert_data[7] 82 | variables['execution_status'] = alert_data[8] 83 | variables['error_message'] = alert_data[9] 84 | variables['sendto'] = alert_data[10] 85 | variables['alert_sent'] = alert_data[11] 86 | variables['date'] = datetime.datetime.now() 87 | variables['pgbackman_version'] = pgbackman.version.__version__.split(':')[1] 88 | 89 | msg = '' 90 | 91 | # 92 | # If SMTP alert is not active we will not send the emails 93 | # alerts but we will update alert_sent = TRUE. 94 | # 95 | # This is done to avoid a storm of old alerts if we activate 96 | # this functionality after a period of time. 97 | # 98 | 99 | if conf.smtp_alerts == 'OFF': 100 | 101 | db.update_alert_sent(variables['alert_id'],'true') 102 | logs.logger.info('AlertID [%s] for BckID [%s] registered as sent with smtp_alerts=OFF',variables['alert_id'],variables['bck_id']) 103 | 104 | elif conf.smtp_alerts == 'ON': 105 | 106 | # 107 | # Add the From, To and user-agent headers 108 | # 109 | headers = ("From: %s\r\nTo: %s\r\nUser-agent: pgbackman_alerts (v.%s)\r\n" 110 | % (conf.smtp_from_address, variables['sendto'],variables['pgbackman_version'])) 111 | 112 | # 113 | # Get the email body 114 | # 115 | body = parse_alert_template(conf,variables) 116 | 117 | # 118 | # Connect to SNMP. 119 | # 120 | # We will not use SSL when connecting via localhost, even if 121 | # smtp_ssl = ON 122 | # 123 | 124 | server = conf.smtp_server + ':' + conf.smtp_port 125 | 126 | if conf.smtp_server == 'localhost': 127 | smtp = smtplib.SMTP(server) 128 | else: 129 | server = conf.smtp_server + ':' + conf.smtp_port 130 | 131 | if conf.smtp_ssl == 'ON': 132 | smtp = smtplib.SMTP_SSL(server) 133 | 134 | elif conf.smtp_ssl == 'OFF': 135 | smtp = smtplib.SMTP(server) 136 | 137 | smtp.login(conf.smtp_user,conf.smtp_password) 138 | 139 | # 140 | # Send email 141 | # 142 | 143 | smtp.sendmail(conf.smtp_from_address, variables['sendto'], headers + body) 144 | logs.logger.info('Email alert for BckID [%s] sent to [%s]',variables['bck_id'],variables['sendto']) 145 | 146 | smtp.quit() 147 | 148 | db.update_alert_sent(variables['alert_id'],'true') 149 | logs.logger.info('AlertID [%s] for BckID [%s] registered in the database as sent with smtp_alerts=ON',variables['alert_id'],variables['bck_id']) 150 | 151 | except psycopg2.OperationalError as e: 152 | raise e 153 | except Exception as e: 154 | logs.logger.error('Problems sending alertID [%s] via SMTP - %s.',variables['alert_id'],e) 155 | 156 | 157 | # ############################################ 158 | # Function parse_alert_template() 159 | # ############################################ 160 | 161 | def parse_alert_template(conf,variables): 162 | 163 | try: 164 | 165 | f = open(conf.alerts_template, 'r') 166 | template = f.read() 167 | 168 | t = Template(template) 169 | body = t.safe_substitute(variables) 170 | 171 | return body 172 | 173 | except Exception as e: 174 | raise Exception("Problems parsing alert template [%s] for alarmID [%s]- %s." % (conf.alerts_template,variables['alert_id'],e)) 175 | 176 | 177 | # ############################################ 178 | # Function signal_handler() 179 | # ############################################ 180 | 181 | def signal_handler(signum, frame): 182 | logs.logger.info('**** pgbackman_maintenance stopped. ****') 183 | sys.exit(0) 184 | 185 | 186 | # ############################################ 187 | # Function check_database_connection() 188 | # ############################################ 189 | 190 | def check_database_connection(db): 191 | '''Check if we can connect to the database server and the pgbackman database''' 192 | 193 | try: 194 | db.pg_connect() 195 | return True 196 | except Exception as e: 197 | return False 198 | 199 | 200 | # ############################################ 201 | # Function main() 202 | # ############################################ 203 | 204 | def main(): 205 | 206 | conf = PgbackmanConfiguration() 207 | dsn = conf.dsn 208 | 209 | if conf.smtp_alerts == 'OFF': 210 | logs.logger.info('SMTP Alerts is not active. Check your configuration file and define smtp_alerts=ON to activate this and restart pgbackman.') 211 | 212 | # 213 | # We exit pgbackman_alerts if sending of alerts via SMTP is not 214 | # activated 215 | # 216 | 217 | logs.logger.debug('Backup server ID from config file: %s',conf.backup_server) 218 | logs.logger.debug('Backup server FQDN: %s',socket.getfqdn()) 219 | logs.logger.debug('DSN: host=%s hostaddr=%s port=%s database=%s user=%s ',conf.dbhost,conf.dbhostaddr,conf.dbport,conf.dbname,conf.dbuser) 220 | 221 | db = PgbackmanDB(dsn, 'pgbackman_alerts') 222 | 223 | # 224 | # We check before starting if the database is available. 225 | # If it is not available we will wait conf.pg_connect_retry_interval 226 | # and try again 227 | 228 | check_db = check_database_connection(db) 229 | 230 | while not check_db: 231 | logs.logger.critical('The pgbackman database is not available. Waiting %s seconds before trying again',conf.pg_connect_retry_interval) 232 | 233 | time.sleep(conf.pg_connect_retry_interval) 234 | check_db = check_database_connection(db) 235 | 236 | logs.logger.debug('Database server is up and running and pgbackman database is available') 237 | 238 | # 239 | # Check backup server information 240 | # 241 | 242 | if conf.backup_server != '': 243 | backup_server_fqdn = conf.backup_server 244 | else: 245 | backup_server_fqdn = socket.getfqdn() 246 | 247 | try: 248 | backup_server_id = db.get_backup_server_id(backup_server_fqdn) 249 | logs.logger.info('Backup server [%s] is registered in pgbackman',backup_server_fqdn) 250 | 251 | except psycopg2.Error as e: 252 | logs.logger.critical('Cannot find backup server [%s] in pgbackman. Stopping pgbackman_alerts.',backup_server_fqdn) 253 | logs.logger.info('**** pgbackman_alerts stopped. ****') 254 | sys.exit(1) 255 | 256 | loop = 0 257 | 258 | while loop == 0: 259 | try: 260 | get_alerts(conf,db,backup_server_id) 261 | 262 | except psycopg2.OperationalError as e: 263 | 264 | # 265 | # If we lose the connection to the database, we will wait conf.pg_connect_retry_interval 266 | # before trying to connect again. 267 | # 268 | 269 | logs.logger.critical('Operational error: %s',e) 270 | 271 | check_db = check_database_connection(db) 272 | 273 | while not check_db: 274 | logs.logger.critical('We have lost the connection to the database. Waiting %s seconds before trying again',conf.pg_connect_retry_interval) 275 | 276 | time.sleep(conf.pg_connect_retry_interval) 277 | check_db = check_database_connection(db) 278 | 279 | # Wait for next maintenance run if in loop mode 280 | time.sleep(conf.alerts_check_interval) 281 | 282 | db.pg_close() 283 | 284 | 285 | # ############################################ 286 | # 287 | # ############################################ 288 | 289 | if __name__ == '__main__': 290 | 291 | logs = PgbackmanLogs("pgbackman_alerts", "", "") 292 | 293 | signal.signal(signal.SIGINT,signal_handler) 294 | signal.signal(signal.SIGTERM,signal_handler) 295 | 296 | logs.logger.info('**** pgbackman_alerts started. ****') 297 | 298 | main() 299 | 300 | logs.logger.info('**** pgbackman_alerts finished. ****') 301 | -------------------------------------------------------------------------------- /bin/pgbackman_maintenance: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2013-2014 Rafael Martinez Guerrero / PostgreSQL-es 4 | # rafael@postgresql.org.es / http://www.postgresql.org.es/ 5 | # 6 | # Copyright (c) 2014 USIT-University of Oslo 7 | # 8 | # This file is part of PgBackMan 9 | # https://github.com/rafaelma/pgbackman 10 | # 11 | # PgBackMan is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # PgBackMan is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with Pgbackman. If not, see . 23 | 24 | import subprocess 25 | import tempfile 26 | import datetime 27 | import sys 28 | import os 29 | import time 30 | import socket 31 | import signal 32 | import argparse 33 | import errno 34 | import shutil 35 | 36 | from pgbackman.logs import * 37 | from pgbackman.database import * 38 | from pgbackman.config import * 39 | 40 | ''' 41 | This program is used by PgBackMan to run some maintenance tasks in this backup server. 42 | 43 | These are the task implemented: 44 | 45 | * Delete restore logs files when restore definitions/catalogs are deleted. 46 | * Delete backup and log files from catalog entries associated to a backup definition after 47 | this definition has been deleted with the force parameter. 48 | * Enforce retentions for backup definitions. 49 | * Enforce retentions of snapshot backups 50 | * Process all pending backup catalog log files in the server 51 | * Process all pending restore catalog log files in the server 52 | 53 | ''' 54 | 55 | # ############################################ 56 | # Function delete_restore_logs() 57 | # ############################################ 58 | 59 | def delete_restore_logs(db,backup_server_id): 60 | '''Delete restore log files after restore definitions/catalogs are deleted''' 61 | 62 | logs.logger.debug('## Deleting restore logs after restore definitions/catalogs are deleted ##') 63 | 64 | try: 65 | for record in db.get_restore_logs_to_delete(backup_server_id): 66 | 67 | error_cnt = 0 68 | 69 | # 70 | # record[2] is the file to delete 71 | # 72 | 73 | try: 74 | os.unlink(record[2]) 75 | logs.logger.debug('File: %s deleted',record[2]) 76 | 77 | except OSError as e: 78 | if e.errno != errno.ENOENT: 79 | logs.logger.error('Problems deleting restore log files with DelID: %s - %s',record[0],e) 80 | error_cnt = error_cnt + 1 81 | else: 82 | pass 83 | 84 | # 85 | # We can delete the delID entry from the database only if we can 86 | # delete all the files in the delID entry without errors 87 | # 88 | 89 | if error_cnt == 0: 90 | try: 91 | db.delete_restore_logs_to_delete(record[0]) 92 | logs.logger.info('Restore Log file for DelID: %s deleted',record[0]) 93 | 94 | except psycopg2.OperationalError as e: 95 | raise e 96 | except Exception as e: 97 | logs.logger.error('Problems deleting restore logs to delete entry DelID: %s - %s',record[0],e) 98 | 99 | except psycopg2.OperationalError as e: 100 | raise e 101 | except Exception as e: 102 | logs.logger.error('Could not get the restore log to delete information - %s',e) 103 | 104 | 105 | # ############################################ 106 | # Function delete_files_from_force_deletes() 107 | # ############################################ 108 | 109 | def delete_files_from_force_deletes(db,backup_server_id): 110 | '''Delete dump and log files from force deletions of backup definitions''' 111 | 112 | logs.logger.debug('## Deleting files from forced DefID deletions ##') 113 | 114 | try: 115 | for record in db.get_catalog_entries_to_delete(backup_server_id): 116 | 117 | error_cnt = 0 118 | 119 | # 120 | # record[5...10] are the files to delete 121 | # 122 | 123 | for index in range(5,11): 124 | 125 | try: 126 | 127 | if os.path.isfile(record[index]): 128 | os.unlink(record[index]) 129 | logs.logger.debug('File: %s deleted',record[index]) 130 | 131 | elif os.path.isdir(record[index]): 132 | shutil.rmtree(record[index]) 133 | logs.logger.debug('Directory: %s deleted',record[index]) 134 | 135 | except OSError as e: 136 | if e.errno != errno.ENOENT: 137 | logs.logger.error('Problems deleting files from force deletions of DefIDs: %s',e) 138 | error_cnt = error_cnt + 1 139 | else: 140 | pass 141 | 142 | 143 | # 144 | # We can delete the delID entry from the database only if we can 145 | # delete all the files in the delID entry without errors 146 | # 147 | 148 | if error_cnt == 0: 149 | try: 150 | db.delete_catalog_entries_to_delete(record[0]) 151 | logs.logger.info('Files for catalog ID: %s / DefID: %s deleted',record[3],record[2]) 152 | 153 | except psycopg2.OperationalError as e: 154 | raise e 155 | except Exception as e: 156 | logs.logger.error('Problems deleting cataloginfo from force defid deletions - %s',e) 157 | 158 | except psycopg2.OperationalError as e: 159 | raise e 160 | except Exception as e: 161 | logs.logger.error('Could not get the catalog information for defid force deletions - %s',e) 162 | 163 | 164 | # ############################################ 165 | # Function enforce_backup_retentions() 166 | # ############################################ 167 | 168 | def enforce_backup_retentions(db,backup_server_id): 169 | '''Delete dump and log files according to retention_periods and retention_redundancies''' 170 | 171 | logs.logger.debug('## Enforce backup retentions ##') 172 | 173 | try: 174 | for record in db.get_cron_catalog_entries_to_delete_by_retention(backup_server_id): 175 | 176 | error_cnt = 0 177 | 178 | # 179 | # record[9...14] are the files to delete 180 | # 181 | 182 | for index in range(9,15): 183 | 184 | try: 185 | 186 | if os.path.isfile(record[index]): 187 | os.unlink(record[index]) 188 | logs.logger.debug('File: %s deleted',record[index]) 189 | 190 | elif os.path.isdir(record[index]): 191 | shutil.rmtree(record[index]) 192 | logs.logger.debug('Directory: %s deleted',record[index]) 193 | 194 | except OSError as e: 195 | if e.errno != errno.ENOENT: 196 | logs.logger.error('Problems deleting files from backup enforce retentions: %s',e) 197 | error_cnt = error_cnt + 1 198 | else: 199 | pass 200 | 201 | # 202 | # We can delete the BckID entry from the catalog table only if we can 203 | # delete all the files for the BckID entry without errors 204 | # 205 | 206 | if error_cnt == 0: 207 | try: 208 | db.delete_backup_catalog(record[1]) 209 | logs.logger.info('Files for catalog ID: %s / DefID: %s deleted',record[1],record[2]) 210 | 211 | except psycopg2.OperationalError as e: 212 | raise e 213 | except Exception as e: 214 | logs.logger.error('Problems deleting entry from backup job catalog - %s',e) 215 | 216 | except psycopg2.OperationalError as e: 217 | raise e 218 | except Exception as e: 219 | logs.logger.error('Could not get information to enforce backup file retentions - %s',e) 220 | 221 | 222 | # ############################################ 223 | # Function enforce_snapshot_retentions() 224 | # ############################################ 225 | 226 | def enforce_snapshot_retentions(db,backup_server_id): 227 | '''Delete dump and log snapshot files according to retention_periods''' 228 | 229 | logs.logger.debug('## Enforce snapshot retentions ##') 230 | 231 | try: 232 | for record in db.get_at_catalog_entries_to_delete_by_retention(backup_server_id): 233 | 234 | error_cnt = 0 235 | 236 | # 237 | # record[7...12] are the files to delete 238 | # 239 | 240 | for index in range(7,13): 241 | 242 | try: 243 | 244 | if os.path.isfile(record[index]): 245 | os.unlink(record[index]) 246 | logs.logger.debug('File: %s deleted',record[index]) 247 | 248 | elif os.path.isdir(record[index]): 249 | shutil.rmtree(record[index]) 250 | logs.logger.debug('Directory: %s deleted',record[index]) 251 | 252 | except OSError as e: 253 | if e.errno != errno.ENOENT: 254 | logs.logger.error('Problems deleting files from snapshot enforce retentions: %s',e) 255 | error_cnt = error_cnt + 1 256 | else: 257 | pass 258 | 259 | # 260 | # We can delete the snapshotID entry from the catalog and definition table only if we can 261 | # delete all the files for the SnapshotID entry without errors 262 | # 263 | 264 | if error_cnt == 0: 265 | try: 266 | db.delete_snapshot_definition(record[1]) 267 | logs.logger.info('Files for catalog ID: %s / SnapshotID: %s deleted',record[0],record[1]) 268 | 269 | except psycopg2.OperationalError as e: 270 | raise e 271 | except Exception as e: 272 | logs.logger.error('Problems deleting entry from backup job catalog - %s',e) 273 | 274 | except psycopg2.OperationalError as e: 275 | raise e 276 | except Exception as e: 277 | logs.logger.error('Could not get information to enforce snapshot file retentions - %s',e) 278 | 279 | 280 | # ################################################## 281 | # Function process_pending_backup_catalog_log_file() 282 | # ################################################## 283 | 284 | def process_pending_backup_catalog_log_file(db,backup_server_id): 285 | '''Process all pending backup catalog log files in the server ''' 286 | 287 | role_list = [] 288 | 289 | logs.logger.debug('## Processing pending backup catalog log files ##') 290 | 291 | try: 292 | db.pg_connect() 293 | 294 | root_backup_partition = db.get_backup_server_config_value(backup_server_id,'root_backup_partition') 295 | pending_catalog = root_backup_partition + '/pending_updates' 296 | 297 | for pending_log_file in os.listdir(pending_catalog): 298 | if pending_log_file.find('backup_jobs_pending_log_updates_nodeid') != -1: 299 | with open(pending_catalog + '/' + pending_log_file,'r') as pending_file: 300 | for line in pending_file: 301 | parameters = line.split('::') 302 | 303 | if len(parameters) == 25: 304 | 305 | # 306 | # Fix when def_id and snapshot_id are like ''. This is not a valid 307 | # integer value 308 | # 309 | 310 | def_id = parameters[0] 311 | snapshot_id = parameters[21] 312 | 313 | if def_id == '': 314 | def_id = None 315 | elif snapshot_id == '': 316 | snapshot_id = None 317 | 318 | # Generate role list 319 | 320 | role_list = parameters[22].split(' ') 321 | 322 | # 323 | # Updating the database with the information in the pending file 324 | # 325 | 326 | db.register_backup_catalog(def_id, 327 | parameters[1], 328 | parameters[2], 329 | parameters[3], 330 | parameters[4], 331 | parameters[5], 332 | parameters[6], 333 | parameters[7], 334 | parameters[8], 335 | parameters[9], 336 | parameters[10], 337 | parameters[11], 338 | parameters[12], 339 | parameters[13], 340 | parameters[14], 341 | parameters[15], 342 | parameters[16], 343 | parameters[17], 344 | parameters[18], 345 | parameters[19], 346 | parameters[20], 347 | snapshot_id, 348 | role_list, 349 | parameters[23], 350 | parameters[24].replace('\n','')) 351 | 352 | logs.logger.info('Backup job catalog for DefID: %s or snapshotID: %s in pending file %s updated in the database',def_id,snapshot_id,pending_log_file) 353 | 354 | # 355 | # Deleting the pending file if we can update the database with 356 | # the information in the file 357 | # 358 | 359 | os.unlink(pending_catalog + '/' + pending_log_file) 360 | logs.logger.info('Pending backup file: %s deleted',pending_log_file) 361 | 362 | else: 363 | logs.logger.error('Wrong format in pending backup file: %s',pending_log_file) 364 | 365 | except psycopg2.OperationalError as e: 366 | raise e 367 | except Exception as e: 368 | logs.logger.error('Problems processing pending backup files - %s',e) 369 | 370 | 371 | # ################################################## 372 | # Function process_pending_restore_catalog_log_file() 373 | # ################################################## 374 | 375 | def process_pending_restore_catalog_log_file(db,backup_server_id): 376 | '''Process all pending restore catalog log files in the server ''' 377 | 378 | role_list = [] 379 | 380 | logs.logger.debug('## Processing pending restore catalog log files ##') 381 | 382 | try: 383 | db.pg_connect() 384 | 385 | root_backup_partition = db.get_backup_server_config_value(backup_server_id,'root_backup_partition') 386 | pending_catalog = root_backup_partition + '/pending_updates' 387 | 388 | for pending_log_file in os.listdir(pending_catalog): 389 | if pending_log_file.find('restore_jobs_pending_log_updates_nodeid') != -1: 390 | with open(pending_catalog + '/' + pending_log_file,'r') as pending_file: 391 | for line in pending_file: 392 | parameters = line.split('::') 393 | 394 | if len(parameters) == 17: 395 | 396 | # 397 | # Updating the database with the information in the pending file 398 | # 399 | 400 | db.register_restore_catalog(parameters[0], 401 | parameters[1], 402 | parameters[2], 403 | parameters[3], 404 | parameters[4], 405 | parameters[5], 406 | parameters[6], 407 | parameters[7], 408 | parameters[8], 409 | parameters[9], 410 | parameters[10], 411 | parameters[11], 412 | parameters[12], 413 | parameters[13], 414 | parameters[14].split(' '), 415 | parameters[15], 416 | parameters[16].replace('\n','')) 417 | 418 | logs.logger.info('Restore job catalog for restoreDef: %s in pending file %s updated in the database',parameters[0],pending_log_file) 419 | 420 | # 421 | # Deleting the pending file if we can update the database with 422 | # the information in the file 423 | # 424 | 425 | os.unlink(pending_catalog + '/' + pending_log_file) 426 | logs.logger.info('Pending restore file: %s deleted',pending_log_file) 427 | 428 | else: 429 | logs.logger.error('Wrong format in pending restore file: %s',pending_log_file) 430 | 431 | except psycopg2.OperationalError as e: 432 | raise e 433 | except Exception as e: 434 | logs.logger.error('Problems processing pending restore files - %s',e) 435 | 436 | 437 | 438 | # ############################################################ 439 | # Function process_backup_definitions_from_deleted_databases() 440 | # ############################################################ 441 | 442 | def process_backup_definitions_from_deleted_databases(db,backup_server_id): 443 | ''' 444 | This function stops backup definitions for databases that have been deleted 445 | in the PgSQL nodes running them. 446 | ''' 447 | 448 | logs.logger.debug('## Processing backup definitions for deleted databases ##') 449 | 450 | # 451 | # Processing data for all PgSQL nodes with status "RUNNING" 452 | # 453 | 454 | try: 455 | for record in db.get_pgsql_nodes_list(): 456 | 457 | backup_def_full_list = [] 458 | backup_def_database_list = [] 459 | backup_def_list_to_process = [] 460 | database_list = [] 461 | db_node = None 462 | 463 | pgsql_node_id = record[0] 464 | pgsql_node_fqdn = record[1] 465 | 466 | logs.logger.debug('Proccesing backup definitions for deleted databases on: %s',pgsql_node_fqdn) 467 | 468 | dsn_value = db.get_pgsql_node_dsn(pgsql_node_id) 469 | db_node = PgbackmanDB(dsn_value, 'pgbackman_maintenance') 470 | 471 | # 472 | # Get all backup definitions for this Backup server - PgSQL node 473 | # 474 | for backup_def_id in db.get_all_backup_definitions(backup_server_id,pgsql_node_id): 475 | backup_def_full_list.append(backup_def_id[0]) 476 | 477 | # 478 | # Get backup definitions for databases that exist in the PgSQL_node 479 | # 480 | 481 | for database in db_node.get_pgsql_node_database_list(): 482 | 483 | for backup_def_id in db.get_database_backup_definitions(backup_server_id,pgsql_node_id,database[0]): 484 | backup_def_database_list.append(backup_def_id[0]) 485 | 486 | # 487 | # List of backup definitions to process 488 | # 489 | 490 | backup_def_list_to_process = set(backup_def_full_list) - set(backup_def_database_list) 491 | 492 | # 493 | # Update the backup def status to "DELETED" 494 | # 495 | 496 | for backup_def_id in backup_def_list_to_process: 497 | 498 | logs.logger.info('Updating status of bck_def: %s to DELETED',backup_def_id) 499 | db.update_backup_definition_status_to_delete(int(backup_def_id)) 500 | 501 | # 502 | # Update catalog_entries_to_delete and delete def_id from backup_definition 503 | # 504 | # The files associated to the backup definition and the 505 | # backup definition entry will we deleted if the backup 506 | # definition got the DELETED status for more than the 507 | # period of time defined by the parameter 508 | # automatic_deletion_retention for the PgSQL node that was 509 | # running the deleted database. 510 | # 511 | 512 | for backup_def_id in db.get_deleted_backup_definitions_to_delete_by_retention(): 513 | try: 514 | db.delete_force_backup_definition_id(backup_def_id[0]) 515 | logs.logger.info('Updating catalog_entries_to_delete for DefID: %s with status DELETED',backup_def_id[0]) 516 | except Exception as e: 517 | logs.logger.error('Could not update catalog_entries_to_delete for DefID: %s with status DELETED - %s',backup_def_id[0],e) 518 | 519 | except psycopg2.OperationalError as e: 520 | raise e 521 | except Exception as e: 522 | logs.logger.error('Could not process backup definitions for deleted databases - %s',e) 523 | 524 | 525 | # ############################################ 526 | # Function signal_handler() 527 | # ############################################ 528 | 529 | def signal_handler(signum, frame): 530 | logs.logger.info('**** pgbackman_maintenance stopped. ****') 531 | sys.exit(0) 532 | 533 | 534 | # ############################################ 535 | # Function check_database_connection() 536 | # ############################################ 537 | 538 | def check_database_connection(db): 539 | '''Check if we can connect to the database server and the pgbackman database''' 540 | 541 | try: 542 | db.pg_connect() 543 | return True 544 | except Exception as e: 545 | return False 546 | 547 | 548 | # ############################################ 549 | # Function main() 550 | # ############################################ 551 | 552 | def main(): 553 | 554 | conf = PgbackmanConfiguration() 555 | dsn = conf.dsn 556 | 557 | logs.logger.debug('Backup server ID from config file: %s',conf.backup_server) 558 | logs.logger.debug('Backup server FQDN: %s',socket.getfqdn()) 559 | logs.logger.debug('DSN: host=%s hostaddr=%s port=%s database=%s user=%s ',conf.dbhost,conf.dbhostaddr,conf.dbport,conf.dbname,conf.dbuser) 560 | logs.logger.debug('Maintenance interval: %s',conf.maintenance_interval) 561 | 562 | db = PgbackmanDB(dsn, 'pgbackman_maintenance') 563 | 564 | # 565 | # We check before starting if the database is available. 566 | # If it is not available we will wait conf.pg_connect_retry_interval 567 | # and try again 568 | 569 | check_db = check_database_connection(db) 570 | 571 | while not check_db: 572 | logs.logger.critical('The pgbackman database is not available. Waiting %s seconds before trying again',conf.pg_connect_retry_interval) 573 | 574 | time.sleep(conf.pg_connect_retry_interval) 575 | check_db = check_database_connection(db) 576 | 577 | logs.logger.debug('Database server is up and running and pgbackman database is available') 578 | 579 | # 580 | # Check backup server information 581 | # 582 | 583 | if conf.backup_server != '': 584 | backup_server_fqdn = conf.backup_server 585 | else: 586 | backup_server_fqdn = socket.getfqdn() 587 | 588 | try: 589 | backup_server_id = db.get_backup_server_id(backup_server_fqdn) 590 | logs.logger.info('Backup server: %s is registered in pgbackman',backup_server_fqdn) 591 | 592 | except psycopg2.Error as e: 593 | logs.logger.critical('Cannot find backup server %s in pgbackman. Stopping pgbackman2cron.',backup_server_fqdn) 594 | logs.logger.info('**** pgbackman_maintenance stopped. ****') 595 | sys.exit(1) 596 | 597 | loop = 0 598 | 599 | while loop == 0: 600 | try: 601 | delete_files_from_force_deletes(db,backup_server_id) 602 | enforce_backup_retentions(db,backup_server_id) 603 | enforce_snapshot_retentions(db,backup_server_id) 604 | delete_restore_logs(db,backup_server_id) 605 | process_pending_backup_catalog_log_file(db,backup_server_id) 606 | process_pending_restore_catalog_log_file(db,backup_server_id) 607 | process_backup_definitions_from_deleted_databases(db,backup_server_id) 608 | 609 | except psycopg2.OperationalError as e: 610 | 611 | # 612 | # If we lose the connection to the database, we will wait conf.pg_connect_retry_interval 613 | # before trying to connect again. 614 | # 615 | 616 | logs.logger.critical('Operational error: %s',e) 617 | 618 | check_db = check_database_connection(db) 619 | 620 | while not check_db: 621 | logs.logger.critical('We have lost the connection to the database. Waiting %s seconds before trying again',conf.pg_connect_retry_interval) 622 | 623 | time.sleep(conf.pg_connect_retry_interval) 624 | check_db = check_database_connection(db) 625 | 626 | if cron: 627 | loop = 1 628 | else: 629 | # Wait for next maintenance run if in loop mode 630 | time.sleep(conf.maintenance_interval) 631 | 632 | db.pg_close() 633 | 634 | 635 | # ############################################ 636 | # 637 | # ############################################ 638 | 639 | if __name__ == '__main__': 640 | 641 | logs = PgbackmanLogs("pgbackman_maintenance", "", "") 642 | 643 | signal.signal(signal.SIGINT,signal_handler) 644 | signal.signal(signal.SIGTERM,signal_handler) 645 | 646 | parser = argparse.ArgumentParser(prog=sys.argv[0]) 647 | parser.add_argument('--cron', required=False, help='Single run to use via cron', action="store_true") 648 | 649 | args = parser.parse_args() 650 | 651 | logs.logger.info('**** pgbackman_maintenance started. ****') 652 | 653 | if args.cron: 654 | cron = True 655 | logs.logger.info('Running in cron mode') 656 | else: 657 | cron = False 658 | logs.logger.info('Running in loop mode') 659 | 660 | main() 661 | 662 | logs.logger.info('**** pgbackman_maintenance finished. ****') 663 | -------------------------------------------------------------------------------- /bin/pgbackman_status_info: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2013-2015 Rafael Martinez Guerrero / PostgreSQL-es 4 | # rafael@postgresql.org.es / http://www.postgresql.org.es/ 5 | # 6 | # Copyright (c) 2014-2015 USIT-University of Oslo 7 | # 8 | # This file is part of PgBackMan 9 | # https://github.com/rafaelma/pgbackman 10 | # 11 | # PgBackMan is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # PgBackMan is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with PgBck. If not, see . 23 | 24 | import sys 25 | import os 26 | import signal 27 | import argparse 28 | import json 29 | 30 | from pgbackman.database import * 31 | from pgbackman.config import * 32 | from pgbackman.logs import * 33 | 34 | 35 | ''' 36 | This program is used to get some status information from 37 | pgbackman. 38 | 39 | This can be used e.g. by Zabbix to define new Items 40 | 41 | ''' 42 | 43 | # ############################################ 44 | # Function signal_handler() 45 | # ############################################ 46 | 47 | def signal_handler(signum, frame): 48 | sys.exit(0) 49 | 50 | 51 | # ############################################ 52 | # Function Main() 53 | # ############################################ 54 | 55 | def get_status_info(parameter_status,backup_server_fqdn,backup_def_id): 56 | 57 | try: 58 | 59 | conf = PgbackmanConfiguration() 60 | pgbackman_dsn = conf.dsn 61 | 62 | db = PgbackmanDB(pgbackman_dsn, 'pgbackman_status_info') 63 | 64 | if backup_server_fqdn != '': 65 | backup_server_id = db.get_backup_server_id(backup_server_fqdn) 66 | else: 67 | backup_server_id = '' 68 | 69 | return_value = db.get_status_info(parameter_status,backup_server_id,backup_def_id) 70 | print return_value 71 | 72 | logs.logger.debug('Getting status information for parameter [%s]',parameter_status) 73 | 74 | except Exception as e: 75 | logs.logger.error('Problems getting status information for parameter [%s] - %s',parameter_status,e) 76 | sys.exit(1) 77 | 78 | 79 | # ############################################ 80 | # 81 | # ############################################ 82 | 83 | if __name__ == '__main__': 84 | 85 | signal.signal(signal.SIGINT,signal_handler) 86 | signal.signal(signal.SIGTERM,signal_handler) 87 | 88 | # 89 | # Initializing logging 90 | # 91 | 92 | logs = PgbackmanLogs("pgbackman_status_info", "", "") 93 | logs.logger.debug('**** pgbackman_status_info started. ****') 94 | 95 | parser = argparse.ArgumentParser(prog=sys.argv[0]) 96 | 97 | parser.add_argument('--backup-server','-b', metavar='BACKUP-SERVER-FQDN', required=False, help='Backup server FQDN', dest='backup_server_fqdn') 98 | parser.add_argument('--backup-definition-id','-d', metavar='BACKUP-DEFINITION-ID', required=False, help='Backup definition ID', dest='backup_def_id') 99 | parser.add_argument('--parameter-status','-p', metavar='PARAMETER-STATUS', required=False, help='Parameter status', dest='parameter_status') 100 | 101 | args = parser.parse_args() 102 | 103 | if args.backup_server_fqdn: 104 | backup_server = args.backup_server_fqdn 105 | else: 106 | backup_server = '' 107 | 108 | if args.backup_def_id: 109 | backup_def_id = args.backup_def_id 110 | else: 111 | backup_def_id = '0' 112 | 113 | if args.parameter_status: 114 | parameter_status = args.parameter_status 115 | else: 116 | parameter_status = '' 117 | 118 | get_status_info(parameter_status,backup_server,backup_def_id) 119 | 120 | logs.logger.debug('**** pgbackman_status_info finished. ****') 121 | -------------------------------------------------------------------------------- /bin/pgbackman_zabbix_autodiscovery: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2013-2015 Rafael Martinez Guerrero / PostgreSQL-es 4 | # rafael@postgresql.org.es / http://www.postgresql.org.es/ 5 | # 6 | # Copyright (c) 2014-2015 USIT-University of Oslo 7 | # 8 | # This file is part of PgBackMan 9 | # https://github.com/rafaelma/pgbackman 10 | # 11 | # PgBackMan is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # PgBackMan is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with PgBck. If not, see . 23 | 24 | import sys 25 | import os 26 | import signal 27 | import argparse 28 | import json 29 | 30 | from pgbackman.database import * 31 | from pgbackman.config import * 32 | from pgbackman.logs import * 33 | 34 | 35 | ''' 36 | This program is used to generate the JSON output that Zabbix needs 37 | in a low-level discovery rule. 38 | 39 | The JSON output will have information about the databases with a 40 | backup definition in PgBackMan. {#PGSQL_NODE} and {#DBNAME} are the 41 | macros Zabbix will use to get the data. 42 | 43 | ''' 44 | 45 | # ############################################ 46 | # Function signal_handler() 47 | # ############################################ 48 | 49 | def signal_handler(signum, frame): 50 | sys.exit(0) 51 | 52 | 53 | # ############################################ 54 | # Function Main() 55 | # ############################################ 56 | 57 | def get_pgbackman_zabbix_autodiscovery(backup_server_fqdn): 58 | 59 | backup_definitions_list = [] 60 | 61 | try: 62 | 63 | conf = PgbackmanConfiguration() 64 | pgbackman_dsn = conf.dsn 65 | 66 | db = PgbackmanDB(pgbackman_dsn, 'pgbackman_zabbix_autodiscovery') 67 | 68 | backup_server_id = db.get_backup_server_id(backup_server_fqdn) 69 | 70 | for def_id,pgsql_node,dbname in db.get_backup_server_bckdef_list(backup_server_id): 71 | backup_definition = {} 72 | backup_definition = {"{#DEFID}":def_id,"{#PGSQL_NODE}":pgsql_node,"{#DBNAME}":dbname} 73 | 74 | backup_definitions_list.append(backup_definition) 75 | 76 | result = {"data":backup_definitions_list} 77 | print json.dumps(result,sort_keys=True,indent=2) 78 | 79 | logs.logger.info('Zabbix autodiscovery data for backup server: [%s] delivered. (Total bckdef: %s)',backup_server_fqdn,len(backup_definitions_list)) 80 | 81 | except Exception as e: 82 | logs.logger.error('Problems getting Zabbix autodiscovery data for backup server: [%s] - %s',args.backup_server_fqdn,e) 83 | sys.exit(1) 84 | 85 | 86 | # ############################################ 87 | # 88 | # ############################################ 89 | 90 | if __name__ == '__main__': 91 | 92 | signal.signal(signal.SIGINT,signal_handler) 93 | signal.signal(signal.SIGTERM,signal_handler) 94 | 95 | # 96 | # Initializing logging 97 | # 98 | 99 | logs = PgbackmanLogs("pgbackman_zabbix_autodiscovery", "", "") 100 | logs.logger.info('**** pgbackman_zabbix_autodiscovery started. ****') 101 | 102 | parser = argparse.ArgumentParser(prog=sys.argv[0]) 103 | parser.add_argument('--backup-server','-b', metavar='BACKUP-SERVER-FQDN', required=True, help='Backup server FQDN', dest='backup_server_fqdn') 104 | 105 | args = parser.parse_args() 106 | 107 | if args.backup_server_fqdn: 108 | 109 | logs.logger.debug('Getting Zabbix autodiscovery data for backup server: [%s]',args.backup_server_fqdn) 110 | get_pgbackman_zabbix_autodiscovery(args.backup_server_fqdn) 111 | 112 | else: 113 | print('Backup server fqdn parameter not defined') 114 | logs.logger.error('Backup server fqdn parameter not defined') 115 | 116 | logs.logger.info('**** pgbackman_zabbix_autodiscovery finished. ****') 117 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | pgbackman (1.2.0) unstable; urgency=low 2 | 3 | * New release 1.2.0 4 | 5 | -- Rafael Martinez Guerrero Tue, 13 Jun 2017 14:14:05 +0000 6 | 7 | pgbackman (1.1.0) unstable; urgency=low 8 | 9 | * New release 1.1.0 10 | 11 | -- Rafael Martinez Guerrero Sat, 08 Oct 2015 03:07:05 +0000 12 | 13 | pgbackman (1.0.0) unstable; urgency=low 14 | 15 | * Initial release 1.0.0 16 | 17 | -- Rafael Martinez Guerrero Sat, 21 Jun 2014 13:48:05 +0000 18 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: pgbackman 2 | Section: database 3 | Priority: optional 4 | Maintainer: Rafael Martinez Guerrero 5 | Build-Depends: debhelper (>= 9~) 6 | , python | python-all | python-dev | python-all-dev 7 | , python-setuptools 8 | Standards-Version: 3.9.8 9 | Homepage: http://www.pgbackman.org/ 10 | Vcs-Browser: https://github.com/rafaelma/pgbackman 11 | Vcs-Git: git://github.com/rafaelma/pgbackman.git 12 | X-Python-Version: >= 2.6 13 | 14 | Package: pgbackman 15 | Architecture: all 16 | Depends: ${misc:Depends} 17 | , ${python:Depends} 18 | , python-psycopg2 (>= 2.4.0) 19 | , python-argparse 20 | , at 21 | , cron 22 | ,adduser 23 | Description: PostgreSQL Backup Manager 24 | PgBackMan is a tool for managing PostgreSQL logical backups created 25 | with pg_dump and pg_dumpall. 26 | . 27 | It is designed to manage backups from thousands of databases running 28 | in multiple PostgreSQL nodes, and it supports a multiple backup 29 | server topology. 30 | . 31 | It also manages role and database configuration information when 32 | creating a backup of a database. This information is necessary to 33 | ensure a 100% restoration of a logical backup of a database and the 34 | elements associated to it. 35 | . 36 | PgBackMan is not a tool for managing PITR (Point in time recovery) 37 | backups. There are several other solutions that can be used for 38 | managing PITR backups, such as PITRTools, OmniPITR, and Barman. 39 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | 3 | Files: * 4 | Copyright: 2013-2017, Rafael Martinez Guerrero 5 | 2014-2015, USIT / University of Oslo 6 | License: GPL-3.0+ 7 | 8 | Files: pgbackman/prettytable.py 9 | Copyright: 2009-2013, Luke Maurits 10 | License: BSD-3 11 | 12 | License: GPL-3.0+ 13 | This program is free software: you can redistribute it and/or modify 14 | it under the terms of the GNU General Public License as published by 15 | the Free Software Foundation, either version 3 of the License, or 16 | (at your option) any later version. 17 | . 18 | This package is distributed in the hope that it will be useful, 19 | but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | GNU General Public License for more details. 22 | . 23 | You should have received a copy of the GNU General Public License 24 | along with this program. If not, see . 25 | . 26 | On Debian systems, the complete text of the GNU General 27 | Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". 28 | 29 | License: BSD-3 30 | Redistribution and use in source and binary forms, with or without 31 | modification, are permitted provided that the following conditions 32 | are met: 33 | . 34 | 1) Redistributions of source code must retain the above copyright 35 | notice, this list of conditions and the following disclaimer. 36 | . 37 | 2) Redistributions in binary form must reproduce the above copyright 38 | notice, this list of conditions and the following disclaimer in the 39 | documentation and/or other materials provided with the distribution. 40 | . 41 | 3) Neither the name of the ORGANIZATION nor the names of its 42 | contributors may be used to endorse or promote products derived from 43 | this software without specific prior written permission. 44 | . 45 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 46 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 47 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 48 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 49 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 50 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 51 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 52 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 53 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 54 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 55 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 56 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # postinst script for pgbackman 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | PGBACKMAN_GROUP=pgbackman 9 | PGBACKMAN_USER=pgbackman 10 | PGBACKMAN_LOGDIR=/var/log/pgbackman 11 | 12 | 13 | case "$1" in 14 | configure) 15 | if ! getent group $PGBACKMAN_GROUP > /dev/null; then 16 | groupadd -f -r $PGBACKMAN_GROUP 17 | fi 18 | if ! getent passwd $PGBACKMAN_GROUP > /dev/null; then 19 | useradd -m -N -g $PGBACKMAN_GROUP -r -d /var/lib/pgbackman -s /bin/bash -c "PostgreSQL Backup Manager" $PGBACKMAN_USER 20 | fi 21 | 22 | if [ -d "$PGBACKMAN_LOGDIR" ] 23 | then 24 | touch ${PGBACKMAN_LOGDIR}/pgbackman.log 25 | chown -R ${PGBACKMAN_USER}:${PGBACKMAN_GROUP} ${PGBACKMAN_LOGDIR} 26 | chmod -R 775 ${PGBACKMAN_LOGDIR} 27 | fi 28 | 29 | systemctl daemon-reload 30 | ;; 31 | 32 | abort-upgrade|abort-remove|abort-deconfigure) 33 | ;; 34 | 35 | *) 36 | echo "postinst called with unknown argument \`$1'" >&2 37 | exit 1 38 | ;; 39 | esac 40 | 41 | # dh_installdeb will replace this with shell code automatically 42 | # generated by other debhelper scripts. 43 | 44 | #DEBHELPER# 45 | 46 | exit 0 47 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | %: 3 | dh $@ --with python2 4 | 5 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # 4 | 5 | VERSION="1.2.0" 6 | 7 | all: html man pdf 8 | 9 | html: 10 | rst2html -gdt --stylesheet-path=style.css manual.rst > pgbackman-manual-$(VERSION).html 11 | rst2html -gdt --stylesheet-path=style.css manual_es.rst > pgbackman-manual-$(VERSION)_es.html 12 | 13 | man: 14 | rst2man manual.rst > pgbackman-manual-$(VERSION).man 15 | rst2man manual_es.rst > pgbackman-manual-$(VERSION)_es.man 16 | 17 | pdf: 18 | cat manual.rst | sed s/":scale: 50%"/":scale: 100%"/g > manual.tmp 19 | rst2pdf --output pgbackman-manual-$(VERSION).pdf manual.tmp 20 | rm -f manual.tmp 21 | cat manual_es.rst | sed s/":scale: 50%"/":scale: 100%"/g > manual_es.tmp 22 | rst2pdf --output pgbackman-manual-$(VERSION)_es.pdf manual_es.tmp 23 | rm -f manual_es.tmp 24 | 25 | clean: 26 | rm -f pgbackman-manual-$(VERSION)*.html 27 | rm -f pgbackman-manual-$(VERSION)*.man 28 | rm -f pgbackman-manual-$(VERSION)*.pdf 29 | rm -f *~ 30 | -------------------------------------------------------------------------------- /docs/images/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelma/pgbackman/b0a1878abbf3aa976ea08dba64c24ccafffc6680/docs/images/architecture.jpg -------------------------------------------------------------------------------- /docs/images/components.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelma/pgbackman/b0a1878abbf3aa976ea08dba64c24ccafffc6680/docs/images/components.jpg -------------------------------------------------------------------------------- /docs/images/register_restore.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelma/pgbackman/b0a1878abbf3aa976ea08dba64c24ccafffc6680/docs/images/register_restore.jpg -------------------------------------------------------------------------------- /docs/release-notes.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Release Notes 3 | ============= 4 | 5 | Version 1.1.0 6 | ============= 7 | 8 | Date: 2015-10-28 9 | 10 | This new release implements some new features and fix some bugs from 11 | version 1.0.0. 12 | 13 | New features 14 | ------------ 15 | 16 | * Add functionality to stop automatically all backup definitions for 17 | databases that have been deleted with DROP DATABASE or renamed in 18 | the PgSQL nodes running them. 19 | 20 | The affected backup definitions will get a DELETED status and all 21 | files associated to the affected backup definitions will be kept 22 | only for the period of time defined by the 23 | automatic_deletion_retention parameter for the PgSQL node that was 24 | running the deleted database. 25 | 26 | The associated files will get deleted regardless of the 27 | retention_period and retention_redundancy values for the affected 28 | backup definitions. 29 | 30 | * Possibility of pausing replication on slaves nodes when taking 31 | backups of large databases to avoid the termination of the backup 32 | process by postgreSQL. 33 | 34 | * Possibility of activating sending of alerts via SMTP when a backup, 35 | snapshot or restore terminates with an error message. 36 | 37 | * Overview of databases without backup definitions in the PgSQL nodes 38 | registered in PgBackMan. 39 | 40 | * Possibility of defining a backup definition in one run for all 41 | databases in a PgSQL node without defined backup definitions. 42 | 43 | * More parameters available when searching for backups in the catalog. 44 | 45 | * Possibility of viewing snapshots and restores in progress. 46 | 47 | * Add the possibility to define the release version of pg_dump / 48 | pg_dumpall to use when taking a backup of type snapshot. 49 | 50 | * Use PgSQL node and database information in the central PgBackMan 51 | logfile. 52 | 53 | * Automatic upgrade of the 'pgbackman' database to a new version 54 | via the PgBackMan shell. 55 | 56 | 57 | Migration to 1.1.0 58 | ------------------ 59 | 60 | It is very important to check the upgrade procedure to version 1.1.0 61 | in the documentation to avoid problems and errors when and after 62 | upgrading to the new version. 63 | 64 | Check the documentation: 65 | 66 | * **[All versions]:** http://www.pgbackman.org/docs/ 67 | * **[English]:** http://www.pgbackman.org/docs/pgbackman-manual-1.1.0.html 68 | * **[Español]:** http://www.pgbackman.org/docs/pgbackman-manual-1.1.0_es.html 69 | 70 | 71 | Bugfixes 72 | -------- 73 | 74 | * The parameter channel_check_interval is not supported. Update 75 | pgbackman command show_pgbackman_config to not show this 76 | information. 77 | 78 | * Add dependency information for psycopg2. We need at least version 79 | 2.4 to avoid problems when executing pgbackman. 80 | 81 | * Fix problems restoring backups with several almost identical roles 82 | that include each others. 83 | 84 | * Include GRANT ... TO ... GRANTED BY .... statements in the roles 85 | backup file. We did not this if If they did not own something or had 86 | privileges in the database we were backing up 87 | 88 | * Use copytruncate with logrotate to truncate the old log file after 89 | taken a copy. We do not have functionality to tell pgbackman to 90 | close its logfile, therefor it continued writing (appending) to the 91 | previous log file forever. 92 | 93 | * Change the default pg_dump / pg_restore format to directory. We need 94 | to use this format insteed of custom if we want to have the 95 | possibility of dumping data in parallel with pg_dump. 96 | 97 | * Fix a problem when deleting a backup definition by dbname and having 98 | snapshots backups in our system. We delete data only from backup 99 | definitions and not snapshot definitions. 100 | 101 | * Standardize the use of all/* values for some parameters. It was not 102 | used consistently. 103 | 104 | * Delete /etc/pgbackman.conf as a configuration file possibility. Only 105 | /etc/pgbackman/pgbackman.log and $HOME/.pgbackman/pgbackman.conf are 106 | valid now, and the version under the home directory of the user 107 | running PgBackMan will have preference (if it exists) to the central 108 | configuration file. 109 | 110 | * Fix that command inputs with only spaces crashed the pgbackman 111 | shell. 112 | 113 | 114 | Version 1.0.0 115 | ============= 116 | 117 | Date: 2014-06-26 118 | 119 | First version available to the public. 120 | 121 | Main features 122 | ------------- 123 | 124 | * Central database with metadata information. 125 | * PgBackMan shell for interaction with the system. 126 | * Management of multiple backup servers. 127 | * Management of multiple PostgreSQL servers. 128 | * Management of thousands of backups dumps through a catalogue. 129 | * Manual and scheduled backups. 130 | * Management of retention policies for backups. 131 | * Fully detailed backup reports. 132 | * Multiple predefined database backup types, CLUSTER,FULL,SCHEMA,DATA. 133 | * Full backup of role information for a database. 134 | * Full backup of database configuration for a database. 135 | * Automatic definitions of backups for all databases running in a PgSQL node. 136 | * Automatic restore procedures. 137 | * Autonomous pgbackman_dump program that functions even if the central database with metadata information is not available. 138 | * Handling of error situations. 139 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | @import url(html4css1.css); 2 | 3 | /** 4 | * :Author: Chad Skeeters 5 | * :Contact: cskeeters@nciinc.com 6 | * Stylesheet for use with Docutils/rst2html. 7 | * Example: rst2html --stylesheet=rst2html.css README.rst doc/html/README.html 8 | */ 9 | 10 | html { 11 | font-size: 100%; 12 | -webkit-text-size-adjust: 100%; 13 | -ms-text-size-adjust: 100%; 14 | } 15 | 16 | a:focus { 17 | outline: thin dotted #333; 18 | outline: 5px auto -webkit-focus-ring-color; 19 | outline-offset: -2px; 20 | } 21 | 22 | a:hover, 23 | a:active { 24 | outline: 0; 25 | } 26 | 27 | sub, 28 | sup { 29 | position: relative; 30 | font-size: 75%; 31 | line-height: 0; 32 | vertical-align: baseline; 33 | } 34 | 35 | sup { 36 | top: -0.5em; 37 | } 38 | 39 | sub { 40 | bottom: -0.25em; 41 | } 42 | 43 | img { 44 | width: auto\9; 45 | height: auto; 46 | max-width: 100%; 47 | vertical-align: middle; 48 | border: 0; 49 | -ms-interpolation-mode: bicubic; 50 | } 51 | 52 | @media print { 53 | * { 54 | color: #000 !important; 55 | text-shadow: none !important; 56 | background: transparent !important; 57 | box-shadow: none !important; 58 | } 59 | 60 | a, 61 | a:visited { 62 | text-decoration: underline; 63 | } 64 | 65 | a[href]:after { 66 | content: " (" attr(href) ")"; 67 | } 68 | 69 | abbr[title]:after { 70 | content: " (" attr(title) ")"; 71 | } 72 | 73 | .ir a:after, 74 | a[href^="javascript:"]:after, 75 | a[href^="#"]:after { 76 | content: ""; 77 | } 78 | 79 | pre, 80 | blockquote { 81 | border: 1px solid #999; 82 | page-break-inside: avoid; 83 | } 84 | 85 | thead { 86 | display: table-header-group; 87 | } 88 | 89 | tr, 90 | img { 91 | page-break-inside: avoid; 92 | } 93 | 94 | img { 95 | max-width: 100% !important; 96 | } 97 | @ page { 98 | margin: 0.5cm; 99 | } 100 | 101 | h1 { 102 | page-break-before: always; 103 | } 104 | 105 | h1.title { 106 | page-break-before: avoid; 107 | } 108 | 109 | p, 110 | h2, 111 | h3 { 112 | orphans: 3; 113 | widows: 3; 114 | } 115 | 116 | h2, 117 | h3 { 118 | page-break-after: avoid; 119 | } 120 | } 121 | 122 | body { 123 | margin: 40px; 124 | margin-right: auto; 125 | margin-left: auto; 126 | width: 880px; 127 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 128 | font-size: 14px; 129 | line-height: 20px; 130 | color: #333333; 131 | background-color: #ffffff; 132 | } 133 | 134 | a { 135 | color: #0088cc; 136 | text-decoration: none; 137 | } 138 | 139 | a:hover, 140 | a:focus { 141 | color: #005580; 142 | text-decoration: underline; 143 | } 144 | 145 | .img-rounded { 146 | -webkit-border-radius: 6px; 147 | -moz-border-radius: 6px; 148 | border-radius: 6px; 149 | } 150 | 151 | .img-polaroid { 152 | padding: 4px; 153 | background-color: #fff; 154 | border: 1px solid #ccc; 155 | border: 1px solid rgba(0, 0, 0, 0.2); 156 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 157 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 158 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 159 | } 160 | 161 | p { 162 | margin: 0 0 10px; 163 | } 164 | 165 | small { 166 | font-size: 85%; 167 | } 168 | 169 | strong { 170 | font-weight: bold; 171 | } 172 | 173 | em { 174 | font-style: italic; 175 | } 176 | 177 | cite { 178 | font-style: normal; 179 | } 180 | 181 | h1, 182 | h2, 183 | h3, 184 | h4, 185 | h5, 186 | h6 { 187 | font-family: inherit; 188 | font-weight: bold; 189 | line-height: 20px; 190 | color: inherit; 191 | text-rendering: optimizelegibility; 192 | } 193 | 194 | h1 { 195 | font-size: 2em; 196 | padding-bottom: .2em; 197 | border-bottom: 1px solid grey; 198 | } 199 | 200 | h1.title { 201 | padding-bottom: 1em; 202 | border-bottom: 0px; 203 | } 204 | 205 | h2 { 206 | font-size: 1.5em; 207 | } 208 | 209 | h3 { 210 | font-size: 1.3em; 211 | font-family: Georgia, serif; 212 | font-style: italic; 213 | /*font-weight:normal;*/; 214 | } 215 | 216 | h4 { 217 | font-size: 1.3em; 218 | } 219 | 220 | h5 { 221 | font-size: 1.2em; 222 | } 223 | 224 | h6 { 225 | font-size: 1.1em; 226 | } 227 | 228 | ul, 229 | ol { 230 | padding: 0; 231 | margin: 0 0 10px 25px; 232 | } 233 | 234 | ul ul, 235 | ul ol, 236 | ol ol, 237 | ol ul { 238 | margin-bottom: 0; 239 | } 240 | 241 | li { 242 | line-height: 20px; 243 | } 244 | 245 | dl { 246 | margin-bottom: 20px; 247 | } 248 | 249 | dt, 250 | dd { 251 | line-height: 20px; 252 | } 253 | 254 | dt { 255 | font-weight: bold; 256 | } 257 | 258 | dd { 259 | margin-left: 10px; 260 | } 261 | 262 | hr { 263 | margin: 20px 0; 264 | border: 0; 265 | border-top: 1px solid #eeeeee; 266 | border-bottom: 1px solid #ffffff; 267 | } 268 | 269 | abbr[title], 270 | abbr[data-original-title] { 271 | cursor: help; 272 | border-bottom: 1px dotted #999999; 273 | } 274 | 275 | abbr.initialism { 276 | font-size: 90%; 277 | text-transform: uppercase; 278 | } 279 | 280 | blockquote { 281 | padding: 0 0 0 15px; 282 | margin: 0 0 20px; 283 | border-left: 5px solid #eeeeee; 284 | } 285 | 286 | blockquote p { 287 | margin-bottom: 0; 288 | font-size: 17.5px; 289 | font-weight: 300; 290 | line-height: 1.25; 291 | } 292 | 293 | q:before, 294 | q:after, 295 | blockquote:before, 296 | blockquote:after { 297 | content: ""; 298 | } 299 | 300 | address { 301 | display: block; 302 | margin-bottom: 20px; 303 | font-style: normal; 304 | line-height: 20px; 305 | } 306 | 307 | code, 308 | pre { 309 | padding: 0 3px 2px; 310 | font-family: Monaco, Menlo, Consolas, "Courier New", monospace; 311 | font-size: 12px; 312 | color: #333333; 313 | -webkit-border-radius: 3px; 314 | -moz-border-radius: 3px; 315 | border-radius: 3px; 316 | } 317 | 318 | code { 319 | padding: 2px 4px; 320 | color: #d14; 321 | white-space: nowrap; 322 | background-color: #f7f7f9; 323 | border: 1px solid #e1e1e8; 324 | } 325 | 326 | pre { 327 | display: block; 328 | padding: 9.5px; 329 | margin: 0 0 10px; 330 | font-size: 13px; 331 | line-height: 20px; 332 | #word-break: break-all; 333 | #word-wrap: break-word; 334 | #white-space: pre; 335 | #white-space: pre-wrap; 336 | background-color: #f5f5f5; 337 | border: 1px solid #ccc; 338 | border: 1px solid rgba(0, 0, 0, 0.15); 339 | -webkit-border-radius: 4px; 340 | -moz-border-radius: 4px; 341 | border-radius: 4px; 342 | overflow: auto; 343 | } 344 | 345 | pre.prettyprint { 346 | margin-bottom: 20px; 347 | } 348 | 349 | pre code { 350 | padding: 0; 351 | color: inherit; 352 | white-space: pre; 353 | white-space: pre-wrap; 354 | background-color: transparent; 355 | border: 0; 356 | } 357 | 358 | .pre-scrollable { 359 | max-height: 340px; 360 | overflow-y: scroll; 361 | } 362 | 363 | table { 364 | max-width: 100%; 365 | background-color: transparent; 366 | border-collapse: collapse; 367 | border-spacing: 0; 368 | } 369 | 370 | .table { 371 | width: 100%; 372 | margin-bottom: 20px; 373 | } 374 | 375 | .table th, 376 | .table td { 377 | padding: 8px; 378 | line-height: 20px; 379 | text-align: left; 380 | vertical-align: top; 381 | border-top: 1px solid #dddddd; 382 | } 383 | 384 | .table th { 385 | font-weight: bold; 386 | } 387 | 388 | .table thead th { 389 | vertical-align: bottom; 390 | } 391 | 392 | .table caption + thead tr:first-child th, 393 | .table caption + thead tr:first-child td, 394 | .table colgroup + thead tr:first-child th, 395 | .table colgroup + thead tr:first-child td, 396 | .table thead:first-child tr:first-child th, 397 | .table thead:first-child tr:first-child td { 398 | border-top: 0; 399 | } 400 | 401 | .table tbody + tbody { 402 | border-top: 2px solid #dddddd; 403 | } 404 | 405 | .table .table { 406 | background-color: #ffffff; 407 | } 408 | 409 | .table-condensed th, 410 | .table-condensed td { 411 | padding: 4px 5px; 412 | } 413 | 414 | .table-bordered { 415 | border: 1px solid #dddddd; 416 | border-collapse: separate; 417 | *border-collapse: collapse; 418 | border-left: 0; 419 | -webkit-border-radius: 4px; 420 | -moz-border-radius: 4px; 421 | border-radius: 4px; 422 | } 423 | 424 | .table-bordered th, 425 | .table-bordered td { 426 | border-left: 1px solid #dddddd; 427 | } 428 | 429 | .table-bordered caption + thead tr:first-child th, 430 | .table-bordered caption + tbody tr:first-child th, 431 | .table-bordered caption + tbody tr:first-child td, 432 | .table-bordered colgroup + thead tr:first-child th, 433 | .table-bordered colgroup + tbody tr:first-child th, 434 | .table-bordered colgroup + tbody tr:first-child td, 435 | .table-bordered thead:first-child tr:first-child th, 436 | .table-bordered tbody:first-child tr:first-child th, 437 | .table-bordered tbody:first-child tr:first-child td { 438 | border-top: 0; 439 | } 440 | 441 | .table-bordered thead:first-child tr:first-child > th:first-child, 442 | .table-bordered tbody:first-child tr:first-child > td:first-child, 443 | .table-bordered tbody:first-child tr:first-child > th:first-child { 444 | -webkit-border-top-left-radius: 4px; 445 | border-top-left-radius: 4px; 446 | -moz-border-radius-topleft: 4px; 447 | } 448 | 449 | .table-bordered thead:first-child tr:first-child > th:last-child, 450 | .table-bordered tbody:first-child tr:first-child > td:last-child, 451 | .table-bordered tbody:first-child tr:first-child > th:last-child { 452 | -webkit-border-top-right-radius: 4px; 453 | border-top-right-radius: 4px; 454 | -moz-border-radius-topright: 4px; 455 | } 456 | 457 | .table-bordered thead:last-child tr:last-child > th:first-child, 458 | .table-bordered tbody:last-child tr:last-child > td:first-child, 459 | .table-bordered tbody:last-child tr:last-child > th:first-child, 460 | .table-bordered tfoot:last-child tr:last-child > td:first-child, 461 | .table-bordered tfoot:last-child tr:last-child > th:first-child { 462 | -webkit-border-bottom-left-radius: 4px; 463 | border-bottom-left-radius: 4px; 464 | -moz-border-radius-bottomleft: 4px; 465 | } 466 | 467 | .table-bordered thead:last-child tr:last-child > th:last-child, 468 | .table-bordered tbody:last-child tr:last-child > td:last-child, 469 | .table-bordered tbody:last-child tr:last-child > th:last-child, 470 | .table-bordered tfoot:last-child tr:last-child > td:last-child, 471 | .table-bordered tfoot:last-child tr:last-child > th:last-child { 472 | -webkit-border-bottom-right-radius: 4px; 473 | border-bottom-right-radius: 4px; 474 | -moz-border-radius-bottomright: 4px; 475 | } 476 | 477 | .table-bordered tfoot + tbody:last-child tr:last-child td:first-child { 478 | -webkit-border-bottom-left-radius: 0; 479 | border-bottom-left-radius: 0; 480 | -moz-border-radius-bottomleft: 0; 481 | } 482 | 483 | .table-bordered tfoot + tbody:last-child tr:last-child td:last-child { 484 | -webkit-border-bottom-right-radius: 0; 485 | border-bottom-right-radius: 0; 486 | -moz-border-radius-bottomright: 0; 487 | } 488 | 489 | .table-bordered caption + thead tr:first-child th:first-child, 490 | .table-bordered caption + tbody tr:first-child td:first-child, 491 | .table-bordered colgroup + thead tr:first-child th:first-child, 492 | .table-bordered colgroup + tbody tr:first-child td:first-child { 493 | -webkit-border-top-left-radius: 4px; 494 | border-top-left-radius: 4px; 495 | -moz-border-radius-topleft: 4px; 496 | } 497 | 498 | .table-bordered caption + thead tr:first-child th:last-child, 499 | .table-bordered caption + tbody tr:first-child td:last-child, 500 | .table-bordered colgroup + thead tr:first-child th:last-child, 501 | .table-bordered colgroup + tbody tr:first-child td:last-child { 502 | -webkit-border-top-right-radius: 4px; 503 | border-top-right-radius: 4px; 504 | -moz-border-radius-topright: 4px; 505 | } 506 | 507 | .table-striped tbody > tr:nth-child(odd) > td, 508 | .table-striped tbody > tr:nth-child(odd) > th { 509 | background-color: #f9f9f9; 510 | } 511 | 512 | .table-hover tbody tr:hover > td, 513 | .table-hover tbody tr:hover > th { 514 | background-color: #f5f5f5; 515 | } 516 | 517 | table td[class*="span"], 518 | table th[class*="span"], 519 | .row-fluid table td[class*="span"], 520 | .row-fluid table th[class*="span"] { 521 | display: table-cell; 522 | float: none; 523 | margin-left: 0; 524 | } 525 | 526 | .hero-unit { 527 | padding: 60px; 528 | margin-bottom: 30px; 529 | font-size: 18px; 530 | font-weight: 200; 531 | line-height: 30px; 532 | color: inherit; 533 | background-color: #eeeeee; 534 | -webkit-border-radius: 6px; 535 | -moz-border-radius: 6px; 536 | border-radius: 6px; 537 | } 538 | 539 | .hero-unit h1 { 540 | margin-bottom: 0; 541 | font-size: 60px; 542 | line-height: 1; 543 | letter-spacing: -1px; 544 | color: inherit; 545 | } 546 | 547 | .hero-unit li { 548 | line-height: 30px; 549 | } 550 | 551 | 552 | /* rst2html default used to remove borders from tables and images */ 553 | .borderless, table.borderless td, table.borderless th { 554 | border: 0; 555 | } 556 | 557 | table.borderless td, table.borderless th { 558 | /* Override padding for "table.docutils td" with "! important". 559 | The right padding separates the table cells. */ 560 | padding: 0 0.5em 0 0 ! important; 561 | } 562 | 563 | .first { 564 | /* Override more specific margin styles with "! important". */ 565 | margin-top: 0 ! important; 566 | } 567 | 568 | .last, .with-subtitle { 569 | margin-bottom: 0 ! important; 570 | } 571 | 572 | .hidden { 573 | display: none; 574 | } 575 | 576 | a.toc-backref { 577 | text-decoration: none; 578 | color: black; 579 | } 580 | 581 | blockquote.epigraph { 582 | margin: 2em 5em; 583 | } 584 | 585 | dl.docutils dd { 586 | margin-bottom: 0.5em; 587 | } 588 | 589 | object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { 590 | overflow: hidden; 591 | } 592 | 593 | /* Uncomment (and remove this text!) to get bold-faced definition list terms 594 | dl.docutils dt { 595 | font-weight: bold } 596 | */ 597 | 598 | div.abstract { 599 | margin: 2em 5em; 600 | } 601 | 602 | div.abstract p.topic-title { 603 | font-weight: bold; 604 | text-align: center; 605 | } 606 | 607 | div.admonition, div.attention, div.caution, div.danger, div.error, 608 | div.hint, div.important, div.note, div.tip, div.warning { 609 | margin: 2em; 610 | border: medium outset; 611 | padding: 1em; 612 | } 613 | 614 | div.note, div.warning { 615 | margin: 1.5em 0px; 616 | border: none; 617 | } 618 | 619 | div.note p.admonition-title, 620 | div.warning p.admonition-title { 621 | display: none; 622 | } 623 | 624 | /* Clearfix 625 | * http://css-tricks.com/snippets/css/clear-fix/ 626 | */ 627 | 628 | div.note:after, 629 | div.warning:after { 630 | content: ""; 631 | display: table; 632 | clear: both; 633 | } 634 | 635 | div.note p:before, 636 | div.warning p:before { 637 | display: block; 638 | float: left; 639 | font-size: 4em; 640 | line-height: 1em; 641 | margin-right: 20px; 642 | margin-left: 0em; 643 | margin-top: -10px; 644 | content: '\0270D'; 645 | /*handwriting*/; 646 | } 647 | 648 | div.warning p:before { 649 | content: '\026A0'; 650 | /*warning*/; 651 | } 652 | 653 | div.admonition p.admonition-title, div.hint p.admonition-title, 654 | div.important p.admonition-title, div.note p.admonition-title, 655 | div.tip p.admonition-title { 656 | font-weight: bold; 657 | font-family: sans-serif; 658 | } 659 | 660 | div.attention p.admonition-title, div.caution p.admonition-title, 661 | div.danger p.admonition-title, div.error p.admonition-title, 662 | div.warning p.admonition-title, .code .error { 663 | color: red; 664 | font-weight: bold; 665 | font-family: sans-serif; 666 | } 667 | 668 | /* Uncomment (and remove this text!) to get reduced vertical space in 669 | compound paragraphs. 670 | div.compound .compound-first, div.compound .compound-middle { 671 | margin-bottom: 0.5em } 672 | 673 | div.compound .compound-last, div.compound .compound-middle { 674 | margin-top: 0.5em } 675 | */ 676 | 677 | div.dedication { 678 | margin: 2em 5em; 679 | text-align: center; 680 | font-style: italic; 681 | } 682 | 683 | div.dedication p.topic-title { 684 | font-weight: bold; 685 | font-style: normal; 686 | } 687 | 688 | div.figure { 689 | margin-left: 2em; 690 | margin-right: 2em; 691 | } 692 | 693 | div.footer, div.header { 694 | clear: both; 695 | font-size: smaller; 696 | } 697 | 698 | div.line-block { 699 | display: block; 700 | margin-top: 1em; 701 | margin-bottom: 1em; 702 | } 703 | 704 | div.line-block div.line-block { 705 | margin-top: 0; 706 | margin-bottom: 0; 707 | margin-left: 1.5em; 708 | } 709 | 710 | div.sidebar { 711 | margin: 0 0 0.5em 1em; 712 | border: medium outset; 713 | padding: 1em; 714 | background-color: #ffffee; 715 | width: 40%; 716 | float: right; 717 | clear: right; 718 | } 719 | 720 | div.sidebar p.rubric { 721 | font-family: sans-serif; 722 | font-size: medium; 723 | } 724 | 725 | div.system-messages { 726 | margin: 5em; 727 | } 728 | 729 | div.system-messages h1 { 730 | color: red; 731 | } 732 | 733 | div.system-message { 734 | border: medium outset; 735 | padding: 1em; 736 | } 737 | 738 | div.system-message p.system-message-title { 739 | color: red; 740 | font-weight: bold; 741 | } 742 | 743 | div.topic { 744 | margin: 2em; 745 | } 746 | 747 | h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, 748 | h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { 749 | margin-top: 0.4em; 750 | } 751 | 752 | h1.title { 753 | text-align: center; 754 | } 755 | 756 | h2.subtitle { 757 | text-align: center; 758 | } 759 | 760 | hr.docutils { 761 | width: 75%; 762 | } 763 | 764 | img.align-left, .figure.align-left, object.align-left { 765 | clear: left; 766 | float: left; 767 | margin-right: 1em; 768 | } 769 | 770 | img.align-right, .figure.align-right, object.align-right { 771 | clear: right; 772 | float: right; 773 | margin-left: 1em; 774 | } 775 | 776 | img.align-center, .figure.align-center, object.align-center { 777 | display: block; 778 | margin-left: auto; 779 | margin-right: auto; 780 | } 781 | 782 | .align-left { 783 | text-align: left; 784 | } 785 | 786 | .align-center { 787 | clear: both; 788 | text-align: center; 789 | } 790 | 791 | .align-right { 792 | text-align: right; 793 | } 794 | 795 | /* reset inner alignment in figures */ 796 | div.align-right { 797 | text-align: inherit; 798 | } 799 | 800 | /* div.align-center * { */ 801 | /* text-align: left } */ 802 | 803 | ol.simple, ul.simple { 804 | margin-bottom: 1em; 805 | } 806 | 807 | ol.arabic { 808 | list-style: decimal; 809 | } 810 | 811 | ol.loweralpha { 812 | list-style: lower-alpha; 813 | } 814 | 815 | ol.upperalpha { 816 | list-style: upper-alpha; 817 | } 818 | 819 | ol.lowerroman { 820 | list-style: lower-roman; 821 | } 822 | 823 | ol.upperroman { 824 | list-style: upper-roman; 825 | } 826 | 827 | p.attribution { 828 | text-align: right; 829 | margin-left: 50%; 830 | } 831 | 832 | p.caption { 833 | font-style: italic; 834 | } 835 | 836 | p.credits { 837 | font-style: italic; 838 | font-size: smaller; 839 | } 840 | 841 | p.label { 842 | white-space: nowrap; 843 | } 844 | 845 | p.rubric { 846 | font-weight: bold; 847 | font-size: larger; 848 | color: maroon; 849 | text-align: center; 850 | } 851 | 852 | p.sidebar-title { 853 | font-family: sans-serif; 854 | font-weight: bold; 855 | font-size: larger; 856 | } 857 | 858 | p.sidebar-subtitle { 859 | font-family: sans-serif; 860 | font-weight: bold; 861 | } 862 | 863 | p.topic-title { 864 | font-weight: bold; 865 | } 866 | 867 | pre.address { 868 | margin-bottom: 0; 869 | margin-top: 0; 870 | font: inherit; 871 | } 872 | 873 | pre.literal-block, pre.doctest-block, pre.math, pre.code { 874 | margin-left: 2em; 875 | margin-right: 2em; 876 | } 877 | 878 | pre.code .ln { 879 | color: grey; 880 | } /* line numbers */ 881 | pre.code, code { 882 | background-color: #eeeeee; 883 | } 884 | 885 | pre.code .comment, code .comment { 886 | color: #5C6576; 887 | } 888 | 889 | pre.code .keyword, code .keyword { 890 | color: #3B0D06; 891 | font-weight: bold; 892 | } 893 | 894 | pre.code .literal.string, code .literal.string { 895 | color: #0C5404; 896 | } 897 | 898 | pre.code .name.builtin, code .name.builtin { 899 | color: #352B84; 900 | } 901 | 902 | pre.code .deleted, code .deleted { 903 | background-color: #DEB0A1; 904 | } 905 | 906 | pre.code .inserted, code .inserted { 907 | background-color: #A3D289; 908 | } 909 | 910 | span.classifier { 911 | font-family: sans-serif; 912 | font-style: oblique; 913 | } 914 | 915 | span.classifier-delimiter { 916 | font-family: sans-serif; 917 | font-weight: bold; 918 | } 919 | 920 | span.interpreted { 921 | font-family: sans-serif; 922 | } 923 | 924 | span.option { 925 | white-space: nowrap; 926 | } 927 | 928 | span.pre { 929 | white-space: pre; 930 | } 931 | 932 | span.problematic { 933 | color: red; 934 | } 935 | 936 | span.section-subtitle { 937 | /* font-size relative to parent (h1..h6 element) */ 938 | font-size: 80%; 939 | } 940 | 941 | table.citation { 942 | border-left: solid 1px gray; 943 | margin-left: 1px; 944 | } 945 | 946 | table.docinfo { 947 | margin: 2em 4em; 948 | } 949 | 950 | table.docutils { 951 | margin-top: 0.5em; 952 | margin-bottom: 0.5em; 953 | } 954 | 955 | table.footnote { 956 | border-left: solid 1px black; 957 | margin-left: 1px; 958 | } 959 | 960 | table.docutils td, table.docutils th, 961 | table.docinfo td, table.docinfo th { 962 | padding-left: 0.5em; 963 | padding-right: 0.5em; 964 | vertical-align: top; 965 | } 966 | 967 | table.docutils th.field-name, table.docinfo th.docinfo-name { 968 | font-weight: bold; 969 | text-align: left; 970 | white-space: nowrap; 971 | padding-left: 0; 972 | } 973 | 974 | h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, 975 | h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { 976 | font-size: 100%; 977 | } 978 | 979 | ul.auto-toc { 980 | list-style-type: none; 981 | } 982 | 983 | .code .pygments-hll { 984 | background-color: #ffffcc; 985 | } 986 | 987 | .code .pygments-c { 988 | color: #60a0b0; 989 | font-style: italic; 990 | } /* Comment */ 991 | .code .pygments-err { 992 | border: 1px solid #FF0000; 993 | } /* Error */ 994 | .code .pygments-k { 995 | color: #007020; 996 | font-weight: bold; 997 | } /* Keyword */ 998 | .code .pygments-o { 999 | color: #666666; 1000 | } /* Operator */ 1001 | .code .pygments-cm { 1002 | color: #60a0b0; 1003 | font-style: italic; 1004 | } /* Comment.Multiline */ 1005 | .code .pygments-cp { 1006 | color: #007020; 1007 | } /* Comment.Preproc */ 1008 | .code .pygments-c1 { 1009 | color: #60a0b0; 1010 | font-style: italic; 1011 | } /* Comment.Single */ 1012 | .code .pygments-cs { 1013 | color: #60a0b0; 1014 | background-color: #fff0f0; 1015 | } /* Comment.Special */ 1016 | .code .pygments-gd { 1017 | color: #A00000; 1018 | } /* Generic.Deleted */ 1019 | .code .pygments-ge { 1020 | font-style: italic; 1021 | } /* Generic.Emph */ 1022 | .code .pygments-gr { 1023 | color: #FF0000; 1024 | } /* Generic.Error */ 1025 | .code .pygments-gh { 1026 | color: #000080; 1027 | font-weight: bold; 1028 | } /* Generic.Heading */ 1029 | .code .pygments-gi { 1030 | color: #00A000; 1031 | } /* Generic.Inserted */ 1032 | .code .pygments-go { 1033 | color: #888888; 1034 | } /* Generic.Output */ 1035 | .code .pygments-gp { 1036 | color: #c65d09; 1037 | font-weight: bold; 1038 | } /* Generic.Prompt */ 1039 | .code .pygments-gs { 1040 | font-weight: bold; 1041 | } /* Generic.Strong */ 1042 | .code .pygments-gu { 1043 | color: #800080; 1044 | font-weight: bold; 1045 | } /* Generic.Subheading */ 1046 | .code .pygments-gt { 1047 | color: #0044DD; 1048 | } /* Generic.Traceback */ 1049 | .code .pygments-kc { 1050 | color: #007020; 1051 | font-weight: bold; 1052 | } /* Keyword.Constant */ 1053 | .code .pygments-kd { 1054 | color: #007020; 1055 | font-weight: bold; 1056 | } /* Keyword.Declaration */ 1057 | .code .pygments-kn { 1058 | color: #007020; 1059 | font-weight: bold; 1060 | } /* Keyword.Namespace */ 1061 | .code .pygments-kp { 1062 | color: #007020; 1063 | } /* Keyword.Pseudo */ 1064 | .code .pygments-kr { 1065 | color: #007020; 1066 | font-weight: bold; 1067 | } /* Keyword.Reserved */ 1068 | .code .pygments-kt { 1069 | color: #902000; 1070 | } /* Keyword.Type */ 1071 | .code .pygments-m { 1072 | color: #40a070; 1073 | } /* Literal.Number */ 1074 | .code .pygments-s { 1075 | color: #4070a0; 1076 | } /* Literal.String */ 1077 | .code .pygments-na { 1078 | color: #4070a0; 1079 | } /* Name.Attribute */ 1080 | .code .pygments-nb { 1081 | color: #007020; 1082 | } /* Name.Builtin */ 1083 | .code .pygments-nc { 1084 | color: #0e84b5; 1085 | font-weight: bold; 1086 | } /* Name.Class */ 1087 | .code .pygments-no { 1088 | color: #60add5; 1089 | } /* Name.Constant */ 1090 | .code .pygments-nd { 1091 | color: #555555; 1092 | font-weight: bold; 1093 | } /* Name.Decorator */ 1094 | .code .pygments-ni { 1095 | color: #d55537; 1096 | font-weight: bold; 1097 | } /* Name.Entity */ 1098 | .code .pygments-ne { 1099 | color: #007020; 1100 | } /* Name.Exception */ 1101 | .code .pygments-nf { 1102 | color: #06287e; 1103 | } /* Name.Function */ 1104 | .code .pygments-nl { 1105 | color: #002070; 1106 | font-weight: bold; 1107 | } /* Name.Label */ 1108 | .code .pygments-nn { 1109 | color: #0e84b5; 1110 | font-weight: bold; 1111 | } /* Name.Namespace */ 1112 | .code .pygments-nt { 1113 | color: #062873; 1114 | font-weight: bold; 1115 | } /* Name.Tag */ 1116 | .code .pygments-nv { 1117 | color: #bb60d5; 1118 | } /* Name.Variable */ 1119 | .code .pygments-ow { 1120 | color: #007020; 1121 | font-weight: bold; 1122 | } /* Operator.Word */ 1123 | .code .pygments-w { 1124 | color: #bbbbbb; 1125 | } /* Text.Whitespace */ 1126 | .code .pygments-mf { 1127 | color: #40a070; 1128 | } /* Literal.Number.Float */ 1129 | .code .pygments-mh { 1130 | color: #40a070; 1131 | } /* Literal.Number.Hex */ 1132 | .code .pygments-mi { 1133 | color: #40a070; 1134 | } /* Literal.Number.Integer */ 1135 | .code .pygments-mo { 1136 | color: #40a070; 1137 | } /* Literal.Number.Oct */ 1138 | .code .pygments-sb { 1139 | color: #4070a0; 1140 | } /* Literal.String.Backtick */ 1141 | .code .pygments-sc { 1142 | color: #4070a0; 1143 | } /* Literal.String.Char */ 1144 | .code .pygments-sd { 1145 | color: #4070a0; 1146 | font-style: italic; 1147 | } /* Literal.String.Doc */ 1148 | .code .pygments-s2 { 1149 | color: #4070a0; 1150 | } /* Literal.String.Double */ 1151 | .code .pygments-se { 1152 | color: #4070a0; 1153 | font-weight: bold; 1154 | } /* Literal.String.Escape */ 1155 | .code .pygments-sh { 1156 | color: #4070a0; 1157 | } /* Literal.String.Heredoc */ 1158 | .code .pygments-si { 1159 | color: #70a0d0; 1160 | font-style: italic; 1161 | } /* Literal.String.Interpol */ 1162 | .code .pygments-sx { 1163 | color: #c65d09; 1164 | } /* Literal.String.Other */ 1165 | .code .pygments-sr { 1166 | color: #235388; 1167 | } /* Literal.String.Regex */ 1168 | .code .pygments-s1 { 1169 | color: #4070a0; 1170 | } /* Literal.String.Single */ 1171 | .code .pygments-ss { 1172 | color: #517918; 1173 | } /* Literal.String.Symbol */ 1174 | .code .pygments-bp { 1175 | color: #007020; 1176 | } /* Name.Builtin.Pseudo */ 1177 | .code .pygments-vc { 1178 | color: #bb60d5; 1179 | } /* Name.Variable.Class */ 1180 | .code .pygments-vg { 1181 | color: #bb60d5; 1182 | } /* Name.Variable.Global */ 1183 | .code .pygments-vi { 1184 | color: #bb60d5; 1185 | } /* Name.Variable.Instance */ 1186 | .code .pygments-il { 1187 | color: #40a070; 1188 | } /* Literal.Number.Integer.Long */ 1189 | -------------------------------------------------------------------------------- /etc/pgbackman-alerts.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=pgbackman alerts service 3 | 4 | [Service] 5 | User=root 6 | Group=root 7 | Restart=always 8 | ExecStart=/usr/bin/pgbackman_alerts 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /etc/pgbackman-control.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=pgbackman control service 3 | 4 | [Service] 5 | User=root 6 | Group=root 7 | Restart=always 8 | ExecStart=/usr/bin/pgbackman_control 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /etc/pgbackman-maintenance.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=pgbackman maintenance service 3 | 4 | [Service] 5 | User=root 6 | Group=root 7 | Restart=always 8 | ExecStart=/usr/bin/pgbackman_maintenance 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /etc/pgbackman.conf: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2013-2015 Rafael Martinez Guerrero / PostgreSQL-es 3 | ; rafael@postgresql.org.es / http://www.postgresql.org.es/ 4 | ; 5 | ; Copyright (c) 2014-2015 USIT-University of Oslo 6 | ; 7 | ; This file is part of PgBackMan 8 | ; https://github.com/rafaelma/pgbackman 9 | ; 10 | ; PgBackMan is free software: you can redistribute it and/or modify 11 | ; it under the terms of the GNU General Public License as published by 12 | ; the Free Software Foundation, either version 3 of the License, or 13 | ; (at your option) any later version. 14 | ; 15 | ; PgBackMan is distributed in the hope that it will be useful, 16 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ; GNU General Public License for more details. 19 | ; 20 | ; You should have received a copy of the GNU General Public License 21 | ; along with Pgbackman. If not, see . 22 | ; 23 | ; 24 | ; Configuration file for PgBackMan 1.1.0 25 | ; 26 | 27 | ; ###################### 28 | ; Backup server section 29 | ; ###################### 30 | [backup_server] 31 | 32 | ; If this server is a backup server used by pgbackman 33 | ; we can define the backup server FQDN here. 34 | ; 35 | ; If this parameter is not defined here, pgbackman will try to get this 36 | ; value from the system with socket.getfqdn() and db.get_backup_server_id() 37 | ; 38 | ;backup_server=pgbackup.example.org 39 | 40 | 41 | ; ########################### 42 | ; pgbackman database section 43 | ; ########################### 44 | [pgbackman_database] 45 | 46 | ; Database server fqdn running the pgbackman database 47 | ;host=dbserver.example.org 48 | 49 | ; Database server IP running the pgbackman databas 50 | ;hostaddr=127.0.0.1 51 | 52 | ; Database port used by pgbackman 53 | ; Default: 5432 54 | ;port=5432 55 | 56 | ; Database name used by pgbackman 57 | ; Default: pgbackman 58 | dbname=pgbackman 59 | 60 | ; User used to connect to the pgbackman database 61 | ; Default: pgbackman_role_rw 62 | user=pgbackman_role_rw 63 | 64 | ; Password for dbuser 65 | ;password=mypassword 66 | 67 | ; Interval in seconds to wait before we retry to (re)connect to the database 68 | ; in case the database server is not running 69 | ; Default: 10 70 | pg_connect_retry_interval=10 71 | 72 | ; Directory with PgBackMan database source files 73 | ; Default: /usr/share/pgbackman 74 | database_source_dir=/usr/share/pgbackman 75 | 76 | ; ###################### 77 | ; pgbackman_dump section 78 | ; ###################### 79 | [pgbackman_dump] 80 | 81 | ; Temp directory used to create temp files 82 | ; Default: /tmp 83 | tmp_dir=/tmp 84 | 85 | ; Activate pause/resume of recovery process when running a backup in a 86 | ; PgSQL node that is a slave/standby node in a replication 87 | ; installation 88 | ; 89 | ; NOTE: **Be carefull** with this parameter and understand the 90 | ; implications of activating it for a slave node. 91 | ; 92 | ; If you activate this parameter, the pgbackman_dump process will 93 | ; pause the recovery process on the slave node for the time it takes 94 | ; to generate the dump of the database. 95 | ; 96 | ; If you run multiple backups on the slave node that overlaps in time, 97 | ; the replication will be paused until the last backup is finish. 98 | ; 99 | ; This will increase the replication lag between the master and the 100 | ; slave. This could have an impact on your system e.g. in how much 101 | ; diskspace will be used on master and slave or if you would be able 102 | ; to catch again with the master in a very busy system. 103 | ; 104 | ; PgBackMan **does not check** if the replication has been paused for 105 | ; a long time or if the replication lag is too big. 106 | ; 107 | ; Default: OFF 108 | pause_recovery_process_on_slave=OFF 109 | 110 | 111 | ; ############################## 112 | ; pgbackman_maintenance section 113 | ; ############################## 114 | [pgbackman_maintenance] 115 | 116 | ; Interval in seconds to wait between maintenance runs 117 | ; Default: 70 118 | maintenance_interval=70 119 | 120 | ; ############################## 121 | ; pgbackman_alerts section 122 | ; ############################## 123 | [pgbackman_alerts] 124 | 125 | ; Activate email sending of pgbackman alerts 126 | ; Default: OFF 127 | smtp_alerts=OFF 128 | 129 | ; Interval in secons to wait between alarms checks 130 | ; Default: 300 131 | alerts_check_interval=300 132 | 133 | ; SMTP server 134 | ; Default: localhost 135 | smtp_server=localhost 136 | 137 | ; SMTP port SSL:465 / No-SSL:25 138 | ; Default: 25 139 | smtp_port=25 140 | 141 | ; SMTP via SSL 142 | ; Default: ON 143 | smtp_ssl=ON 144 | 145 | ; Username to login into the SMTP server 146 | ; Default: '' 147 | smtp_user= 148 | 149 | ; password to login into the SMTP server 150 | ; Default: '' 151 | smtp_password= 152 | 153 | ; SMTP From address 154 | ; smtp_from_address=user@example.org 155 | 156 | ; Template to use when sending alerts 157 | ; Default: /etc/pgbackman/pgbackman_alerts.template 158 | alerts_template=/etc/pgbackman/pgbackman_alerts.template 159 | 160 | 161 | ; ###################### 162 | ; Logging section 163 | ; ###################### 164 | [logging] 165 | 166 | ; Log level: DEBUG, INFO, WARN, ERROR, CRITICAL 167 | ; Default: ERROR 168 | log_level=INFO 169 | 170 | ; Log file used by pgbackman 171 | ; Default: /var/log/pgbackman/pgbackman.log 172 | log_file=/var/log/pgbackman/pgbackman.log 173 | 174 | -------------------------------------------------------------------------------- /etc/pgbackman.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelma/pgbackman/b0a1878abbf3aa976ea08dba64c24ccafffc6680/etc/pgbackman.log -------------------------------------------------------------------------------- /etc/pgbackman.logrotate: -------------------------------------------------------------------------------- 1 | /var/log/pgbackman/pgbackman.log { 2 | copytruncate 3 | missingok 4 | notifempty 5 | create 0600 pgbackman pgbackman 6 | weekly 7 | rotate 6 8 | } 9 | -------------------------------------------------------------------------------- /etc/pgbackman_alerts.template: -------------------------------------------------------------------------------- 1 | Subject: [pgbackman_alerts] Last backup [BckID: $bck_id] for [DBname: $dbname] failed. 2 | 3 | ------------------------------------------------- 4 | AlertID: $alert_id 5 | Registered: $registered 6 | ------------------------------------------------- 7 | This backup job has been terminated with an error 8 | 9 | Backup server: $backup_server_fqdn 10 | PgSQL node: $pgsql_node_fqdn 11 | DBname: $dbname 12 | 13 | $alert_type ID: $ref_id 14 | BckID: $bck_id 15 | 16 | Execution status: $execution_status 17 | Error message: $error_message 18 | 19 | Run 'pgbackman show_backup_details $bck_id' for details. 20 | ------------------------------------------------- 21 | Date: $date 22 | ------------------------------------------------- 23 | -------------------------------------------------------------------------------- /etc/pgbackman_init_debian.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: pgbackman 4 | # Required-Start: $network $local_fs $remote_fs 5 | # Required-Stop: $network $local_fs $remote_fs 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: PostgreSQL Backup Manager 9 | ### END INIT INFO 10 | 11 | # Author: Rafael Martinez Guerrero 12 | # Homepage: http://www.pgbackman.org/ 13 | 14 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 15 | DESC=pgbackman # Introduce a short description here 16 | NAME=pgbackman # Introduce the short server's name here 17 | DAEMON_CONTROL=/usr/bin/pgbackman_control 18 | DAEMON_MAINTENANCE=/usr/bin/pgbackman_maintenance 19 | DAEMON_ALERTS=/usr/bin/pgbackman_alerts 20 | PIDFILE_CONTROL=/var/run/pgbackman_control.pid 21 | PIDFILE_MAINTENANCE=/var/run/pgbackman_maintenance.pid 22 | PIDFILE_ALERTS=/var/run/pgbackman_alerts.pid 23 | SCRIPTNAME=/etc/init.d/$NAME 24 | 25 | # Exit if the package is not installed 26 | [ -x $DAEMON_CONTROL ] || exit 0 27 | [ -x $DAEMON_MAINTENANCE ] || exit 0 28 | [ -x $DAEMON_ALERTS ] || exit 0 29 | 30 | # Read configuration variable file if it is present 31 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME 32 | 33 | # Load the VERBOSE setting and other rcS variables 34 | . /lib/init/vars.sh 35 | 36 | # Define LSB log_* functions. 37 | # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. 38 | . /lib/lsb/init-functions 39 | 40 | # 41 | # Function that starts the daemon/service 42 | # 43 | 44 | do_start_control() 45 | { 46 | # Return 47 | # 0 if daemon has been started 48 | # 1 if daemon was already running 49 | # 2 if daemon could not be started 50 | 51 | start-stop-daemon --start --quiet --background --make-pidfile --pidfile $PIDFILE_CONTROL --startas $DAEMON_CONTROL 52 | RETVAL="$?" 53 | 54 | case $RETVAL in 55 | 0) 56 | return 0 57 | ;; 58 | 1) 59 | return 1 60 | ;; 61 | 2) 62 | return 2 63 | ;; 64 | esac 65 | } 66 | 67 | do_start_maintenance() 68 | { 69 | # Return 70 | # 0 if daemon has been started 71 | # 1 if daemon was already running 72 | # 2 if daemon could not be started 73 | 74 | start-stop-daemon --start --quiet --background --make-pidfile --pidfile $PIDFILE_MAINTENANCE --startas $DAEMON_MAINTENANCE 75 | RETVAL="$?" 76 | 77 | case $RETVAL in 78 | 0) 79 | return 0 80 | ;; 81 | 1) 82 | return 1 83 | ;; 84 | 2) 85 | return 2 86 | ;; 87 | esac 88 | } 89 | 90 | do_start_alerts() 91 | { 92 | # Return 93 | # 0 if daemon has been started 94 | # 1 if daemon was already running 95 | # 2 if daemon could not be started 96 | 97 | start-stop-daemon --start --quiet --background --make-pidfile --pidfile $PIDFILE_ALERTS --startas $DAEMON_ALERTS 98 | RETVAL="$?" 99 | 100 | case $RETVAL in 101 | 0) 102 | return 0 103 | ;; 104 | 1) 105 | return 1 106 | ;; 107 | 2) 108 | return 2 109 | ;; 110 | esac 111 | } 112 | 113 | 114 | # 115 | # Function that stops the daemon/service 116 | # 117 | 118 | do_stop_control() 119 | { 120 | # Return 121 | # 0 if daemon has been stopped 122 | # 1 if daemon was already stopped 123 | # 2 if daemon could not be stopped 124 | 125 | start-stop-daemon --stop --quiet --retry=10 --pidfile $PIDFILE_CONTROL 126 | RETVAL="$?" 127 | 128 | case $RETVAL in 129 | 0) 130 | rm -f $PIDFILE_CONTROL 131 | return 0 132 | ;; 133 | 1) 134 | return 1 135 | ;; 136 | 2) 137 | return 2 138 | ;; 139 | esac 140 | } 141 | 142 | do_stop_maintenance() 143 | { 144 | # Return 145 | # 0 if daemon has been stopped 146 | # 1 if daemon was already stopped 147 | # 2 if daemon could not be stopped 148 | 149 | start-stop-daemon --stop --quiet --retry=10 --pidfile $PIDFILE_MAINTENANCE 150 | RETVAL="$?" 151 | 152 | case $RETVAL in 153 | 0) 154 | rm -f $PIDFILE_MAINTENANCE 155 | return 0 156 | ;; 157 | 1) 158 | return 1 159 | ;; 160 | 2) 161 | return 2 162 | ;; 163 | esac 164 | } 165 | 166 | do_stop_alerts() 167 | { 168 | # Return 169 | # 0 if daemon has been stopped 170 | # 1 if daemon was already stopped 171 | # 2 if daemon could not be stopped 172 | 173 | start-stop-daemon --stop --quiet --retry=10 --pidfile $PIDFILE_ALERTS 174 | RETVAL="$?" 175 | 176 | case $RETVAL in 177 | 0) 178 | rm -f $PIDFILE_ALERTS 179 | return 0 180 | ;; 181 | 1) 182 | return 1 183 | ;; 184 | 2) 185 | return 2 186 | ;; 187 | esac 188 | } 189 | 190 | 191 | case "$1" in 192 | start) 193 | 194 | log_daemon_msg "Starting pgbackman control" 195 | do_start_control 196 | 197 | case "$?" in 198 | 0|1) log_end_msg 0 ;; 199 | 2) log_end_msg 1 ;; 200 | esac 201 | 202 | log_daemon_msg "Starting pgbackman maintenance" 203 | do_start_maintenance 204 | 205 | case "$?" in 206 | 0|1) log_end_msg 0 ;; 207 | 2) log_end_msg 1 ;; 208 | esac 209 | 210 | log_daemon_msg "Starting pgbackman alerts" 211 | do_start_alerts 212 | 213 | case "$?" in 214 | 0|1) log_end_msg 0 ;; 215 | 2) log_end_msg 1 ;; 216 | esac 217 | ;; 218 | 219 | stop) 220 | 221 | log_daemon_msg "Stopping pgabckman control" 222 | do_stop_control 223 | case "$?" in 224 | 0|1) log_end_msg 0 ;; 225 | 2) log_end_msg 1 ;; 226 | esac 227 | 228 | log_daemon_msg "Stopping pgbackman maintenance" 229 | do_stop_maintenance 230 | case "$?" in 231 | 0|1) log_end_msg 0 ;; 232 | 2) log_end_msg 1 ;; 233 | esac 234 | 235 | log_daemon_msg "Stopping pgbackman alerts" 236 | do_stop_alerts 237 | case "$?" in 238 | 0|1) log_end_msg 0 ;; 239 | 2) log_end_msg 1 ;; 240 | esac 241 | ;; 242 | 243 | status) 244 | status_of_proc -p "$PIDFILE_CONTROL" "$DAEMON_CONTROL" "pgbackman control" 245 | status_of_proc -p "$PIDFILE_MAINTENANCE" "$DAEMON_MAINTENANCE" "pgbackman maintenance" 246 | status_of_proc -p "$PIDFILE_ALERTS" "$DAEMON_ALERTS" "pgbackman alerts" 247 | exit 0 248 | ;; 249 | 250 | restart|force-reload) 251 | 252 | log_daemon_msg "Restarting pgbackman control" 253 | do_stop_control 254 | case "$?" in 255 | 0|1) 256 | do_start_control 257 | case "$?" in 258 | 0) log_end_msg 0 ;; 259 | 1) log_end_msg 1 ;; # Old process is still running 260 | *) log_end_msg 1 ;; # Failed to start 261 | esac 262 | ;; 263 | *) 264 | # Failed to stop 265 | log_end_msg 1 266 | ;; 267 | esac 268 | 269 | log_daemon_msg "Restarting pgbackman maintenance" 270 | do_stop_maintenance 271 | case "$?" in 272 | 0|1) 273 | do_start_maintenance 274 | case "$?" in 275 | 0) log_end_msg 0 ;; 276 | 1) log_end_msg 1 ;; # Old process is still running 277 | *) log_end_msg 1 ;; # Failed to start 278 | esac 279 | ;; 280 | *) 281 | # Failed to stop 282 | log_end_msg 1 283 | ;; 284 | esac 285 | 286 | log_daemon_msg "Restarting pgbackman alerts" 287 | do_stop_alerts 288 | case "$?" in 289 | 0|1) 290 | do_start_alerts 291 | case "$?" in 292 | 0) log_end_msg 0 ;; 293 | 1) log_end_msg 1 ;; # Old process is still running 294 | *) log_end_msg 1 ;; # Failed to start 295 | esac 296 | ;; 297 | *) 298 | # Failed to stop 299 | log_end_msg 1 300 | ;; 301 | esac 302 | ;; 303 | 304 | *) 305 | echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2 306 | exit 3 307 | ;; 308 | esac 309 | -------------------------------------------------------------------------------- /etc/pgbackman_init_rh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # /etc/init.d/pgbackman 4 | # Init script for PgBackman 5 | # 6 | # chkconfig: 2345 95 05 7 | # description: PgBackMan - PostgreSQL Backup Manager 8 | # 9 | 10 | # Source function library. 11 | . /etc/rc.d/init.d/functions 12 | 13 | # Pull in sysconfig settings 14 | [ -f /etc/sysconfig/pgbackman ] && . /etc/sysconfig/pgbackman 15 | 16 | # Variables 17 | BINDIR="/usr/bin" 18 | PGBACKMAN_STARTUP_LOG=/var/log/pgbackman/pgbackman_startup.log 19 | PGBACKMAN_LOG=/var/log/pgbackman/pgbackman.log 20 | 21 | LOCKFILE_CONTROL="/var/lock/subsys/pgbackman_control" 22 | PIDFILE_CONTROL="/var/run/pgbackman_control.pid" 23 | 24 | LOCKFILE_MAINTENANCE="/var/lock/subsys/pgbackman_maintenance" 25 | PIDFILE_MAINTENANCE="/var/run/pgbackman_maintenance.pid" 26 | 27 | LOCKFILE_ALERTS="/var/lock/subsys/pgbackman_alerts" 28 | PIDFILE_ALERTS="/var/run/pgbackman_alerts.pid" 29 | 30 | # Exit if the scripts are not installed 31 | [ -f "$BINDIR/pgbackman_control" ] || exit 1 32 | [ -f "$BINDIR/pgbackman_maintenance" ] || exit 1 33 | [ -f "$BINDIR/pgbackman_alerts" ] || exit 1 34 | 35 | # Create logfile if it does not exist 36 | if [ ! -f "$PGBACKMAN_LOG" ] 37 | then 38 | touch "$PGBACKMAN_LOG" 39 | chown -R pgbackman:pgbackman "$PGBACKMAN_LOG" 40 | else 41 | chown -R pgbackman:pgbackman "$PGBACKMAN_LOG" 42 | fi 43 | 44 | start(){ 45 | PGBACKMAN_CONTROL_START="Starting pgbackman_control service: " 46 | PGBACKMAN_MAINTENANCE_START="Starting pgbackman_maintenance service: " 47 | PGBACKMAN_ALERTS_START="Starting pgbackman_alerts service: " 48 | 49 | if [ ! -e "$PGBACKMAN_STARTUP_LOG" ] 50 | then 51 | touch "$PGBACKMAN_STARTUP_LOG" || exit 1 52 | chown pgbackman:pgbackman "$PGBACKMAN_STARTUP_LOG" 53 | chmod go-rwx "$PGBACKMAN_STARTUP_LOG" 54 | fi 55 | 56 | echo -n "$PGBACKMAN_CONTROL_START" 57 | 58 | if [ ! -e "$LOCKFILE_CONTROL" ] 59 | then 60 | $BINDIR/pgbackman_control >> "$PGBACKMAN_STARTUP_LOG" 2>&1 & 61 | RETVAL=$? 62 | PID=$! 63 | sleep 2 64 | 65 | if [ "x$PID" != "x" ] 66 | then 67 | success 68 | echo 69 | 70 | touch "$LOCKFILE_CONTROL" 71 | echo $PID > "$PIDFILE_CONTROL" 72 | else 73 | failure 74 | echo 75 | 76 | return $RETVAL 77 | fi 78 | 79 | else 80 | failure 81 | echo 82 | fi 83 | 84 | echo -n "$PGBACKMAN_MAINTENANCE_START" 85 | 86 | if [ ! -e "$LOCKFILE_MAINTENANCE" ] 87 | then 88 | $BINDIR/pgbackman_maintenance >> "$PGBACKMAN_STARTUP_LOG" 2>&1 & 89 | RETVAL=$? 90 | PID=$! 91 | sleep 2 92 | 93 | if [ "x$PID" != "x" ] 94 | then 95 | success 96 | echo 97 | 98 | touch "$LOCKFILE_MAINTENANCE" 99 | echo $PID > "$PIDFILE_MAINTENANCE" 100 | else 101 | failure 102 | echo 103 | 104 | return $RETVAL 105 | fi 106 | 107 | else 108 | failure 109 | echo 110 | fi 111 | 112 | echo -n "$PGBACKMAN_ALERTS_START" 113 | 114 | if [ ! -e "$LOCKFILE_ALERTS" ] 115 | then 116 | $BINDIR/pgbackman_alerts >> "$PGBACKMAN_STARTUP_LOG" 2>&1 & 117 | RETVAL=$? 118 | PID=$! 119 | sleep 2 120 | 121 | if [ "x$PID" != "x" ] 122 | then 123 | success 124 | echo 125 | 126 | touch "$LOCKFILE_ALERTS" 127 | echo $PID > "$PIDFILE_ALERTS" 128 | else 129 | failure 130 | echo 131 | 132 | return $RETVAL 133 | fi 134 | 135 | else 136 | failure 137 | echo 138 | fi 139 | 140 | } 141 | 142 | stop(){ 143 | 144 | PGBACKMAN_CONTROL_STOP="Stopping pgbackman_control service: " 145 | PGBACKMAN_MAINTENANCE_STOP="Stopping pgbackman_maintenance service: " 146 | PGBACKMAN_ALERTS_STOP="Stopping pgbackman_alerts service: " 147 | 148 | echo -n $PGBACKMAN_CONTROL_STOP 149 | 150 | if [ -e "$LOCKFILE_CONTROL" ] 151 | then 152 | killproc -p "$PIDFILE_CONTROL" pgbackman_control 153 | RETVAL=$? 154 | 155 | if [ $RETVAL -eq 0 ] 156 | then 157 | success 158 | echo 159 | 160 | rm -f $LOCKFILE_CONTROL 161 | rm -f $PIDFILE_CONTROL 162 | 163 | else 164 | failure 165 | echo 166 | return $RETVAL 167 | fi 168 | 169 | else 170 | # not running; per LSB standards this is "ok" 171 | success 172 | echo 173 | fi 174 | 175 | echo -n $PGBACKMAN_MAINTENANCE_STOP 176 | 177 | if [ -e "$LOCKFILE_MAINTENANCE" ] 178 | then 179 | killproc -p "$PIDFILE_MAINTENANCE" pgbackman_maintenance 180 | RETVAL=$? 181 | 182 | if [ $RETVAL -eq 0 ] 183 | then 184 | success 185 | echo 186 | 187 | rm -f $LOCKFILE_MAINTENANCE 188 | rm -f $PIDFILE_MAINTENANCE 189 | 190 | else 191 | failure 192 | echo 193 | 194 | return $RETVAL 195 | fi 196 | 197 | else 198 | # not running; per LSB standards this is "ok" 199 | success 200 | echo 201 | fi 202 | 203 | echo -n $PGBACKMAN_ALERTS_STOP 204 | 205 | if [ -e "$LOCKFILE_ALERTS" ] 206 | then 207 | killproc -p "$PIDFILE_ALERTS" pgbackman_alerts 208 | RETVAL=$? 209 | 210 | if [ $RETVAL -eq 0 ] 211 | then 212 | success 213 | echo 214 | 215 | rm -f $LOCKFILE_ALERTS 216 | rm -f $PIDFILE_ALERTS 217 | 218 | else 219 | failure 220 | echo 221 | 222 | return $RETVAL 223 | fi 224 | 225 | else 226 | # not running; per LSB standards this is "ok" 227 | success 228 | echo 229 | fi 230 | 231 | 232 | } 233 | 234 | restart(){ 235 | stop 236 | start 237 | } 238 | 239 | # See how we were called. 240 | case "$1" in 241 | start) 242 | start 243 | ;; 244 | stop) 245 | stop 246 | ;; 247 | status) 248 | status -p $PIDFILE_CONTROL pgbackman_control 249 | status -p $PIDFILE_MAINTENANCE pgbackman_maintenance 250 | status -p $PIDFILE_ALERTS pgbackman_alerts 251 | ;; 252 | restart) 253 | restart 254 | ;; 255 | *) 256 | echo $"Usage: $0 {start|stop|status|restart}" 257 | exit 1 258 | ;; 259 | esac 260 | 261 | exit $? 262 | -------------------------------------------------------------------------------- /pgbackman/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaelma/pgbackman/b0a1878abbf3aa976ea08dba64c24ccafffc6680/pgbackman/__init__.py -------------------------------------------------------------------------------- /pgbackman/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2013-2014 Rafael Martinez Guerrero / PostgreSQL-es 4 | # rafael@postgresql.org.es / http://www.postgresql.org.es/ 5 | # 6 | # Copyright (c) 2014 USIT-University of Oslo 7 | # 8 | # This file is part of PgBackMan 9 | # https://github.com/rafaelma/pgbackman 10 | # 11 | # PgBackMan is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # PgBackMan is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with Pgbackman. If not, see . 23 | 24 | import socket 25 | import os 26 | import ConfigParser 27 | 28 | 29 | class PgbackmanConfiguration(): 30 | # ############################################ 31 | # Constructor 32 | # ############################################ 33 | 34 | def __init__(self): 35 | """ The Constructor.""" 36 | 37 | self.config_file = '' 38 | 39 | # Backup server section 40 | self.backup_server = '' 41 | 42 | # pgbackman database section 43 | self.dbhost = '' 44 | self.dbhostaddr = '' 45 | self.dbport = '5432' 46 | self.dbname = 'pgbackman' 47 | self.dbuser = 'pgbackman_role_rw' 48 | self.dbpassword = '' 49 | self.dsn = '' 50 | self.pg_connect_retry_interval = 10 51 | self.database_source_dir = '/usr/share/pgbackman' 52 | 53 | # pgbackman_dump section 54 | self.tmp_dir = '/tmp' 55 | self.pause_recovery_process_on_slave = 'OFF' 56 | 57 | # pgbackman_maintenance section 58 | self.maintenance_interval = 70 59 | 60 | # pgbackman_alerts section 61 | self.smtp_alerts = 'OFF' 62 | self.alerts_check_interval = 300 63 | self.smtp_server = 'localhost' 64 | self.smtp_port = '25' 65 | self.smtp_ssl = 'ON' 66 | self.smtp_user = '' 67 | self.smtp_password = '' 68 | self.smtp_from_address = '' 69 | self.alerts_template = '/etc/pgbackman/pgbackman_alerts.template' 70 | 71 | # Logging section 72 | self.log_level = 'ERROR' 73 | self.log_file = '/var/log/pgbackman/pgbackman.log' 74 | 75 | self.set_configuration_file() 76 | self.set_configuration_parameters() 77 | 78 | # ############################################ 79 | # Method 80 | # ############################################ 81 | 82 | def set_configuration_file(self): 83 | """Set the pgbackman configuration file""" 84 | 85 | config_file_list = ['/etc/pgbackman/pgbackman.conf'] 86 | 87 | if os.getenv('HOME') is not None: 88 | config_file_list.insert(0, os.getenv('HOME') + '/.pgbackman/pgbackman.conf') 89 | 90 | for file in config_file_list: 91 | if os.path.isfile(file): 92 | self.config_file = file 93 | break 94 | 95 | # ############################################ 96 | # Method 97 | # ############################################ 98 | 99 | def set_configuration_parameters(self): 100 | """Set configuration parameters""" 101 | 102 | dsn_parameters = [] 103 | 104 | if self.config_file: 105 | 106 | config = ConfigParser.RawConfigParser() 107 | config.read(self.config_file) 108 | 109 | # Backup server section 110 | if config.has_option('backup_server', 'backup_server'): 111 | self.backup_server = config.get('backup_server', 'backup_server') 112 | 113 | # pgbackman database section 114 | if config.has_option('pgbackman_database', 'host'): 115 | self.dbhost = config.get('pgbackman_database', 'host') 116 | 117 | if config.has_option('pgbackman_database', 'hostaddr'): 118 | self.dbhostaddr = config.get('pgbackman_database', 'hostaddr') 119 | 120 | if config.has_option('pgbackman_database', 'port'): 121 | self.dbport = config.get('pgbackman_database', 'port') 122 | 123 | if config.has_option('pgbackman_database', 'dbname'): 124 | self.dbname = config.get('pgbackman_database', 'dbname') 125 | 126 | if config.has_option('pgbackman_database', 'user'): 127 | self.dbuser = config.get('pgbackman_database', 'user') 128 | 129 | if config.has_option('pgbackman_database', 'password'): 130 | self.dbpassword = config.get('pgbackman_database', 'password') 131 | 132 | if config.has_option('pgbackman_database', 'pg_connect_retry_interval'): 133 | self.pg_connect_retry_interval = int(config.get('pgbackman_database', 'pg_connect_retry_interval')) 134 | 135 | if config.has_option('pgbackman_database', 'database_source_dir'): 136 | self.database_source_dir = config.get('pgbackman_database', 'database_source_dir') 137 | 138 | # pgbackman_dump section 139 | if config.has_option('pgbackman_dump', 'tmp_dir'): 140 | self.tmp_dir = config.get('pgbackman_dump', 'tmp_dir') 141 | 142 | if config.has_option('pgbackman_dump', 'pause_recovery_process_on_slave'): 143 | self.pause_recovery_process_on_slave = config.get('pgbackman_dump', 144 | 'pause_recovery_process_on_slave').upper() 145 | 146 | # pgbackman_maintenance section 147 | if config.has_option('pgbackman_maintenance', 'maintenance_interval'): 148 | self.maintenance_interval = int(config.get('pgbackman_maintenance', 'maintenance_interval')) 149 | 150 | # pgbackman_alerts section 151 | if config.has_option('pgbackman_alerts', 'smtp_alerts'): 152 | self.smtp_alerts = config.get('pgbackman_alerts', 'smtp_alerts').upper() 153 | 154 | if config.has_option('pgbackman_alerts', 'alerts_check_interval'): 155 | self.alerts_check_interval = int(config.get('pgbackman_alerts', 'alerts_check_interval')) 156 | 157 | if config.has_option('pgbackman_alerts', 'smtp_server'): 158 | self.smtp_server = config.get('pgbackman_alerts', 'smtp_server') 159 | 160 | if config.has_option('pgbackman_alerts', 'smtp_port'): 161 | self.smtp_port = config.get('pgbackman_alerts', 'smtp_port') 162 | 163 | if config.has_option('pgbackman_alerts', 'smtp_ssl'): 164 | self.smtp_ssl = config.get('pgbackman_alerts', 'smtp_ssl').upper() 165 | 166 | if config.has_option('pgbackman_alerts', 'smtp_user'): 167 | self.smtp_user = config.get('pgbackman_alerts', 'smtp_user') 168 | 169 | if config.has_option('pgbackman_alerts', 'smtp_password'): 170 | self.smtp_password = config.get('pgbackman_alerts', 'smtp_password') 171 | 172 | if config.has_option('pgbackman_alerts', 'smtp_from_address'): 173 | self.smtp_from_address = config.get('pgbackman_alerts', 'smtp_from_address') 174 | 175 | if config.has_option('pgbackman_alerts', 'alerts_template'): 176 | self.alerts_template = config.get('pgbackman_alerts', 'alerts_template') 177 | 178 | # Logging section 179 | if config.has_option('logging', 'log_level'): 180 | self.log_level = config.get('logging', 'log_level').upper() 181 | 182 | if config.has_option('logging', 'log_file'): 183 | self.log_file = config.get('logging', 'log_file') 184 | 185 | # Generate the DSN string 186 | 187 | if self.dbhost != '': 188 | dsn_parameters.append('host=''' + self.dbhost + '') 189 | 190 | if self.dbhostaddr != '': 191 | dsn_parameters.append('hostaddr=''' + self.dbhostaddr + '') 192 | 193 | if self.dbport != '': 194 | dsn_parameters.append('port=''' + self.dbport + '') 195 | 196 | if self.dbname != '': 197 | dsn_parameters.append('dbname=''' + self.dbname + '') 198 | 199 | if self.dbuser != '': 200 | dsn_parameters.append('user=''' + self.dbuser + '') 201 | 202 | if self.dbpassword != '': 203 | dsn_parameters.append('password=''' + self.dbpassword + '') 204 | 205 | for parameter in dsn_parameters: 206 | self.dsn = self.dsn + parameter + ' ' 207 | -------------------------------------------------------------------------------- /pgbackman/logs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2013-2014 Rafael Martinez Guerrero / PostgreSQL-es 4 | # rafael@postgresql.org.es / http://www.postgresql.org.es/ 5 | # 6 | # Copyright (c) 2014 USIT-University of Oslo 7 | # 8 | # This file is part of PgBackMan 9 | # https://github.com/rafaelma/pgbackman 10 | # 11 | # PgBackMan is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # Pgbackman is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with PgBck. If not, see . 23 | 24 | import os 25 | import sys 26 | import logging 27 | 28 | from pgbackman.config import * 29 | 30 | class PgbackmanLogs(logging.Logger): 31 | 32 | # ############################################ 33 | # Constructor 34 | # ############################################ 35 | 36 | def __init__(self, logger_name,pgsql_node,dbname): 37 | """ The Constructor.""" 38 | 39 | self.logger_name = logger_name 40 | self.pgsql_node = pgsql_node 41 | self.dbname = dbname 42 | 43 | self.conf = PgbackmanConfiguration() 44 | 45 | self.logger = logging.getLogger(logger_name) 46 | self.level = logging.getLevelName(self.conf.log_level.upper()) 47 | 48 | self.logger.setLevel(self.level) 49 | 50 | try: 51 | self.fh = logging.FileHandler(self.conf.log_file) 52 | self.fh.setLevel(self.level) 53 | 54 | self.formatter = logging.Formatter("%(asctime)s [%(name)s]" + self.pgsql_node + self. dbname + "[%(process)d][%(levelname)s]: %(message)s") 55 | self.fh.setFormatter(self.formatter) 56 | self.logger.addHandler(self.fh) 57 | 58 | except Exception as e: 59 | print "ERROR: Problems with the log configuration needed by pgbackman: %s" % e 60 | sys.exit(1) 61 | 62 | 63 | -------------------------------------------------------------------------------- /pgbackman/ordereddict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Ref: http://code.activestate.com/recipes/576693-ordered-dictionary-for-py24/ 4 | # Created by Raymond Hettinger on Wed, 18 Mar 2009 (MIT) 5 | # 6 | # We support python 2.6 and needs OrderedDict(). 7 | # 8 | # Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy. 9 | # Passes Python2.7's test suite and incorporates all the latest updates. 10 | 11 | try: 12 | from thread import get_ident as _get_ident 13 | except ImportError: 14 | from dummy_thread import get_ident as _get_ident 15 | 16 | try: 17 | from _abcoll import KeysView, ValuesView, ItemsView 18 | except ImportError: 19 | pass 20 | 21 | 22 | class OrderedDict(dict): 23 | 'Dictionary that remembers insertion order' 24 | # An inherited dict maps keys to values. 25 | # The inherited dict provides __getitem__, __len__, __contains__, and get. 26 | # The remaining methods are order-aware. 27 | # Big-O running times for all methods are the same as for regular dictionaries. 28 | 29 | # The internal self.__map dictionary maps keys to links in a doubly linked list. 30 | # The circular doubly linked list starts and ends with a sentinel element. 31 | # The sentinel element never gets deleted (this simplifies the algorithm). 32 | # Each link is stored as a list of length three: [PREV, NEXT, KEY]. 33 | 34 | def __init__(self, *args, **kwds): 35 | '''Initialize an ordered dictionary. Signature is the same as for 36 | regular dictionaries, but keyword arguments are not recommended 37 | because their insertion order is arbitrary. 38 | 39 | ''' 40 | if len(args) > 1: 41 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) 42 | try: 43 | self.__root 44 | except AttributeError: 45 | self.__root = root = [] # sentinel node 46 | root[:] = [root, root, None] 47 | self.__map = {} 48 | self.__update(*args, **kwds) 49 | 50 | def __setitem__(self, key, value, dict_setitem=dict.__setitem__): 51 | 'od.__setitem__(i, y) <==> od[i]=y' 52 | # Setting a new item creates a new link which goes at the end of the linked 53 | # list, and the inherited dictionary is updated with the new key/value pair. 54 | if key not in self: 55 | root = self.__root 56 | last = root[0] 57 | last[1] = root[0] = self.__map[key] = [last, root, key] 58 | dict_setitem(self, key, value) 59 | 60 | def __delitem__(self, key, dict_delitem=dict.__delitem__): 61 | 'od.__delitem__(y) <==> del od[y]' 62 | # Deleting an existing item uses self.__map to find the link which is 63 | # then removed by updating the links in the predecessor and successor nodes. 64 | dict_delitem(self, key) 65 | link_prev, link_next, key = self.__map.pop(key) 66 | link_prev[1] = link_next 67 | link_next[0] = link_prev 68 | 69 | def __iter__(self): 70 | 'od.__iter__() <==> iter(od)' 71 | root = self.__root 72 | curr = root[1] 73 | while curr is not root: 74 | yield curr[2] 75 | curr = curr[1] 76 | 77 | def __reversed__(self): 78 | 'od.__reversed__() <==> reversed(od)' 79 | root = self.__root 80 | curr = root[0] 81 | while curr is not root: 82 | yield curr[2] 83 | curr = curr[0] 84 | 85 | def clear(self): 86 | 'od.clear() -> None. Remove all items from od.' 87 | try: 88 | for node in self.__map.itervalues(): 89 | del node[:] 90 | root = self.__root 91 | root[:] = [root, root, None] 92 | self.__map.clear() 93 | except AttributeError: 94 | pass 95 | dict.clear(self) 96 | 97 | def popitem(self, last=True): 98 | '''od.popitem() -> (k, v), return and remove a (key, value) pair. 99 | Pairs are returned in LIFO order if last is true or FIFO order if false. 100 | 101 | ''' 102 | if not self: 103 | raise KeyError('dictionary is empty') 104 | root = self.__root 105 | if last: 106 | link = root[0] 107 | link_prev = link[0] 108 | link_prev[1] = root 109 | root[0] = link_prev 110 | else: 111 | link = root[1] 112 | link_next = link[1] 113 | root[1] = link_next 114 | link_next[0] = root 115 | key = link[2] 116 | del self.__map[key] 117 | value = dict.pop(self, key) 118 | return key, value 119 | 120 | # -- the following methods do not depend on the internal structure -- 121 | 122 | def keys(self): 123 | 'od.keys() -> list of keys in od' 124 | return list(self) 125 | 126 | def values(self): 127 | 'od.values() -> list of values in od' 128 | return [self[key] for key in self] 129 | 130 | def items(self): 131 | 'od.items() -> list of (key, value) pairs in od' 132 | return [(key, self[key]) for key in self] 133 | 134 | def iterkeys(self): 135 | 'od.iterkeys() -> an iterator over the keys in od' 136 | return iter(self) 137 | 138 | def itervalues(self): 139 | 'od.itervalues -> an iterator over the values in od' 140 | for k in self: 141 | yield self[k] 142 | 143 | def iteritems(self): 144 | 'od.iteritems -> an iterator over the (key, value) items in od' 145 | for k in self: 146 | yield (k, self[k]) 147 | 148 | def update(*args, **kwds): 149 | '''od.update(E, **F) -> None. Update od from dict/iterable E and F. 150 | 151 | If E is a dict instance, does: for k in E: od[k] = E[k] 152 | If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] 153 | Or if E is an iterable of items, does: for k, v in E: od[k] = v 154 | In either case, this is followed by: for k, v in F.items(): od[k] = v 155 | 156 | ''' 157 | if len(args) > 2: 158 | raise TypeError('update() takes at most 2 positional ' 159 | 'arguments (%d given)' % (len(args),)) 160 | elif not args: 161 | raise TypeError('update() takes at least 1 argument (0 given)') 162 | self = args[0] 163 | # Make progressively weaker assumptions about "other" 164 | other = () 165 | if len(args) == 2: 166 | other = args[1] 167 | if isinstance(other, dict): 168 | for key in other: 169 | self[key] = other[key] 170 | elif hasattr(other, 'keys'): 171 | for key in other.keys(): 172 | self[key] = other[key] 173 | else: 174 | for key, value in other: 175 | self[key] = value 176 | for key, value in kwds.items(): 177 | self[key] = value 178 | 179 | __update = update # let subclasses override update without breaking __init__ 180 | 181 | __marker = object() 182 | 183 | def pop(self, key, default=__marker): 184 | '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. 185 | If key is not found, d is returned if given, otherwise KeyError is raised. 186 | 187 | ''' 188 | if key in self: 189 | result = self[key] 190 | del self[key] 191 | return result 192 | if default is self.__marker: 193 | raise KeyError(key) 194 | return default 195 | 196 | def setdefault(self, key, default=None): 197 | 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' 198 | if key in self: 199 | return self[key] 200 | self[key] = default 201 | return default 202 | 203 | def __repr__(self, _repr_running={}): 204 | 'od.__repr__() <==> repr(od)' 205 | call_key = id(self), _get_ident() 206 | if call_key in _repr_running: 207 | return '...' 208 | _repr_running[call_key] = 1 209 | try: 210 | if not self: 211 | return '%s()' % (self.__class__.__name__,) 212 | return '%s(%r)' % (self.__class__.__name__, self.items()) 213 | finally: 214 | del _repr_running[call_key] 215 | 216 | def __reduce__(self): 217 | 'Return state information for pickling' 218 | items = [[k, self[k]] for k in self] 219 | inst_dict = vars(self).copy() 220 | for k in vars(OrderedDict()): 221 | inst_dict.pop(k, None) 222 | if inst_dict: 223 | return (self.__class__, (items,), inst_dict) 224 | return self.__class__, (items,) 225 | 226 | def copy(self): 227 | 'od.copy() -> a shallow copy of od' 228 | return self.__class__(self) 229 | 230 | @classmethod 231 | def fromkeys(cls, iterable, value=None): 232 | '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S 233 | and values equal to v (which defaults to None). 234 | 235 | ''' 236 | d = cls() 237 | for key in iterable: 238 | d[key] = value 239 | return d 240 | 241 | def __eq__(self, other): 242 | '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive 243 | while comparison to a regular mapping is order-insensitive. 244 | 245 | ''' 246 | if isinstance(other, OrderedDict): 247 | return len(self)==len(other) and self.items() == other.items() 248 | return dict.__eq__(self, other) 249 | 250 | def __ne__(self, other): 251 | return not self == other 252 | 253 | # -- the following methods are only used in Python 2.7 -- 254 | 255 | def viewkeys(self): 256 | "od.viewkeys() -> a set-like object providing a view on od's keys" 257 | return KeysView(self) 258 | 259 | def viewvalues(self): 260 | "od.viewvalues() -> an object providing a view on od's values" 261 | return ValuesView(self) 262 | 263 | def viewitems(self): 264 | "od.viewitems() -> a set-like object providing a view on od's items" 265 | return ItemsView(self) 266 | -------------------------------------------------------------------------------- /pgbackman/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | __version__ = '3:1.2.0' 3 | -------------------------------------------------------------------------------- /rpm/pgbackman.spec: -------------------------------------------------------------------------------- 1 | # 2 | # File: pgbackman.spec 3 | # 4 | # Autor: Rafael Martinez 5 | # 6 | 7 | %define majorversion 1.2 8 | %define minorversion 0 9 | %define pbm_owner pgbackman 10 | %define pbm_group pgbackman 11 | %{!?pybasever: %define pybasever %(python -c "import sys;print(sys.version[0:3])")} 12 | %{!?python_sitelib: %define python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} 13 | 14 | Summary: PostgreSQL backup manager 15 | Name: pgbackman 16 | Version: %{majorversion}.%{minorversion} 17 | Release: 1%{?dist} 18 | License: GPLv3 19 | Group: Applications/Databases 20 | Url: http://www.pgbackman.org/ 21 | Source0: %{name}-%{version}.tar.gz 22 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot-%(%{__id_u} -n) 23 | BuildArch: noarch 24 | Requires: python-psycopg2 >= 2.4.0, python-argparse, at, cronie, python-setuptools, shadow-utils, logrotate 25 | 26 | %description 27 | PgBackMan is a tool for managing PostgreSQL logical backups created 28 | with pg_dump and pg_dumpall. 29 | 30 | It is designed to manage backups from thousands of databases running 31 | in multiple PostgreSQL nodes, and it supports a multiple backup 32 | servers topology. 33 | 34 | It also manages role and database configuration information when 35 | creating a backup of a database. This information is necessary to 36 | ensure a 100% restore of a logical backup of a database and the 37 | elements associated to it. 38 | 39 | %prep 40 | %setup -n %{name}-%{version} -q 41 | 42 | %build 43 | python setup.py build 44 | 45 | %install 46 | python setup.py install -O1 --skip-build --root %{buildroot} 47 | mkdir -p %{buildroot}/var/lib/%{name} 48 | touch %{buildroot}/var/log/%{name}/%{name}.log 49 | 50 | %clean 51 | rm -rf %{buildroot} 52 | 53 | %files 54 | %defattr(-,root,root) 55 | %doc INSTALL 56 | %{python_sitelib}/%{name}-%{version}-py%{pybasever}.egg-info/ 57 | %{python_sitelib}/%{name}/ 58 | %{_bindir}/%{name}* 59 | %{_sysconfdir}/init.d/%{name}* 60 | %{_sysconfdir}/logrotate.d/%{name}* 61 | %{_datadir}/%{name}/* 62 | /var/log/%{name}/* 63 | %config(noreplace) %{_sysconfdir}/%{name}/%{name}.conf 64 | %config(noreplace) %{_sysconfdir}/%{name}/%{name}_alerts.template 65 | %attr(700,%{pbm_owner},%{pbm_group}) %dir /var/lib/%{name} 66 | %attr(755,%{pbm_owner},%{pbm_group}) %dir /var/log/%{name} 67 | %attr(600,%{pbm_owner},%{pbm_group}) %ghost /var/log/%{name}/%{name}.log 68 | 69 | %pre 70 | groupadd -f -r pgbackman >/dev/null 2>&1 || : 71 | useradd -M -N -g pgbackman -r -d /var/lib/pgbackman -s /bin/bash \ 72 | -c "PostgreSQL Backup Manager" pgbackman >/dev/null 2>&1 || : 73 | 74 | %changelog 75 | * Tue Jun 13 2017 - Rafael Martinez Guerrero 1.2.0-1 76 | - New release 1.0.0 77 | 78 | * Mon Jun 24 2014 - Rafael Martinez Guerrero 1.0.0-1 79 | - New release 1.0.0 80 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2013-2014 Rafael Martinez Guerrero / PostgreSQL-es 4 | # rafael@postgresql.org.es / http://www.postgresql.org.es/ 5 | # 6 | # Copyright (c) 2014 USIT-University of Oslo 7 | # 8 | # This file is part of Pgbackman 9 | # https://github.com/rafaelma/pgbackman 10 | # 11 | # Pgbackman is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # Pgbackman is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with Pgbackman. If not, see . 23 | 24 | import subprocess 25 | import platform 26 | import shutil 27 | import sys 28 | import os 29 | import pwd 30 | import grp 31 | from setuptools import setup 32 | 33 | ''' 34 | setup.py installation file 35 | ''' 36 | try: 37 | pgbackman = {} 38 | with open('pgbackman/version.py', 'r') as version_file: 39 | exec (version_file.read(), pgbackman) 40 | 41 | if sys.version_info < (2, 6): 42 | raise SystemExit('ERROR: pgbackman needs at least python 2.6 to work') 43 | else: 44 | install_requires = ['psycopg2>=2.4.0','argparse'] 45 | 46 | install_files = [('/etc/pgbackman', ['etc/pgbackman.conf']), 47 | ('/etc/pgbackman', ['etc/pgbackman_alerts.template']), 48 | ('/etc/logrotate.d', ['etc/pgbackman.logrotate']), 49 | ('/usr/share/pgbackman/', ['sql/pgbackman.sql']), 50 | ('/usr/share/pgbackman/', ['sql/pgbackman_2.sql']), 51 | ('/usr/share/pgbackman/', ['sql/pgbackman_3.sql'])] 52 | # 53 | # Check linux distribution and define init script 54 | # 55 | 56 | distro = platform.linux_distribution()[0] 57 | 58 | if distro in ('CentOS Linux', 'Red Hat Enterprise Linux Server', 'Red Hat Enterprise Linux Workstation', 'Fedora'): 59 | 60 | install_files.append(('/etc/init.d', ['etc/pgbackman_init_rh.sh'])) 61 | 62 | elif distro in ('Ubuntu','debian'): 63 | 64 | install_files.append(('/lib/systemd/system', ['etc/pgbackman-alerts.service'])) 65 | install_files.append(('/lib/systemd/system', ['etc/pgbackman-control.service'])) 66 | install_files.append(('/lib/systemd/system', ['etc/pgbackman-maintenance.service'])) 67 | 68 | else: 69 | 70 | install_files.append(('/etc/init.d', ['etc/pgbackman_init_rh.sh'])) 71 | 72 | # 73 | # Setup 74 | # 75 | 76 | setup(name='pgbackman', 77 | version=pgbackman['__version__'].split(':')[1], 78 | description='PGBACKMAN - PostgreSQL Backup Manager', 79 | author='Rafael Martinez Guerrero', 80 | author_email='rafael@postgresql.org.es', 81 | url='http://www.pgbackman.org/', 82 | packages=['pgbackman',], 83 | scripts=['bin/pgbackman','bin/pgbackman_control','bin/pgbackman_maintenance','bin/pgbackman_dump','bin/pgbackman_restore','bin/pgbackman_zabbix_autodiscovery','bin/pgbackman_status_info','bin/pgbackman_alerts','bin/pgbackman-bulk-update'], 84 | data_files=install_files, 85 | install_requires=install_requires, 86 | platforms=['Linux'], 87 | classifiers=[ 88 | 'Environment :: Console', 89 | 'Development Status :: 5 - Production/Stable', 90 | 'Topic :: System :: Archiving :: Backup', 91 | 'Topic :: Database', 92 | 'Topic :: System :: Recovery Tools', 93 | 'Intended Audience :: System Administrators', 94 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 95 | 'Programming Language :: Python', 96 | 'Programming Language :: Python :: 2.6', 97 | 'Programming Language :: Python :: 2.7', 98 | ], 99 | ) 100 | 101 | except Exception as e: 102 | print e 103 | -------------------------------------------------------------------------------- /setup2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2013-2014 Rafael Martinez Guerrero / PostgreSQL-es 4 | # rafael@postgresql.org.es / http://www.postgresql.org.es/ 5 | # 6 | # Copyright (c) 2014 USIT-University of Oslo 7 | # 8 | # This file is part of Pgbackman 9 | # https://github.com/rafaelma/pgbackman 10 | # 11 | # Pgbackman is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # Pgbackman is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with Pgbackman. If not, see . 23 | 24 | import subprocess 25 | import platform 26 | import shutil 27 | import sys 28 | import os 29 | import pwd 30 | import grp 31 | from setuptools import setup 32 | 33 | ''' 34 | setup.py installation file 35 | ''' 36 | try: 37 | pgbackman = {} 38 | with open('pgbackman/version.py', 'r') as version_file: 39 | exec (version_file.read(), pgbackman) 40 | 41 | if sys.version_info < (2, 6): 42 | raise SystemExit('ERROR: pgbackman needs at least python 2.6 to work') 43 | else: 44 | install_requires = ['psycopg2>=2.4.0','argparse'] 45 | 46 | 47 | # 48 | # Creating pgbackman users and groups 49 | # 50 | 51 | 52 | groupadd_command = '/usr/sbin/groupadd -f -r pgbackman' 53 | proc = subprocess.Popen([groupadd_command],shell=True) 54 | proc.wait() 55 | 56 | if proc.returncode == 0: 57 | print 'Group pgbackman created' 58 | 59 | elif proc.returncode != 0: 60 | raise SystemExit('ERROR: Problems creating group pgbackman. Returncode: ' + str(proc.returncode)) 61 | 62 | useradd_command = '/usr/sbin/useradd -m -N -g pgbackman -r -d /var/lib/pgbackman -s /bin/bash -c "PostgreSQL Backup Manager" pgbackman' 63 | proc = subprocess.Popen([useradd_command],shell=True) 64 | proc.wait() 65 | 66 | if proc.returncode == 0: 67 | print 'User pgbackman created' 68 | 69 | elif proc.returncode == 9: 70 | print 'User pgbackman already exists' 71 | 72 | else: 73 | raise SystemExit('ERROR: Problems creating user pgbackman. Returncode: ' + str(proc.returncode)) 74 | 75 | # 76 | # Check linux distribution and define init script 77 | # 78 | 79 | distro = platform.linux_distribution()[0] 80 | 81 | if distro == 'CentOS' or distro == 'Red Hat Enterprise Linux Server' or distro == 'Red Hat Enterprise Linux Workstation' or distro == 'Fedora': 82 | init_file = 'etc/pgbackman_init_rh.sh' 83 | shutil.copy2(init_file, '/tmp/pgbackman') 84 | 85 | elif distro == 'debian' or distro == 'Ubuntu': 86 | init_file = 'etc/pgbackman_init_debian.sh' 87 | shutil.copy2(init_file, '/tmp/pgbackman') 88 | 89 | else: 90 | init_file = 'etc/pgbackman_init_rh.sh' 91 | shutil.copy2(init_file, '/tmp/pgbackman') 92 | 93 | # 94 | # Setup 95 | # 96 | 97 | setup(name='pgbackman', 98 | version=pgbackman['__version__'].split(':')[1], 99 | description='PGBACKMAN - PostgreSQL Backup Manager', 100 | author='Rafael Martinez Guerrero', 101 | author_email='rafael@postgresql.org.es', 102 | url='http://www.pgbackman.org/', 103 | packages=['pgbackman',], 104 | scripts=['bin/pgbackman','bin/pgbackman_control','bin/pgbackman_maintenance','bin/pgbackman_dump','bin/pgbackman_restore','bin/pgbackman_zabbix_autodiscovery','bin/pgbackman_status_info','bin/pgbackman_alerts','bin/pgbackman-bulk-update'], 105 | data_files=[('/etc/init.d', ['/tmp/pgbackman']), 106 | ('/etc/pgbackman', ['etc/pgbackman.conf']), 107 | ('/etc/pgbackman', ['etc/pgbackman_alerts.template']), 108 | ('/etc/logrotate.d', ['etc/pgbackman.logrotate']), 109 | ('/usr/share/pgbackman/', ['sql/pgbackman.sql']), 110 | ('/usr/share/pgbackman/', ['sql/pgbackman_2.sql']), 111 | ('/usr/share/pgbackman/', ['sql/pgbackman_3.sql']), 112 | ('/var/log/pgbackman',['README.md'])], 113 | install_requires=install_requires, 114 | platforms=['Linux'], 115 | classifiers=[ 116 | 'Environment :: Console', 117 | 'Development Status :: 5 - Production/Stable', 118 | 'Topic :: System :: Archiving :: Backup', 119 | 'Topic :: Database', 120 | 'Topic :: System :: Recovery Tools', 121 | 'Intended Audience :: System Administrators', 122 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 123 | 'Programming Language :: Python', 124 | 'Programming Language :: Python :: 2.6', 125 | 'Programming Language :: Python :: 2.7', 126 | ], 127 | ) 128 | try: 129 | root_uid = pwd.getpwnam('root').pw_uid 130 | pgbackman_uid = pwd.getpwnam('pgbackman').pw_uid 131 | pgbackman_gid = grp.getgrnam('pgbackman').gr_gid 132 | 133 | os.chown('/var/log/pgbackman',pgbackman_uid, pgbackman_gid) 134 | os.chmod('/var/log/pgbackman',01775) 135 | 136 | os.chown('/var/log/pgbackman/pgbackman.log',pgbackman_uid, pgbackman_gid) 137 | os.chmod('/var/log/pgbackman/pgbackman.log',00664) 138 | 139 | print "Privileges defined for user pgbackman" 140 | 141 | except Exception as e: 142 | print e 143 | 144 | except Exception as e: 145 | print e 146 | -------------------------------------------------------------------------------- /sql/pgbackman_3.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- PgBackMan database - Upgrade from 2:1_1_0 to 3:1_2_0 3 | -- 4 | -- Copyright (c) 2013-2015 Rafael Martinez Guerrero / PostgreSQL-es 5 | -- rafael@postgresql.org.es / http://www.postgresql.org.es/ 6 | -- 7 | -- Copyright (c) 2015 USIT-University of Oslo 8 | -- 9 | -- This file is part of PgBackMan 10 | -- https://github.com/rafaelma/pgbackman 11 | -- 12 | 13 | BEGIN; 14 | 15 | --Update function update_backup_server_config with two new parameters (pgsql_bin_9_5,pgsql_bin_9_6) 16 | 17 | DROP FUNCTION update_backup_server_config(INTEGER,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT); 18 | 19 | CREATE OR REPLACE FUNCTION update_backup_server_config(INTEGER,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT) RETURNS VOID 20 | LANGUAGE plpgsql 21 | SECURITY INVOKER 22 | SET search_path = public, pg_temp 23 | AS $$ 24 | DECLARE 25 | backup_server_id_ ALIAS FOR $1; 26 | pgsql_bin_9_0_ ALIAS FOR $2; 27 | pgsql_bin_9_1_ ALIAS FOR $3; 28 | pgsql_bin_9_2_ ALIAS FOR $4; 29 | pgsql_bin_9_3_ ALIAS FOR $5; 30 | pgsql_bin_9_4_ ALIAS FOR $6; 31 | pgsql_bin_9_5_ ALIAS FOR $7; 32 | pgsql_bin_9_6_ ALIAS FOR $8; 33 | root_backup_partition_ ALIAS FOR $9; 34 | 35 | server_cnt INTEGER; 36 | v_msg TEXT; 37 | v_detail TEXT; 38 | v_context TEXT; 39 | BEGIN 40 | 41 | SELECT count(*) FROM backup_server WHERE server_id = backup_server_id_ INTO server_cnt; 42 | 43 | IF server_cnt != 0 THEN 44 | 45 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_0''' 46 | USING backup_server_id_, 47 | pgsql_bin_9_0_; 48 | 49 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_1''' 50 | USING backup_server_id_, 51 | pgsql_bin_9_1_; 52 | 53 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_2''' 54 | USING backup_server_id_, 55 | pgsql_bin_9_2_; 56 | 57 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_3''' 58 | USING backup_server_id_, 59 | pgsql_bin_9_3_; 60 | 61 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_4''' 62 | USING backup_server_id_, 63 | pgsql_bin_9_4_; 64 | 65 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_5''' 66 | USING backup_server_id_, 67 | pgsql_bin_9_5_; 68 | 69 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_6''' 70 | USING backup_server_id_, 71 | pgsql_bin_9_6_; 72 | 73 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''root_backup_partition''' 74 | USING backup_server_id_, 75 | root_backup_partition_; 76 | 77 | ELSE 78 | RAISE EXCEPTION 'Backup server % does not exist',backup_server_id_; 79 | END IF; 80 | 81 | EXCEPTION WHEN others THEN 82 | GET STACKED DIAGNOSTICS 83 | v_msg = MESSAGE_TEXT, 84 | v_detail = PG_EXCEPTION_DETAIL, 85 | v_context = PG_EXCEPTION_CONTEXT; 86 | RAISE EXCEPTION E'\n----------------------------------------------\nEXCEPTION:\n----------------------------------------------\nMESSAGE: % \nDETAIL : % \n----------------------------------------------\n', v_msg, v_detail; 87 | END; 88 | $$; 89 | 90 | ALTER FUNCTION update_backup_server_config(INTEGER,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT) OWNER TO pgbackman_role_rw; 91 | 92 | -- Update view show_jobs_queue 93 | 94 | CREATE OR REPLACE VIEW show_jobs_queue AS 95 | SELECT a.id AS "JobID", 96 | date_trunc('seconds',a.registered) AS "Registered", 97 | a.backup_server_id AS "SrvID", 98 | b.hostname || '.' || b.domain_name AS "Backup server", 99 | a.pgsql_node_id AS "NodeID", 100 | c.hostname || '.' || c.domain_name AS "PgSQL node", 101 | a.is_assigned AS "Assigned" 102 | FROM job_queue a 103 | INNER JOIN backup_server b ON a.backup_server_id = b.server_id 104 | INNER JOIN pgsql_node c ON a.pgsql_node_id = c.node_id 105 | ORDER BY a.registered ASC; 106 | 107 | ALTER VIEW show_jobs_queue OWNER TO pgbackman_role_rw; 108 | 109 | -- Update server_config with a new parameter (pgsql_bin_9.5) for all Backup servers in the system 110 | 111 | INSERT INTO backup_server_config (server_id,parameter,value,description) 112 | SELECT server_id, 113 | 'pgsql_bin_9_5'::text, 114 | '/usr/pgsql-9.5/bin'::text, 115 | 'postgreSQL 9.5 bin directory'::text 116 | FROM backup_server 117 | ORDER BY server_id; 118 | 119 | -- Update server_config with a new parameter (pgsql_bin_9.6) for all Backup servers in the system 120 | 121 | INSERT INTO backup_server_config (server_id,parameter,value,description) 122 | SELECT server_id, 123 | 'pgsql_bin_9_6'::text, 124 | '/usr/pgsql-9.6/bin'::text, 125 | 'postgreSQL 9.6 bin directory'::text 126 | FROM backup_server 127 | ORDER BY server_id; 128 | 129 | -- Update backup_server_default_config with postgresql 9.5 information 130 | 131 | INSERT INTO backup_server_default_config (parameter,value,description) VALUES ('pgsql_bin_9_5','/usr/pgsql-9.5/bin','postgreSQL 9.5 bin directory'); 132 | 133 | -- Update backup_server_default_config with postgresql 9.6 information 134 | 135 | INSERT INTO backup_server_default_config (parameter,value,description) VALUES ('pgsql_bin_9_6','/usr/pgsql-9.6/bin','postgreSQL 9.6 bin directory'); 136 | 137 | -- Update pgbackman_version with information about version 3:1_2_0 138 | 139 | INSERT INTO pgbackman_version (version,tag) VALUES ('3','v_1_2_0'); 140 | 141 | 142 | COMMIT; 143 | -------------------------------------------------------------------------------- /sql/pgbackman_4.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- PgBackMan database - Upgrade from 3:1_2_0 to 4:1_3_0 3 | -- 4 | -- Copyright (c) 2013-2017 Rafael Martinez Guerrero / PostgreSQL-es 5 | -- rafael@postgresql.org.es / http://www.postgresql.org.es/ 6 | -- 7 | -- Copyright (c) 2015 USIT-University of Oslo 8 | -- 9 | -- This file is part of PgBackMan 10 | -- https://github.com/rafaelma/pgbackman 11 | -- 12 | 13 | BEGIN; 14 | 15 | --Update function update_backup_server_config with one new parameter (pgsql_bin_10) 16 | 17 | DROP FUNCTION update_backup_server_config(INTEGER,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT); 18 | 19 | CREATE OR REPLACE FUNCTION update_backup_server_config(INTEGER,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT) RETURNS VOID 20 | LANGUAGE plpgsql 21 | SECURITY INVOKER 22 | SET search_path = public, pg_temp 23 | AS $$ 24 | DECLARE 25 | backup_server_id_ ALIAS FOR $1; 26 | pgsql_bin_9_0_ ALIAS FOR $2; 27 | pgsql_bin_9_1_ ALIAS FOR $3; 28 | pgsql_bin_9_2_ ALIAS FOR $4; 29 | pgsql_bin_9_3_ ALIAS FOR $5; 30 | pgsql_bin_9_4_ ALIAS FOR $6; 31 | pgsql_bin_9_5_ ALIAS FOR $7; 32 | pgsql_bin_9_6_ ALIAS FOR $8; 33 | pgsql_bin_10_ ALIAS FOR $9; 34 | root_backup_partition_ ALIAS FOR $10; 35 | 36 | server_cnt INTEGER; 37 | v_msg TEXT; 38 | v_detail TEXT; 39 | v_context TEXT; 40 | BEGIN 41 | 42 | SELECT count(*) FROM backup_server WHERE server_id = backup_server_id_ INTO server_cnt; 43 | 44 | IF server_cnt != 0 THEN 45 | 46 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_0''' 47 | USING backup_server_id_, 48 | pgsql_bin_9_0_; 49 | 50 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_1''' 51 | USING backup_server_id_, 52 | pgsql_bin_9_1_; 53 | 54 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_2''' 55 | USING backup_server_id_, 56 | pgsql_bin_9_2_; 57 | 58 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_3''' 59 | USING backup_server_id_, 60 | pgsql_bin_9_3_; 61 | 62 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_4''' 63 | USING backup_server_id_, 64 | pgsql_bin_9_4_; 65 | 66 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_5''' 67 | USING backup_server_id_, 68 | pgsql_bin_9_5_; 69 | 70 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_9_6''' 71 | USING backup_server_id_, 72 | pgsql_bin_9_6_; 73 | 74 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''pgsql_bin_10''' 75 | USING backup_server_id_, 76 | pgsql_bin_10_; 77 | 78 | EXECUTE 'UPDATE backup_server_config SET value = $2 WHERE server_id = $1 AND parameter = ''root_backup_partition''' 79 | USING backup_server_id_, 80 | root_backup_partition_; 81 | 82 | ELSE 83 | RAISE EXCEPTION 'Backup server % does not exist',backup_server_id_; 84 | END IF; 85 | 86 | EXCEPTION WHEN others THEN 87 | GET STACKED DIAGNOSTICS 88 | v_msg = MESSAGE_TEXT, 89 | v_detail = PG_EXCEPTION_DETAIL, 90 | v_context = PG_EXCEPTION_CONTEXT; 91 | RAISE EXCEPTION E'\n----------------------------------------------\nEXCEPTION:\n----------------------------------------------\nMESSAGE: % \nDETAIL : % \n----------------------------------------------\n', v_msg, v_detail; 92 | END; 93 | $$; 94 | 95 | ALTER FUNCTION update_backup_server_config(INTEGER,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT) OWNER TO pgbackman_role_rw; 96 | 97 | -- Update server_config with a new parameter (pgsql_bin_10) for all Backup servers in the system 98 | 99 | INSERT INTO backup_server_config (server_id,parameter,value,description) 100 | SELECT server_id, 101 | 'pgsql_bin_10'::text, 102 | '/usr/pgsql-10/bin'::text, 103 | 'postgreSQL 10 bin directory'::text 104 | FROM backup_server 105 | ORDER BY server_id; 106 | 107 | -- Update backup_code with a new backup code (RDS) to take backups on RDS instances. 108 | 109 | INSERT INTO backup_code (code,description) VALUES ('RDS','Only runs a pg_dump of the data so that it does not try to access globals that postgres cannot access on RDS'); 110 | 111 | -- Update backup_server_default_config with postgresql 10 information 112 | 113 | INSERT INTO backup_server_default_config (parameter,value,description) VALUES ('pgsql_bin_10','/usr/pgsql-10/bin','postgreSQL 10 bin directory'); 114 | 115 | -- Update pgbackman_version with information about version 4:1_3_0 116 | 117 | INSERT INTO pgbackman_version (version,tag) VALUES ('4','v_1_3_0'); 118 | 119 | 120 | COMMIT; 121 | -------------------------------------------------------------------------------- /vagrant/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 5 | VAGRANTFILE_API_VERSION = "2" 6 | 7 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 8 | # All Vagrant configuration is done here. The most common configuration 9 | # options are documented and commented below. For a complete reference, 10 | # please see the online documentation at vagrantup.com. 11 | 12 | # Every Vagrant virtual environment requires a box to build off of. 13 | #config.vm.box = "chef/centos-6.6" 14 | 15 | config.vm.define "pg-backup01" do |pgbackup1| 16 | pgbackup1.vm.box = "chef/centos-6.6" 17 | pgbackup1.vm.network "private_network", ip: "10.10.10.101" 18 | pgbackup1.vm.hostname = "pg-backup01.example.net" 19 | pgbackup1.vm.provision :shell, :path => "bootstrap.sh" 20 | 21 | pgbackup1.vm.provider "virtualbox" do |vb1| 22 | # Use VBoxManage to customize the VM. For example to change memory: 23 | vb1.customize ["modifyvm", :id, "--memory", "512"] 24 | end 25 | end 26 | 27 | config.vm.define "pg-backup02" do |pgbackup2| 28 | pgbackup2.vm.box = "chef/debian-7.4" 29 | pgbackup2.vm.network "private_network", ip: "10.10.10.102" 30 | pgbackup2.vm.hostname = "pg-backup02.example.net" 31 | pgbackup2.vm.provision :shell, :path => "bootstrap_debian.sh" 32 | 33 | pgbackup2.vm.provider "virtualbox" do |vb2| 34 | # Use VBoxManage to customize the VM. For example to change memory: 35 | vb2.customize ["modifyvm", :id, "--memory", "512"] 36 | end 37 | end 38 | 39 | config.vm.define "pg-node01" do |pgnode1| 40 | pgnode1.vm.box = "chef/centos-6.6" 41 | pgnode1.vm.network "private_network", ip: "10.10.10.103" 42 | pgnode1.vm.hostname = "pg-node01.example.net" 43 | pgnode1.vm.provision :shell, :path => "bootstrap3.sh" 44 | 45 | pgnode1.vm.provider "virtualbox" do |vb3| 46 | # Use VBoxManage to customize the VM. For example to change memory: 47 | vb3.customize ["modifyvm", :id, "--memory", "512"] 48 | end 49 | end 50 | 51 | config.vm.define "pg-node02" do |pgnode2| 52 | pgnode2.vm.box = "chef/centos-6.6" 53 | pgnode2.vm.network "private_network", ip: "10.10.10.108" 54 | pgnode2.vm.hostname = "pg-node02.example.net" 55 | pgnode2.vm.provision :shell, :path => "bootstrap3.sh" 56 | 57 | pgnode2.vm.provider "virtualbox" do |vb3| 58 | # Use VBoxManage to customize the VM. For example to change memory: 59 | vb3.customize ["modifyvm", :id, "--memory", "512"] 60 | end 61 | end 62 | 63 | config.vm.define "pgbackmandb" do |pgbackmandb| 64 | pgbackmandb.vm.box = "chef/centos-6.6" 65 | pgbackmandb.vm.network "private_network", ip: "10.10.10.104" 66 | pgbackmandb.vm.hostname = "pgbackmandb.example.net" 67 | pgbackmandb.vm.provision :shell, :path => "bootstrap2.sh" 68 | 69 | pgbackmandb.vm.provider "virtualbox" do |vb4| 70 | # Use VBoxManage to customize the VM. For example to change memory: 71 | vb4.customize ["modifyvm", :id, "--memory", "512"] 72 | end 73 | end 74 | 75 | config.vm.define "centos6" do |centos6| 76 | centos6.vm.box = "chef/centos-6.6" 77 | centos6.vm.network "private_network", ip: "10.10.10.105" 78 | centos6.vm.hostname = "centos6.example.net" 79 | centos6.vm.provision :shell, :path => "bootstrap_centos.sh" 80 | 81 | centos6.vm.provider "virtualbox" do |vb5| 82 | # Use VBoxManage to customize the VM. For example to change memory: 83 | vb5.customize ["modifyvm", :id, "--memory", "512"] 84 | end 85 | end 86 | 87 | config.vm.define "debian7" do |debian7| 88 | debian7.vm.box = "chef/debian-7.4" 89 | debian7.vm.network "private_network", ip: "10.10.10.106" 90 | debian7.vm.hostname = "debian7.example.net" 91 | debian7.vm.provision :shell, :path => "bootstrap_debian.sh" 92 | 93 | debian7.vm.provider "virtualbox" do |vb6| 94 | # Use VBoxManage to customize the VM. For example to change memory: 95 | vb6.customize ["modifyvm", :id, "--memory", "512"] 96 | end 97 | end 98 | 99 | config.vm.define "ubuntu14" do |ubuntu14| 100 | ubuntu14.vm.box = "chef/ubuntu-14.04" 101 | ubuntu14.vm.network "private_network", ip: "10.10.10.107" 102 | ubuntu14.vm.hostname = "ubuntu14.example.net" 103 | ubuntu14.vm.provision :shell, :path => "bootstrap_debian.sh" 104 | 105 | ubuntu14.vm.provider "virtualbox" do |vb7| 106 | # Use VBoxManage to customize the VM. For example to change memory: 107 | vb7.customize ["modifyvm", :id, "--memory", "512"] 108 | end 109 | end 110 | 111 | config.vm.define "centos7" do |centos7| 112 | centos7.vm.box = "centos7" 113 | centos7.vm.network "private_network", ip: "10.10.10.108" 114 | centos7.vm.hostname = "centos7.example.net" 115 | centos7.vm.provision :shell, :path => "bootstrap_centos.sh" 116 | 117 | centos7.vm.provider "virtualbox" do |vb8| 118 | # Use VBoxManage to customize the VM. For example to change memory: 119 | vb8.customize ["modifyvm", :id, "--memory", "1024"] 120 | end 121 | end 122 | 123 | 124 | # The url from where the 'config.vm.box' box will be fetched if it 125 | # doesn't already exist on the user's system. 126 | # config.vm.box_url = "http://domain.com/path/to/above.box" 127 | 128 | # Create a forwarded port mapping which allows access to a specific port 129 | # within the machine from a port on the host machine. In the example below, 130 | # accessing "localhost:8080" will access port 80 on the guest machine. 131 | # config.vm.network "forwarded_port", guest: 80, host: 8080 132 | 133 | # Create a private network, which allows host-only access to the machine 134 | # using a specific IP. 135 | #config.vm.network "private_network", ip: "10.10.10.100" 136 | 137 | 138 | # Create a public network, which generally matched to bridged network. 139 | # Bridged networks make the machine appear as another physical device on 140 | # your network. 141 | # config.vm.network "public_network" 142 | 143 | # If true, then any SSH connections made will enable agent forwarding. 144 | # Default value: false 145 | # config.ssh.forward_agent = true 146 | 147 | # Share an additional folder to the guest VM. The first argument is 148 | # the path on the host to the actual folder. The second argument is 149 | # the path on the guest to mount the folder. And the optional third 150 | # argument is a set of non-required options. 151 | # config.vm.synced_folder "../data", "/vagrant_data" 152 | 153 | # Provider-specific configuration so you can fine-tune various 154 | # backing providers for Vagrant. These expose provider-specific options. 155 | # Example for VirtualBox: 156 | # 157 | # config.vm.provider "virtualbox" do |vb| 158 | # # Don't boot with headless mode 159 | # vb.gui = true 160 | # 161 | # # Use VBoxManage to customize the VM. For example to change memory: 162 | # vb.customize ["modifyvm", :id, "--memory", "512"] 163 | # end 164 | # 165 | # View the documentation for the provider you're using for more 166 | # information on available options. 167 | 168 | # Enable provisioning with Puppet stand alone. Puppet manifests 169 | # are contained in a directory path relative to this Vagrantfile. 170 | # You will need to create the manifests directory and a manifest in 171 | # the file chef/centos-6.6.pp in the manifests_path directory. 172 | # 173 | # An example Puppet manifest to provision the message of the day: 174 | # 175 | # # group { "puppet": 176 | # # ensure => "present", 177 | # # } 178 | # # 179 | # # File { owner => 0, group => 0, mode => 0644 } 180 | # # 181 | # # file { '/etc/motd': 182 | # # content => "Welcome to your Vagrant-built virtual machine! 183 | # # Managed by Puppet.\n" 184 | # # } 185 | # 186 | # config.vm.provision "puppet" do |puppet| 187 | # puppet.manifests_path = "manifests" 188 | # puppet.manifest_file = "site.pp" 189 | # end 190 | 191 | # Enable provisioning with chef solo, specifying a cookbooks path, roles 192 | # path, and data_bags path (all relative to this Vagrantfile), and adding 193 | # some recipes and/or roles. 194 | # 195 | # config.vm.provision "chef_solo" do |chef| 196 | # chef.cookbooks_path = "../my-recipes/cookbooks" 197 | # chef.roles_path = "../my-recipes/roles" 198 | # chef.data_bags_path = "../my-recipes/data_bags" 199 | # chef.add_recipe "mysql" 200 | # chef.add_role "web" 201 | # 202 | # # You may also specify custom JSON attributes: 203 | # chef.json = { :mysql_password => "foo" } 204 | # end 205 | 206 | # Enable provisioning with chef server, specifying the chef server URL, 207 | # and the path to the validation key (relative to this Vagrantfile). 208 | # 209 | # The Opscode Platform uses HTTPS. Substitute your organization for 210 | # ORGNAME in the URL and validation key. 211 | # 212 | # If you have your own Chef Server, use the appropriate URL, which may be 213 | # HTTP instead of HTTPS depending on your configuration. Also change the 214 | # validation key to validation.pem. 215 | # 216 | # config.vm.provision "chef_client" do |chef| 217 | # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME" 218 | # chef.validation_key_path = "ORGNAME-validator.pem" 219 | # end 220 | # 221 | # If you're using the Opscode platform, your validator client is 222 | # ORGNAME-validator, replacing ORGNAME with your organization name. 223 | # 224 | # If you have your own Chef Server, the default validation client name is 225 | # chef-validator, unless you changed the configuration. 226 | # 227 | # chef.validation_client_name = "ORGNAME-validator" 228 | end 229 | -------------------------------------------------------------------------------- /vagrant/Vagrantfile_orig: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 5 | VAGRANTFILE_API_VERSION = "2" 6 | 7 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 8 | # All Vagrant configuration is done here. The most common configuration 9 | # options are documented and commented below. For a complete reference, 10 | # please see the online documentation at vagrantup.com. 11 | 12 | # Every Vagrant virtual environment requires a box to build off of. 13 | config.vm.box = "chef/centos-6.5" 14 | 15 | # The url from where the 'config.vm.box' box will be fetched if it 16 | # doesn't already exist on the user's system. 17 | # config.vm.box_url = "http://domain.com/path/to/above.box" 18 | 19 | # Create a forwarded port mapping which allows access to a specific port 20 | # within the machine from a port on the host machine. In the example below, 21 | # accessing "localhost:8080" will access port 80 on the guest machine. 22 | # config.vm.network "forwarded_port", guest: 80, host: 8080 23 | 24 | # Create a private network, which allows host-only access to the machine 25 | # using a specific IP. 26 | # config.vm.network "private_network", ip: "192.168.33.10" 27 | 28 | # Create a public network, which generally matched to bridged network. 29 | # Bridged networks make the machine appear as another physical device on 30 | # your network. 31 | # config.vm.network "public_network" 32 | 33 | # If true, then any SSH connections made will enable agent forwarding. 34 | # Default value: false 35 | # config.ssh.forward_agent = true 36 | 37 | # Share an additional folder to the guest VM. The first argument is 38 | # the path on the host to the actual folder. The second argument is 39 | # the path on the guest to mount the folder. And the optional third 40 | # argument is a set of non-required options. 41 | # config.vm.synced_folder "../data", "/vagrant_data" 42 | 43 | # Provider-specific configuration so you can fine-tune various 44 | # backing providers for Vagrant. These expose provider-specific options. 45 | # Example for VirtualBox: 46 | # 47 | # config.vm.provider "virtualbox" do |vb| 48 | # # Don't boot with headless mode 49 | # vb.gui = true 50 | # 51 | # # Use VBoxManage to customize the VM. For example to change memory: 52 | # vb.customize ["modifyvm", :id, "--memory", "1024"] 53 | # end 54 | # 55 | # View the documentation for the provider you're using for more 56 | # information on available options. 57 | 58 | # Enable provisioning with Puppet stand alone. Puppet manifests 59 | # are contained in a directory path relative to this Vagrantfile. 60 | # You will need to create the manifests directory and a manifest in 61 | # the file chef/centos-6.5.pp in the manifests_path directory. 62 | # 63 | # An example Puppet manifest to provision the message of the day: 64 | # 65 | # # group { "puppet": 66 | # # ensure => "present", 67 | # # } 68 | # # 69 | # # File { owner => 0, group => 0, mode => 0644 } 70 | # # 71 | # # file { '/etc/motd': 72 | # # content => "Welcome to your Vagrant-built virtual machine! 73 | # # Managed by Puppet.\n" 74 | # # } 75 | # 76 | # config.vm.provision "puppet" do |puppet| 77 | # puppet.manifests_path = "manifests" 78 | # puppet.manifest_file = "site.pp" 79 | # end 80 | 81 | # Enable provisioning with chef solo, specifying a cookbooks path, roles 82 | # path, and data_bags path (all relative to this Vagrantfile), and adding 83 | # some recipes and/or roles. 84 | # 85 | # config.vm.provision "chef_solo" do |chef| 86 | # chef.cookbooks_path = "../my-recipes/cookbooks" 87 | # chef.roles_path = "../my-recipes/roles" 88 | # chef.data_bags_path = "../my-recipes/data_bags" 89 | # chef.add_recipe "mysql" 90 | # chef.add_role "web" 91 | # 92 | # # You may also specify custom JSON attributes: 93 | # chef.json = { :mysql_password => "foo" } 94 | # end 95 | 96 | # Enable provisioning with chef server, specifying the chef server URL, 97 | # and the path to the validation key (relative to this Vagrantfile). 98 | # 99 | # The Opscode Platform uses HTTPS. Substitute your organization for 100 | # ORGNAME in the URL and validation key. 101 | # 102 | # If you have your own Chef Server, use the appropriate URL, which may be 103 | # HTTP instead of HTTPS depending on your configuration. Also change the 104 | # validation key to validation.pem. 105 | # 106 | # config.vm.provision "chef_client" do |chef| 107 | # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME" 108 | # chef.validation_key_path = "ORGNAME-validator.pem" 109 | # end 110 | # 111 | # If you're using the Opscode platform, your validator client is 112 | # ORGNAME-validator, replacing ORGNAME with your organization name. 113 | # 114 | # If you have your own Chef Server, the default validation client name is 115 | # chef-validator, unless you changed the configuration. 116 | # 117 | # chef.validation_client_name = "ORGNAME-validator" 118 | end 119 | -------------------------------------------------------------------------------- /vagrant/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rpm -ivh http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-centos93-9.3-1.noarch.rpm 4 | rpm -ivh http://yum.postgresql.org/9.2/redhat/rhel-6-x86_64/pgdg-centos92-9.2-6.noarch.rpm 5 | rpm -ivh http://yum.postgresql.org/9.1/redhat/rhel-6-x86_64/pgdg-centos91-9.1-4.noarch.rpm 6 | rpm -ivh http://yum.postgresql.org/9.0/redhat/rhel-6-x86_64/pgdg-centos90-9.0-5.noarch.rpm 7 | 8 | yum -y update 9 | yum -y install python-setuptools gcc postgresql93 postgresql92 postgresql91 postgresql90 python-devel python-psycopg2 python-argparse 10 | yum -y upgrade 11 | 12 | echo "10.10.10.101 pg-backup01.example.net pg-backup01" >> /etc/hosts 13 | echo "10.10.10.102 pg-backup02.example.net pg-backup02" >> /etc/hosts 14 | echo "10.10.10.103 pg-node01.example.net pg-node01" >> /etc/hosts 15 | echo "10.10.10.104 pgbackmandb.example.net pgbackmandb" >> /etc/hosts 16 | echo "10.10.10.105 pg-node02.example.net pg-node02" >> /etc/hosts 17 | 18 | -------------------------------------------------------------------------------- /vagrant/bootstrap2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rpm -ivh http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-centos93-9.3-1.noarch.rpm 4 | yum -y update 5 | yum -y groupinstall "PostgreSQL Database Server 9.3 PGDG" 6 | yum -y upgrade 7 | 8 | echo "10.10.10.101 pg-backup01.example.net pg-backup01" >> /etc/hosts 9 | echo "10.10.10.102 pg-backup02.example.net pg-backup02" >> /etc/hosts 10 | echo "10.10.10.103 pg-node01.example.net pg-node01" >> /etc/hosts 11 | echo "10.10.10.104 pgbackmandb.example.net pgbackmandb" >> /etc/hosts 12 | echo "10.10.10.105 pg-node02.example.net pg-node02" >> /etc/hosts 13 | 14 | sudo /etc/init.d/postgresql-9.3 initdb 15 | 16 | echo "host pgbackman pgbackman_role_rw 10.10.10.101/32 trust" > /var/lib/pgsql/9.3/data/pg_hba.conf 17 | echo "host pgbackman pgbackman_role_rw 10.10.10.102/32 trust" >> /var/lib/pgsql/9.3/data/pg_hba.conf 18 | echo "local all all peer" >> /var/lib/pgsql/9.3/data/pg_hba.conf 19 | 20 | echo "listen_addresses = '10.10.10.104'" >> /var/lib/pgsql/9.3/data/postgresql.conf 21 | 22 | sudo /etc/init.d/postgresql-9.3 start 23 | 24 | -------------------------------------------------------------------------------- /vagrant/bootstrap3.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rpm -ivh http://yum.postgresql.org/9.1/redhat/rhel-6-x86_64/pgdg-centos91-9.1-4.noarch.rpm 4 | yum -y update 5 | yum -y groupinstall "PostgreSQL Database Server 9.1 PGDG" 6 | yum -y upgrade 7 | 8 | echo "10.10.10.101 pg-backup01.example.net pg-backup01" >> /etc/hosts 9 | echo "10.10.10.102 pg-backup02.example.net pg-backup02" >> /etc/hosts 10 | echo "10.10.10.103 pg-node01.example.net pg-node01" >> /etc/hosts 11 | echo "10.10.10.104 pgbackmandb.example.net pgbackmandb" >> /etc/hosts 12 | echo "10.10.10.105 pg-node02.example.net pg-node02" >> /etc/hosts 13 | 14 | sudo /etc/init.d/postgresql-9.1 initdb 15 | 16 | echo "host all postgres 10.10.10.101/32 trust" > /var/lib/pgsql/9.1/data/pg_hba.conf 17 | echo "host all postgres 10.10.10.102/32 trust" >> /var/lib/pgsql/9.1/data/pg_hba.conf 18 | echo "local all all peer" >> /var/lib/pgsql/9.1/data/pg_hba.conf 19 | 20 | echo "listen_addresses = '10.10.10.103'" >> /var/lib/pgsql/9.1/data/postgresql.conf 21 | 22 | sudo /etc/init.d/postgresql-9.1 start 23 | 24 | -------------------------------------------------------------------------------- /vagrant/bootstrap_centos.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "10.10.10.105 centos6.example.net centos6" >> /etc/hosts 4 | echo "10.10.10.106 debian7.example.net debian7" >> /etc/hosts 5 | echo "10.10.10.107 ubuntu14.example.net ubuntu14" >> /etc/hosts 6 | echo "10.10.10.108 centos7.example.net centos7" >> /etc/hosts 7 | 8 | 9 | yum -y update 10 | yum -y install rpm-build git make 11 | yum -y upgrade 12 | -------------------------------------------------------------------------------- /vagrant/bootstrap_debian.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "10.10.10.105 centos6.example.net centos6" >> /etc/hosts 4 | echo "10.10.10.106 debian7.example.net debian7" >> /etc/hosts 5 | echo "10.10.10.107 ubuntu14.example.net ubuntu14" >> /etc/hosts 6 | 7 | apt-get -y update 8 | apt-get -y install git make 9 | apt-get -y upgrade 10 | 11 | --------------------------------------------------------------------------------