Compare commits

...

128 commits

Author SHA1 Message Date
Lance Edgar 8bddce1329 bump: version 0.4.4 → 0.4.5 2025-02-01 15:19:59 -06:00
Lance Edgar 542875a44c fix: purge email settings for wuttjamaican also 2024-12-18 19:07:30 -06:00
Lance Edgar 8bd4236291 bump: version 0.4.3 → 0.4.4 2024-10-03 10:36:07 -05:00
Lance Edgar 00b0082302 fix: update project source links, kallithea -> forgejo 2024-09-14 11:51:19 -05:00
Lance Edgar 515ccc9f0c docs: use markdown for readme file 2024-09-13 18:40:32 -05:00
Lance Edgar e2ce4fd257 bump: version 0.4.2 → 0.4.3 2024-08-06 23:20:33 -05:00
Lance Edgar fcfe741b51 fix: setup basic log files for CORE Lane 2024-08-04 10:11:45 -05:00
Lance Edgar 59ab46a335 fix: avoid rich traceback for overnight luigi commands 2024-08-03 11:54:42 -05:00
Lance Edgar 8ab21914bd fix: avoid rich traceback for backup script 2024-07-25 10:08:41 -05:00
Lance Edgar 9eb44fa172 fix: avoid rich traceback for overnight luigi commands 2024-07-18 10:09:56 -05:00
Lance Edgar 65a9a7c9be fix: install more dependencies for borg 2024-07-18 10:09:47 -05:00
Lance Edgar 5ffcf0297c fix: install emacs-common-non-dfsg only if available
which it is not always, depending on apt sources
2024-07-12 12:00:39 -05:00
Lance Edgar 7d031e1a41 bump: version 0.4.1 → 0.4.2 2024-07-05 11:10:21 -05:00
Lance Edgar 04c586a11e fix: install non-dfsg package for emacs
for sake of tramp info manual

cf. https://www.reddit.com/r/emacs/comments/d6r3dt/comment/f0vujx6/
2024-07-03 14:18:29 -05:00
Lance Edgar d0425115da fix: remove references, dependency for six package 2024-07-01 16:35:08 -05:00
Lance Edgar 8b5bb956ae bump: version 0.4.0 → 0.4.1 2024-06-30 11:12:39 -05:00
Lance Edgar 5c5038144f fix: always install venv pkg when bootstrapping python 2024-06-15 21:17:42 -05:00
Lance Edgar 014982028b bump: version 0.3.6 → 0.4.0 2024-06-10 19:14:58 -05:00
Lance Edgar 8c51ee8735 feat: switch from setup.cfg to pyproject.toml + hatchling 2024-06-10 19:12:50 -05:00
Lance Edgar 4e3b8f6520 Update changelog 2024-05-31 17:47:41 -05:00
Lance Edgar be728e9bb3 Fix default dist filename for release task
not sure why this fix was needed, did setuptools behavior change?

and stop declaring explicit support for python 3.5, that makes no sense
2024-05-31 17:44:56 -05:00
Lance Edgar 284e55c05c Update changelog 2024-05-31 17:43:30 -05:00
Lance Edgar 88acb15c1c Fix command line args in scripts, per typer 2024-05-29 06:31:41 -05:00
Lance Edgar 8f8013aee0 Update changelog 2024-05-07 14:14:33 -05:00
Lance Edgar 305e4c40c7 Fix shell when creating new linux user account 2024-05-07 14:13:46 -05:00
Lance Edgar 6a15b5ae5e Add docstring to explain param 2023-11-06 21:25:13 -06:00
Lance Edgar c13e9ff23c Update changelog 2023-09-25 18:09:31 -05:00
Lance Edgar ae7cb45ab3 Remove bash -c prefix for pg_dump command
need to keep the `sudo -u postgres pg_dump ..` part as simple as
possible, since must declare `sudoers` allowance for that
2023-09-17 11:10:48 -05:00
Lance Edgar 24d632b7e3 Add option to skip raw SQL file when dumping postgres DB
trying to cut down on disk space, we'll see how well this works..
2023-09-16 16:57:10 -05:00
Lance Edgar 8e9a685006 Try again, to move postgres dump file to /tmp before restoring
so the postgres can cd to current workdir without error..fingers
crossed this doesn't break anything else again
2023-08-08 19:19:33 -05:00
Lance Edgar bedca74ca1 Revert "Move sql file to temp path when restoring postgres db"
This reverts commit 94945fbc30.

apparently that breaks some nightly cloning (prod -> stage)
2023-08-06 19:20:53 -05:00
Lance Edgar e2369b1f53 Add mysql.get_version_string() convenience function 2023-08-04 16:32:42 -05:00
Lance Edgar ab1baaa6d2 Add clang workaround for pythonz
needed that to install python 3.6.8 on debian 12
2023-08-03 21:26:47 -05:00
Lance Edgar 94945fbc30 Move sql file to temp path when restoring postgres db
otherwise postgres may throw an error, if it can't cd to current workdir
2023-08-03 10:54:33 -05:00
Lance Edgar 6479af6a57 Preserve correct owner for .bashrc when configuring nodejs 2023-08-03 10:53:59 -05:00
Lance Edgar 075f931b5e Add separate functions for dump, restore of mysql DB 2023-07-18 15:17:18 -05:00
Lance Edgar 989f1574dc Update changelog 2023-06-10 19:03:49 -05:00
Lance Edgar d713bbe522 Let caller override default fannie/config.php
also make `deploy.full_path()` honor absolute path when given one
2023-06-10 18:57:57 -05:00
Lance Edgar 64c69aab3c Update changelog 2023-06-10 16:13:11 -05:00
Lance Edgar 9eab7a7555 Add password support for make_normal_user() 2023-06-09 18:19:49 -05:00
Lance Edgar 3f931445a0 Touch fannie.log when installing CORE Office 2023-06-09 18:19:15 -05:00
Lance Edgar 8bafaef796 Update changelog 2023-06-08 21:10:03 -05:00
Lance Edgar 753e939422 Do not define rattail mail templates by default
pretty sure that value was/is already fallback default anyway
2023-06-02 14:42:53 -05:00
Lance Edgar 88e92588f2 Add a couple more options for rsync()
sometimes you just need something different to happen..
2023-06-01 15:18:27 -05:00
Lance Edgar 47bc4ce01d Add __version__ attr to root namespace
for sake of `setup.cfg`
2023-05-17 07:22:09 -05:00
Lance Edgar 1356bd0310 Replace setup.py contents with setup.cfg 2023-05-16 13:47:10 -05:00
Lance Edgar 86d922708a Make installing composer packages optional, for CORE-POS 2023-05-15 18:36:29 -05:00
Lance Edgar cf32571c83 Pass LUIGI2 context when generating config file 2023-03-09 15:31:03 -06:00
Lance Edgar 21a071d324 Touch debug_fannie.log when installing CORE
just to prevent the warning
2023-02-28 21:21:06 -06:00
Lance Edgar 43d8df3c5d Add MAILTO for backup crontab 2023-02-23 13:05:54 -06:00
Lance Edgar 2097136844 Use --global when allowing Composer plugins for CORE
this keeps allowances out of the local `composer.json` file and
therefore keeps the working folder clean
2023-02-19 12:40:11 -06:00
Lance Edgar 20a3871522 Use composer install instead of composer update for CORE 2023-02-19 12:05:22 -06:00
Lance Edgar bdd89db30d Add install_collectd() function 2023-02-19 11:19:37 -06:00
Lance Edgar 6c78d3c1d5 Add new/improved install_corepos() function
probably needs still more improvement, but getting here.  so far it
only does the office setup
2023-02-17 22:16:30 -06:00
Lance Edgar cde124b916 Add logrotate support for backup apps 2023-02-17 11:01:29 -06:00
Lance Edgar a76d38c201 Add make_normal_user() function
also add `disabled_password` kwarg for `make_system_user()`
2023-02-17 11:00:31 -06:00
Lance Edgar 9e0a1cde19 Warn if caller deploys old-style soffice daemon 2023-01-10 20:40:36 -06:00
Lance Edgar c8ab84e462 Install libssl-dev when installing pythonz 2023-01-09 09:01:31 -06:00
Lance Edgar 492e8da5c3 Remove some luigi config if using newer package
apparently some of these settings are no longer used by latest luigi,
and it complains when they're present
2023-01-09 09:00:50 -06:00
Lance Edgar 7d536499d6 Add logic to create db, configure supervisor, for install_luigi() 2023-01-08 14:55:33 -06:00
Lance Edgar d68b57baa8 Allow for custom mail recipients in luigi crontab 2023-01-08 13:04:46 -06:00
Lance Edgar 6fb9389e45 Provide default crontab for luigi, to rotate logs and restart 2023-01-08 12:41:25 -06:00
Lance Edgar 8c556e6176 Fix logic to bind postgres to all network interfaces
ugh, finally solved this
2023-01-07 17:22:21 -06:00
Lance Edgar 5fcfc91d83 Inject default mail sender/recips for machine-wide rattail.conf
also tweak timezone handling
2023-01-07 17:21:34 -06:00
Lance Edgar 375cd1a36f Add docker module for installing that 2023-01-05 11:41:39 -06:00
Lance Edgar dc5556651f Update clone URL for byjove 2023-01-05 08:41:54 -06:00
Lance Edgar cfba5425a1 Slight tweaks when making backup venv 2023-01-02 15:01:39 -06:00
Lance Edgar a3339ac062 Avoid workon command when deploying backup app 2022-11-28 16:49:56 -06:00
Lance Edgar 1fedc314b3 Add pyfuse3 support for backup (borg) app 2022-11-27 18:47:43 -06:00
Lance Edgar a9bbee572a Let caller override config paths for luigi overnight scripts 2022-11-27 12:47:39 -06:00
Lance Edgar ebf94fb7dc Specify logging, retcode config for luigi 2022-11-23 14:47:09 -06:00
Lance Edgar 5acf8d5304 Use workaround for shlex.join() on older python 2022-11-21 20:09:51 -06:00
Lance Edgar f1ecf9a1e4 Don't write overnight scripts to invoke luigi directly, by default
`rattail overnight` command can handle that natively now
2022-11-21 20:09:16 -06:00
Lance Edgar 4c8e956fec Must pass -k flag to rattail overnight command 2022-11-21 15:39:49 -06:00
Lance Edgar 25891902b8 Make each overnight script optional
trying to phase out at least 1, maybe 2 of them...

this also cleans up the 'restart' script to use 'rattail overnight'
per recent changes.  this seems the most useful to have around, in
case of web app failure
2022-11-20 20:03:28 -06:00
Lance Edgar a36ecdb2ad Use new rattail overnight command in cron-overnight.sh script
this is all a bit of WIP at the moment, perhaps..
2022-11-20 15:54:58 -06:00
Lance Edgar 7ce3aae20c Be more explicit about virtualenvwrapper script 2022-11-20 15:31:50 -06:00
Lance Edgar 46440b6a95 Add basic composer.install() function 2022-11-01 18:42:11 -05:00
Lance Edgar 614fd92a20 Use shlex.join() when adding postfix alias
a bit convoluted perhaps, but still better...
2022-11-01 18:41:27 -05:00
Lance Edgar a2dca4ea65 Add generic script to check mountpoints, for collectd 2022-08-21 22:57:30 -05:00
Lance Edgar 987731885b Allow for deploying v02 certbot account 2022-08-14 13:57:10 -05:00
Lance Edgar ba05d616d8 Always make soffice daemon init scripts executable 2022-07-25 21:32:28 -05:00
Lance Edgar 303f650a0b Tweak logic for setting PostgreSQL listen_addresses
this still isn't right, but i'm calling this a savepoint at least
2022-07-25 19:53:12 -05:00
Lance Edgar f97401009e Allow custom email key for overnight scripts 2022-03-21 17:54:23 -05:00
Lance Edgar 5a4e2701df Tweak default luigi config; allow specifying version
to accommodate one particular install, but is generally okay i think..
2022-03-18 21:46:41 -05:00
Lance Edgar 5df704679f Improve error message when deploying backup w/ no config 2022-02-16 15:26:46 -06:00
Lance Edgar a1b580e51a Add function to get MariaDB version string 2022-02-10 23:25:24 -06:00
Lance Edgar 097a96fc07 Add --progress-bar arg for pip in backup app's upgrade script
for some reason latest pip has progress bar showing up in cron output
and causing unwanted email
2022-02-07 10:47:43 -06:00
Lance Edgar fd0e55ab60 Add logic to install with get-pip.py on python2 2022-01-30 10:45:53 -06:00
Lance Edgar a2f5542ed8 Fix typo in overnight luigi cron script 2022-01-29 17:28:46 -06:00
Lance Edgar 6bf697da1d Add generic Luigi install logic
at least try to do what we can to reduce boilerplate
2022-01-28 15:29:32 -06:00
Lance Edgar ca59000287 Allow specifying alternate config file for datasync checks 2021-11-30 13:38:05 -06:00
Lance Edgar b58c0da7a4 Use python instead of shell script, for datasync checks
seems to give more clarity, and we'll have more options this way
2021-11-11 11:58:09 -06:00
Lance Edgar 5b985fb803 Fix syntax error, clone URL for deploy.backup_app() 2021-11-10 20:56:55 -06:00
Lance Edgar cbf93624e0 Update README link for FreeTDS 2021-10-05 10:11:42 -04:00
Lance Edgar 955ceea801 Add function for installing byjove from source 2021-09-03 19:24:48 -05:00
Lance Edgar c413c2a1f2 Add postgres functions, set_listen_addresses and add_hba_entry 2021-06-15 14:16:46 -05:00
Lance Edgar 3287505f88 Bring in the uncomment function to root namespace 2021-06-15 14:16:29 -05:00
Lance Edgar 1fd9ad48d6 Add bootstrap_rattail_base() and related tweaks
the idea here is to have a "one stop shop" for base requirements,
we'll see how useful it is in practice i guess
2021-06-11 18:38:16 -05:00
Lance Edgar 506b88d3e2 Avoid use of workon when deploying backup app
should not assume presence of virtualenvwrapper for this
2021-05-04 15:49:46 -05:00
Lance Edgar 9ae0015aba Allow skipping of triggers when dumping MySQL DB
specifically this is b/c of a production demo which makes use of multiple
triggers of the same "type" but my dev maching has older MariaDB which doesn't
allow such multiple triggers
2021-02-02 11:31:27 -06:00
Lance Edgar 9048403130 Tweak how python packages are installed for backup app
per some issues i encountered with pip 20.3.3 and its "new resolver" although
not totally clear what fixed it... :/
2021-01-13 13:13:35 -06:00
Lance Edgar f87f1e875c Add python.configure_pythonz() convenience function
not sure how i managed to get by without this until now..?
2021-01-13 13:12:35 -06:00
Lance Edgar 70c5d661a3 Add basic standard datasync check scripts 2021-01-02 20:04:36 -06:00
Lance Edgar 7500202af0 Add basic ejabberd module
doesn't do much really, but still worth sharing i suppose
2020-12-14 11:09:46 -06:00
Lance Edgar 8b8df95633 Add postgresql.script() for running arbitrary SQL script 2020-12-13 15:54:03 -06:00
Lance Edgar ab822411be Tweak regex used to determine PHP version 2020-10-17 10:21:20 -05:00
Lance Edgar a77b1ec100 Fix typo for installing POD images 2020-10-14 09:21:44 -05:00
Lance Edgar 7963380715 Remove some deprecated init scripts for rattail 2020-10-09 16:39:17 -05:00
Lance Edgar f5a7f80fd4 More tweaks to logic for caching ssh host key 2020-10-09 16:38:19 -05:00
Lance Edgar 8c08412d7b Be more discerning about how/when we cache ssh host key 2020-10-09 16:09:34 -05:00
Lance Edgar 47c27eaba5 Don't use sudo for parts of postgresql.restore_db()
since apparently that is a problem
2020-10-08 13:58:55 -05:00
Lance Edgar caf94ac65a Add port and user kwargs for ssh.cache_host_key()
deprecate the `for_user` kwarg.  this also will now try a basic command over
ssh before bothering with an update/cache for the host key
2020-10-08 13:15:06 -05:00
Lance Edgar 97ed8eeff0 Split up the dump/restore DB logic for postgresql
so that i can swap out portions of that in order to use rsync for big DBs
2020-10-07 17:54:45 -05:00
Lance Edgar 96f950d958 Try to set mysql user password in 2 different ways
so some versions of MySQL and/or MariaDB do not agree as to how the password
should be set?  weird, but oh well, hopefully this works everywhere...
2020-09-30 18:19:36 -05:00
Lance Edgar d744257af6 Allow specifying python binary for common backup app environment
also be sure to upgrade setuptools, wheel for it.  this is needed esp. when
deploying to older machines e.g. debian 8
2020-09-29 17:59:31 -05:00
Lance Edgar c67cc74d3d Add apt.upgrade() convenience function 2020-09-29 17:59:21 -05:00
Lance Edgar c9aebd84e9 Change how we set password when creating a MySQL user
the way we were doing it just failed on MySQL (proper, not MariaDB) 8.0
2020-09-26 14:35:52 -05:00
Lance Edgar c2cca7aa76 Update changelog 2020-09-25 18:05:38 -05:00
Lance Edgar 7471dd1bf5 Add is_debian() convenience function 2020-09-23 22:40:59 -05:00
Lance Edgar e8821b3395 Add mssql module for installing MS SQL Server ODBC driver 2020-09-23 22:40:27 -05:00
Lance Edgar 19fcc94dc7 Add method kwarg for python.install_pip()
ugh, this still needs to be better probably
2020-09-20 17:43:50 -05:00
Lance Edgar 6ebc5bbf59 Require the 'rattail' package
just for convenience, might as well
2020-09-20 17:43:04 -05:00
Lance Edgar 6d8cf3adb6 Add "auto" method of installing pip 2020-09-18 15:38:56 -05:00
Lance Edgar 6cf17a054b Pass arbitrary kwargs along, for apt.install()
also return the result
2020-09-18 15:38:20 -05:00
Lance Edgar 6cb6ddaba7 Allow kwargs for template context when deploying sudoers file 2020-09-16 15:48:34 -05:00
Lance Edgar d6a11908ce Update changelog 2020-09-08 18:03:07 -05:00
Lance Edgar 3d5b7c1d6e Improve support for installing pip etc. on python3
namely this is for the sake of newer Ubuntu which doesn't include python2 by
default, and this affects some other package names
2020-09-08 18:01:53 -05:00
58 changed files with 1748 additions and 976 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
*~
*.pyc
dist/
rattail_fabric2.egg-info/

53
CHANGELOG.md Normal file
View file

@ -0,0 +1,53 @@
# Changelog
All notable changes to rattail-fabric2 will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## v0.4.5 (2025-02-01)
### Fix
- purge email settings for wuttjamaican also
## v0.4.4 (2024-10-03)
### Fix
- update project source links, kallithea -> forgejo
## v0.4.3 (2024-08-06)
### Fix
- setup basic log files for CORE Lane
- avoid rich traceback for overnight luigi commands
- avoid rich traceback for backup script
- avoid rich traceback for overnight luigi commands
- install more dependencies for borg
- install `emacs-common-non-dfsg` only if available
## v0.4.2 (2024-07-05)
### Fix
- install non-dfsg package for emacs
- remove references, dependency for `six` package
## v0.4.1 (2024-06-30)
### Fix
- always install `venv` pkg when bootstrapping python
## v0.4.0 (2024-06-10)
### Feat
- switch from setup.cfg to pyproject.toml + hatchling
## Older Releases
Please see `docs/OLDCHANGES.rst` for older release notes.

View file

@ -1,20 +0,0 @@
CHANGELOG
=========
0.2.2 (2020-09-08)
------------------
- Include all "deploy" files within manifest.
0.2.1 (2020-09-08)
------------------
- OMG so many changes. Just a "save point" more or less.
0.2.0 (2018-12-03)
------------------
- Initial release, somewhat forked from ``rattail-fabric`` package.

11
README.md Normal file
View file

@ -0,0 +1,11 @@
# rattail-fabric2
Rattail is a retail software framework, released under the GNU General Public
License.
This package contains various utility functions for use with
[Fabric](http://www.fabfile.org/) (v2).
Please see Rattail's [home page](https://rattailproject.org/) for more
information.

View file

@ -1,14 +0,0 @@
rattail-fabric2
===============
Rattail is a retail software framework, released under the GNU General Public
License.
This package contains various utility functions for use with `Fabric`_ (v2).
.. _`Fabric`: http://www.fabfile.org/
Please see Rattail's `home page`_ for more information.
.. _`home page`: https://rattailproject.org/

95
docs/OLDCHANGES.rst Normal file
View file

@ -0,0 +1,95 @@
CHANGELOG
=========
NB. this file contains "old" release notes only. for newer releases
see the `CHANGELOG.md` file in the source root folder.
0.3.6 (2024-05-31)
------------------
* Bump version to fix PyPI upload.
0.3.5 (2024-05-31)
------------------
* Fix command line args in scripts, per typer.
0.3.4 (2024-05-07)
------------------
* Fix shell when creating new linux user account.
0.3.3 (2023-09-25)
------------------
* Add separate functions for dump, restore of mysql DB.
* Preserve correct owner for ``.bashrc`` when configuring node.js.
* Move sql file to temp path when restoring postgres db.
* Add ``clang`` workaround for pythonz.
* Add ``mysql.get_version_string()`` convenience function.
* Add option to skip raw SQL file when dumping postgres DB.
0.3.2 (2023-06-10)
------------------
* Let caller override default ``fannie/config.php``.
0.3.1 (2023-06-10)
------------------
* Touch ``fannie.log`` when installing CORE Office.
* Add password support for ``make_normal_user()``.
0.3.0 (2023-06-08)
------------------
- OMG so many changes...just needed a fresh release.
0.2.4 (2020-09-25)
------------------
- Allow kwargs for template context when deploying sudoers file.
- Pass arbitrary kwargs along, for ``apt.install()``.
- Add ``method`` kwarg for ``python.install_pip()``.
- Require the 'rattail' package.
- Add ``mssql`` module for installing MS SQL Server ODBC driver.
- Add ``is_debian()`` convenience function.
0.2.3 (2020-09-08)
------------------
- Improve support for installing pip etc. on python3.
0.2.2 (2020-09-08)
------------------
- Include all "deploy" files within manifest.
0.2.1 (2020-09-08)
------------------
- OMG so many changes. Just a "save point" more or less.
0.2.0 (2018-12-03)
------------------
- Initial release, somewhat forked from ``rattail-fabric`` package.

41
pyproject.toml Normal file
View file

@ -0,0 +1,41 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "rattail-fabric2"
version = "0.4.5"
description = "Fabric (v2) Utilities for Rattail"
readme = "README.md"
authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]
license = {text = "GNU GPL v3+"}
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Topic :: System :: Systems Administration",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"fabric2",
"invoke",
"rattail",
]
[project.urls]
Homepage = "https://rattailproject.org"
Repository = "https://forgejo.wuttaproject.org/rattail/rattail-fabric2"
Changelog = "https://forgejo.wuttaproject.org/rattail/rattail-fabric2/src/branch/master/CHANGELOG.md"
[tool.commitizen]
version_provider = "pep621"
tag_format = "v$version"
update_changelog_on_bump = true

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2019 Lance Edgar
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@ -27,15 +27,19 @@ This package contains various tasks and associated functions for use with
Fabric deployment and maintenance.
"""
from ._version import __version__
from .core import (
is_debian,
get_debian_version,
get_ubuntu_version,
Deployer,
make_deploy,
make_normal_user,
make_system_user,
mkdir,
rsync,
set_timezone,
UNSPECIFIED,
)
from .util import exists, contains, append, sed
from .util import exists, contains, append, sed, uncomment

View file

@ -1,3 +1,9 @@
# -*- coding: utf-8; -*-
__version__ = '0.2.2'
try:
from importlib.metadata import version
except ImportError:
from importlib_metadata import version
__version__ = version('rattail-fabric2')

View file

@ -73,7 +73,7 @@ def get_php_version(c):
"""
result = c.sudo('php --version')
if not result.failed:
match = re.match(r'^PHP (\d+\.\d+)\.\d+-', result.stdout)
match = re.match(r'^PHP (\d+\.\d+)\.\d+', result.stdout)
if match:
return float(match.group(1))

View file

@ -36,12 +36,12 @@ def install(c, *packages, **kwargs):
"""
Install one or more packages via ``apt-get install``.
"""
frontend = kwargs.get('frontend', 'noninteractive')
target = kwargs.get('target_release')
frontend = kwargs.pop('frontend', 'noninteractive')
target = kwargs.pop('target_release', None)
target = '--target-release={}'.format(target) if target else ''
force_yes = ' --force-yes' if kwargs.get('force_yes') else ''
c.sudo('DEBIAN_FRONTEND={} apt-get --assume-yes {}{} install {}'.format(
frontend, target, force_yes, ' '.join(packages)))
force_yes = ' --force-yes' if kwargs.pop('force_yes', False) else ''
return c.sudo('DEBIAN_FRONTEND={} apt-get --assume-yes {}{} install {}'.format(
frontend, target, force_yes, ' '.join(packages)), **kwargs)
def purge(c, *packages):
@ -67,6 +67,16 @@ def add_source(c, entry):
update(c)
def upgrade(c, frontend='noninteractive'):
"""
Perform an ``apt-get upgrade`` operation.
"""
options = ''
if frontend == 'noninteractive':
options = '--option Dpkg::Options::="--force-confdef" --option Dpkg::Options::="--force-confold"'
c.sudo('DEBIAN_FRONTEND={} apt-get --assume-yes {} upgrade'.format(frontend, options))
def dist_upgrade(c, frontend='noninteractive'):
"""
Perform a full ``apt-get dist-upgrade`` operation.
@ -102,4 +112,10 @@ def install_emacs(c):
if ubuntu_version and ubuntu_version < 16:
emacs = 'emacs23-nox'
install(c, emacs, 'emacs-goodies-el')
install(c, emacs,
'emacs-goodies-el',
)
# nb. this includes tramp manual
if c.sudo('dpkg -s emacs-common-non-dfsg', warn=True).ok:
install(c, 'emacs-common-non-dfsg')

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2019 Lance Edgar
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@ -53,7 +53,9 @@ def deploy_backup_everything(c, **context):
def deploy_backup_app(c, deploy, envname, mkvirtualenv=True, user='rattail',
python_exe='/usr/bin/python3',
install_borg=False,
pyfuse3=False,
link_borg_to_bin=True,
install_luigi=False,
luigi_history_db=None,
@ -63,7 +65,9 @@ def deploy_backup_app(c, deploy, envname, mkvirtualenv=True, user='rattail',
rattail_backup_script=None,
everything=None,
crontab=None,
crontab_mailto=None,
runat=UNSPECIFIED,
logrotate=False,
context={}):
"""
Make an app which can run backups for the server.
@ -77,7 +81,8 @@ def deploy_backup_app(c, deploy, envname, mkvirtualenv=True, user='rattail',
if deploy.local_exists(path):
config = path
else:
raise ValueError("Must provide config path for backup app")
raise ValueError("Config file not found for backup; "
"please add {} to your deploy folder".format(path))
if install_borg:
borg.install_dependencies(c)
@ -86,27 +91,37 @@ def deploy_backup_app(c, deploy, envname, mkvirtualenv=True, user='rattail',
c.sudo('supervisorctl stop backup:')
# virtualenv
if mkvirtualenv:
python.mkvirtualenv(c, envname, python='/usr/bin/python3', runas_user=user)
envpath = '/srv/envs/{}'.format(envname)
c.sudo('chown -R {}: {}'.format(user, envpath))
mkdir(c, os.path.join(envpath, 'src'), use_sudo=True, runas_user=user)
c.sudo("bash -l -c 'workon {} && pip install --upgrade pip'".format(envname), user=user)
if mkvirtualenv and not exists(c, envpath):
mkdir(c, envpath, use_sudo=True, owner=user)
python.mkvirtualenv(c, envname, python=python_exe, runas_user=user)
c.sudo("bash -c 'PIP_CONFIG_FILE={0}/pip.conf {0}/bin/pip install -U pip setuptools wheel'".format(envpath),
user=user)
if install_rattail:
# rattail
mkdir(c, os.path.join(envpath, 'src'), use_sudo=True, runas_user=user)
if not exists(c, os.path.join(envpath, 'src/rattail')):
c.sudo('git clone https://rattailproject.org/git/rattail.git {}/src/rattail'.format(envpath), user=user)
c.sudo("bash -l -c 'workon {} && cdvirtualenv && bin/pip install --editable src/rattail'".format(envname), user=user)
c.sudo(f'git clone https://forgejo.wuttaproject.org/rattail/rattail.git {envpath}/src/rattail', user=user)
c.sudo("bash -c 'PIP_CONFIG_FILE={0}/pip.conf {0}/bin/pip install --editable {0}/src/rattail'".format(envpath),
user=user)
deploy_generic(c, 'backup/git-exclude', os.path.join(envpath, 'src/rattail/.git/info/exclude'), use_sudo=True, owner=user)
# config
c.sudo("bash -l -c 'workon {} && cdvirtualenv && rattail make-appdir'".format(envname), user=user)
if not exists(c, os.path.join(envpath, 'app')):
c.sudo("bash -c 'cd {} && bin/rattail make-appdir'".format(envpath),
user=user)
# note, config is owned by root regardless of `user` - since we always run backups as root
deploy(c, config, os.path.join(envpath, 'app/rattail.conf'), owner='root:{}'.format(user), mode='0640', use_sudo=True, context=context)
c.sudo("bash -l -c 'workon {} && cdvirtualenv && bin/rattail -c app/rattail.conf make-config -T quiet -O app/'".format(envname), user=user)
c.sudo("bash -l -c 'workon {} && cdvirtualenv && bin/rattail -c app/rattail.conf make-config -T silent -O app/'".format(envname), user=user)
deploy(c, config, os.path.join(envpath, 'app/rattail.conf'),
use_sudo=True, owner='root:{}'.format(user), mode='0640',
context=context)
if not exists(c, os.path.join(envpath, 'app', 'quiet.conf')):
c.sudo("bash -c 'cd {} && bin/rattail -c app/rattail.conf make-config -T quiet -O app/'".format(envpath),
user=user)
if not exists(c, os.path.join(envpath, 'app', 'silent.conf')):
c.sudo("bash -c 'cd {} && bin/rattail -c app/rattail.conf make-config -T silent -O app/'".format(envpath),
user=user)
# rattail-backup script
script_context = dict(context)
@ -119,18 +134,20 @@ def deploy_backup_app(c, deploy, envname, mkvirtualenv=True, user='rattail',
# borg
if install_borg:
if install_rattail:
packages = [
'rattail[backup]',
]
if isinstance(install_borg, list):
packages = install_borg
elif isinstance(install_borg, str):
packages = [install_borg]
else:
# these should be same as rattail[backup]
packages = [
'msgpack',
'borgbackup',
'llfuse==1.3.4',
]
c.sudo("bash -l -c 'workon {} && cdvirtualenv && bin/pip install {}'".format(envname, ' '.join(packages)), user=user)
packages = ['msgpack']
if pyfuse3:
apt.install(c, 'libfuse3-dev')
packages.append('borgbackup[pyfuse3]')
else:
# TODO: this is legacy and should stop being default
packages.append('borgbackup[fuse]')
c.sudo("bash -c 'PIP_CONFIG_FILE={0}/pip.conf {0}/bin/pip install {1}'".format(envpath, ' '.join(packages)),
user=user)
if link_borg_to_bin:
c.sudo("ln -sf {}/bin/borg /usr/local/bin/borg".format(envpath))
@ -141,7 +158,8 @@ def deploy_backup_app(c, deploy, envname, mkvirtualenv=True, user='rattail',
packages.append('SQLAlchemy')
if luigi_history_db.startswith('postgresql://'):
packages.append('psycopg2')
c.sudo("bash -l -c 'workon {}; pip install {}'".format(envname, ' '.join(packages)), user=user)
c.sudo("bash -c 'PIP_CONFIG_FILE={0}/pip.conf {0}/bin/pip install {1}'".format(envpath, ' '.join(packages)),
user=user)
# basic config
mkdir(c, ['{}/app/luigitasks'.format(envpath),
@ -208,7 +226,19 @@ def deploy_backup_app(c, deploy, envname, mkvirtualenv=True, user='rattail',
crontab_context['envname'] = envname
crontab_context['pretty_time'] = runat.strftime('%I:%M %p')
crontab_context['cron_time'] = runat.strftime('%M %H')
if crontab_mailto:
if isinstance(crontab_mailto, str):
crontab_mailto = [crontab_mailto]
crontab_mailto = ','.join(crontab_mailto)
crontab_context['mailto'] = crontab_mailto
if crontab:
deploy(c, crontab, '/etc/cron.d/backup', context=crontab_context, use_sudo=True)
else:
deploy_generic(c, 'backup/crontab.mako', '/etc/cron.d/backup', context=crontab_context, use_sudo=True)
# logrotate
if logrotate:
deploy_generic(c, 'backup/logrotate.conf', '/etc/logrotate.d/backup',
use_sudo=True)

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
# Copyright © 2010-2024 Lance Edgar
#
# This file is part of Rattail.
#
@ -26,8 +26,6 @@ Fabric library for Borg backups
https://www.borgbackup.org/
"""
from __future__ import unicode_literals, absolute_import
from rattail_fabric2 import apt
@ -39,6 +37,9 @@ def install_dependencies(c):
c,
'libacl1-dev',
'libfuse-dev',
'liblz4-dev',
'libssl-dev',
'libxxhash-dev',
'libzstd-dev',
'pkg-config',
)

41
rattail_fabric2/byjove.py Normal file
View file

@ -0,0 +1,41 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Fabric library for Byjove
"""
from rattail_fabric2 import exists, mkdir
def install_from_source(c, user='rattail'):
"""
Install the 'byjove' source code package, for staging systems
"""
if not exists(c, '/usr/local/src/byjove'):
mkdir(c, '/usr/local/src/byjove', use_sudo=True, owner=user)
c.sudo('git clone https://forgejo.wuttaproject.org/rattail/byjove.git /usr/local/src/byjove',
user=user)
c.sudo("bash -l -c 'cd /usr/local/src/byjove; npm link'",
user=user)
c.sudo("bash -l -c 'cd /usr/local/src/byjove; npm run build'",
user=user)

View file

@ -0,0 +1,69 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Fabric library for collectd
"""
from rattail_fabric2 import apt, make_deploy, sed
deploy = make_deploy(__file__)
def install_collectd(c, interval=None, rrdtool=None, restart_=False):
"""
Install the ``collectd`` service.
:param interval: Optional override for the collectd ``Interval``
setting.
"""
apt.install(c, 'collectd')
if interval:
sed(c, '/etc/collectd/collectd.conf',
r'^#? ?Interval\s+[0-9]+.*$',
'Interval {}'.format(interval),
use_sudo=True)
if rrdtool is not None:
sed(c, '/etc/collectd/collectd.conf',
r'^#? ?LoadPlugin rrdtool\s*$',
'{}LoadPlugin rrdtool'.format('' if rrdtool else '#'),
use_sudo=True)
if restart_:
restart(c)
def restart(c):
"""
Restart the collectd service.
"""
c.sudo('systemctl restart collectd')
def deploy_mountpoint_check_script(c, dest, **kwargs):
"""
Deploy generic mountpoint check script to specified destination.
"""
deploy(c, 'collectd/check-mountpoints.py', dest, **kwargs)

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2019 Lance Edgar
# Copyright © 2010-2022 Lance Edgar
#
# This file is part of Rattail.
#
@ -24,14 +24,19 @@
Fabric lib for Composer (PHP dependency manager)
"""
from __future__ import unicode_literals, absolute_import
from rattail_fabric2 import make_deploy, exists
from rattail_fabric2 import apt, make_deploy, exists
deploy = make_deploy(__file__)
def install(c, with_apt=True):
if with_apt:
apt.install(c, 'composer')
else:
install_globally(c)
def install_globally(c):
"""
Install `composer.phar` in global location

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2019 Lance Edgar
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@ -27,6 +27,7 @@ Core Utilities
import os
import re
import tempfile
import warnings
from mako.template import Template
@ -64,6 +65,27 @@ def is_link(c, path, use_sudo=False):
return False if result.failed else True
def get_os_release_id(c):
"""
Return the "ID" field from `/etc/os-release` file
"""
contents = c.run('cat /etc/os-release').stdout.strip()
data = {}
for line in contents.split('\n'):
field, value = line.split('=')
data[field] = value
return data['ID']
def is_debian(c):
"""
Return boolean indicating if this machine is Debian (proper, as opposed to
some derivative).
"""
release_id = get_os_release_id(c)
return release_id == 'debian'
def get_debian_version(c):
"""
Fetch the version of Debian running on the target system.
@ -106,7 +128,37 @@ def mkdir(c, paths, owner=None, mode=None,
func('chmod {} {}'.format(mode, ' '.join(paths)))
def make_system_user(c, name, home=None, uid=None, shell=None):
def make_normal_user(c, username, full_name=None,
shell='/bin/bash',
password=None,
disabled_login=False):
"""
Make a new "normal" user account.
:param disabled_login: If true, will leave the account in a
non-usable state, i.e. with invalid shell.
"""
# do not bother if user exists
missing = c.run(f'getent passwd {username}', warn=True).failed
if not missing:
return
# nb. specify --disabled-login to avoid being prompted for password
c.sudo("adduser --gecos '{}' --disabled-login {}".format(full_name or username,
username))
# then fix the shell unless we shouldn't
if not disabled_login:
c.sudo(f'usermod -s {shell} {username}')
# and maybe set password
if password:
c.sudo(f"bash -c 'echo {username}:{password} | chpasswd'",
echo=False)
def make_system_user(c, name, home=None, uid=None, shell=None,
disabled_password=None):
"""
Make a new system user account, with the given home folder and shell path.
"""
@ -116,7 +168,9 @@ def make_system_user(c, name, home=None, uid=None, shell=None):
home = '--home {}'.format(home) if home else ''
uid = '--uid {}'.format(uid) if uid else ''
shell = '--shell {}'.format(shell) if shell else ''
c.sudo('adduser --system --group {} {} {} {}'.format(name, home, uid, shell))
disabled_password = '--disabled-password' if disabled_password else ''
c.sudo('adduser --system --group {} {} {} {} {}'.format(name, home, uid, shell,
disabled_password))
def set_timezone(c, timezone):
@ -193,6 +247,8 @@ class Deployer(object):
self.deploy(c, local_path, remote_path, **kwargs)
def full_path(self, local_path):
if local_path.startswith('/'):
return local_path
return '{}/{}'.format(self.deploy_path, local_path)
def local_exists(self, local_path):
@ -208,7 +264,7 @@ class Deployer(object):
put(c, local_path, remote_path, **kwargs)
def sudoers(self, c, local_path, remote_path, owner='root:', mode='0440', **kwargs):
self.deploy(c, local_path, '/tmp/sudoers', owner=owner, mode=mode, use_sudo=True)
self.deploy(c, local_path, '/tmp/sudoers', owner=owner, mode=mode, use_sudo=True, **kwargs)
c.sudo('mv /tmp/sudoers {}'.format(remote_path))
def apache_site(self, c, local_path, name, **kwargs):
@ -225,7 +281,8 @@ class Deployer(object):
from rattail_fabric2.backup import deploy_backup_app
deploy_backup_app(c, self, envname, *args, **kwargs)
def certbot_account(self, c, uuid, localdir='certbot/account'):
def certbot_account(self, c, uuid, localdir='certbot/account',
version='01'):
"""
Deploy files to establish a certbot account on target server
"""
@ -234,8 +291,8 @@ class Deployer(object):
localdir = localdir.rstrip('/')
paths = [
'/etc/letsencrypt/accounts',
'/etc/letsencrypt/accounts/acme-v01.api.letsencrypt.org',
'/etc/letsencrypt/accounts/acme-v01.api.letsencrypt.org/directory',
'/etc/letsencrypt/accounts/acme-v{}.api.letsencrypt.org'.format(version),
'/etc/letsencrypt/accounts/acme-v{}.api.letsencrypt.org/directory'.format(version),
]
final_path = '{}/{}'.format(paths[-1], uuid)
paths.append(final_path)
@ -249,9 +306,13 @@ class Deployer(object):
"""
Deploy a "soffice" (headless LibreOffice) daemon.
"""
warnings.warn("deploy.soffice_daemon() is deprecated as it only supports "
"/etc/init.d scripts. please deploy your own systemd config "
"instead, as needed.", DeprecationWarning)
if name is None:
name = local_path.split('/')[-1]
kwargs.setdefault('use_sudo', True)
kwargs.setdefault('mode', '0755')
self.deploy(c, local_path, '/etc/init.d/{}'.format(name), **kwargs)
if register:
c.sudo('update-rc.d {} defaults'.format(name))
@ -309,13 +370,21 @@ def make_deploy(deploy_path, last_segment='deploy'):
return Deployer(deploy_path, last_segment)
def rsync(c, host, *paths):
def rsync(c, host, *paths, files=False, target=None):
"""
Runs rsync as root, for the given host and file paths.
:param files: If set, this indicates that each of the ``paths``
are actual files, as opposed to directories.
:param target: If set, this will be used instead of the given
path, for destination path. This only works correctly if
``paths`` contains only one path.
"""
for path in paths:
assert path.startswith('/')
path = path.rstrip('/')
# escape path for rsync
path = path.replace(' ', r'\\\ ').replace("'", r"\\\'")
agent_sudo(c, 'rsync -aP --del root@{0}:{1}/ {1}'.format(host, path))
suffix = '' if files else '/'
agent_sudo(c, f'rsync -aP --del root@{host}:{path}{suffix} {target or path}')

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2019 Lance Edgar
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@ -24,13 +24,103 @@
Fabric library for CORE-POS (IS4C)
"""
from __future__ import unicode_literals, absolute_import
import os
from rattail_fabric2 import mysql, exists, mkdir
from rattail_fabric2 import mysql, exists, make_deploy, mkdir
deploy_generic = make_deploy(__file__)
def install_corepos(c, rootdir, rooturl_office, production=True,
user='www-data',
repo='https://github.com/CORE-POS/IS4C.git',
branch='master',
mysql_username='corepos',
mysql_password='corepos',
mysql_name_prefix='',
composer='composer.phar',
composer_install=True,
make_shadowread=False,
fannie_config=True):
"""
Install the CORE software to the given location.
This will clone CORE code to the ``IS4C`` folder within the
specified ``rootdir`` location.
"""
rooturl_office = rooturl_office.rstrip('/')
mkdir(c, rootdir, use_sudo=True, owner=user)
# CORE source
is4c = os.path.join(rootdir, 'IS4C')
if not exists(c, is4c):
c.sudo('git clone --branch {} {} {}'.format(branch, repo, is4c),
user=user)
if production:
c.sudo('rm -f {}/fannie/DEV_MODE'.format(is4c), user=user)
else:
c.sudo('touch {}/fannie/DEV_MODE'.format(is4c), user=user)
# composer install
if composer_install:
# TODO: these 'allow' entries are needed for composer 2.4 at least...
c.sudo("bash -c 'cd {} && {} config --global --no-plugins allow-plugins.composer/installers true'".format(is4c, composer),
user=user)
c.sudo("bash -c 'cd {} && {} config --global --no-plugins allow-plugins.oomphinc/composer-installers-extender true'".format(is4c, composer),
user=user)
c.sudo("bash -c 'cd {} && {} config --global --no-plugins allow-plugins.corepos/composer-installer true'".format(is4c, composer),
user=user)
c.sudo("bash -c 'cd {} && {} install'".format(is4c, composer),
user=user)
# TODO: (why) is 'update' needed instead of 'install' ?
# c.sudo("bash -c 'cd {} && {} update'".format(is4c, composer),
# user=user)
# shadowread
if make_shadowread:
c.sudo("bash -c 'cd {}/fannie/auth/shadowread && make'".format(is4c),
user=user)
# nb. must run `make install` as root
c.sudo("bash -c 'cd {}/fannie/auth/shadowread && make install'".format(is4c))
# fannie databases
mysql.create_db(c, '{}core_op'.format(mysql_name_prefix),
user='{}@localhost'.format(mysql_username))
mysql.create_db(c, '{}core_trans'.format(mysql_name_prefix),
user='{}@localhost'.format(mysql_username))
mysql.create_db(c, '{}trans_archive'.format(mysql_name_prefix),
user='{}@localhost'.format(mysql_username))
# fannie config
if fannie_config:
remote_path = '{}/IS4C/fannie/config.php'.format(rootdir)
if not exists(c, remote_path):
if fannie_config is True:
fannie_config = 'corepos/fannie-config.php.mako'
deploy_generic(c, fannie_config, remote_path,
use_sudo=True, owner='www-data:{}'.format(user), mode='0640',
context={'rootdir': rootdir,
'rooturl': rooturl_office,
'mysql_username': mysql_username,
'mysql_password': mysql_password,
'mysql_name_prefix': mysql_name_prefix})
# office logging
mkdir(c, f'{is4c}/fannie/logs', use_sudo=True,
owner=f'{user}:www-data', mode='0775')
c.sudo(f"bash -c 'cd {is4c}/fannie/logs && touch fannie.log debug_fannie.log'",
user='www-data')
# lane logging
mkdir(c, f'{is4c}/pos/is4c-nf/log', use_sudo=True,
owner=f'{user}:www-data', mode='0775')
c.sudo(f"bash -c 'cd {is4c}/pos/is4c-nf/log && touch lane.log debug_lane.log'",
user='www-data')
# TODO: deprecate / remove this
def install_fannie(c, rootdir, user='www-data', branch='version-2.10',
mysql_user='is4c', mysql_pass='is4c'):
"""

View file

@ -11,6 +11,8 @@ else
fi
RATTAIL="/srv/envs/${envname}/bin/rattail --config=$CONFIG $PROGRESS $VERBOSE"
# nb. avoid rich-formatted traceback here since it's so "noisy"
export _TYPER_STANDARD_TRACEBACK=1
# sanity check

View file

@ -1,4 +1,8 @@
# -*- mode: conf; -*-
% if mailto:
MAILTO="${mailto}"
% endif
# backup everything of importance at ${pretty_time}
${'' if env.machine_is_live else '# '}${cron_time} * * * root /usr/local/bin/backup-everything

View file

@ -0,0 +1,10 @@
/srv/envs/backup/app/log/rattail.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 600 rattail rattail
}

View file

@ -3,16 +3,18 @@
if [ "$1" = "-v" -o "$1" = "--verbose" ]; then
VERBOSE='--verbose'
QUIET=
PROGRESSBAR='--progress-bar=on'
else
VERBOSE=
QUIET='--quiet'
PROGRESSBAR='--progress-bar=off'
fi
PIP="sudo -H -u ${user} PIP_CONFIG_FILE=${envpath}/pip.conf ${envpath}/bin/pip"
# upgrade pip
$PIP install $QUIET --upgrade pip
$PIP install $QUIET $PROGRESSBAR --upgrade pip
# upgrade rattail
cd ${envpath}/src/rattail
@ -22,4 +24,4 @@ if [ "$(sudo -H -u ${user} git status --porcelain)" != '' ]; then
fi
sudo -H -u ${user} git pull $QUIET
find . -name '*.pyc' -delete
$PIP install $QUIET --upgrade --upgrade-strategy eager --editable .
$PIP install $QUIET $PROGRESSBAR --upgrade --upgrade-strategy eager --editable .

View file

@ -1,142 +0,0 @@
#!/bin/sh
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
# This script is mostly based on the ``/etc/init.d/skeleton`` file from a
# Debian 6 system.
DESC=${DESC:-"Rattail Email Bouncer"}
NAME=${NAME:-"rattail-bouncer"}
SCRIPTNAME=${SCRIPTNAME:-"/etc/init.d/$NAME"}
PIDFILE=${PIDFILE:-"/var/run/rattail/$NAME.pid"}
PYTHON=${PYTHON:-"/usr/bin/python"}
RATTAIL=${RATTAIL:-"/usr/local/bin/rattail"}
RATTAIL_ARGS=${RATTAIL_ARGS:-""}
BOUNCER_ARGS=${BOUNCER_ARGS:-"--pidfile=$PIDFILE"}
USER=${USER:-"rattail"}
GROUP=${GROUP:-"rattail"}
# Read configuration variable files if present.
[ -r /etc/default/rattail ] && . /etc/default/rattail
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# Load the VERBOSE setting and other rcS variables.
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
create_pid_dir() {
PIDDIR=`dirname "$PIDFILE"`
if [ ! -d "$PIDDIR" ]; then
mkdir --parents "$PIDDIR"
fi
chown $USER:$GROUP "$PIDDIR"
}
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
create_pid_dir
start-stop-daemon --start --pidfile $PIDFILE --exec $PYTHON --user $USER --test --quiet > /dev/null \
|| return 1
start-stop-daemon --start --pidfile $PIDFILE --exec $PYTHON --startas $RATTAIL --chuid $USER --group $GROUP --quiet -- \
$RATTAIL_ARGS bouncer $BOUNCER_ARGS start \
|| return 2
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
sudo -u $USER $RATTAIL $RATTAIL_ARGS bouncer $BOUNCER_ARGS stop > /dev/null 2>&1
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
:

View file

@ -0,0 +1,63 @@
# -*- coding: utf-8; -*-
import argparse
import os
import socket
import subprocess
import sys
def check_mounts(mountpoints):
for path in mountpoints:
sys.stdout.write("{} is {}mounted\n".format(
path, '' if is_mounted(path) else 'NOT '))
def check_mounts_collectd(mountpoints):
hostname = os.environ.get('COLLECTD_HOSTNAME')
if not hostname:
hostname = socket.getfqdn()
plugin = 'mountpoints'
interval = os.environ.get('COLLECTD_INTERVAL')
if interval:
interval = ' interval={}'.format(interval)
else:
interval = ''
for path in mountpoints:
name = path.strip('/').replace('/', '-')
plugin_instance = '{}-{}'.format(plugin, name)
data_type = 'gauge-state'
value = 1 if is_mounted(path) else 0
value = 'N:{}'.format(value)
msg = 'PUTVAL {}/{}/{}{} {}\n'.format(
hostname,
plugin_instance,
data_type,
interval,
value)
sys.stdout.write(msg)
def is_mounted(path):
cmd = "mount | grep '{}' || true".format(path)
output = subprocess.check_output(cmd, shell=True)
return bool(output)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--collectd', action='store_true')
parser.add_argument('mountpoints', nargs='+', metavar='MOUNTPOINT')
args = parser.parse_args()
if args.collectd:
check_mounts_collectd(args.mountpoints)
else:
check_mounts(args.mountpoints)

View file

@ -0,0 +1,13 @@
<?php
$FANNIE_ROOT = '${rootdir}/IS4C/fannie/';
$FANNIE_URL = '${rooturl}/';
$FANNIE_SERVER = '127.0.0.1';
$FANNIE_SERVER_DBMS = 'MYSQLI';
$FANNIE_SERVER_USER = '${mysql_username}';
$FANNIE_SERVER_PW = '${mysql_password}';
$FANNIE_OP_DB = '${mysql_name_prefix}core_op';
$FANNIE_TRANS_DB = '${mysql_name_prefix}core_trans';
$FANNIE_ARCHIVE_DB = '${mysql_name_prefix}trans_archive';
?>

View file

@ -1,148 +0,0 @@
#!/bin/sh
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
#
# Generic 'init' script for all Rattail daemons. Originally based on the
# skeleton file from a Debian 6 system, but tweaked plenty since...
#
# NOTE: The "calling" script *must* define $NAME and $DESC (at least).
#
################################################################################
# Files in /etc/default and/or the "calling" init script may define any of the
# following as necessary, to override:
SCRIPTNAME=${SCRIPTNAME:-"/etc/init.d/$NAME"}
PIDFILE=${PIDFILE:-"/var/run/rattail/$NAME.pid"}
PYTHON=${PYTHON:-"/usr/bin/python"}
RATTAIL=${RATTAIL:-"/usr/local/bin/rattail"}
RATTAIL_ARGS=${RATTAIL_ARGS:-""}
RATTAIL_SUBCMD=${RATTAIL_SUBCMD:-"${NAME##*-}"}
RATTAIL_SUBCMD_ARGS=${RATTAIL_SUBCMD_ARGS:-""}
USER=${USER:-"rattail"}
GROUP=${GROUP:-"rattail"}
# Read configuration variable files if present.
[ -r /etc/default/rattail ] && . /etc/default/rattail
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# Load the VERBOSE setting and other rcS variables.
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
create_pid_dir() {
PIDDIR=`dirname "$PIDFILE"`
if [ ! -d "$PIDDIR" ]; then
mkdir --parents "$PIDDIR"
fi
chown $USER:$GROUP "$PIDDIR"
}
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
create_pid_dir
start-stop-daemon --start --pidfile "$PIDFILE" --exec "$PYTHON" --user $USER --test --quiet >/dev/null \
|| return 1
start-stop-daemon --start --pidfile "$PIDFILE" --exec "$PYTHON" --startas "$RATTAIL" --chuid $USER --group $GROUP --quiet -- \
$RATTAIL_ARGS $RATTAIL_SUBCMD --pidfile="$PIDFILE" $RATTAIL_SUBCMD_ARGS start \
|| return 2
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
/usr/local/bin/check-rattail-daemon "$NAME" >/dev/null \
|| return 1
sudo -u $USER "$RATTAIL" $RATTAIL_ARGS $RATTAIL_SUBCMD --pidfile="$PIDFILE" $RATTAIL_SUBCMD_ARGS stop >/dev/null 2>&1 \
|| return 2
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
/usr/local/bin/check-rattail-daemon "$NAME" && exit 0 || exit $?
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
:

View file

@ -1,142 +0,0 @@
#!/bin/sh
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
# This script is mostly based on the ``/etc/init.d/skeleton`` file from a
# Debian 6 system.
DESC=${DESC:-"Rattail Data Synchronizer"}
NAME=${NAME:-"rattail-datasync"}
SCRIPTNAME=${SCRIPTNAME:-"/etc/init.d/$NAME"}
PIDFILE=${PIDFILE:-"/var/run/rattail/$NAME.pid"}
PYTHON=${PYTHON:-"/usr/bin/python"}
RATTAIL=${RATTAIL:-"/usr/local/bin/rattail"}
RATTAIL_ARGS=${RATTAIL_ARGS:-""}
DATASYNC_ARGS=${DATASYNC_ARGS:-"--pidfile=$PIDFILE"}
USER=${USER:-"rattail"}
GROUP=${GROUP:-"rattail"}
# Read configuration variable files if present.
[ -r /etc/default/rattail ] && . /etc/default/rattail
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# Load the VERBOSE setting and other rcS variables.
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
create_pid_dir() {
PIDDIR=`dirname "$PIDFILE"`
if [ ! -d "$PIDDIR" ]; then
mkdir --parents "$PIDDIR"
fi
chown $USER:$GROUP "$PIDDIR"
}
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
create_pid_dir
start-stop-daemon --start --pidfile $PIDFILE --exec $PYTHON --user $USER --test --quiet > /dev/null \
|| return 1
start-stop-daemon --start --pidfile $PIDFILE --exec $PYTHON --startas $RATTAIL --chuid $USER --group $GROUP --quiet -- \
$RATTAIL_ARGS datasync $DATASYNC_ARGS start \
|| return 2
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
sudo -u $USER $RATTAIL $RATTAIL_ARGS datasync $DATASYNC_ARGS stop > /dev/null 2>&1
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
:

View file

@ -1,142 +0,0 @@
#!/bin/sh
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
# This script is mostly based on the ``/etc/init.d/skeleton`` file from a
# Debian 6 system.
DESC=${DESC:-"Rattail File Monitor"}
NAME=${NAME:-"rattail-filemon"}
SCRIPTNAME=${SCRIPTNAME:-"/etc/init.d/$NAME"}
PIDFILE=${PIDFILE:-"/var/run/rattail/$NAME.pid"}
PYTHON=${PYTHON:-"/usr/bin/python"}
RATTAIL=${RATTAIL:-"/usr/local/bin/rattail"}
RATTAIL_ARGS=${RATTAIL_ARGS:-""}
FILEMON_ARGS=${FILEMON_ARGS:-"--pidfile=$PIDFILE"}
USER=${USER:-"rattail"}
GROUP=${GROUP:-"rattail"}
# Read configuration variable files if present.
[ -r /etc/default/rattail ] && . /etc/default/rattail
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# Load the VERBOSE setting and other rcS variables.
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
create_pid_dir() {
PIDDIR=`dirname "$PIDFILE"`
if [ ! -d "$PIDDIR" ]; then
mkdir --parents "$PIDDIR"
fi
chown $USER:$GROUP "$PIDDIR"
}
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
create_pid_dir
start-stop-daemon --start --pidfile $PIDFILE --exec $PYTHON --user $USER --test --quiet > /dev/null \
|| return 1
start-stop-daemon --start --pidfile $PIDFILE --exec $PYTHON --startas $RATTAIL --chuid $USER --group $GROUP --quiet -- \
$RATTAIL_ARGS filemon $FILEMON_ARGS start \
|| return 2
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
sudo -u $USER $RATTAIL $RATTAIL_ARGS filemon $FILEMON_ARGS stop > /dev/null 2>&1
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
:

View file

@ -0,0 +1,14 @@
#!/bin/sh -e
cd ${envroot}
export RATTAIL_CONFIG_FILES=${overnight_conf}
# nb. avoid rich-formatted traceback here since it's so "noisy"
export _TYPER_STANDARD_TRACEBACK=1
bin/rattail --no-versioning overnight -k ${automation.lower()} <%text>\</%text>
% if email_key is not Undefined and email_key:
--email-key '${email_key}' <%text>\</%text>
% endif
--no-email-if-empty <%text>\</%text>
--wait

View file

@ -0,0 +1,8 @@
## -*- mode: conf; -*-
% if mailto:
MAILTO="${','.join(mailto)}"
% endif
# rotate logs and restart Luigi at *just before* 12:00am midnight
55 23 * * * root ${appdir}/rotate-luigi-logs.sh

View file

@ -0,0 +1,39 @@
## -*- mode: conf; -*-
<%text>############################################################</%text>
#
# Luigi logging config
#
<%text>############################################################</%text>
[loggers]
keys = root
[handlers]
keys = file, console
[formatters]
keys = generic, console
[logger_root]
handlers = file, console
level = DEBUG
[handler_file]
class = handlers.WatchedFileHandler
args = ('${appdir}/luigi/log/luigi.log', 'a', 'utf_8')
formatter = generic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
formatter = console
level = WARNING
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
datefmt = %Y-%m-%d %H:%M:%S
[formatter_console]
format = %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s

View file

@ -0,0 +1,24 @@
## -*- mode: conf; -*-
${appdir}/luigi/log/luigi.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 600 rattail rattail
}
${appdir}/luigi/log/luigi-server.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 600 rattail rattail
postrotate
supervisorctl restart luigi:luigid > /dev/null
endscript
}

View file

@ -0,0 +1,52 @@
## -*- mode: conf; -*-
<%text>############################################################</%text>
#
# Luigi config
#
# cf. https://luigi.readthedocs.io/en/stable/configuration.html
#
<%text>############################################################</%text>
[core]
logging_conf_file = ${appdir}/luigi/logging.conf
% if LUIGI2:
# Number of seconds to wait before timing out when making an API call. Defaults
# to 10.0
# (sometimes things can lag for us and we simply need to give it more time)
rpc_connect_timeout = 60
# The maximum number of retries to connect the central scheduler before giving
# up. Defaults to 3
# (occasional network issues seem to cause us to need more/longer retries)
rpc_retry_attempts = 10
# Number of seconds to wait before the next attempt will be started to connect
# to the central scheduler between two retry attempts. Defaults to 30
# (occasional network issues seem to cause us to need more/longer retries)
rpc_retry_wait = 60
% endif
[retcode]
# cf. https://luigi.readthedocs.io/en/stable/configuration.html#retcode-config
# The following return codes are the recommended exit codes for Luigi
# They are in increasing level of severity (for most applications)
already_running=10
missing_data=20
not_run=25
task_failed=30
scheduling_error=35
unhandled_exception=40
[scheduler]
state_path = ${appdir}/luigi/state.pickle
% if db_connection:
record_task_history = true
[task_history]
db_connection = ${db_connection}
% endif

View file

@ -0,0 +1,28 @@
#!/bin/bash
<%text>############################################################</%text>
#
# overnight automation for '${automation}'
#
<%text>############################################################</%text>
set -e
DATE=$1
if [ "$1" = "--verbose" ]; then
DATE=$2
VERBOSE='--verbose'
else
VERBOSE=
fi
if [ "$DATE" = "" ]; then
DATE=`date --date='yesterday' +%Y-%m-%d`
fi
LUIGI='${envroot}/bin/luigi --logging-conf-file logging.conf'
export PYTHONPATH=${appdir}
cd ${appdir}/luigi
$LUIGI --module luigitasks.overnight_${automation.lower()} Overnight${automation} --date $DATE

View file

@ -0,0 +1,14 @@
#!/bin/sh -e
cd ${envroot}
export RATTAIL_CONFIG_FILES=${overnight_conf}
# nb. avoid rich-formatted traceback here since it's so "noisy"
export _TYPER_STANDARD_TRACEBACK=1
bin/rattail --no-versioning overnight -k ${automation.lower()} <%text>\</%text>
% if email_key is not Undefined and email_key:
--email-key '${email_key}' <%text>\</%text>
% endif
--email-if-empty <%text>\</%text>
--no-wait

View file

@ -0,0 +1,14 @@
#!/bin/sh -e
<%text>######################################################################</%text>
#
# rotate Luigi server log file
#
<%text>######################################################################</%text>
if [ "$1" = "--verbose" ]; then
VERBOSE='--verbose'
else
VERBOSE=
fi
/usr/sbin/logrotate $VERBOSE ${appdir}/luigi/logrotate.conf

View file

@ -0,0 +1,11 @@
## -*- mode: conf; -*-
[group:luigi]
programs=luigid
[program:luigid]
command=${envroot}/bin/luigid --logdir ${appdir}/luigi/log --state-path ${appdir}/luigi/state.pickle --address 127.0.0.1
directory=${appdir}/work
environment=LUIGI_CONFIG_PATH="${appdir}/luigi/luigi.cfg"
user=${user}
autostart=${'true' if autostart else 'false'}

View file

@ -1,127 +0,0 @@
#!/bin/sh
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
#
# Generic 'init' script for Luigi central scheduler (luigid)
#
################################################################################
# "calling" init script may define any of the following as necessary:
NAME=${NAME:-"rattail-luigid"}
SCRIPTNAME=${SCRIPTNAME:-"/etc/init.d/$NAME"}
DESC=${DESC:-"Luigi Scheduler for Rattail"}
DAEMON=${DAEMON:-"/usr/local/bin/luigid"}
PYTHON=${PYTHON:-"/usr/local/bin/python"}
USER=${USER:-"rattail"}
GROUP=${GROUP:-"rattail"}
PIDFILE=${PIDFILE:-"/var/run/rattail/$NAME.pid"}
LOGDIR=${LOGDIR:-"/var/log/rattail/$NAME"}
STATEFILE=${STATEFILE:-"/var/run/rattail/$NAME.pickle"}
ADDRESS=${ADDRESS:-"0.0.0.0"}
create_pid_dir() {
PIDDIR=`dirname "$PIDFILE"`
if [ ! -d "$PIDDIR" ]; then
mkdir --parents "$PIDDIR"
fi
chown $USER:$GROUP "$PIDDIR"
}
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
do_start() {
create_pid_dir
start-stop-daemon --test --start --exec $DAEMON --user $USER --pidfile $PIDFILE --quiet > /dev/null \
|| return 1
start-stop-daemon --start --chuid $USER --exec $DAEMON --pidfile $PIDFILE --background --quiet -- \
--background --pidfile $PIDFILE --logdir $LOGDIR --state-path $STATEFILE --address $ADDRESS \
|| return 2
}
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
do_stop()
{
start-stop-daemon --test --stop --exec $PYTHON --user $USER --pidfile $PIDFILE --quiet > /dev/null \
|| return 1
start-stop-daemon --stop --exec $PYTHON --user $USER --pidfile $PIDFILE --quiet > /dev/null \
|| return 2
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
/usr/local/bin/check-rattail-daemon "$NAME" && exit 0 || exit $?
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
:

View file

@ -0,0 +1,33 @@
#!${envroot}/bin/python
import os
import sys
import argparse
import subprocess
def check_datasync_queue(timeout):
os.chdir(sys.prefix)
retcode = subprocess.call([
'bin/rattail',
'-c', 'app/${config}.conf',
'--no-versioning',
'datasync',
'--timeout', timeout,
'check',
])
if retcode == 1:
sys.exit(2)
elif retcode:
print("unknown issue")
sys.exit(3)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('timeout')
args = parser.parse_args()
check_datasync_queue(args.timeout)

View file

@ -0,0 +1,34 @@
#!${envroot}/bin/python
import os
import sys
import argparse
import subprocess
def check_datasync_queue(timeout):
os.chdir(sys.prefix)
retcode = subprocess.call([
'bin/rattail',
'-c', 'app/datasync.conf',
'-c', 'app/${config}.conf',
'--no-versioning',
'datasync',
'--timeout', timeout,
'check-watchers',
])
if retcode == 1:
sys.exit(2)
elif retcode:
print("unknown issue")
sys.exit(3)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('timeout')
args = parser.parse_args()
check_datasync_queue(args.timeout)

View file

@ -19,9 +19,8 @@ configure_logging = true
[rattail.mail]
smtp.server = localhost
templates = rattail:templates/mail
default.from = root
default.to = root
default.from = ${getattr(env, 'email_default_sender', 'rattail@localhost')}
default.to = ${', '.join(getattr(env, 'email_default_recipients', ['root@localhost']))}
${'#'}#############################
@ -88,7 +87,7 @@ formatter = console
[handler_email]
class = handlers.SMTPHandler
args = ('localhost', 'root', ['root'], "[Rattail] Logging")
args = ('localhost', '${getattr(env, 'email_default_sender', 'rattail@localhost')}', ${getattr(env, 'email_default_recipients', ['root@localhost'])}, "[Rattail] Logging")
formatter = generic
level = ERROR

61
rattail_fabric2/docker.py Normal file
View file

@ -0,0 +1,61 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Fabric library for Docker
"""
from rattail_fabric2 import apt, exists, mkdir
def setup_repository(c, flavor='debian'):
"""
Setup the APT repository for Docker
https://docs.docker.com/engine/install/debian/#install-using-the-repository
https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
"""
apt.install(c, 'ca-certificates',
'curl',
'gnupg',
'lsb-release')
mkdir(c, '/etc/apt/keyrings', use_sudo=True)
if not exists(c, '/etc/apt/keyrings/docker.gpg'):
c.sudo("bash -c 'curl -fsSL https://download.docker.com/linux/{}/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg'".format(flavor))
c.sudo("""bash -c 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/{} $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null'""".format(flavor))
apt.update(c)
def install_engine(c):
"""
Install the Docker engine
https://docs.docker.com/engine/install/debian/#install-docker-engine
"""
apt.install(c, 'docker-ce',
'docker-ce-cli',
'containerd.io',
'docker-compose-plugin')

View file

@ -0,0 +1,38 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2020 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Fabric library for ejabberd XMPP server
"""
import re
def get_node_name(c):
"""
Returns the ejabberd node name for the current server.
"""
result = c.sudo('ejabberdctl status')
match = re.match(r'^The node (\S+@\S+) is started with status: started',
result.stdout)
if match:
return match.group(1)

View file

@ -32,7 +32,7 @@ def install_from_source(c, user='rattail', branch=None):
Install the FreeTDS library from source.
Per instructions found here:
https://github.com/FreeTDS/freetds/blob/master/INSTALL.GIT
https://github.com/FreeTDS/freetds/blob/master/INSTALL.md
"""
apt.install(
c,

175
rattail_fabric2/luigi.py Normal file
View file

@ -0,0 +1,175 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Fabric library for Luigi apps
"""
import os
import re
from pkg_resources import parse_version
from rattail_fabric2 import postgresql, make_deploy, mkdir
deploy_common = make_deploy(__file__)
def install_luigi(c, envroot, luigi='luigi', user='rattail',
db=False, dbuser='rattail', dbpass='TODO_PASSWORD',
db_connection=None,
supervisor=False, autostart=False,
crontab=False, crontab_mailto=None):
"""
Install and configure Luigi to the given virtualenv.
"""
envroot = envroot.rstrip('/')
envname = os.path.basename(envroot)
appdir = '{}/app'.format(envroot)
# package
c.sudo("""bash -lc "workon {} && pip install '{}'" """.format(envname, luigi),
user=user)
# detect luigi version
LUIGI2 = False
output = c.sudo("bash -lc 'workon {} && pip show luigi | grep Version'".format(envname), user=user).stdout.strip()
match = re.match(r'^Version: (\d+\S+)$', output)
if match:
if parse_version(match.group(1)) < parse_version('3'):
LUIGI2 = True
# dirs
mkdir(c, ['{}/luigi'.format(appdir),
'{}/luigi/log'.format(appdir),
'{}/luigitasks'.format(appdir),
], use_sudo=True, owner=user)
# tasks
c.sudo('touch {}/luigitasks/__init__.py'.format(appdir),
user=user)
# database
if db:
postgresql.create_db(c, 'luigi', owner=dbuser)
if not db_connection:
db_connection = 'postgresql://{}:{}@localhost/luigi'.format(
dbuser, dbpass)
# config
deploy_common(c, 'luigi/luigi.cfg.mako', '{}/luigi/luigi.cfg'.format(appdir),
use_sudo=True, owner=user, mode='0640',
context={'appdir': appdir,
'LUIGI2': LUIGI2,
'db_connection': db_connection})
deploy_common(c, 'luigi/logging.conf.mako', '{}/luigi/logging.conf'.format(appdir),
use_sudo=True, owner=user,
context={'appdir': appdir})
# supervisor
if supervisor:
c.sudo('supervisorctl stop luigi:', warn=True)
deploy_common(c, 'luigi/supervisor.conf.mako',
'/etc/supervisor/conf.d/luigi.conf',
use_sudo=True,
context={'envroot': envroot,
'appdir': appdir,
'user': user,
'autostart': autostart})
c.sudo('supervisorctl update')
if autostart:
c.sudo('supervisorctl start luigi:')
# logrotate
deploy_common(c, 'luigi/luigi-logrotate.conf.mako', '{}/luigi/logrotate.conf'.format(appdir),
use_sudo=True, owner='root:', # must be owned by root (TODO: why is that again?)
context={'appdir': appdir})
deploy_common(c, 'luigi/rotate-logs.sh.mako', '{}/rotate-luigi-logs.sh'.format(appdir),
use_sudo=True, owner=user,
context={'appdir': appdir})
if crontab:
deploy_common(c, 'luigi/crontab.mako', '/etc/cron.d/luigi',
use_sudo=True, context={'appdir': appdir,
'mailto': crontab_mailto})
def install_overnight_script(c, envroot, user='rattail', automation='All',
email_key=None,
luigi=False,
cron=True, cron_conf='app/cron.conf',
restart=True, restart_conf='app/silent.conf'):
"""
Install an overnight automation script
"""
envroot = envroot.rstrip('/')
appdir = '{}/app'.format(envroot)
# overnight-*.sh
if luigi:
filename = 'overnight-{}.sh'.format(automation.lower())
deploy_common(c, 'luigi/overnight.sh.mako',
'{}/{}'.format(appdir, filename),
use_sudo=True, owner=user, mode='0755',
context={'envroot': envroot, 'appdir': appdir,
'automation': automation})
# cron-overnight-*.sh
if cron:
filename = 'cron-overnight-{}.sh'.format(automation.lower())
deploy_common(c, 'luigi/cron-overnight.sh.mako',
'{}/{}'.format(appdir, filename),
use_sudo=True, owner=user, mode='0755',
context={'envroot': envroot,
'overnight_conf': cron_conf,
'automation': automation,
'email_key': email_key})
# restart-overnight-*.sh
if restart:
filename = 'restart-overnight-{}.sh'.format(automation.lower())
deploy_common(c, 'luigi/restart-overnight.sh.mako',
'{}/{}'.format(appdir, filename),
use_sudo=True, owner=user, mode='0755',
context={'envroot': envroot,
'appdir': appdir,
'overnight_conf': restart_conf,
'automation': automation,
'email_key': email_key})
def register_with_supervisor(c, envroot, user='rattail', autostart=False):
"""
Register the Luigi scheduler daemon with supervisor
"""
envroot = envroot.rstrip('/')
appdir = '{}/app'.format(envroot)
deploy_common(c, 'luigi/supervisor.conf.mako',
'/etc/supervisor/conf.d/luigi.conf',
use_sudo=True,
context={'envroot': envroot,
'appdir': appdir,
'user': user,
'autostart': autostart})
c.sudo('supervisorctl update')
if autostart:
c.sudo('supervisorctl start luigi:')

View file

@ -0,0 +1,38 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2022 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Fabric Library for MariaDB
"""
import re
def get_version_string(c):
"""
Fetch the version of MariaDB running on the target system
"""
result = c.sudo('mysql --version')
if not result.failed:
match = re.match(r'^mysql .*?(\d+\.\d+\.\d+)-MariaDB', result.stdout)
if match:
return match.group(1)

43
rattail_fabric2/mssql.py Normal file
View file

@ -0,0 +1,43 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2020 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Fabric Library for MS SQL Server
"""
from rattail_fabric2 import apt
def install_mssql_odbc(c, accept_eula=None):
"""
Install the MS SQL Server ODBC driver
https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017
"""
apt.install(c, 'apt-transport-https', 'curl')
c.sudo("bash -c 'curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -'")
c.sudo("bash -c 'curl https://packages.microsoft.com/config/debian/9/prod.list > /etc/apt/sources.list.d/mssql-release.list'")
apt.update(c)
cmd = 'apt-get --assume-yes install msodbcsql17'
if accept_eula:
cmd = 'ACCEPT_EULA=Y {}'.format(cmd)
c.sudo(cmd)

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2020 Lance Edgar
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@ -24,6 +24,8 @@
Fabric Library for MySQL
"""
import re
from rattail_fabric2 import apt, make_deploy, sed
@ -38,6 +40,18 @@ def install(c):
apt.install(c, 'default-mysql-server')
def get_version_string(c):
"""
Fetch the version of MySQL running on the target system
"""
result = c.sudo('mysql --version', warn=True)
if not result.failed:
# match = re.match(r'^mysql .*?(\d+\.\d+\.\d+)-MariaDB', result.stdout)
match = re.match(r'^mysql +Ver +(\d+\.\d+\.\d+)-.*', result.stdout)
if match:
return match.group(1)
def set_bind_address(c, address):
"""
Configure the 'bind-address' setting with the given value.
@ -70,8 +84,15 @@ def create_user(c, name, host='localhost', password=None, checkfirst=True):
if not checkfirst or not user_exists(c, name, host):
sql(c, "CREATE USER '{}'@'{}';".format(name, host))
if password:
sql(c, "SET PASSWORD FOR '{}'@'{}' = PASSWORD('{}');".format(
name, host, password), hide=True, echo=False)
# supposedly this is the new way to do it
result = sql(c, "ALTER USER '{}'@'{}' IDENTIFIED BY '{}';".format(
name, host, password),
echo=False, hide=True, warn=True)
if result.failed: # but this may fail for older systems
# in which case we try it the old way
sql(c, "SET PASSWORD FOR '{}'@'{}' = PASSWORD('{}');".format(
name, host, password),
echo=False, hide=True)
def db_exists(c, name):
@ -142,18 +163,31 @@ def script(c, path, database=''):
c.sudo("bash -c 'mysql {} < {}'".format(database, path))
def download_db(c, name, destination=None):
def dump_db(c, name, skip_triggers=False):
"""
Dump a database to file, on the server represented by ``c`` param.
This function returns the name of the DB dump file. The name will
not have a path component as it's assumed to be in the home folder
of the connection user.
"""
skip_triggers = '--skip-triggers' if skip_triggers else ''
# note, we force sudo "as root" to ensure -H flag is used
# (which allows us to leverage /root/.my.cnf config file)
c.sudo(f'mysqldump {skip_triggers} --result-file={name}.sql {name}',
user='root')
c.sudo(f'gzip --force {name}.sql')
return f'{name}.sql.gz'
def download_db(c, name, destination=None, **kwargs):
"""
Download a database from the "current" server.
"""
if destination is None:
destination = './{}.sql.gz'.format(name)
# note, we force sudo "as root" to ensure -H flag is used
# (which allows us to leverage /root/.my.cnf config file)
c.sudo('mysqldump --result-file={0}.sql {0}'.format(name), user='root')
c.sudo('gzip --force {}.sql'.format(name))
c.get('{}.sql.gz'.format(name), destination)
c.sudo('rm {}.sql.gz'.format(name))
filename = dump_db(c, name,
skip_triggers=kwargs.get('skip_triggers', False))
c.get(filename, destination or f'./{filename}')
c.sudo(f'rm {filename}')
def clone_db(c, name, download, user=None, force=False):
@ -184,3 +218,20 @@ def clone_db(c, name, download, user=None, force=False):
c.run('gunzip --force {}.sql.gz'.format(name))
c.sudo("bash -c 'mysql {0} < {0}.sql'".format(name))
c.run('rm {}.sql'.format(name))
def restore_db(c, name, path):
"""
Restore a database from a dump file.
:param name: Name of the database to restore.
:param path: Path to the DB dump file, which should end in ``.sql.gz``
"""
if not path.endswith('.sql.gz'):
raise ValueError("Path to dump file must end in `.sql.gz`")
c.sudo(f'gunzip --force {path}')
sql_path = path[:-3]
c.sudo(f"bash -c 'mysql {name} < {sql_path}'")
c.sudo(f'rm {sql_path}')

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2019 Lance Edgar
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@ -46,6 +46,9 @@ def install(c, version=None, user=None):
profile = os.path.join(home, '.profile')
kwargs = {'use_sudo': bool(user)}
if kwargs['use_sudo']:
c.sudo(f'touch {profile}')
c.sudo(f'chown {user}: {profile}')
append(c, profile, 'export NVM_DIR="{}"'.format(nvm), **kwargs)
append(c, profile, '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"', **kwargs)
append(c, profile, '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"', **kwargs)

View file

@ -38,6 +38,6 @@ def install_pod(c, path='/srv/pod', download_url=None):
if not exists(c, os.path.join(path, 'pod_pictures_gtin.zip')):
if not download_url:
download_url = 'http://www.product-open-data.com/docs/pod_pictures_gtin_2013.08.29_01.zip'
c.sudo("bash -c 'cd {}; wget --output-document=pod_pictures_gtin.zip {}'".format(path, url))
c.sudo("bash -c 'cd {}; wget --output-document=pod_pictures_gtin.zip {}'".format(path, download_url))
if not exists(c, os.path.join(path, 'pictures/gtin')):
c.sudo("bash -c 'cd {}; unzip pod_pictures_gtin.zip -d pictures'".format(path))

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2019 Lance Edgar
# Copyright © 2010-2022 Lance Edgar
#
# This file is part of Rattail.
#
@ -25,6 +25,7 @@ Fabric library for Postfix
"""
from rattail_fabric2 import apt
from rattail.util import shlex_join
def install(c):
@ -42,10 +43,17 @@ def alias(c, name, alias_to, path='/etc/aliases'):
# does alias entry already exist?
if c.run("grep '^{}:' /etc/aliases".format(name), warn=True).failed:
# append new entry
c.sudo("""bash -c 'echo "{}: {}" >> /etc/aliases'""".format(name, alias_to))
entry = '{}: {}'.format(name, alias_to)
echo = shlex_join(['echo', entry])
cmd = '{} >> /etc/aliases'.format(echo)
cmd = shlex_join(['bash', '-c', cmd])
c.sudo(cmd)
else:
# update existing entry
c.sudo('sed -i.bak -e "s/^{}: .*/{}: {}/" /etc/aliases'.format(name, name, alias_to))
alias_to = alias_to.replace('|', '\\|')
sub = "s|^{}: .*|{}: {}|".format(name, name, alias_to)
cmd = shlex_join(['sed', '-i.bak', '-E', sub, '/etc/aliases'])
c.sudo(cmd)
c.sudo('newaliases')

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2019 Lance Edgar
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@ -27,14 +27,14 @@ Fabric Library for PostgreSQL
import os
import re
from rattail_fabric2 import apt
from rattail_fabric2 import apt, append, contains, sed, uncomment
def install(c):
"""
Install the PostgreSQL database service
"""
apt.install(c, 'postgresql')
apt.install(c, 'postgresql', 'libpq-dev')
def get_version(c):
@ -47,6 +47,48 @@ def get_version(c):
if match:
return float(match.group(1))
def set_listen_addresses(c, *addresses):
"""
Overwrite the `listen_addresses` config setting in `postgresql.conf`.
"""
version = get_version(c)
if version > 12:
version = int(version)
addresses = ','.join(addresses)
if not contains(c, '/etc/postgresql/{}/main/postgresql.conf'.format(version),
"listen_addresses = '{}'".format(addresses),
exact=True):
uncomment(c, '/etc/postgresql/{}/main/postgresql.conf'.format(version),
r'^# *listen_addresses\s*=.*',
use_sudo=True)
sed(c, '/etc/postgresql/{}/main/postgresql.conf'.format(version),
r'listen_addresses\s*=.*',
"listen_addresses = '{}'".format(addresses),
use_sudo=True)
restart(c)
def add_hba_entry(c, entry):
"""
Add an entry to the `pg_hba.conf` file.
"""
version = get_version(c)
if version > 12:
version = int(version)
if not contains(c, '/etc/postgresql/{}/main/pg_hba.conf'.format(version),
entry, use_sudo=True):
append(c, '/etc/postgresql/{}/main/pg_hba.conf'.format(version),
entry, use_sudo=True)
reload_(c)
def restart(c):
"""
Restart the PostgreSQL database service
@ -54,12 +96,15 @@ def restart(c):
c.sudo('systemctl restart postgresql.service')
def reload(c):
def reload_(c):
"""
Reload config for the PostgreSQL database service
"""
c.sudo('systemctl reload postgresql.service')
# TODO: deprecate / remove this
reload = reload_
def sql(c, sql, database='', port=None, **kwargs):
"""
@ -71,6 +116,23 @@ def sql(c, sql, database='', port=None, **kwargs):
return c.sudo(cmd, user='postgres', **kwargs)
def script(c, path, database='', port=None, user=None, password=None):
"""
Execute a SQL script. By default this will run as 'postgres' user, but can
use PGPASSWORD authentication if necessary.
"""
port = '--port={}'.format(port) if port else ''
if user and password:
kw = dict(pw=password, user=user, port=port, path=path, db=database)
return c.sudo(" PGPASSWORD='{pw}' psql --host=localhost {port} --username='{user}' --file='{path}' {db}".format(**kw),
echo=False)
else: # run as postgres
kw = dict(port=port, path=path, db=database)
return c.sudo("psql {port} --file='{path}' {db}".format(**kw),
user='postgres')
def user_exists(c, name, port=None):
"""
Determine if a given PostgreSQL user exists.
@ -137,22 +199,52 @@ def drop_db(c, name, checkfirst=True):
c.sudo('dropdb {}'.format(name), user='postgres')
def download_db(c, name, destination=None, port=None, exclude_tables=None):
def dump_db(c, name, port=None, exclude_tables=None,
skip_raw_file=False):
"""
Dump a database to file, on the server represented by ``c`` param.
This function returns the name of the DB dump file. The name will not have
a path component as it's assumed to be in the home folder of the connection
user.
"""
c.run('touch {}.sql'.format(name))
c.run('chmod 0666 {}.sql'.format(name))
sql_name = f'{name}.sql'
gz_name = f'{sql_name}.gz'
filename = gz_name if skip_raw_file else sql_name
port = f'--port={port}' if port else ''
exclude_tables = f'--exclude-table-data={exclude_tables}' if exclude_tables else ''
filename = '' if skip_raw_file else f'--file={filename}'
cmd = f'pg_dump {port} {exclude_tables} {filename} {name}'
if skip_raw_file:
tmp_name = f'/tmp/{gz_name}'
cmd = f'{cmd} | gzip -c > {tmp_name}'
c.sudo(cmd, user='postgres')
# TODO: should remove this file
c.run(f"cp {tmp_name} {gz_name}")
else:
c.sudo(cmd, user='postgres')
c.run(f'gzip --force {sql_name}')
return gz_name
def download_db(c, name, destination=None, port=None, exclude_tables=None,
skip_raw_file=False):
"""
Download a database from the server represented by ``c`` param.
"""
if destination is None:
destination = './{}.sql.gz'.format(name)
c.run('touch {}.sql'.format(name))
c.run('chmod 0666 {}.sql'.format(name))
cmd = 'pg_dump {port} {exclude_tables} --file={name}.sql {name}'.format(
name=name,
port='--port={}'.format(port) if port else '',
exclude_tables='--exclude-table-data={}'.format(exclude_tables) if exclude_tables else '')
c.sudo(cmd, user='postgres')
c.run('gzip --force {}.sql'.format(name))
c.get('{}.sql.gz'.format(name), destination)
c.run('rm {}.sql.gz'.format(name))
dumpfile = dump_db(c, name, port=port, exclude_tables=exclude_tables,
skip_raw_file=skip_raw_file)
c.get(dumpfile, destination)
c.run('rm {}'.format(dumpfile))
def clone_db(c, name, owner, download, user='rattail', force=False, workdir=None):
@ -185,6 +277,27 @@ def clone_db(c, name, owner, download, user='rattail', force=False, workdir=None
os.chdir(curdir)
# restore database on target server
c.run('gunzip --force {}.sql.gz'.format(name))
c.sudo('psql --echo-errors --file={0}.sql {0}'.format(name), user='postgres')
c.run('rm {}.sql'.format(name))
# TODO: first tried c.sudo('mv ...') but that did not work for the "typical"
# scenario of connecting as rattail@server to obtain db dump, since the dump
# cmd is normally carved out via sudoers config, but 'sudo mv ..' is not
filename = f'{name}.sql.gz'
c.run(f'mv {filename} /tmp/')
restore_db(c, name, f'/tmp/{filename}')
def restore_db(c, name, path):
"""
Restore a database from a dump file.
:param name: Name of the database to restore.
:param path: Path to the DB dump file, which should end in ``.sql.gz``
"""
if not path.endswith('.sql.gz'):
raise ValueError("Path to dump file must end in `.sql.gz`")
c.run('gunzip --force {}'.format(path))
sql_path = path[:-3]
c.sudo('psql --echo-errors --file={} {}'.format(sql_path, name),
user='postgres')
c.run('rm {}'.format(sql_path))

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2020 Lance Edgar
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@ -26,31 +26,44 @@ Fabric Library for Python
from contextlib import contextmanager
from rattail_fabric2 import apt, exists, make_deploy, mkdir
from rattail_fabric2 import apt, append, exists, make_deploy, mkdir
deploy = make_deploy(__file__)
deploy_common = make_deploy(__file__)
def bootstrap_python(c, pip_from_apt=True, pip_eager=True,
def bootstrap_python(c, deploy=None,
pip_from_apt=True,
pip_eager=True,
pip_method=None,
pip_auto=False,
pip_package_name=None,
virtualenvwrapper_from_apt=False,
upgrade_virtualenvwrapper=True,
workon_home='/srv/envs', user='rattail'):
workon_home='/srv/envs',
user='rattail',
python3=False):
"""
Bootstrap a "complete" Python install.
"""
# build dependencies
apt.install(
c,
'python-dev',
'python3-dev',
'python3-venv',
'libffi-dev',
'libjpeg-dev',
'libssl-dev',
)
if not python3:
apt.install(c, 'python-dev')
# pip
install_pip(c, use_apt=pip_from_apt, eager=pip_eager)
install_pip(c, method=pip_method, python3=python3,
auto=pip_auto,
use_apt=pip_from_apt,
apt_package_name=pip_package_name,
eager=pip_eager)
# virtualenvwrapper
workon_home = workon_home.rstrip('/')
@ -58,7 +71,8 @@ def bootstrap_python(c, pip_from_apt=True, pip_eager=True,
use_apt=virtualenvwrapper_from_apt,
upgrade=upgrade_virtualenvwrapper,
configure_me=False)
deploy(c, 'python/premkvirtualenv', '{}/premkvirtualenv'.format(workon_home), owner=user, use_sudo=True)
# TODO: let custom deploy override this
deploy_common(c, 'python/premkvirtualenv', '{}/premkvirtualenv'.format(workon_home), owner=user, use_sudo=True)
def install_pythonz(c):
@ -72,6 +86,7 @@ def install_pythonz(c):
'curl',
# these are needed when building python:
'libsqlite3-dev',
'libssl-dev',
'zlib1g-dev',
)
if not exists(c, '/usr/local/pythonz'):
@ -83,7 +98,23 @@ def install_pythonz(c):
c.sudo('/usr/local/src/pythonz/pythonz-install')
def install_python(c, version, globally=False, verbose=False):
def configure_pythonz(c, user):
"""
Configure the given user's ``.bashrc`` to allow use of pythonz.
"""
# TODO: should normalize this get_home_dir() logic?
home = c.run('getent passwd {} | cut -d: -f6'.format(user)).stdout.strip()
home = home.rstrip('/')
script = '{}/.bashrc'.format(home)
append(c, script, 'export PYTHONZ_ROOT=/usr/local/pythonz',
use_sudo=True)
append(c, script, '[[ -s /usr/local/pythonz/etc/bashrc ]] && source /usr/local/pythonz/etc/bashrc',
use_sudo=True)
def install_python(c, version, globally=False, verbose=False,
use_clang=False):
"""
Install a specific version of python, via pythonz.
@ -92,23 +123,67 @@ def install_python(c, version, globally=False, verbose=False):
symlink, if installed, will use the "short" version, e.g. if the
``version`` specified is ``'3.5.3'`` then the symlink will be named
``'python3.5'``.
:param use_clang: Use `clang` instead of default compiler. May be
needed in rare cases for older python versions. See also
https://stackoverflow.com/a/73267352
"""
if use_clang:
apt.install(c, 'clang')
if not exists(c, '/usr/local/pythonz/pythons/CPython-{}'.format(version)):
clang = 'CC=clang' if use_clang else ''
verbose = '--verbose' if verbose else ''
c.sudo("bash -lc 'pythonz install {} {}'".format(verbose, version))
c.sudo(f"bash -lc '{clang} pythonz install {verbose} {version}'")
if globally:
short_version = '.'.join(version.split('.')[:2])
c.sudo('ln -sf /usr/local/pythonz/pythons/CPython-{0}/bin/python{1} /usr/local/bin/python{1}'.format(
version, short_version))
def install_pip(c, use_apt=False, eager=True):
def install_pip(c, method=None,
auto=False, python3=False,
use_apt=False, apt_package_name=None,
eager=True):
"""
Install/upgrade the Pip installer for Python.
"""
if use_apt:
apt.install(c, 'python-pip')
else:
# first check for existing pip; we do nothing if already present
pip_ = 'pip3' if python3 else 'pip2'
if not c.sudo('which {}'.format(pip_), warn=True).failed:
return
if method == 'apt':
package = apt_package_name
if not package:
package = 'python3-pip' if python3 else 'python-pip'
apt.install(c, package, warn=True)
elif method == 'get-pip':
url = 'https://bootstrap.pypa.io/get-pip.py'
if not python3:
url = 'https://bootstrap.pypa.io/pip/2.7/get-pip.py'
c.sudo('wget -O get-pip.py {}'.format(url))
python = 'python3' if python3 else 'python2'
c.sudo('{} get-pip.py'.format(python))
c.sudo('rm get-pip.py')
elif auto: # try apt first, then fall back to get-pip.py
package = apt_package_name
if not package:
package = 'python3-pip' if python3 else 'python-pip'
result = apt.install(c, package, warn=True)
if result.failed:
c.sudo('wget -O get-pip.py https://bootstrap.pypa.io/get-pip.py')
python = 'python3' if python3 else 'python2'
c.sudo('{} get-pip.py'.format(python))
c.sudo('rm get-pip.py')
elif use_apt: # use apt only, w/ no fallback
if not apt_package_name:
apt_package_name = 'python3-pip' if python3 else 'python-pip'
apt.install(c, apt_package_name)
else: # *deprecated* method using easy_install
apt.install(c, 'build-essential', 'python-dev', 'libssl-dev', 'libffi-dev')
if c.run('which pip', warn=True).failed:
apt.install(c, 'python-pkg-resources', 'python-setuptools')
@ -157,9 +232,12 @@ def install_virtualenvwrapper(c, workon_home='/srv/envs', user='root',
mkdir(c, workon_home, owner=user, use_sudo=True)
if use_apt:
apt.install(c, 'virtualenvwrapper')
configure_virtualenvwrapper(c, user, workon_home,
wrapper='/usr/share/virtualenvwrapper/virtualenvwrapper.sh')
else:
pip(c, 'virtualenvwrapper', upgrade=upgrade)
configure_virtualenvwrapper(c, user, workon_home)
configure_virtualenvwrapper(c, user, workon_home,
wrapper='/usr/local/bin/virtualenvwrapper.sh')
if configure_me:
# TODO
# configure_virtualenvwrapper(c, env.user, workon_home)
@ -167,6 +245,7 @@ def install_virtualenvwrapper(c, workon_home='/srv/envs', user='root',
def configure_virtualenvwrapper(c, user, workon_home='/srv/envs',
# TODO: this default should change, per apt
wrapper='/usr/local/bin/virtualenvwrapper.sh'):
"""
Configure virtualenvwrapper for the given user account.

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@ -28,49 +28,94 @@ from __future__ import unicode_literals, absolute_import
import os
from rattail_fabric2 import postgresql, make_deploy, make_system_user, mkdir
from rattail_fabric2 import apache, apt, postfix, postgresql, python, make_deploy, make_system_user, mkdir
deploy = make_deploy(__file__)
deploy_common = make_deploy(__file__)
def bootstrap_rattail(c, home='/var/lib/rattail', uid=None, shell='/bin/bash'):
def bootstrap_rattail_base(c, deploy=None, timezone='America/Chicago',
**context):
"""
Bootstrap the base system requirements, common to all machines
running Rattail apps. Note that this includes basic installation
of Python, PostgreSQL and Aapche.
"""
env = context.get('env')
context['timezone'] = timezone
# make system user, install init scripts etc.
bootstrap_rattail(c, alias='root')
# machine-wide config
if deploy and deploy.local_exists('rattail/rattail.conf.mako'):
deploy(c, 'rattail/rattail.conf.mako', '/etc/rattail/rattail.conf',
use_sudo=True, context=context)
else:
deploy_machine_conf(c, timezone=timezone, context=context)
# python
python.bootstrap_python(c, deploy,
python3=True,
virtualenvwrapper_from_apt=True)
# postgres
postgresql.install(c)
if env and hasattr(env, 'password_postgresql_rattail'):
postgresql.create_user(c, 'rattail', password=env.password_postgresql_rattail)
# apache
apache.install(c)
apache.enable_mod(c, 'proxy_http')
# supervisor
apt.install(c, 'supervisor')
def bootstrap_rattail(c, home='/var/lib/rattail', uid=None, shell='/bin/bash',
alias=None):
"""
Bootstrap a basic Rattail software environment.
"""
make_system_user(c, 'rattail', home=home, uid=uid, shell=shell)
mkdir(c, os.path.join(home, '.ssh'), owner='rattail:', mode='0700', use_sudo=True)
if alias:
postfix.alias(c, 'rattail', alias)
mkdir(c, '/etc/rattail', use_sudo=True)
mkdir(c, '/srv/rattail', use_sudo=True)
mkdir(c, '/var/log/rattail', owner='rattail:rattail', mode='0775', use_sudo=True)
mkdir(c, '/srv/rattail/init', use_sudo=True)
deploy(c, 'daemon', '/srv/rattail/init/daemon', use_sudo=True)
deploy(c, 'check-rattail-daemon', '/usr/local/bin/check-rattail-daemon', use_sudo=True)
deploy(c, 'check-supervisor-process', '/usr/local/bin/check-supervisor-process', use_sudo=True)
deploy(c, 'check-systemd-service', '/usr/local/bin/check-systemd-service', use_sudo=True)
deploy(c, 'luigid', '/srv/rattail/init/luigid', use_sudo=True)
deploy(c, 'soffice', '/srv/rattail/init/soffice', use_sudo=True)
# TODO: deprecate / remove these
deploy(c, 'bouncer', '/srv/rattail/init/bouncer', use_sudo=True)
deploy(c, 'datasync', '/srv/rattail/init/datasync', use_sudo=True)
deploy(c, 'filemon', '/srv/rattail/init/filemon', use_sudo=True)
deploy_common(c, 'check-rattail-daemon', '/usr/local/bin/check-rattail-daemon', use_sudo=True)
deploy_common(c, 'check-supervisor-process', '/usr/local/bin/check-supervisor-process', use_sudo=True)
deploy_common(c, 'check-systemd-service', '/usr/local/bin/check-systemd-service', use_sudo=True)
deploy_common(c, 'soffice', '/srv/rattail/init/soffice', use_sudo=True)
def deploy_machine_conf(c, timezone='America/Chicago'):
def deploy_machine_conf(c, env=None, timezone=None, context={}):
"""
Deploy the standard machine-wide ``rattail.conf`` file.
"""
mkdir(c, '/etc/rattail', use_sudo=True)
deploy(c, 'rattail/rattail.conf.mako', '/etc/rattail/rattail.conf', use_sudo=True,
context={'timezone': timezone})
context['env'] = env
context['timezone'] = timezone or getattr(env, 'timezone', 'America/Chicago')
deploy_common(c, 'rattail/rattail.conf.mako', '/etc/rattail/rattail.conf', use_sudo=True,
context=context)
def delete_email_recipients(c, dbname):
"""
Purge all email recipient settings for the given database.
"""
# purge new-style for wuttjamaican
postgresql.sql(c, "delete from setting where name like 'rattail.email.%.sender';", database=dbname)
postgresql.sql(c, "delete from setting where name like 'rattail.email.%.to';", database=dbname)
postgresql.sql(c, "delete from setting where name like 'rattail.email.%.cc';", database=dbname)
postgresql.sql(c, "delete from setting where name like 'rattail.email.%.bcc';", database=dbname)
# purge old-style for rattail
postgresql.sql(c, "delete from setting where name like 'rattail.mail.%.from';", database=dbname)
postgresql.sql(c, "delete from setting where name like 'rattail.mail.%.to';", database=dbname)
postgresql.sql(c, "delete from setting where name like 'rattail.mail.%.cc';", database=dbname)
postgresql.sql(c, "delete from setting where name like 'rattail.mail.%.bcc';", database=dbname)
@ -81,3 +126,17 @@ def disable_emails(c, dbname):
Disable all emails for the given database.
"""
postgresql.sql(c, "update setting set value = 'false' where name like 'rattail.mail.%.enabled';", database=dbname)
def deploy_datasync_checks(c, envroot, **kwargs):
"""
Deploy the standard datasync (queue, watcher) check scripts.
"""
envroot = envroot.rstrip('/')
context = kwargs.pop('context', {})
context['envroot'] = envroot
context.setdefault('config', kwargs.pop('config', 'quiet'))
deploy_common(c, 'rattail/check-datasync-queue.mako', '{}/app/check-datasync-queue'.format(envroot),
context=context, **kwargs)
deploy_common(c, 'rattail/check-datasync-watchers.mako', '{}/app/check-datasync-watchers'.format(envroot),
context=context, **kwargs)

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2019 Lance Edgar
# Copyright © 2010-2020 Lance Edgar
#
# This file is part of Rattail.
#
@ -25,13 +25,40 @@ Fabric Library for SSH
"""
def cache_host_key(c, host, for_user='root'):
def cache_host_key(c, host, port=None, user=None, **kwargs):
"""
Cache the SSH host key for the given host, for the given user.
"""
cmd = 'ssh -o StrictHostKeyChecking=no {} echo'.format(host)
user = None if for_user == 'root' else for_user
c.sudo(cmd, user=user, warn=True)
if 'for_user' in kwargs:
pass # TODO: deprecation warning
if not user and kwargs.get('for_user'):
user = kwargs['for_user']
port = '-p {}'.format(port) if port else ''
# first try to run basic command over ssh; if it works then we don't
# actually need to update/cache the host key
cmd = 'ssh {} {} whoami'.format(port, host)
if user:
result = c.sudo(cmd, warn=True, user=None if user == 'root' else user)
else:
result = c.run(cmd, warn=True)
if result.failed:
# basic command failed, but in some cases that is simply b/c normal
# commands are not allowed, although the ssh connection itself was
# established okay. here we check for that situation.
if "Disallowed command" not in result.stderr:
# okay then we now think that the ssh connection itself was not
# made, which presumably means we *do* need to cache the host key,
# so try that now
cmd = 'ssh -o StrictHostKeyChecking=no {} {} whoami'.format(port, host)
if user:
c.sudo(cmd, user=None if user == 'root' else user, warn=True)
else:
c.run(cmd, warn=True)
def restart(c):

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2019 Lance Edgar
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@ -111,8 +111,6 @@ def append(c, filename, text, use_sudo=False, partial=False, escape=True,
"""
func = use_sudo and c.sudo or c.run
# Normalize non-list input to be a list
# TODO: do we need to check for six.something here?
# if isinstance(text, basestring):
if isinstance(text, str):
text = [text]
for line in text:
@ -240,6 +238,10 @@ def sed(c, filename, before, after, limit='', use_sudo=False, backup='.bak',
after = after.replace(char, r'\%s' % char)
if limit:
limit = r'/%s/ ' % limit
# if replacement text contains single quote chars, must escape them
after = after.replace("'", "'\"'\"'")
context = {
'script': r"'%ss/%s/%s/%sg'" % (limit, before, after, flags),
'filename': _expand_path(c, filename),

View file

@ -1,97 +0,0 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
from __future__ import unicode_literals, absolute_import
import os
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
exec(open(os.path.join(here, 'rattail_fabric2', '_version.py')).read())
README = open(os.path.join(here, 'README.rst')).read()
requires = [
#
# Version numbers within comments below have specific meanings.
# Basically the 'low' value is a "soft low," and 'high' a "soft high."
# In other words:
#
# If either a 'low' or 'high' value exists, the primary point to be
# made about the value is that it represents the most current (stable)
# version available for the package (assuming typical public access
# methods) whenever this project was started and/or documented.
# Therefore:
#
# If a 'low' version is present, you should know that attempts to use
# versions of the package significantly older than the 'low' version
# may not yield happy results. (A "hard" high limit may or may not be
# indicated by a true version requirement.)
#
# Similarly, if a 'high' version is present, and especially if this
# project has laid dormant for a while, you may need to refactor a bit
# when attempting to support a more recent version of the package. (A
# "hard" low limit should be indicated by a true version requirement
# when a 'high' version is present.)
#
# In any case, developers and other users are encouraged to play
# outside the lines with regard to these soft limits. If bugs are
# encountered then they should be filed as such.
#
# package # low high
'fabric2', # 2.4.0
'invoke', # 1.2.0
'six', # 1.11.0
]
setup(
name = "rattail-fabric2",
version = __version__,
author = "Lance Edgar",
author_email = "lance@edbob.org",
url = "https://rattailproject.org/",
license = "GNU GPL v3",
description = "Fabric (v2) Utilities for Rattail",
long_description = README,
classifiers = [
'Development Status :: 3 - Alpha',
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Topic :: System :: Systems Administration',
'Topic :: Software Development :: Libraries :: Python Modules',
],
install_requires = requires,
packages = find_packages(),
include_package_data = True,
zip_safe = False,
)

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2020 Lance Edgar
# Copyright © 2010-2024 Lance Edgar
#
# This file is part of Rattail.
#
@ -24,16 +24,25 @@
Tasks for rattail-fabric2
"""
from __future__ import unicode_literals, absolute_import
import os
import re
import shutil
from invoke import task
here = os.path.abspath(os.path.dirname(__file__))
exec(open(os.path.join(here, 'rattail_fabric2', '_version.py')).read())
__version__ = None
pattern = re.compile(r'^version = "(\d+\.\d+\.\d+)"$')
with open(os.path.join(here, 'pyproject.toml'), 'rt') as f:
for line in f:
line = line.rstrip('\n')
match = pattern.match(line)
if match:
__version__ = match.group(1)
break
if not __version__:
raise RuntimeError("could not parse version!")
@task
@ -41,9 +50,10 @@ def release(c):
"""
Release a new version of 'rattail-fabric2'.
"""
shutil.rmtree('rattail_fabric2.egg-info')
if os.path.exists('rattail_fabric2.egg-info'):
shutil.rmtree('rattail_fabric2.egg-info')
# TODO: this seems heavy-handed? for sake of recursive-include in MANIFEST
# TODO: what i esp. don't like is, this doesn't consider .gitignore
c.run("find . -name '*~' -delete")
c.run('python setup.py sdist --formats=gztar')
c.run('twine upload dist/rattail-fabric2-{}.tar.gz'.format(__version__))
c.run('python -m build --sdist')
c.run(f'twine upload dist/rattail_fabric2-{__version__}.tar.gz')