Compare commits
128 commits
Author | SHA1 | Date | |
---|---|---|---|
|
8bddce1329 | ||
|
542875a44c | ||
![]() |
8bd4236291 | ||
![]() |
00b0082302 | ||
![]() |
515ccc9f0c | ||
![]() |
e2ce4fd257 | ||
![]() |
fcfe741b51 | ||
![]() |
59ab46a335 | ||
![]() |
8ab21914bd | ||
![]() |
9eb44fa172 | ||
![]() |
65a9a7c9be | ||
![]() |
5ffcf0297c | ||
![]() |
7d031e1a41 | ||
![]() |
04c586a11e | ||
![]() |
d0425115da | ||
![]() |
8b5bb956ae | ||
![]() |
5c5038144f | ||
![]() |
014982028b | ||
![]() |
8c51ee8735 | ||
![]() |
4e3b8f6520 | ||
![]() |
be728e9bb3 | ||
![]() |
284e55c05c | ||
![]() |
88acb15c1c | ||
![]() |
8f8013aee0 | ||
![]() |
305e4c40c7 | ||
![]() |
6a15b5ae5e | ||
![]() |
c13e9ff23c | ||
![]() |
ae7cb45ab3 | ||
![]() |
24d632b7e3 | ||
![]() |
8e9a685006 | ||
![]() |
bedca74ca1 | ||
![]() |
e2369b1f53 | ||
![]() |
ab1baaa6d2 | ||
![]() |
94945fbc30 | ||
![]() |
6479af6a57 | ||
![]() |
075f931b5e | ||
![]() |
989f1574dc | ||
![]() |
d713bbe522 | ||
![]() |
64c69aab3c | ||
![]() |
9eab7a7555 | ||
![]() |
3f931445a0 | ||
![]() |
8bafaef796 | ||
![]() |
753e939422 | ||
![]() |
88e92588f2 | ||
![]() |
47bc4ce01d | ||
![]() |
1356bd0310 | ||
![]() |
86d922708a | ||
![]() |
cf32571c83 | ||
![]() |
21a071d324 | ||
![]() |
43d8df3c5d | ||
![]() |
2097136844 | ||
![]() |
20a3871522 | ||
![]() |
bdd89db30d | ||
![]() |
6c78d3c1d5 | ||
![]() |
cde124b916 | ||
![]() |
a76d38c201 | ||
![]() |
9e0a1cde19 | ||
![]() |
c8ab84e462 | ||
![]() |
492e8da5c3 | ||
![]() |
7d536499d6 | ||
![]() |
d68b57baa8 | ||
![]() |
6fb9389e45 | ||
![]() |
8c556e6176 | ||
![]() |
5fcfc91d83 | ||
![]() |
375cd1a36f | ||
![]() |
dc5556651f | ||
![]() |
cfba5425a1 | ||
![]() |
a3339ac062 | ||
![]() |
1fedc314b3 | ||
![]() |
a9bbee572a | ||
![]() |
ebf94fb7dc | ||
![]() |
5acf8d5304 | ||
![]() |
f1ecf9a1e4 | ||
![]() |
4c8e956fec | ||
![]() |
25891902b8 | ||
![]() |
a36ecdb2ad | ||
![]() |
7ce3aae20c | ||
![]() |
46440b6a95 | ||
![]() |
614fd92a20 | ||
![]() |
a2dca4ea65 | ||
![]() |
987731885b | ||
![]() |
ba05d616d8 | ||
![]() |
303f650a0b | ||
![]() |
f97401009e | ||
![]() |
5a4e2701df | ||
![]() |
5df704679f | ||
![]() |
a1b580e51a | ||
![]() |
097a96fc07 | ||
![]() |
fd0e55ab60 | ||
![]() |
a2f5542ed8 | ||
![]() |
6bf697da1d | ||
![]() |
ca59000287 | ||
![]() |
b58c0da7a4 | ||
![]() |
5b985fb803 | ||
![]() |
cbf93624e0 | ||
![]() |
955ceea801 | ||
![]() |
c413c2a1f2 | ||
![]() |
3287505f88 | ||
![]() |
1fd9ad48d6 | ||
![]() |
506b88d3e2 | ||
![]() |
9ae0015aba | ||
![]() |
9048403130 | ||
![]() |
f87f1e875c | ||
![]() |
70c5d661a3 | ||
![]() |
7500202af0 | ||
![]() |
8b8df95633 | ||
![]() |
ab822411be | ||
![]() |
a77b1ec100 | ||
![]() |
7963380715 | ||
![]() |
f5a7f80fd4 | ||
![]() |
8c08412d7b | ||
![]() |
47c27eaba5 | ||
![]() |
caf94ac65a | ||
![]() |
97ed8eeff0 | ||
![]() |
96f950d958 | ||
![]() |
d744257af6 | ||
![]() |
c67cc74d3d | ||
![]() |
c9aebd84e9 | ||
![]() |
c2cca7aa76 | ||
![]() |
7471dd1bf5 | ||
![]() |
e8821b3395 | ||
![]() |
19fcc94dc7 | ||
![]() |
6ebc5bbf59 | ||
![]() |
6d8cf3adb6 | ||
![]() |
6cf17a054b | ||
![]() |
6cb6ddaba7 | ||
![]() |
d6a11908ce | ||
![]() |
3d5b7c1d6e |
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,2 +1,4 @@
|
||||||
|
*~
|
||||||
*.pyc
|
*.pyc
|
||||||
|
dist/
|
||||||
rattail_fabric2.egg-info/
|
rattail_fabric2.egg-info/
|
||||||
|
|
53
CHANGELOG.md
Normal file
53
CHANGELOG.md
Normal 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.
|
20
CHANGES.rst
20
CHANGES.rst
|
@ -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
11
README.md
Normal 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.
|
14
README.rst
14
README.rst
|
@ -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
95
docs/OLDCHANGES.rst
Normal 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
41
pyproject.toml
Normal 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
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2019 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# 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.
|
Fabric deployment and maintenance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from ._version import __version__
|
||||||
|
|
||||||
from .core import (
|
from .core import (
|
||||||
|
is_debian,
|
||||||
get_debian_version,
|
get_debian_version,
|
||||||
get_ubuntu_version,
|
get_ubuntu_version,
|
||||||
Deployer,
|
Deployer,
|
||||||
make_deploy,
|
make_deploy,
|
||||||
|
make_normal_user,
|
||||||
make_system_user,
|
make_system_user,
|
||||||
mkdir,
|
mkdir,
|
||||||
rsync,
|
rsync,
|
||||||
set_timezone,
|
set_timezone,
|
||||||
UNSPECIFIED,
|
UNSPECIFIED,
|
||||||
)
|
)
|
||||||
from .util import exists, contains, append, sed
|
from .util import exists, contains, append, sed, uncomment
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
# -*- coding: utf-8; -*-
|
# -*- coding: utf-8; -*-
|
||||||
|
|
||||||
__version__ = '0.2.2'
|
try:
|
||||||
|
from importlib.metadata import version
|
||||||
|
except ImportError:
|
||||||
|
from importlib_metadata import version
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = version('rattail-fabric2')
|
||||||
|
|
|
@ -73,7 +73,7 @@ def get_php_version(c):
|
||||||
"""
|
"""
|
||||||
result = c.sudo('php --version')
|
result = c.sudo('php --version')
|
||||||
if not result.failed:
|
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:
|
if match:
|
||||||
return float(match.group(1))
|
return float(match.group(1))
|
||||||
|
|
||||||
|
|
|
@ -36,12 +36,12 @@ def install(c, *packages, **kwargs):
|
||||||
"""
|
"""
|
||||||
Install one or more packages via ``apt-get install``.
|
Install one or more packages via ``apt-get install``.
|
||||||
"""
|
"""
|
||||||
frontend = kwargs.get('frontend', 'noninteractive')
|
frontend = kwargs.pop('frontend', 'noninteractive')
|
||||||
target = kwargs.get('target_release')
|
target = kwargs.pop('target_release', None)
|
||||||
target = '--target-release={}'.format(target) if target else ''
|
target = '--target-release={}'.format(target) if target else ''
|
||||||
force_yes = ' --force-yes' if kwargs.get('force_yes') else ''
|
force_yes = ' --force-yes' if kwargs.pop('force_yes', False) else ''
|
||||||
c.sudo('DEBIAN_FRONTEND={} apt-get --assume-yes {}{} install {}'.format(
|
return c.sudo('DEBIAN_FRONTEND={} apt-get --assume-yes {}{} install {}'.format(
|
||||||
frontend, target, force_yes, ' '.join(packages)))
|
frontend, target, force_yes, ' '.join(packages)), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def purge(c, *packages):
|
def purge(c, *packages):
|
||||||
|
@ -67,6 +67,16 @@ def add_source(c, entry):
|
||||||
update(c)
|
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'):
|
def dist_upgrade(c, frontend='noninteractive'):
|
||||||
"""
|
"""
|
||||||
Perform a full ``apt-get dist-upgrade`` operation.
|
Perform a full ``apt-get dist-upgrade`` operation.
|
||||||
|
@ -102,4 +112,10 @@ def install_emacs(c):
|
||||||
if ubuntu_version and ubuntu_version < 16:
|
if ubuntu_version and ubuntu_version < 16:
|
||||||
emacs = 'emacs23-nox'
|
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')
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2019 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# 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',
|
def deploy_backup_app(c, deploy, envname, mkvirtualenv=True, user='rattail',
|
||||||
|
python_exe='/usr/bin/python3',
|
||||||
install_borg=False,
|
install_borg=False,
|
||||||
|
pyfuse3=False,
|
||||||
link_borg_to_bin=True,
|
link_borg_to_bin=True,
|
||||||
install_luigi=False,
|
install_luigi=False,
|
||||||
luigi_history_db=None,
|
luigi_history_db=None,
|
||||||
|
@ -63,7 +65,9 @@ def deploy_backup_app(c, deploy, envname, mkvirtualenv=True, user='rattail',
|
||||||
rattail_backup_script=None,
|
rattail_backup_script=None,
|
||||||
everything=None,
|
everything=None,
|
||||||
crontab=None,
|
crontab=None,
|
||||||
|
crontab_mailto=None,
|
||||||
runat=UNSPECIFIED,
|
runat=UNSPECIFIED,
|
||||||
|
logrotate=False,
|
||||||
context={}):
|
context={}):
|
||||||
"""
|
"""
|
||||||
Make an app which can run backups for the server.
|
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):
|
if deploy.local_exists(path):
|
||||||
config = path
|
config = path
|
||||||
else:
|
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:
|
if install_borg:
|
||||||
borg.install_dependencies(c)
|
borg.install_dependencies(c)
|
||||||
|
@ -86,27 +91,37 @@ def deploy_backup_app(c, deploy, envname, mkvirtualenv=True, user='rattail',
|
||||||
c.sudo('supervisorctl stop backup:')
|
c.sudo('supervisorctl stop backup:')
|
||||||
|
|
||||||
# virtualenv
|
# virtualenv
|
||||||
if mkvirtualenv:
|
|
||||||
python.mkvirtualenv(c, envname, python='/usr/bin/python3', runas_user=user)
|
|
||||||
envpath = '/srv/envs/{}'.format(envname)
|
envpath = '/srv/envs/{}'.format(envname)
|
||||||
c.sudo('chown -R {}: {}'.format(user, envpath))
|
if mkvirtualenv and not exists(c, envpath):
|
||||||
mkdir(c, os.path.join(envpath, 'src'), use_sudo=True, runas_user=user)
|
mkdir(c, envpath, use_sudo=True, owner=user)
|
||||||
c.sudo("bash -l -c 'workon {} && pip install --upgrade pip'".format(envname), user=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:
|
if install_rattail:
|
||||||
|
|
||||||
# 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')):
|
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(f'git clone https://forgejo.wuttaproject.org/rattail/rattail.git {envpath}/src/rattail', user=user)
|
||||||
c.sudo("bash -l -c 'workon {} && cdvirtualenv && bin/pip install --editable src/rattail'".format(envname), 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)
|
deploy_generic(c, 'backup/git-exclude', os.path.join(envpath, 'src/rattail/.git/info/exclude'), use_sudo=True, owner=user)
|
||||||
|
|
||||||
# config
|
# 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
|
# 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)
|
deploy(c, config, os.path.join(envpath, 'app/rattail.conf'),
|
||||||
c.sudo("bash -l -c 'workon {} && cdvirtualenv && bin/rattail -c app/rattail.conf make-config -T quiet -O app/'".format(envname), user=user)
|
use_sudo=True, owner='root:{}'.format(user), mode='0640',
|
||||||
c.sudo("bash -l -c 'workon {} && cdvirtualenv && bin/rattail -c app/rattail.conf make-config -T silent -O app/'".format(envname), user=user)
|
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
|
# rattail-backup script
|
||||||
script_context = dict(context)
|
script_context = dict(context)
|
||||||
|
@ -119,18 +134,20 @@ def deploy_backup_app(c, deploy, envname, mkvirtualenv=True, user='rattail',
|
||||||
|
|
||||||
# borg
|
# borg
|
||||||
if install_borg:
|
if install_borg:
|
||||||
if install_rattail:
|
if isinstance(install_borg, list):
|
||||||
packages = [
|
packages = install_borg
|
||||||
'rattail[backup]',
|
elif isinstance(install_borg, str):
|
||||||
]
|
packages = [install_borg]
|
||||||
else:
|
else:
|
||||||
# these should be same as rattail[backup]
|
packages = ['msgpack']
|
||||||
packages = [
|
if pyfuse3:
|
||||||
'msgpack',
|
apt.install(c, 'libfuse3-dev')
|
||||||
'borgbackup',
|
packages.append('borgbackup[pyfuse3]')
|
||||||
'llfuse==1.3.4',
|
else:
|
||||||
]
|
# TODO: this is legacy and should stop being default
|
||||||
c.sudo("bash -l -c 'workon {} && cdvirtualenv && bin/pip install {}'".format(envname, ' '.join(packages)), user=user)
|
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:
|
if link_borg_to_bin:
|
||||||
c.sudo("ln -sf {}/bin/borg /usr/local/bin/borg".format(envpath))
|
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')
|
packages.append('SQLAlchemy')
|
||||||
if luigi_history_db.startswith('postgresql://'):
|
if luigi_history_db.startswith('postgresql://'):
|
||||||
packages.append('psycopg2')
|
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
|
# basic config
|
||||||
mkdir(c, ['{}/app/luigitasks'.format(envpath),
|
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['envname'] = envname
|
||||||
crontab_context['pretty_time'] = runat.strftime('%I:%M %p')
|
crontab_context['pretty_time'] = runat.strftime('%I:%M %p')
|
||||||
crontab_context['cron_time'] = runat.strftime('%M %H')
|
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:
|
if crontab:
|
||||||
deploy(c, crontab, '/etc/cron.d/backup', context=crontab_context, use_sudo=True)
|
deploy(c, crontab, '/etc/cron.d/backup', context=crontab_context, use_sudo=True)
|
||||||
else:
|
else:
|
||||||
deploy_generic(c, 'backup/crontab.mako', '/etc/cron.d/backup', context=crontab_context, use_sudo=True)
|
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)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2018 Lance Edgar
|
# Copyright © 2010-2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -26,8 +26,6 @@ Fabric library for Borg backups
|
||||||
https://www.borgbackup.org/
|
https://www.borgbackup.org/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
from rattail_fabric2 import apt
|
from rattail_fabric2 import apt
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,6 +37,9 @@ def install_dependencies(c):
|
||||||
c,
|
c,
|
||||||
'libacl1-dev',
|
'libacl1-dev',
|
||||||
'libfuse-dev',
|
'libfuse-dev',
|
||||||
|
'liblz4-dev',
|
||||||
'libssl-dev',
|
'libssl-dev',
|
||||||
|
'libxxhash-dev',
|
||||||
|
'libzstd-dev',
|
||||||
'pkg-config',
|
'pkg-config',
|
||||||
)
|
)
|
||||||
|
|
41
rattail_fabric2/byjove.py
Normal file
41
rattail_fabric2/byjove.py
Normal 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)
|
69
rattail_fabric2/collectd.py
Normal file
69
rattail_fabric2/collectd.py
Normal 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)
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2019 Lance Edgar
|
# Copyright © 2010-2022 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,14 +24,19 @@
|
||||||
Fabric lib for Composer (PHP dependency manager)
|
Fabric lib for Composer (PHP dependency manager)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
from rattail_fabric2 import apt, make_deploy, exists
|
||||||
|
|
||||||
from rattail_fabric2 import make_deploy, exists
|
|
||||||
|
|
||||||
|
|
||||||
deploy = make_deploy(__file__)
|
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):
|
def install_globally(c):
|
||||||
"""
|
"""
|
||||||
Install `composer.phar` in global location
|
Install `composer.phar` in global location
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2019 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -27,6 +27,7 @@ Core Utilities
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import warnings
|
||||||
|
|
||||||
from mako.template import Template
|
from mako.template import Template
|
||||||
|
|
||||||
|
@ -64,6 +65,27 @@ def is_link(c, path, use_sudo=False):
|
||||||
return False if result.failed else True
|
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):
|
def get_debian_version(c):
|
||||||
"""
|
"""
|
||||||
Fetch the version of Debian running on the target system.
|
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)))
|
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.
|
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 ''
|
home = '--home {}'.format(home) if home else ''
|
||||||
uid = '--uid {}'.format(uid) if uid else ''
|
uid = '--uid {}'.format(uid) if uid else ''
|
||||||
shell = '--shell {}'.format(shell) if shell 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):
|
def set_timezone(c, timezone):
|
||||||
|
@ -193,6 +247,8 @@ class Deployer(object):
|
||||||
self.deploy(c, local_path, remote_path, **kwargs)
|
self.deploy(c, local_path, remote_path, **kwargs)
|
||||||
|
|
||||||
def full_path(self, local_path):
|
def full_path(self, local_path):
|
||||||
|
if local_path.startswith('/'):
|
||||||
|
return local_path
|
||||||
return '{}/{}'.format(self.deploy_path, local_path)
|
return '{}/{}'.format(self.deploy_path, local_path)
|
||||||
|
|
||||||
def local_exists(self, local_path):
|
def local_exists(self, local_path):
|
||||||
|
@ -208,7 +264,7 @@ class Deployer(object):
|
||||||
put(c, local_path, remote_path, **kwargs)
|
put(c, local_path, remote_path, **kwargs)
|
||||||
|
|
||||||
def sudoers(self, c, local_path, remote_path, owner='root:', mode='0440', **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))
|
c.sudo('mv /tmp/sudoers {}'.format(remote_path))
|
||||||
|
|
||||||
def apache_site(self, c, local_path, name, **kwargs):
|
def apache_site(self, c, local_path, name, **kwargs):
|
||||||
|
@ -225,7 +281,8 @@ class Deployer(object):
|
||||||
from rattail_fabric2.backup import deploy_backup_app
|
from rattail_fabric2.backup import deploy_backup_app
|
||||||
deploy_backup_app(c, self, envname, *args, **kwargs)
|
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
|
Deploy files to establish a certbot account on target server
|
||||||
"""
|
"""
|
||||||
|
@ -234,8 +291,8 @@ class Deployer(object):
|
||||||
localdir = localdir.rstrip('/')
|
localdir = localdir.rstrip('/')
|
||||||
paths = [
|
paths = [
|
||||||
'/etc/letsencrypt/accounts',
|
'/etc/letsencrypt/accounts',
|
||||||
'/etc/letsencrypt/accounts/acme-v01.api.letsencrypt.org',
|
'/etc/letsencrypt/accounts/acme-v{}.api.letsencrypt.org'.format(version),
|
||||||
'/etc/letsencrypt/accounts/acme-v01.api.letsencrypt.org/directory',
|
'/etc/letsencrypt/accounts/acme-v{}.api.letsencrypt.org/directory'.format(version),
|
||||||
]
|
]
|
||||||
final_path = '{}/{}'.format(paths[-1], uuid)
|
final_path = '{}/{}'.format(paths[-1], uuid)
|
||||||
paths.append(final_path)
|
paths.append(final_path)
|
||||||
|
@ -249,9 +306,13 @@ class Deployer(object):
|
||||||
"""
|
"""
|
||||||
Deploy a "soffice" (headless LibreOffice) daemon.
|
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:
|
if name is None:
|
||||||
name = local_path.split('/')[-1]
|
name = local_path.split('/')[-1]
|
||||||
kwargs.setdefault('use_sudo', True)
|
kwargs.setdefault('use_sudo', True)
|
||||||
|
kwargs.setdefault('mode', '0755')
|
||||||
self.deploy(c, local_path, '/etc/init.d/{}'.format(name), **kwargs)
|
self.deploy(c, local_path, '/etc/init.d/{}'.format(name), **kwargs)
|
||||||
if register:
|
if register:
|
||||||
c.sudo('update-rc.d {} defaults'.format(name))
|
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)
|
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.
|
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:
|
for path in paths:
|
||||||
assert path.startswith('/')
|
assert path.startswith('/')
|
||||||
path = path.rstrip('/')
|
path = path.rstrip('/')
|
||||||
# escape path for rsync
|
# escape path for rsync
|
||||||
path = path.replace(' ', r'\\\ ').replace("'", r"\\\'")
|
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}')
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2019 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,13 +24,103 @@
|
||||||
Fabric library for CORE-POS (IS4C)
|
Fabric library for CORE-POS (IS4C)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import os
|
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',
|
def install_fannie(c, rootdir, user='www-data', branch='version-2.10',
|
||||||
mysql_user='is4c', mysql_pass='is4c'):
|
mysql_user='is4c', mysql_pass='is4c'):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -11,6 +11,8 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
RATTAIL="/srv/envs/${envname}/bin/rattail --config=$CONFIG $PROGRESS $VERBOSE"
|
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
|
# sanity check
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
# -*- mode: conf; -*-
|
# -*- mode: conf; -*-
|
||||||
|
|
||||||
|
% if mailto:
|
||||||
|
MAILTO="${mailto}"
|
||||||
|
% endif
|
||||||
|
|
||||||
# backup everything of importance at ${pretty_time}
|
# backup everything of importance at ${pretty_time}
|
||||||
${'' if env.machine_is_live else '# '}${cron_time} * * * root /usr/local/bin/backup-everything
|
${'' if env.machine_is_live else '# '}${cron_time} * * * root /usr/local/bin/backup-everything
|
||||||
|
|
10
rattail_fabric2/deploy/backup/logrotate.conf
Normal file
10
rattail_fabric2/deploy/backup/logrotate.conf
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
/srv/envs/backup/app/log/rattail.log {
|
||||||
|
daily
|
||||||
|
missingok
|
||||||
|
rotate 30
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
notifempty
|
||||||
|
create 600 rattail rattail
|
||||||
|
}
|
|
@ -3,16 +3,18 @@
|
||||||
if [ "$1" = "-v" -o "$1" = "--verbose" ]; then
|
if [ "$1" = "-v" -o "$1" = "--verbose" ]; then
|
||||||
VERBOSE='--verbose'
|
VERBOSE='--verbose'
|
||||||
QUIET=
|
QUIET=
|
||||||
|
PROGRESSBAR='--progress-bar=on'
|
||||||
else
|
else
|
||||||
VERBOSE=
|
VERBOSE=
|
||||||
QUIET='--quiet'
|
QUIET='--quiet'
|
||||||
|
PROGRESSBAR='--progress-bar=off'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PIP="sudo -H -u ${user} PIP_CONFIG_FILE=${envpath}/pip.conf ${envpath}/bin/pip"
|
PIP="sudo -H -u ${user} PIP_CONFIG_FILE=${envpath}/pip.conf ${envpath}/bin/pip"
|
||||||
|
|
||||||
|
|
||||||
# upgrade pip
|
# upgrade pip
|
||||||
$PIP install $QUIET --upgrade pip
|
$PIP install $QUIET $PROGRESSBAR --upgrade pip
|
||||||
|
|
||||||
# upgrade rattail
|
# upgrade rattail
|
||||||
cd ${envpath}/src/rattail
|
cd ${envpath}/src/rattail
|
||||||
|
@ -22,4 +24,4 @@ if [ "$(sudo -H -u ${user} git status --porcelain)" != '' ]; then
|
||||||
fi
|
fi
|
||||||
sudo -H -u ${user} git pull $QUIET
|
sudo -H -u ${user} git pull $QUIET
|
||||||
find . -name '*.pyc' -delete
|
find . -name '*.pyc' -delete
|
||||||
$PIP install $QUIET --upgrade --upgrade-strategy eager --editable .
|
$PIP install $QUIET $PROGRESSBAR --upgrade --upgrade-strategy eager --editable .
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
:
|
|
63
rattail_fabric2/deploy/collectd/check-mountpoints.py
Normal file
63
rattail_fabric2/deploy/collectd/check-mountpoints.py
Normal 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)
|
13
rattail_fabric2/deploy/corepos/fannie-config.php.mako
Normal file
13
rattail_fabric2/deploy/corepos/fannie-config.php.mako
Normal 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';
|
||||||
|
|
||||||
|
?>
|
|
@ -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
|
|
||||||
|
|
||||||
:
|
|
|
@ -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
|
|
||||||
|
|
||||||
:
|
|
|
@ -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
|
|
||||||
|
|
||||||
:
|
|
14
rattail_fabric2/deploy/luigi/cron-overnight.sh.mako
Executable file
14
rattail_fabric2/deploy/luigi/cron-overnight.sh.mako
Executable 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
|
8
rattail_fabric2/deploy/luigi/crontab.mako
Normal file
8
rattail_fabric2/deploy/luigi/crontab.mako
Normal 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
|
39
rattail_fabric2/deploy/luigi/logging.conf.mako
Normal file
39
rattail_fabric2/deploy/luigi/logging.conf.mako
Normal 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
|
24
rattail_fabric2/deploy/luigi/luigi-logrotate.conf.mako
Normal file
24
rattail_fabric2/deploy/luigi/luigi-logrotate.conf.mako
Normal 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
|
||||||
|
}
|
52
rattail_fabric2/deploy/luigi/luigi.cfg.mako
Normal file
52
rattail_fabric2/deploy/luigi/luigi.cfg.mako
Normal 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
|
28
rattail_fabric2/deploy/luigi/overnight.sh.mako
Executable file
28
rattail_fabric2/deploy/luigi/overnight.sh.mako
Executable 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
|
14
rattail_fabric2/deploy/luigi/restart-overnight.sh.mako
Normal file
14
rattail_fabric2/deploy/luigi/restart-overnight.sh.mako
Normal 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
|
14
rattail_fabric2/deploy/luigi/rotate-logs.sh.mako
Executable file
14
rattail_fabric2/deploy/luigi/rotate-logs.sh.mako
Executable 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
|
11
rattail_fabric2/deploy/luigi/supervisor.conf.mako
Normal file
11
rattail_fabric2/deploy/luigi/supervisor.conf.mako
Normal 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'}
|
|
@ -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
|
|
||||||
|
|
||||||
:
|
|
33
rattail_fabric2/deploy/rattail/check-datasync-queue.mako
Executable file
33
rattail_fabric2/deploy/rattail/check-datasync-queue.mako
Executable 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)
|
34
rattail_fabric2/deploy/rattail/check-datasync-watchers.mako
Executable file
34
rattail_fabric2/deploy/rattail/check-datasync-watchers.mako
Executable 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)
|
|
@ -19,9 +19,8 @@ configure_logging = true
|
||||||
|
|
||||||
[rattail.mail]
|
[rattail.mail]
|
||||||
smtp.server = localhost
|
smtp.server = localhost
|
||||||
templates = rattail:templates/mail
|
default.from = ${getattr(env, 'email_default_sender', 'rattail@localhost')}
|
||||||
default.from = root
|
default.to = ${', '.join(getattr(env, 'email_default_recipients', ['root@localhost']))}
|
||||||
default.to = root
|
|
||||||
|
|
||||||
|
|
||||||
${'#'}#############################
|
${'#'}#############################
|
||||||
|
@ -88,7 +87,7 @@ formatter = console
|
||||||
|
|
||||||
[handler_email]
|
[handler_email]
|
||||||
class = handlers.SMTPHandler
|
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
|
formatter = generic
|
||||||
level = ERROR
|
level = ERROR
|
||||||
|
|
||||||
|
|
61
rattail_fabric2/docker.py
Normal file
61
rattail_fabric2/docker.py
Normal 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')
|
38
rattail_fabric2/ejabberd.py
Normal file
38
rattail_fabric2/ejabberd.py
Normal 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)
|
|
@ -32,7 +32,7 @@ def install_from_source(c, user='rattail', branch=None):
|
||||||
Install the FreeTDS library from source.
|
Install the FreeTDS library from source.
|
||||||
|
|
||||||
Per instructions found here:
|
Per instructions found here:
|
||||||
https://github.com/FreeTDS/freetds/blob/master/INSTALL.GIT
|
https://github.com/FreeTDS/freetds/blob/master/INSTALL.md
|
||||||
"""
|
"""
|
||||||
apt.install(
|
apt.install(
|
||||||
c,
|
c,
|
||||||
|
|
175
rattail_fabric2/luigi.py
Normal file
175
rattail_fabric2/luigi.py
Normal 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:')
|
38
rattail_fabric2/mariadb.py
Normal file
38
rattail_fabric2/mariadb.py
Normal 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
43
rattail_fabric2/mssql.py
Normal 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)
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2020 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,6 +24,8 @@
|
||||||
Fabric Library for MySQL
|
Fabric Library for MySQL
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from rattail_fabric2 import apt, make_deploy, sed
|
from rattail_fabric2 import apt, make_deploy, sed
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,6 +40,18 @@ def install(c):
|
||||||
apt.install(c, 'default-mysql-server')
|
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):
|
def set_bind_address(c, address):
|
||||||
"""
|
"""
|
||||||
Configure the 'bind-address' setting with the given value.
|
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):
|
if not checkfirst or not user_exists(c, name, host):
|
||||||
sql(c, "CREATE USER '{}'@'{}';".format(name, host))
|
sql(c, "CREATE USER '{}'@'{}';".format(name, host))
|
||||||
if password:
|
if password:
|
||||||
sql(c, "SET PASSWORD FOR '{}'@'{}' = PASSWORD('{}');".format(
|
# supposedly this is the new way to do it
|
||||||
name, host, password), hide=True, echo=False)
|
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):
|
def db_exists(c, name):
|
||||||
|
@ -142,18 +163,31 @@ def script(c, path, database=''):
|
||||||
c.sudo("bash -c 'mysql {} < {}'".format(database, path))
|
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.
|
Download a database from the "current" server.
|
||||||
"""
|
"""
|
||||||
if destination is None:
|
filename = dump_db(c, name,
|
||||||
destination = './{}.sql.gz'.format(name)
|
skip_triggers=kwargs.get('skip_triggers', False))
|
||||||
# note, we force sudo "as root" to ensure -H flag is used
|
c.get(filename, destination or f'./{filename}')
|
||||||
# (which allows us to leverage /root/.my.cnf config file)
|
c.sudo(f'rm {filename}')
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
def clone_db(c, name, download, user=None, force=False):
|
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.run('gunzip --force {}.sql.gz'.format(name))
|
||||||
c.sudo("bash -c 'mysql {0} < {0}.sql'".format(name))
|
c.sudo("bash -c 'mysql {0} < {0}.sql'".format(name))
|
||||||
c.run('rm {}.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}')
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2019 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -46,6 +46,9 @@ def install(c, version=None, user=None):
|
||||||
|
|
||||||
profile = os.path.join(home, '.profile')
|
profile = os.path.join(home, '.profile')
|
||||||
kwargs = {'use_sudo': bool(user)}
|
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, '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/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"', **kwargs)
|
||||||
append(c, profile, '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"', **kwargs)
|
append(c, profile, '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"', **kwargs)
|
||||||
|
|
|
@ -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 exists(c, os.path.join(path, 'pod_pictures_gtin.zip')):
|
||||||
if not download_url:
|
if not download_url:
|
||||||
download_url = 'http://www.product-open-data.com/docs/pod_pictures_gtin_2013.08.29_01.zip'
|
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')):
|
if not exists(c, os.path.join(path, 'pictures/gtin')):
|
||||||
c.sudo("bash -c 'cd {}; unzip pod_pictures_gtin.zip -d pictures'".format(path))
|
c.sudo("bash -c 'cd {}; unzip pod_pictures_gtin.zip -d pictures'".format(path))
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2019 Lance Edgar
|
# Copyright © 2010-2022 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -25,6 +25,7 @@ Fabric library for Postfix
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rattail_fabric2 import apt
|
from rattail_fabric2 import apt
|
||||||
|
from rattail.util import shlex_join
|
||||||
|
|
||||||
|
|
||||||
def install(c):
|
def install(c):
|
||||||
|
@ -42,10 +43,17 @@ def alias(c, name, alias_to, path='/etc/aliases'):
|
||||||
# does alias entry already exist?
|
# does alias entry already exist?
|
||||||
if c.run("grep '^{}:' /etc/aliases".format(name), warn=True).failed:
|
if c.run("grep '^{}:' /etc/aliases".format(name), warn=True).failed:
|
||||||
# append new entry
|
# 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:
|
else:
|
||||||
# update existing entry
|
# 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')
|
c.sudo('newaliases')
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2019 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -27,14 +27,14 @@ Fabric Library for PostgreSQL
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from rattail_fabric2 import apt
|
from rattail_fabric2 import apt, append, contains, sed, uncomment
|
||||||
|
|
||||||
|
|
||||||
def install(c):
|
def install(c):
|
||||||
"""
|
"""
|
||||||
Install the PostgreSQL database service
|
Install the PostgreSQL database service
|
||||||
"""
|
"""
|
||||||
apt.install(c, 'postgresql')
|
apt.install(c, 'postgresql', 'libpq-dev')
|
||||||
|
|
||||||
|
|
||||||
def get_version(c):
|
def get_version(c):
|
||||||
|
@ -47,6 +47,48 @@ def get_version(c):
|
||||||
if match:
|
if match:
|
||||||
return float(match.group(1))
|
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):
|
def restart(c):
|
||||||
"""
|
"""
|
||||||
Restart the PostgreSQL database service
|
Restart the PostgreSQL database service
|
||||||
|
@ -54,12 +96,15 @@ def restart(c):
|
||||||
c.sudo('systemctl restart postgresql.service')
|
c.sudo('systemctl restart postgresql.service')
|
||||||
|
|
||||||
|
|
||||||
def reload(c):
|
def reload_(c):
|
||||||
"""
|
"""
|
||||||
Reload config for the PostgreSQL database service
|
Reload config for the PostgreSQL database service
|
||||||
"""
|
"""
|
||||||
c.sudo('systemctl reload postgresql.service')
|
c.sudo('systemctl reload postgresql.service')
|
||||||
|
|
||||||
|
# TODO: deprecate / remove this
|
||||||
|
reload = reload_
|
||||||
|
|
||||||
|
|
||||||
def sql(c, sql, database='', port=None, **kwargs):
|
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)
|
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):
|
def user_exists(c, name, port=None):
|
||||||
"""
|
"""
|
||||||
Determine if a given PostgreSQL user exists.
|
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')
|
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.
|
Download a database from the server represented by ``c`` param.
|
||||||
"""
|
"""
|
||||||
if destination is None:
|
if destination is None:
|
||||||
destination = './{}.sql.gz'.format(name)
|
destination = './{}.sql.gz'.format(name)
|
||||||
c.run('touch {}.sql'.format(name))
|
dumpfile = dump_db(c, name, port=port, exclude_tables=exclude_tables,
|
||||||
c.run('chmod 0666 {}.sql'.format(name))
|
skip_raw_file=skip_raw_file)
|
||||||
cmd = 'pg_dump {port} {exclude_tables} --file={name}.sql {name}'.format(
|
c.get(dumpfile, destination)
|
||||||
name=name,
|
c.run('rm {}'.format(dumpfile))
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
def clone_db(c, name, owner, download, user='rattail', force=False, workdir=None):
|
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)
|
os.chdir(curdir)
|
||||||
|
|
||||||
# restore database on target server
|
# restore database on target server
|
||||||
c.run('gunzip --force {}.sql.gz'.format(name))
|
# TODO: first tried c.sudo('mv ...') but that did not work for the "typical"
|
||||||
c.sudo('psql --echo-errors --file={0}.sql {0}'.format(name), user='postgres')
|
# scenario of connecting as rattail@server to obtain db dump, since the dump
|
||||||
c.run('rm {}.sql'.format(name))
|
# 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))
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2020 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -26,31 +26,44 @@ Fabric Library for Python
|
||||||
|
|
||||||
from contextlib import contextmanager
|
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,
|
virtualenvwrapper_from_apt=False,
|
||||||
upgrade_virtualenvwrapper=True,
|
upgrade_virtualenvwrapper=True,
|
||||||
workon_home='/srv/envs', user='rattail'):
|
workon_home='/srv/envs',
|
||||||
|
user='rattail',
|
||||||
|
python3=False):
|
||||||
"""
|
"""
|
||||||
Bootstrap a "complete" Python install.
|
Bootstrap a "complete" Python install.
|
||||||
"""
|
"""
|
||||||
# build dependencies
|
# build dependencies
|
||||||
apt.install(
|
apt.install(
|
||||||
c,
|
c,
|
||||||
'python-dev',
|
|
||||||
'python3-dev',
|
'python3-dev',
|
||||||
|
'python3-venv',
|
||||||
'libffi-dev',
|
'libffi-dev',
|
||||||
'libjpeg-dev',
|
'libjpeg-dev',
|
||||||
'libssl-dev',
|
'libssl-dev',
|
||||||
)
|
)
|
||||||
|
if not python3:
|
||||||
|
apt.install(c, 'python-dev')
|
||||||
|
|
||||||
# pip
|
# 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
|
# virtualenvwrapper
|
||||||
workon_home = workon_home.rstrip('/')
|
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,
|
use_apt=virtualenvwrapper_from_apt,
|
||||||
upgrade=upgrade_virtualenvwrapper,
|
upgrade=upgrade_virtualenvwrapper,
|
||||||
configure_me=False)
|
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):
|
def install_pythonz(c):
|
||||||
|
@ -72,6 +86,7 @@ def install_pythonz(c):
|
||||||
'curl',
|
'curl',
|
||||||
# these are needed when building python:
|
# these are needed when building python:
|
||||||
'libsqlite3-dev',
|
'libsqlite3-dev',
|
||||||
|
'libssl-dev',
|
||||||
'zlib1g-dev',
|
'zlib1g-dev',
|
||||||
)
|
)
|
||||||
if not exists(c, '/usr/local/pythonz'):
|
if not exists(c, '/usr/local/pythonz'):
|
||||||
|
@ -83,7 +98,23 @@ def install_pythonz(c):
|
||||||
c.sudo('/usr/local/src/pythonz/pythonz-install')
|
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.
|
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
|
symlink, if installed, will use the "short" version, e.g. if the
|
||||||
``version`` specified is ``'3.5.3'`` then the symlink will be named
|
``version`` specified is ``'3.5.3'`` then the symlink will be named
|
||||||
``'python3.5'``.
|
``'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)):
|
if not exists(c, '/usr/local/pythonz/pythons/CPython-{}'.format(version)):
|
||||||
|
clang = 'CC=clang' if use_clang else ''
|
||||||
verbose = '--verbose' if verbose 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:
|
if globally:
|
||||||
short_version = '.'.join(version.split('.')[:2])
|
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(
|
c.sudo('ln -sf /usr/local/pythonz/pythons/CPython-{0}/bin/python{1} /usr/local/bin/python{1}'.format(
|
||||||
version, short_version))
|
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.
|
Install/upgrade the Pip installer for Python.
|
||||||
"""
|
"""
|
||||||
if use_apt:
|
# first check for existing pip; we do nothing if already present
|
||||||
apt.install(c, 'python-pip')
|
pip_ = 'pip3' if python3 else 'pip2'
|
||||||
else:
|
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')
|
apt.install(c, 'build-essential', 'python-dev', 'libssl-dev', 'libffi-dev')
|
||||||
if c.run('which pip', warn=True).failed:
|
if c.run('which pip', warn=True).failed:
|
||||||
apt.install(c, 'python-pkg-resources', 'python-setuptools')
|
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)
|
mkdir(c, workon_home, owner=user, use_sudo=True)
|
||||||
if use_apt:
|
if use_apt:
|
||||||
apt.install(c, 'virtualenvwrapper')
|
apt.install(c, 'virtualenvwrapper')
|
||||||
|
configure_virtualenvwrapper(c, user, workon_home,
|
||||||
|
wrapper='/usr/share/virtualenvwrapper/virtualenvwrapper.sh')
|
||||||
else:
|
else:
|
||||||
pip(c, 'virtualenvwrapper', upgrade=upgrade)
|
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:
|
if configure_me:
|
||||||
# TODO
|
# TODO
|
||||||
# configure_virtualenvwrapper(c, env.user, workon_home)
|
# 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',
|
def configure_virtualenvwrapper(c, user, workon_home='/srv/envs',
|
||||||
|
# TODO: this default should change, per apt
|
||||||
wrapper='/usr/local/bin/virtualenvwrapper.sh'):
|
wrapper='/usr/local/bin/virtualenvwrapper.sh'):
|
||||||
"""
|
"""
|
||||||
Configure virtualenvwrapper for the given user account.
|
Configure virtualenvwrapper for the given user account.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2018 Lance Edgar
|
# Copyright © 2010-2021 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -28,49 +28,94 @@ from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
import os
|
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.
|
Bootstrap a basic Rattail software environment.
|
||||||
"""
|
"""
|
||||||
make_system_user(c, 'rattail', home=home, uid=uid, shell=shell)
|
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)
|
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, '/etc/rattail', use_sudo=True)
|
||||||
mkdir(c, '/srv/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, '/var/log/rattail', owner='rattail:rattail', mode='0775', use_sudo=True)
|
||||||
|
|
||||||
mkdir(c, '/srv/rattail/init', use_sudo=True)
|
mkdir(c, '/srv/rattail/init', use_sudo=True)
|
||||||
deploy(c, 'daemon', '/srv/rattail/init/daemon', use_sudo=True)
|
deploy_common(c, 'check-rattail-daemon', '/usr/local/bin/check-rattail-daemon', use_sudo=True)
|
||||||
deploy(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(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(c, 'check-systemd-service', '/usr/local/bin/check-systemd-service', use_sudo=True)
|
deploy_common(c, 'soffice', '/srv/rattail/init/soffice', 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)
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
Deploy the standard machine-wide ``rattail.conf`` file.
|
||||||
"""
|
"""
|
||||||
mkdir(c, '/etc/rattail', use_sudo=True)
|
mkdir(c, '/etc/rattail', use_sudo=True)
|
||||||
deploy(c, 'rattail/rattail.conf.mako', '/etc/rattail/rattail.conf', use_sudo=True,
|
context['env'] = env
|
||||||
context={'timezone': timezone})
|
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):
|
def delete_email_recipients(c, dbname):
|
||||||
"""
|
"""
|
||||||
Purge all email recipient settings for the given database.
|
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.%.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.%.cc';", database=dbname)
|
||||||
postgresql.sql(c, "delete from setting where name like 'rattail.mail.%.bcc';", 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.
|
Disable all emails for the given database.
|
||||||
"""
|
"""
|
||||||
postgresql.sql(c, "update setting set value = 'false' where name like 'rattail.mail.%.enabled';", database=dbname)
|
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)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2019 Lance Edgar
|
# Copyright © 2010-2020 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# 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.
|
Cache the SSH host key for the given host, for the given user.
|
||||||
"""
|
"""
|
||||||
cmd = 'ssh -o StrictHostKeyChecking=no {} echo'.format(host)
|
if 'for_user' in kwargs:
|
||||||
user = None if for_user == 'root' else for_user
|
pass # TODO: deprecation warning
|
||||||
c.sudo(cmd, user=user, warn=True)
|
|
||||||
|
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):
|
def restart(c):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2019 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# 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
|
func = use_sudo and c.sudo or c.run
|
||||||
# Normalize non-list input to be a list
|
# 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):
|
if isinstance(text, str):
|
||||||
text = [text]
|
text = [text]
|
||||||
for line in 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)
|
after = after.replace(char, r'\%s' % char)
|
||||||
if limit:
|
if limit:
|
||||||
limit = r'/%s/ ' % limit
|
limit = r'/%s/ ' % limit
|
||||||
|
|
||||||
|
# if replacement text contains single quote chars, must escape them
|
||||||
|
after = after.replace("'", "'\"'\"'")
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'script': r"'%ss/%s/%s/%sg'" % (limit, before, after, flags),
|
'script': r"'%ss/%s/%s/%sg'" % (limit, before, after, flags),
|
||||||
'filename': _expand_path(c, filename),
|
'filename': _expand_path(c, filename),
|
||||||
|
|
97
setup.py
97
setup.py
|
@ -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,
|
|
||||||
)
|
|
24
tasks.py
24
tasks.py
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2020 Lance Edgar
|
# Copyright © 2010-2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,16 +24,25 @@
|
||||||
Tasks for rattail-fabric2
|
Tasks for rattail-fabric2
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from invoke import task
|
from invoke import task
|
||||||
|
|
||||||
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
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
|
@task
|
||||||
|
@ -41,9 +50,10 @@ def release(c):
|
||||||
"""
|
"""
|
||||||
Release a new version of 'rattail-fabric2'.
|
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: this seems heavy-handed? for sake of recursive-include in MANIFEST
|
||||||
# TODO: what i esp. don't like is, this doesn't consider .gitignore
|
# TODO: what i esp. don't like is, this doesn't consider .gitignore
|
||||||
c.run("find . -name '*~' -delete")
|
c.run("find . -name '*~' -delete")
|
||||||
c.run('python setup.py sdist --formats=gztar')
|
c.run('python -m build --sdist')
|
||||||
c.run('twine upload dist/rattail-fabric2-{}.tar.gz'.format(__version__))
|
c.run(f'twine upload dist/rattail_fabric2-{__version__}.tar.gz')
|
||||||
|
|
Loading…
Reference in a new issue