File tuptime-5.2.4.obscpio of Package tuptime

07070100000000000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001600000000tuptime-5.2.4/.github07070100000001000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000002000000000tuptime-5.2.4/.github/workflows07070100000002000081A40000000000000000000000016693DA1900000937000000000000000000000000000000000000003400000000tuptime-5.2.4/.github/workflows/codeql-analysis.yml# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
# ******** NOTE ********

name: "CodeQL"

on:
  push:
    branches: [ dev ]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [ dev ]
  schedule:
    - cron: '16 07 * * 0'

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        language: [ 'python' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
        # Learn more...
        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection

    steps:
    - name: Checkout repository
      uses: actions/checkout@v2

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v2
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.
        # queries: ./path/to/local/query, your-org/your-repo/queries@main

    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
    # If this step fails, then you should remove it and run the build manually (see below)
    - name: Autobuild
      uses: github/codeql-action/autobuild@v2

    # ℹ️ Command-line programs to run using the OS shell.
    # 📚 https://git.io/JvXDl

    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
    #    and modify them (or add more) to build your code if your project
    #    uses a compiled language

    #- run: |
    #   make bootstrap
    #   make release

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v2
07070100000003000081A40000000000000000000000016693DA1900002B47000000000000000000000000000000000000001800000000tuptime-5.2.4/CHANGELOGtuptime (5.2.4) unstable; urgency=low

  * Move files from /lib to /usr/lib in DEB package
  * Replace adduser with sysusers.d in DEB package
  * Update documentation
  * Minnor code refactoring

 -- Ricardo Fraile <r@rfmoz.eu>  Sat, 13 Jul 2024 13:21:00 +0100

tuptime (5.2.3) unstable; urgency=low

  * Update distribution packages
  * Documentation revised
  * Minnor code refactoring

 -- Ricardo Fraile <r@rfmoz.eu>  Fri, 05 Jan 2024 10:32:00 +0100

tuptime (5.2.2) unstable; urgency=low

  * Swich between longest/shortest on default output with -i
  * Option -x, --silent renamed to -q, --quiet
  * Change repository name

 -- Ricardo Fraile <r@rfmoz.eu>  Thu, 05 Jan 2023 20:17:00 +0100

tuptime (5.2.1) unstable; urgency=low

  * Set cron file with systemd execute exclusion on .deb package
  * Return -e option as an argument for --dec decimals
  * Update .deb package
  * Update .rpm package

 -- Ricardo Fraile <rfraile@rfraile.eu>  Fri, 19 Aug 2022 16:21:00 +0100

tuptime (5.2.0) unstable; urgency=low

  * Rename timer units to tuptime-sync.timer and tuptime-sync.service
  * Using StateDirectory= on systemd unit
  * Available tuptime.sysusers file for systemd-sysusers
  * Replace cron file with systemd timer on .deb package
  * Add rc script for OpenBSD and update installation doc
  * Deprecate --pctl option
  * Adding -E, --exclude option
  * Print longest uptime and downtime values on default output
  * Update distribution packages
  * Code refactoring

 -- Ricardo Fraile <rfraile@rfraile.eu>  Tue, 02 Aug 2022 21:55:00 +0100

tuptime (5.1.0) unstable; urgency=low

  * Cover DB writes with transactions
  * Register DB version on PRAGMA user_version
  * Adding --pctl option. Show percentil over average values
  * Code refactoring
  * Update .deb package

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sun, 16 Jan 2022 13:09:00 +0100

tuptime (5.0.2) unstable; urgency=low

  * Option --decp usable but deprecated
  * Using subprocess module on BSDs

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 02 Jan 2021 09:01:00 +0100

tuptime (5.0.1) unstable; urgency=low

  * Identify new startups with boot_id on FreeBSD
  * Option --decp renamed to --dec and -e equal to it.
  * Minnor code refactoring

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 31 Oct 2020 15:06:00 +0100

tuptime (5.0.0) unstable; urgency=low

  * Abbreviate format in time counters
  * Identify new startups with boot_id on Linux
  * Adding -b, --bootid option to show boot IDs
  * Adding -i, --invert option to show startup number in reverse count
  * Change database specs. Add bootid column
  * After a graceful shutdown register, delay 5 seconds next DB update
  * Change execution user from 'tuptime' to '_tuptime'
  * Large refactoring
  * Update .deb package

 -- Ricardo Fraile <rfraile@rfraile.eu>  Tue, 26 May 2020 19:58:00 +0100

tuptime (4.1.0) unstable; urgency=low

  * Register shutdowns lower than a second
  * Adding option -A --at
  * Refactoring --tat output
  * Rename suspend time to sleep time

 -- Ricardo Fraile <rfraile@rfraile.eu>  Wed, 01 Jan 2020 17:03:00 +0100

tuptime (4.0.0) unstable; urgency=low

  * Adding support for running and suspended time. (-p, --power)
  * Adding --tat to report system state at specific timestamp.
  * Change database specs. Add rntime and spdtime columns.
  * Update .deb package

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sun, 28 Jul 2019 20:12:00 +0100

tuptime (3.5.0) unstable; urgency=low

  * Avoid execution if the startup date is prior to 01/Jan/2000 00:00
  * Avoid correct drift when it decreases uptime under 0
  * Fix register shutdown with negative downtime
  * Minnor code refactoring

 -- Ricardo Fraile <rfraile@rfraile.eu>  Wed, 20 Feb 2019 17:02:00 +0100

tuptime (3.4.2) unstable; urgency=low

  * Fix Debian Bug Fails to upgrade (Closes: #914954)
  * Change deb architecture from any to all

 -- Ricardo Fraile <rfraile@rfraile.eu>  Fri, 07 Dec 2018 15:48:00 +0100

tuptime (3.4.1) unstable; urgency=low

  * Adding Launchd .plist
  * Code quality checker fixes
  * Update .deb package
  * Adding Uptimed migration script
  * Fix Debian Bug about real time definition (Closes: #912996)

 -- Ricardo Fraile <rfraile@rfraile.eu>  Fri, 23 Nov 2018 21:16:00 +0100

tuptime (3.4.0) unstable; urgency=low

  * Adding DragonflyBSD, OpenBSD and NetBSD support
  * Improve error report when checking db path and file
  * Arguments refactoring. Change deprecated module optparse to argpaser
  * Adding --decp option. Allow change decimal length in percentages
  * Fixed equal number of values per line in csv output
  * Fix right termination when SIGPIPE signal is received
  * Upgrade timer unit with tuptime-cron.timer and tuptime-cron.service
  * Adding OpenRC init script
  * Tuptime user without /bin/sh shell
  * Support environmental variable with DB file path

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sun, 23 Sep 2018 13:40:00 +0200

tuptime (3.3.3) unstable; urgency=low

  * Unify order arguments under -o
  * Improve behaviour when time drift
  * Requirement of time-sync in systemd unit
  * Warn about negative values produced by inconsistent time sync.
  * Validate right assignment of system variables
  * Update .deb package
  * Fix Debian Bug Files to install (Closes: #884955)
  * Python 3.x as requirement

 -- Ricardo Fraile <rfraile@rfraile.eu>  Wed, 03 Jan 2018 23:56:00 +0200

tuptime (3.3.2) unstable; urgency=low

  * Fix print kernel in list and table outputs in partial registers
  * Consistent decimals output when seconds are used in table and list
  * Accurate report in largest/shortest uptime/downtime with partial
    registers in narrow ranges
  * Avoid import subprocess module
  * Report in default output the state of last shutdown
  * Adding systemd .timer as alternative to cron schedule

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 21 Jan 2017 19:43:00 +0100

tuptime (3.3.1) unstable; urgency=medium

  * Adding dependency of lsb-base in debian package
  * Change to native debian package
  * Fix typo in doc

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 16 Jan 2017 19:48:27 -0200

tuptime (3.3.0) unstable; urgency=low

  * Refactoring code
  * Adding -S --since option
  * Adding -U --until option
  * Adding --tsince option
  * Adding --tuntil option
  * Adding -n --noup option
  * Adding --csv option
  * Fix inconsistencies with seconds reports due rounded decimals
  * Fix inconsistencies when system clock drift

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 12 Mar 2016 10:51:00 +0100

tuptime (3.2.3) unstable; urgency=low

  * Refactoring code
  * Change execution user from root to tuptime
  * Equivalence between start/stop runlevels in systemd and init.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 12 Dec 2015 22:40:00 +0100

tuptime (3.2.2) unstable; urgency=low

  * Adding compatibility with OS X (Darwin)

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 28 Nov 2015 09:40:00 +0100

tuptime (3.2.01) stable; urgency=low

  * Detect database modification

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 12 Oct 2015 11:00:00 -0400

tuptime (3.2.00) stable; urgency=low

  * Print singular names if is the case
  * Fix round and decimals values
  * Clean and order code

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 05 Oct 2015 11:00:00 -0400

tuptime (3.1.00) stable; urgency=low

  * Register used kernels
  * Adding kernel option

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 28 Sep 2015 11:00:00 -0400

tuptime (3.0.00) stable; urgency=low

  * Change printed output format
  * Adding max/min uptime/downtime reports
  * Adding table format output
  * Rename enumerate option to list and change output format
  * Adding sorting options
  * Change database schema
  * Clean and order code

 -- Ricardo Fraile <rfraile@rfraile.eu>  Fri, 25 Sep 2015 11:00:00 -0400

tuptime (2.6.10) stable; urgency=low

  * Compatibility between python2.7 and python3.X
  * Removing unused modules

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 10 Aug 2015 11:00:00 -0400

tuptime (2.5.20) stable; urgency=low

  * Setting data type for ok/bad shutdown in db creation

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 08 Aug 2015 11:00:00 -0400

tuptime (2.5.12) stable; urgency=low

  * Setting max decimals in the output
  * Fix data type for the ok/bad shutdown registry in db which cause incorrect
    counter output

 -- Ricardo Fraile <rfraile@rfraile.eu>  Fri, 07 Aug 2015 11:00:00 -0400

tuptime (2.5.00) stable; urgency=low

  * Fix false shutdown count and correct locale date format

 -- Ricardo Fraile <rfraile@rfraile.eu>  Tue, 04 Aug 2015 11:00:00 -0400

tuptime (2.4.26) stable; urgency=low

  * Fix from other point of view false shutdown bug

 -- Ricardo Fraile <rfraile@rfraile.eu>  Tue, 04 Aug 2015 10:00:00 -0400

tuptime (2.4.10) stable; urgency=low

  * Fix false wrong shutdown bug when some kernels rarely report +1 or -1
    second inside /proc/stat btime variable

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sun, 26 Jul 2015 10:00:00 -0400

tuptime (2.4.00) stable; urgency=low

  * Adding downtimes reports
  * Adding enumerate option
  * Fix bad shutdowns count

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 20 Jul 2015 10:00:00 -0400

tuptime (2.2.00) stable; urgency=low

  * Completely rewritten in python

 -- Ricardo Fraile <rfraile@rfraile.eu>  Wed, 06 May 2015 10:00:00 -0400

tuptime (1.6.2) stable; urgency=low

  * New init script for debian 7 wheezy

 -- Ricardo Fraile <rfraile@rfraile.eu>  Thu, 14 May 2013 10:00:00 -0400

tuptime (1.6.0) stable; urgency=low

  * Remove usage of syslog for more quick output.
  * Change output.
  * Print rate in output.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Thu, 17 Jan 2012 10:00:00 -0400

tuptime (1.5.0) stable; urgency=low

  * Print estimated uptime between starts.
  * Print actual uptime for the system.
  * Fix and improve code, remove repetitive lines and minnor bugs.
  * Check if the variable in the conf file is a number.
  * Start using Scalar::Util module.
  * Print system uptime date.
  * Fix bug in first update

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 10 Oct 2011 10:00:00 -0400

tuptime (1.4.0) stable; urgency=low

  * Print time more accurate.
  * Cron line change to 5 minutes.
  * Print time values in live time without update.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 06 Aug 2011 10:00:00 -0400

tuptime (1.3.0) stable; urgency=low

  * Jump blank lines in conf file.
  * Adding #REPLACEAT feature.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 20 Jun 2011 10:00:00 -0400

tuptime (1.2.0) stable; urgency=low

  * Change speak option to verbose.
  * Fix lost information in files when update.
  * Minnor corrections in text strings.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 13 Jun 2011 10:00:00 -0400

tuptime (1.1.0) stable; urgency=low

  * Any user can print the times, not only root.
  * Minnor changes to speak option.
  * Minnor corrections.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Fri, 10 Jun 2011 10:00:00 -0400

tuptime (1.0.0) stable; urgency=low

  * First release.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Wed, 23 Mar 2011 10:02:18 -0400
07070100000004000081A40000000000000000000000016693DA190000004D000000000000000000000000000000000000001E00000000tuptime-5.2.4/CONTRIBUTING.md# Contributing to Tuptime

Any pull request will go to "dev" branch. Thanks.
07070100000005000081A40000000000000000000000016693DA190000467F000000000000000000000000000000000000001600000000tuptime-5.2.4/LICENSE                    GNU GENERAL PUBLIC LICENSE
                       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

                    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

                            NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    {description}
    Copyright (C) {year}  {fullname}

    This program 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 2 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

    Gnomovision version 69, Copyright (C) year name of author
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  {signature of Ty Coon}, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

07070100000006000081A40000000000000000000000016693DA190000180B000000000000000000000000000000000000001800000000tuptime-5.2.4/README.mdtuptime
=======

Tuptime reports the historical and statistical real time of the system, preserving it between restarts. Like uptime command, but with more interesting output.


### Sample output

Just after install:

	System startups:        1  since  21:54:09 24/09/15
	System shutdowns:       0 ok  +  0 bad
	System life:            21m 30s

	Longest uptime:         21m 30s  from  21:54:09 24/09/15
	Average uptime:         21m 30s
	System uptime:          100.0%  =  21m 30s

	Longest downtime:	0s
	Average downtime:       0s
	System downtime:        0.0%  =  0s

	Current uptime:         21m 30s  since  21:54:09 24/09/15

A few days later:

	System startups:        110  since  10:15:27 08/08/15
	System shutdowns:       107 ok  +  2 bad
	System life:            47d 12h 2m 15s

	Longest uptime:         2h 10m 44s  from  20:49:17 09/08/15
	Average uptime:         25m 8s
	System uptime:          4.04%  =  1d 22h 4m 44s

	Longest downtime:	7d 10h 17m 26s  from  06:09:45 10/08/15
	Average downtime:       9h 56m 42s
	System downtime:        95.96%  =  45d 13h 57m 30s

	Current uptime:         23m 33s  since  21:54:09 24/09/15

Swich to -t | --table option:

	No.        Startup T.        Uptime         Shutdown T.   End    Downtime
                                                                                                                                    
	1   10:15:27 08/08/15           42s   10:16:09 08/08/15    OK         16s
	2   10:16:26 08/08/15           49s   10:17:15 08/08/15    OK         16s
	3   10:17:32 08/08/15        5m 47s   10:23:19 08/08/15    OK         16s
	4   10:23:36 08/08/15            9s   10:23:45 08/08/15   BAD         42s
	5   10:24:28 08/08/15     2h 9m 27s   12:33:55 08/08/15    OK     41m 44s
        . . .

Or swich to -l | --list option:

	Startup:  1  at  10:15:27 08/08/15
	Uptime:   42s
	Shutdown: OK  at  10:16:09 08/08/15
	Downtime: 16s

	Startup:  2  at  10:16:26 08/08/15
	Uptime:   49s
	Shutdown: OK  at  10:17:15 08/08/15
	Downtime: 16s

	Startup:  3  at  10:17:32 08/08/15
	Uptime:   5m 47s
	Shutdown: OK  at  10:23:19 08/08/15
	Downtime: 16s
	. . .


### Basic Installation


#### By package manager

* Debian: https://packages.debian.org/tuptime
* Ubuntu: https://packages.ubuntu.com/tuptime
* Fedora, EPEL: https://src.fedoraproject.org/rpms/tuptime
* FreeBSD: https://www.freshports.org/sysutils/tuptime
* Archlinux: https://aur.archlinux.org/packages/tuptime
* OpenSUSE: https://software.opensuse.org/package/tuptime (Community Maintained / Unofficial)

#### By one-liner script

	bash < <(curl -Ls https://git.io/tuptime-install.sh)


#### By manual method

Briefly in a Linux or FreeBSD system...

Clone the repo:

	git clone --depth=1 https://github.com/rfmoz/tuptime.git

Copy the 'tuptime' file located under 'latest/' directory to '/usr/bin/' and make it executable:

	cp tuptime/src/tuptime /usr/bin/tuptime
	chmod ugo+x /usr/bin/tuptime

Assure that the system pass the prerequisites:

	python 3.X 

Run first with a privileged user:

	tuptime

Pick from 'src/' folder the right file for your cron and init manager, setup both
properly. See 'tuptime-manual.txt' for more information.


### Highlights about Tuptime internals

- It doesn't run as a daemon, at least, it only needs execution when the init manager startup and shutdown the system. To avoid issues with a switch off without a proper shutdown, like power failures, a cron job and a .timer unit are shipped with the project to update the registers each n minutes. As a system administrator, you can easily choose the best number for your particular system requirements.

- It is written in Python using common modules and as few as possible, quick execution, easy to see what is inside it, and modify it for fit for your particular use case.

- It registers the times in a sqlite database. Any other software can use it. The specs are in the tuptime-manual.txt. Also, it has the option to output the registers in seconds and epoch or/and in csv format, easy to pipe it to other commands.

- Its main purpose is tracking all the system startups/shutdowns and present that information to the user in a more understandable way. Don't have mail alerts when a milestones are reached or the limitation of keep the last n records.

- It's written to avoid false startups registers. This is an issue that sometimes happens when the NTP adjust the system clock, on virtualized environments, on servers with high load, when the system resynchronized with their RTC clock after a suspend and resume cycle...

- It can report:
  - Registers as a table or list ordering by any label.
  - The whole life of the system or only a part of it, closing the range between startups/shutdowns or timestamps.
  - Accumulated running and sleeping time over an uptime.
  - The kernel version used and boot idenfiers.
  - The system state at specific point in time.


### Alternatives

journalctl --list-boots - Show a tabular list of boot numbers (relative to the current boot), their IDs, and the timestamps of the first and last message pertaining to the boot. Close output than 'tuptime  -bit'.
https://github.com/systemd/systemd/

uptimed - Is an uptime record daemon keeping track of the highest uptimes a computer system ever had. It uses the system boot time to keep sessions apart from each other.
https://github.com/rpodgorny/uptimed

downtimed - Is a program for monitoring operating system downtime, uptime, shutdowns and crashes and for keeping record of such events.
https://dist.epipe.com/downtimed/

lastwake - Analyzes the system journal and prints out wake-up and sleep timestamps; for each cycle it tells whether the system was suspended to RAM or to disk (hibernated).
https://github.com/arigit/lastwake.py

(bonus) dateutils - Not an alternative, but it is a nifty collection of tools to work with dates.
https://github.com/hroptatyr/dateutils

ruptime - Is a modern rwhod replacement that is easy to customize, not limited to a network, and does not send clear text data over the network.
https://github.com/alexmyczko/ruptime


### More information

Please, read tuptime-manual.txt for a complete reference guide.
07070100000007000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001500000000tuptime-5.2.4/debian07070100000008000081A40000000000000000000000016693DA1900002EB5000000000000000000000000000000000000001F00000000tuptime-5.2.4/debian/changelogtuptime (5.2.4) unstable; urgency=low

  * Move files from /lib to /usr/lib in DEB package
  * Replace adduser with sysusers.d in DEB package
  * Update documentation
  * Minnor code refactoring

 -- Ricardo Fraile <r@rfmoz.eu>  Sat, 13 Jul 2024 13:21:00 +0100

tuptime (5.2.3) unstable; urgency=low

  * Update distribution packages
  * Documentation revised
  * Minnor code refactoring

 -- Ricardo Fraile <r@rfmoz.eu>  Fri, 05 Jan 2024 10:32:00 +0100

tuptime (5.2.2) unstable; urgency=low

  * Swich between longest/shortest on default output with -i
  * Option -x, --silent renamed to -q, --quiet
  * Change repository name

 -- Ricardo Fraile <r@rfmoz.eu>  Thu, 05 Jan 2023 20:17:00 +0100

tuptime (5.2.1) unstable; urgency=low

  * Set cron file with systemd execute exclusion on .deb package
  * Return -e option as an argument for --dec decimals
  * Update .deb package
  * Update .rpm package

 -- Ricardo Fraile <rfraile@rfraile.eu>  Fri, 19 Aug 2022 16:21:00 +0100

tuptime (5.2.0) unstable; urgency=low

  * Rename timer units to tuptime-sync.timer and tuptime-sync.service
  * Using StateDirectory= on systemd unit
  * Available tuptime.sysusers file for systemd-sysusers
  * Replace cron file with systemd timer on .deb package
  * Add rc script for OpenBSD and update installation doc
  * Deprecate --pctl option
  * Adding -E, --exclude option
  * Print longest uptime and downtime values on default output
  * Update distribution packages
  * Code refactoring

 -- Ricardo Fraile <rfraile@rfraile.eu>  Tue, 02 Aug 2022 21:55:00 +0100

tuptime (5.1.0) unstable; urgency=low

  * Cover DB writes with transactions
  * Register DB version on PRAGMA user_version
  * Adding --pctl option. Show percentil over average values
  * Code refactoring
  * Update .deb package

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sun, 16 Jan 2022 13:09:00 +0100

tuptime (5.0.2) unstable; urgency=low

  * Option --decp usable but deprecated
  * Using subprocess module on BSDs

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 02 Jan 2021 09:01:00 +0100

tuptime (5.0.1) unstable; urgency=low

  * Identify new startups with boot_id on FreeBSD
  * Option --decp renamed to --dec and -e equal to it.
  * Minnor code refactoring

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 31 Oct 2020 15:06:00 +0100

tuptime (5.0.0) unstable; urgency=low

  * Abbreviate format in time counters
  * Identify new startups with boot_id
  * Adding -b, --bootid option to show boot IDs
  * Adding -i, --invert option to show startup number in reverse count
  * Change database specs. Add bootid column
  * After a graceful shutdown register, delay 5 seconds next DB update
  * Change execution user from 'tuptime' to '_tuptime'
  * Large refactoring
  * Update .deb package

 -- Ricardo Fraile <rfraile@rfraile.eu>  Tue, 26 May 2020 19:58:00 +0100

tuptime (4.1.0) unstable; urgency=low

  * Register shutdowns lower than a second
  * Adding option -A --at
  * Refactoring --tat output
  * Rename suspend time to sleep time

 -- Ricardo Fraile <rfraile@rfraile.eu>  Wed, 01 Jan 2020 17:03:00 +0100

tuptime (4.0.0) unstable; urgency=low

  * Adding support for running and suspended time. (-p, --power)
  * Adding --tat to report system state at specific timestamp.
  * Change database specs. Add rntime and spdtime columns.
  * Update .deb package

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sun, 28 Jul 2019 20:12:00 +0100

tuptime (3.5.0) unstable; urgency=low

  * Avoid execution if the startup date is prior to 01/Jan/2000 00:00
  * Avoid correct drift when it decreases uptime under 0
  * Fix register shutdown with negative downtime
  * Minnor code refactoring

 -- Ricardo Fraile <rfraile@rfraile.eu>  Wed, 20 Feb 2019 17:02:00 +0100

tuptime (3.4.2) unstable; urgency=low

  * Fix Debian Bug Fails to upgrade (Closes: #914954)
  * Change deb architecture from any to all

 -- Ricardo Fraile <rfraile@rfraile.eu>  Fri, 07 Dec 2018 15:48:00 +0100

tuptime (3.4.1) unstable; urgency=low

  * Adding Launchd .plist
  * Code quality checker fixes
  * Update .deb package
  * Adding Uptimed migration script
  * Fix Debian Bug about real time definition (Closes: #912996)

 -- Ricardo Fraile <rfraile@rfraile.eu>  Fri, 23 Nov 2018 21:16:00 +0100

tuptime (3.4.0) unstable; urgency=low

  * Adding DragonflyBSD, OpenBSD and NetBSD support
  * Improve error report when checking db path and file
  * Arguments refactoring. Change deprecated module optparse to argpaser
  * Adding --decp option. Allow change decimal length in percentages
  * Fixed equal number of values per line in csv output
  * Fix right termination when SIGPIPE signal is received
  * Upgrade timer unit with tuptime-cron.timer and tuptime-cron.service
  * Adding OpenRC init script
  * Tuptime user without /bin/sh shell
  * Support environmental variable with DB file path

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sun, 23 Sep 2018 13:40:00 +0200

tuptime (3.3.3) unstable; urgency=low

  * Unify order arguments under -o
  * Improve behaviour when time drift
  * Requirement of time-sync in systemd unit
  * Warn about negative values produced by inconsistent time sync.
  * Validate right assignment of system variables
  * Update .deb package
  * Fix Debian Bug Files to install (Closes: #884955)
  * Python 3.x as requirement

 -- Ricardo Fraile <rfraile@rfraile.eu>  Wed, 03 Jan 2018 23:56:00 +0200

tuptime (3.3.2) unstable; urgency=low

  * Fix print kernel in list and table outputs in partial registers
  * Consistent decimals output when seconds are used in table and list
  * Accurate report in largest/shortest uptime/downtime with partial
    registers in narrow ranges
  * Avoid import subprocess module
  * Report in default output the state of last shutdown
  * Adding systemd .timer as alternative to cron schedule

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 21 Jan 2017 19:43:00 +0100

tuptime (3.3.1) unstable; urgency=medium

  * Adding dependency of lsb-base in debian package
  * Change to native debian package
  * Fix typo in doc

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 16 Jan 2017 19:48:27 -0200

tuptime (3.3.0-2) unstable; urgency=medium

  * Remove creation of /var/lib/tuptime from debian/dirs and fall only in
    postinstall adduser command. (Closes: #821725)

 -- Ricardo Fraile <rfraile@rfraile.eu>  Tue, 19 Apr 2016 17:09:27 -0200

tuptime (3.3.0-1) unstable; urgency=medium

  [Antoine Beaupré]
  * Complete switch to non-native package
  * Fix build with git-buildpackage

 -- Ricardo Fraile <rfraile@rfraile.eu>  Tue, 12 Apr 2016 08:59:27 -0400

tuptime (3.3.0) unstable; urgency=low

  * Refactoring code
  * Adding -S --since option
  * Adding -U --until option
  * Adding --tsince option
  * Adding --tuntil option
  * Adding -n --noup option
  * Adding --csv option
  * Fix inconsistencies with seconds reports due rounded decimals
  * Fix inconsistencies when system clock drift

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 12 Mar 2016 10:51:00 +0100

tuptime (3.2.3) unstable; urgency=low

  * Refactoring code
  * Change execution user from root to tuptime
  * Equivalence between start/stop runlevels in systemd and init.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 12 Dec 2015 22:40:00 +0100

tuptime (3.2.2) unstable; urgency=low

  * Adding compatibility with OS X (Darwin)

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 28 Nov 2015 09:40:00 +0100

tuptime (3.2.01+git6db666b-2) unstable; urgency=medium

  * ship tuptime in /usr/bin, upstream will make it usable by non-root
    (Closes: #806380)

 -- Antoine Beaupré <anarcat@debian.org>  Fri, 27 Nov 2015 09:59:43 -0500

tuptime (3.2.01+git6db666b-1) unstable; urgency=low

  * Redo packaging based on upstream binary package. Closes: #638422

 -- Antoine Beaupré <anarcat@debian.org>  Wed, 25 Nov 2015 11:53:00 -0500

tuptime (3.2.01) stable; urgency=low

  * Detect database modification

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 12 Oct 2015 11:00:00 -0400

tuptime (3.2.00) stable; urgency=low

  * Print singular names if is the case
  * Fix round and decimals values
  * Clean and order code

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 05 Oct 2015 11:00:00 -0400

tuptime (3.1.00) stable; urgency=low

  * Register used kernels
  * Adding kernel option

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 28 Sep 2015 11:00:00 -0400

tuptime (3.0.00) stable; urgency=low

  * Change printed output format
  * Adding max/min uptime/downtime reports
  * Adding table format output
  * Rename enumerate option to list and change output format
  * Adding sorting options
  * Change database schema
  * Clean and order code

 -- Ricardo Fraile <rfraile@rfraile.eu>  Fri, 25 Sep 2015 11:00:00 -0400

tuptime (2.6.10) stable; urgency=low

  * Compatibility between python2.7 and python3.X
  * Removing unused modules

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 10 Aug 2015 11:00:00 -0400

tuptime (2.5.20) stable; urgency=low

  * Setting data type for ok/bad shutdown in db creation

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 08 Aug 2015 11:00:00 -0400

tuptime (2.5.12) stable; urgency=low

  * Setting max decimals in the output
  * Fix data type for the ok/bad shutdown registry in db which cause incorrect
    counter output

 -- Ricardo Fraile <rfraile@rfraile.eu>  Fri, 07 Aug 2015 11:00:00 -0400

tuptime (2.5.00) stable; urgency=low

  * Fix false shutdown count and correct locale date format

 -- Ricardo Fraile <rfraile@rfraile.eu>  Tue, 04 Aug 2015 11:00:00 -0400

tuptime (2.4.26) stable; urgency=low

  * Fix from other point of view false shutdown bug

 -- Ricardo Fraile <rfraile@rfraile.eu>  Tue, 04 Aug 2015 10:00:00 -0400

tuptime (2.4.10) stable; urgency=low

  * Fix false wrong shutdown bug when some kernels rarely report +1 or -1
    second inside /proc/stat btime variable

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sun, 26 Jul 2015 10:00:00 -0400

tuptime (2.4.00) stable; urgency=low

  * Adding downtimes reports
  * Adding enumerate option
  * Fix bad shutdowns count

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 20 Jul 2015 10:00:00 -0400

tuptime (2.2.00) stable; urgency=low

  * Completely rewritten in python

 -- Ricardo Fraile <rfraile@rfraile.eu>  Wed, 06 May 2015 10:00:00 -0400

tuptime (1.6.2) stable; urgency=low

  * New init script for debian 7 wheezy

 -- Ricardo Fraile <rfraile@rfraile.eu>  Thu, 14 May 2013 10:00:00 -0400

tuptime (1.6.0) stable; urgency=low

  * Remove usage of syslog for more quick output.
  * Change output.
  * Print rate in output.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Thu, 17 Jan 2012 10:00:00 -0400

tuptime (1.5.0) stable; urgency=low

  * Print estimated uptime between starts.
  * Print actual uptime for the system.
  * Fix and improve code, remove repetitive lines and minnor bugs.
  * Check if the variable in the conf file is a number.
  * Start using Scalar::Util module.
  * Print system uptime date.
  * Fix bug in first update

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 10 Oct 2011 10:00:00 -0400

tuptime (1.4.0) stable; urgency=low

  * Print time more accurate.
  * Cron line change to 5 minutes.
  * Print time values in live time without update.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Sat, 06 Aug 2011 10:00:00 -0400

tuptime (1.3.0) stable; urgency=low

  * Jump blank lines in conf file.
  * Adding #REPLACEAT feature.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 20 Jun 2011 10:00:00 -0400

tuptime (1.2.0) stable; urgency=low

  * Change speak option to verbose.
  * Fix lost information in files when update.
  * Minnor corrections in text strings.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Mon, 13 Jun 2011 10:00:00 -0400

tuptime (1.1.0) stable; urgency=low

  * Any user can print the times, not only root.
  * Minnor changes to speak option.
  * Minnor corrections.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Fri, 10 Jun 2011 10:00:00 -0400

tuptime (1.0.0) stable; urgency=low

  * First release.

 -- Ricardo Fraile <rfraile@rfraile.eu>  Wed, 23 Mar 2011 10:02:18 -0400
07070100000009000081A40000000000000000000000016693DA1900000229000000000000000000000000000000000000001D00000000tuptime-5.2.4/debian/controlSource: tuptime
Section: utils
Priority: optional
Maintainer: Ricardo Fraile <r@rfmoz.eu>
Build-Depends: debhelper-compat (= 13)
Rules-Requires-Root: no
Standards-Version: 4.7.0
Homepage: https://github.com/rfmoz/tuptime

Package: tuptime
Architecture: all
Multi-Arch: foreign
Pre-Depends: ${misc:Pre-Depends}
Depends: ${misc:Depends}, ${shlibs:Depends}
  , python3:any
Description: report historical system real time
 Tuptime tracks and reports historical and statistical real time of
 the system, preserving the uptime and downtime between shutdowns.
0707010000000A000081A40000000000000000000000016693DA19000003C6000000000000000000000000000000000000001F00000000tuptime-5.2.4/debian/copyrightFormat: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: tuptime
Source: https://github.com/rfmoz/tuptime

Files:     *
Copyright: 2011-2018 Ricardo F.
License:   GPL-2.0+
 This program 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 2 of the License, or
 (at your option) any later version.
 .
 This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 .
 On Debian systems, the complete text of the GNU General Public License
 Version 2 can be found in `/usr/share/common-licenses/GPL-2'.
0707010000000B000081A40000000000000000000000016693DA190000004F000000000000000000000000000000000000001E00000000tuptime-5.2.4/debian/gbp.conf[DEFAULT]
debian-branch=master
upstream-tag=%(version)s
debian-tag=%(version)s
0707010000000C000081A40000000000000000000000016693DA1900000429000000000000000000000000000000000000001E00000000tuptime-5.2.4/debian/postinst#!/bin/sh
# postinst script for tuptime
#
# see: dh_installdeb(1)

set -e

# summary of how this script can be called:
# 	 * <postinst> `configure' <most-recently-configured-version>
# 	 * <old-postinst> `abort-upgrade' <new version>
# 	 * <conflictor's-postinst> `abort-remove' `in-favour' <package>
# 	   <new-version>
# 	 * <postinst> `abort-remove'
# 	 * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
# 	   <failed-install-package> <version> `removing'
# 	   <conflicting-package> <version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package


case "$1" in
	configure)

		# Rename to underscore if previous exists
		getent passwd tuptime >/dev/null && usermod -l _tuptime tuptime
		getent group tuptime > /dev/null && groupmod -n _tuptime tuptime
	;;

	abort-upgrade|abort-remove|abort-deconfigure)
	;;

	*)
		echo "postinst called with unknown argument \`$1'" >&2
		exit 1
	;;
esac

# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.

#DEBHELPER#

exit 0
0707010000000D000081A40000000000000000000000016693DA190000039A000000000000000000000000000000000000001C00000000tuptime-5.2.4/debian/postrm#!/bin/sh
# postrm script for tuptime
#
# see: dh_installdeb(1)

set -e

# summary of how this script can be called:
# 	 * <postrm> `remove'
# 	 * <postrm> `purge'
# 	 * <old-postrm> `upgrade' <new-version>
# 	 * <new-postrm> `failed-upgrade' <old-version>
# 	 * <new-postrm> `abort-install'
# 	 * <new-postrm> `abort-install' <old-version>
# 	 * <new-postrm> `abort-upgrade' <old-version>
# 	 * <disappearer's-postrm> `disappear' <overwriter>
# 	   <overwriter-version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package


case "$1" in
	purge|disappear)
		rm -rf /var/lib/tuptime/
		deluser _tuptime || true
	;;

	remove|upgrade|failed-upgrade|abort-install|abort-upgrade)
	;;

	*)
		echo "postrm called with unknown argument \`$1'" >&2
		exit 1
	;;
esac

# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.

#DEBHELPER#

exit 0
0707010000000E000081ED0000000000000000000000016693DA190000017E000000000000000000000000000000000000001B00000000tuptime-5.2.4/debian/rules#!/usr/bin/make -f
# You must remove unused comment lines for the released package.
#export DH_VERBOSE = 1
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
#export DEB_CFLAGS_MAINT_APPEND  = -Wall -pedantic
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed

override_dh_installinit:
	dh_installinit --onlyscripts
	# this is needed until dh compat 14
	dh_installsysusers

%:
	dh $@
0707010000000F000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001C00000000tuptime-5.2.4/debian/source07070100000010000081A40000000000000000000000016693DA190000000D000000000000000000000000000000000000002300000000tuptime-5.2.4/debian/source/format3.0 (native)
07070100000011000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001B00000000tuptime-5.2.4/debian/tests07070100000012000081A40000000000000000000000016693DA190000008E000000000000000000000000000000000000002300000000tuptime-5.2.4/debian/tests/controlTests: exec-simple
Depends: @
Restrictions: isolation-container

Tests: exec-debug
Depends: @
Restrictions: isolation-container, allow-stderr
07070100000013000081A40000000000000000000000016693DA190000001E000000000000000000000000000000000000002600000000tuptime-5.2.4/debian/tests/exec-debug#!/bin/sh

set -e

tuptime -v
07070100000014000081A40000000000000000000000016693DA190000001B000000000000000000000000000000000000002700000000tuptime-5.2.4/debian/tests/exec-simple#!/bin/sh

set -e

tuptime
07070100000015000081A40000000000000000000000016693DA190000011C000000000000000000000000000000000000002400000000tuptime-5.2.4/debian/tuptime.cron.d# /etc/cron.d/tuptime: crontab entry for tuptime update. 

# NOTE: Decrease the execution time for increase accuracity.

# Skip in favour of systemd timer

*/5 * * * *   _tuptime    if [ ! -d /run/systemd/system ] && [ -x /usr/bin/tuptime ]; then /usr/bin/tuptime -q > /dev/null; fi

07070100000016000081A40000000000000000000000016693DA1900000013000000000000000000000000000000000000002200000000tuptime-5.2.4/debian/tuptime.docstuptime-manual.txt
07070100000017000081A40000000000000000000000016693DA1900000117000000000000000000000000000000000000002500000000tuptime-5.2.4/debian/tuptime.installsrc/systemd/tuptime.service usr/lib/systemd/system/
src/systemd/tuptime-sync.service usr/lib/systemd/system/
src/systemd/tuptime-sync.timer usr/lib/systemd/system/
src/systemd/sysusers.d/tuptime.conf usr/lib/sysusers.d/
src/init.d/debian/tuptime etc/init.d/
src/tuptime usr/bin/
07070100000018000081A40000000000000000000000016693DA1900000012000000000000000000000000000000000000002600000000tuptime-5.2.4/debian/tuptime.manpagessrc/man/tuptime.1
07070100000019000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001300000000tuptime-5.2.4/misc0707010000001A000081A40000000000000000000000016693DA1900000DFB000000000000000000000000000000000000002A00000000tuptime-5.2.4/misc/bsd-manual-install.txt====================================
| FreeBSD manual install - Tuptime |
====================================


Install dependencies, latest versions:

    # pkg install python3 git
    # pkg install py39-sqlite3  # (version number must match "python3 -V")

Clone repository and copy executable file:

    # git clone --depth=1 https://github.com/rfmoz/tuptime.git
    # install -m 755 tuptime/src/tuptime /usr/local/bin/tuptime

Add tuptime user:

    # pw adduser _tuptime -d /nonexistent -s /usr/sbin/nologin -c "Tuptime execution user"

Execute tuptime with a privileged user for create db path:

    # tuptime

Change owner of the db path and file:

    # chown -R _tuptime:_tuptime /var/lib/tuptime

Add the cron entry, create a '/usr/local/etc/cron.d/tuptime' file with this content:

    */5 * * * * _tuptime (/usr/sbin/service tuptime enabled) && /usr/local/bin/tuptime -q

Copy rc.d file:

    # install -m 555 tuptime/src/rc.d/freebsd/tuptime /usr/local/etc/rc.d/tuptime
    # sysrc tuptime_enable=YES
    # service tuptime start

It's all done.



====================================
| OpenBSD manual install - Tuptime |
====================================


Install dependencies, latest versions:

    # pkg_add git python

Clone repository and copy executable file:

    # git clone --depth=1 https://github.com/rfmoz/tuptime.git
    # install -m 755 tuptime/src/tuptime /usr/local/bin/tuptime

Add tuptime user:

    # useradd -s /sbin/nologin -d /var/empty -L daemon _tuptime

Execute tuptime with a privileged user for create db path:

    # tuptime

Change owner of the db path and file:

    # chown -R _tuptime:_tuptime /var/lib/tuptime

Add the cron entry, create a '/var/cron/tabs/_tuptime' file with this content:

    SHELL=/bin/sh
    PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin/
    */5       *       *       *       *       tuptime -q

Set the right permissions to cron file and restart the service:

    # chown _tuptime:crontab /var/cron/tabs/_tuptime 
    # chmod 600 /var/cron/tabs/_tuptime 

Copy rc.d file:

    # install -m 555 tuptime/src/rc.d/openbsd/tuptime /etc/rc.d/tuptime
    # rcctl enable tuptime
    # rcctl start tuptime

It's all done.



===================================
| NetBSD manual install - Tuptime |
===================================


Install dependencies:

    # pkg_add git python3? py3?-sqlite3

Create a symlink for the Python3 interpreter:

    # mkdir -p /usr/local/bin/
    # ln -s /usr/pkg/bin/python3.? /usr/local/bin/python3
     
Install tls certs before clone the repo:

    # pkg_add mozilla-rootcerts
    # mozilla-rootcerts install

Clone repository and copy executable file:

    # git clone --depth=1 https://github.com/rfmoz/tuptime.git
    # install -m 755 tuptime/src/tuptime /usr/local/bin/tuptime

Execute tuptime with a privileged user for create db path:

    # tuptime

Add tuptime user:

    # useradd -d /var/lib/tuptime -s /sbin/nologin _tuptime

Change owner of the db path and file:

    # chown -R _tuptime:_tuptime /var/lib/tuptime

Add the cron entry, create a '/var/cron/tabs/_tuptime' file with this content:

    SHELL=/bin/sh
    PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin/
    */5       *       *       *       *       tuptime -q

Set the right permissions to cron file and restart the service:

    # chown _tuptime /var/cron/tabs/_tuptime 
    # chmod 600 /var/cron/tabs/_tuptime
    # service cron restart

Copy rc.d file:

    # install -m 555 tuptime/src/rc.d/freebsd/tuptime /etc/rc.d/tuptime
    # echo 'tuptime_enable="YES"' >> /etc/rc.conf

It's all done.
0707010000001B000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001700000000tuptime-5.2.4/misc/deb0707010000001C000081A40000000000000000000000016693DA19000002B7000000000000000000000000000000000000002600000000tuptime-5.2.4/misc/deb/deb-readme.txt=====================
| Build Tuptime DEB |
=====================


1.- Install dependencies:

    apt-get install dpkg-dev debhelper git

2.- Change to any unprivileged user and create the package. Not build packages using root.

    cd ~
    git clone --depth=1 https://github.com/rfmoz/tuptime.git
    cd tuptime
    dpkg-buildpackage -us -uc

3.- Here is:

    ls ../tuptime-*.deb

4.- As root, install and check if all was ok:

    dpkg -i tuptime-*.deb
    systemctl status tuptime.service




Z.- For testing with "dev" branch. Replace step "2" with the following:

    cd ~
    git clone -b dev --depth=1 https://github.com/rfmoz/tuptime.git
    cd tuptime
    dpkg-buildpackage -us -uc
0707010000001D000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001A00000000tuptime-5.2.4/misc/docker0707010000001E000081A40000000000000000000000016693DA1900000081000000000000000000000000000000000000002500000000tuptime-5.2.4/misc/docker/DockerfileFROM alpine:latest

RUN apk add tuptime --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/
ENTRYPOINT [ "tuptime" ]
0707010000001F000081A40000000000000000000000016693DA1900000654000000000000000000000000000000000000002B00000000tuptime-5.2.4/misc/docker/docker-notes.txt==========================
| Docker notes - Tuptime |
==========================

Tuptime could be executed inside a container via Docker.

It only requires the creation of the path /var/lib/tuptime/ on the host to store the
database file, which is done by Systemd automatically. All other runtime requirements
reside inside the container and their execution is managed via Systemd too.

The container is built over Alpine image, because their small footprint. But it could
be built over any other OS image.

Begin with this installation method getting the repo and creating the container:

    # git clone https://github.com/rfmoz/tuptime.git
    # cd tuptime/misc/docker/
    # docker build  -t 'tuptime' .

Copy the systemd files, the main service and the sync ones for the scheduled execution:

    # install -m 644 tuptime-docker.service /etc/systemd/system/
    # install -m 644 tuptime-sync-docker.* /etc/systemd/system/
    # systemctl daemon-reload
    # systemctl enable tuptime-docker.service && systemctl start tuptime-docker.service
    # systemctl enable tuptime-sync-docker.timer && systemctl start tuptime-sync-docker.timer

Also, create an alias for an easy execution (Maybe set it inside your ~/.profile or
~/.bashrc):

    # alias tuptime='/usr/bin/docker run --network none --rm -v /var/lib/tuptime/:/var/lib/tuptime/ tuptime'
    # tuptime

That's all.

Note: If the same host has a local Tuptime installation, disable their Systemd execution:

    # systemctl disable tuptime.service && systemctl stop tuptime.service
    # systemctl disable tuptime-sync.timer && systemctl stop tuptime-sync.timer
07070100000020000081A40000000000000000000000016693DA19000001A3000000000000000000000000000000000000003100000000tuptime-5.2.4/misc/docker/tuptime-docker.service[Unit]
Description=Tuptime docker service
After=docker.service
Requires=docker.service
Conflicts=tuptime.service

[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/bin/docker run --network none --rm -v /var/lib/tuptime/:/var/lib/tuptime/ tuptime -q
ExecStop=/usr/bin/docker run --network none --rm -v /var/lib/tuptime/:/var/lib/tuptime/ tuptime -qg
StateDirectory=tuptime

[Install]
WantedBy=multi-user.target
07070100000021000081A40000000000000000000000016693DA19000000F4000000000000000000000000000000000000003600000000tuptime-5.2.4/misc/docker/tuptime-sync-docker.service[Unit]
Description=Tuptime docker scheduled sync service
Requires=tuptime-docker.service
Conflicts=tuptime-sync.service

[Service]
Type=oneshot
ExecStart=/usr/bin/docker run --network none --rm -v /var/lib/tuptime/:/var/lib/tuptime/ tuptime -q
07070100000022000081A40000000000000000000000016693DA19000000A3000000000000000000000000000000000000003400000000tuptime-5.2.4/misc/docker/tuptime-sync-docker.timer[Unit]
Description=Tuptime docker scheduled sync timer

[Timer]
OnBootSec=1min
OnCalendar=*:0/5
Unit=tuptime-sync-docker.service

[Install]
WantedBy=timers.target
07070100000023000081A40000000000000000000000016693DA19000004C4000000000000000000000000000000000000002E00000000tuptime-5.2.4/misc/openwrt-manual-install.txt====================================
| OpenWRT manual install - Tuptime |
====================================


Install dependencies:

    # opkg update && opkg install python3-light python3-sqlite3 python3-logging wget unzip

Get the code and copy the executable file:

    # wget https://github.com/rfmoz/tuptime/archive/master.zip
    # unzip master.zip
    # cp tuptime/src/tuptime /usr/bin/ && chmod 755 /usr/bin/tuptime

As OpenWRT have a symlink from /var/ to /tmp/ and Tuptime saves their db on /var/lib/tuptime, their content will be lost in each startup. So, it's needed a new destination for the db (cron init file do the same for /var/spool):

    # mkdir -p /opt/tuptime
    # ln -s /opt/tuptime/ /var/lib/tuptime

Add the cron execution. Include the following line in '/etc/crontabs/root':

    */5 * * * * /usr/bin/python3 /usr/bin/tuptime -q

Copy the init file and set the right permissions:

    # cp tuptime/src/init.d/openwrt/tuptime /etc/init.d/tuptime
    # chmod 755 /etc/init.d/tuptime

Start and enable cron and tuptime:

    # /etc/init.d/cron enable
    # /etc/init.d/cron start
    # /etc/init.d/tuptime enable
    # /etc/init.d/tuptime start

Test it:

    # tuptime

That's all, enjoy it.
07070100000024000081A40000000000000000000000016693DA19000002FD000000000000000000000000000000000000002100000000tuptime-5.2.4/misc/osx-notes.txt=======================
| OSX notes - Tuptime |
=======================


Tuptime is compatible with OSX, which comes with 'python' installed by default.

But for now, I haven't found a clean way to configure a Launchd task to be 
executed before shutdown, so, Tuptime can't register the whole time and the 
shutdown status.

The closest approach is to have a Launchd plist that runs at startup and each
minute. The drawbacks will be an error range <60 secs. and a BAD shutdown
status always.

To install the .plist, copy file to the right location:

    # git clone --depth=1 https://github.com/rfmoz/tuptime.git
    # cp src/launchd/localhost.tuptime.plist /Library/LaunchDaemons/

Load it:

    # launchctl load -w /Library/LaunchDaemons/localhost.tuptime.plist
07070100000025000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001700000000tuptime-5.2.4/misc/rpm07070100000026000081A40000000000000000000000016693DA190000060E000000000000000000000000000000000000002600000000tuptime-5.2.4/misc/rpm/rpm-readme.txt=====================
| Build Tuptime RPM |
=====================


1.- Install dependencies:

    Latest releases of Fedora, RedHat 8, Centos 8:
        dnf -y install rpmdevtools wget python3-rpm-macros python-srpm-macros rpmlint systemd python3-devel

    Older releases of Fedora, Redhat 7, CentOS 7, install from EPEL:
        yum -y install rpmdevtools wget python3-rpm-macros python-srpm-macros

2.- Change to any unprivileged user and create the package. Not build packages using root.

    cd ~
    rpmdev-setuptree
    cd ~/rpmbuild/SPECS/
    wget 'https://raw.githubusercontent.com/rfmoz/tuptime/master/misc/rpm/tuptime.spec'
    spectool -g -R tuptime.spec
    rpmbuild -ba --target=noarch tuptime.spec

3.- Here is:

    ls ~/rpmbuild/RPMS/noarch/tuptime-*.rpm

4.- As root, install and enable:

    dnf install tuptime-*rpm   # (old way: rpm -i tuptime-*.rpm)
    systemctl enable tuptime.service && systemctl start tuptime.service
    systemctl enable tuptime-sync.timer && systemctl start tuptime-sync.timer

5.- Check it all was ok:

    systemctl status tuptime.service
    systemctl status tuptime-sync.timer



Z.- For testing with "dev" branch. Install "git" on step "1" and replace step "2" with the following:

    dnf -y install git
    cd ~
    git clone -b dev --depth=1 https://github.com/rfmoz/tuptime.git tuptime-5.2.4
    rpmdev-setuptree
    cd ~/rpmbuild/SPECS/
    cp ../../tuptime-5.2.4/misc/rpm/tuptime.spec .
    tar -czvf ../SOURCES/5.2.4.tar.gz ../../tuptime-5.2.4
    rpmbuild -ba --target=noarch tuptime.spec
07070100000027000081A40000000000000000000000016693DA1900000FCA000000000000000000000000000000000000002400000000tuptime-5.2.4/misc/rpm/tuptime.specName:		tuptime
Version:	5.2.4
Release:	1%{?dist}
Summary:	Report historical system real time

License:	GPL-2.0-or-later
BuildArch:	noarch
URL:		https://github.com/rfmoz/tuptime/
Source0:	https://github.com/rfmoz/tuptime/archive/%{version}.tar.gz

%{?systemd_requires}
# Check for EPEL Python (python34, python36)
%if 0%{?python3_pkgversion}
BuildRequires:	python%{python3_pkgversion}-devel
%else
BuildRequires:	python3-devel
%endif
%if 0%{?el7}
BuildRequires:	systemd
%else
BuildRequires:	systemd-rpm-macros
%endif
Requires:	systemd
Requires(pre):	shadow-utils


%description
Tuptime tracks and reports historical and statistical real time of
the system, preserving the uptime and downtime between shutdowns.


%prep
%autosetup
# Fix python shebang
%if %{?py3_shebang_fix:1}%{!?py3_shebang_fix:0}
%py3_shebang_fix src/tuptime
%else
# EPEL7 does not have py3_shebang_fix
/usr/bin/pathfix.py -pni "%{__python3} -s" src/tuptime
%endif


%pre
# Conversion to new group and usernames for previously installed version
getent group tuptime >/dev/null && groupmod --new-name _tuptime tuptime
getent passwd tuptime >/dev/null && usermod --login _tuptime tuptime
getent group _tuptime >/dev/null || groupadd --system _tuptime
getent passwd _tuptime >/dev/null || useradd --system --gid _tuptime --home-dir "/var/lib/tuptime" --shell '/sbin/nologin' --comment 'Tuptime execution user' _tuptime > /dev/null


%build


%install
install -d %{buildroot}%{_bindir}/
install -d %{buildroot}%{_unitdir}/
install -d %{buildroot}%{_mandir}/man1/
install -d %{buildroot}%{_sharedstatedir}/tuptime/
install -d %{buildroot}%{_datadir}/tuptime/
cp src/tuptime %{buildroot}%{_bindir}/
cp src/systemd/tuptime.service %{buildroot}%{_unitdir}/
cp src/systemd/tuptime-sync.service %{buildroot}%{_unitdir}/
cp src/systemd/tuptime-sync.timer %{buildroot}%{_unitdir}/
cp src/man/tuptime.1 %{buildroot}%{_mandir}/man1/
cp misc/scripts/* %{buildroot}%{_datadir}/tuptime/
chmod +x %{buildroot}%{_datadir}/tuptime/*.sh
chmod +x %{buildroot}%{_datadir}/tuptime/*.py


%post
# Create and initialise the tuptime DB with consistent permissions, etc.
su -s /bin/sh _tuptime -c "(umask 0022 && /usr/bin/tuptime -q)"
%systemd_post tuptime.service
%systemd_post tuptime-sync.service
%systemd_post tuptime-sync.timer


%preun
%systemd_preun tuptime.service
%systemd_preun tuptime-sync.service
%systemd_preun tuptime-sync.timer


%postun
%systemd_postun_with_restart tuptime.service
%systemd_postun_with_restart tuptime-sync.service
%systemd_postun_with_restart tuptime-sync.timer


%files
%{_unitdir}/tuptime.service
%{_unitdir}/tuptime-sync.service
%{_unitdir}/tuptime-sync.timer
%attr(0755, root, root) %{_bindir}/tuptime
%dir %attr(0755, _tuptime, _tuptime) %{_sharedstatedir}/tuptime/
%doc tuptime-manual.txt
%doc CHANGELOG README.md CONTRIBUTING.md
%license LICENSE
%{_mandir}/man1/tuptime.1.*
%dir %{_datadir}/tuptime
%{_datadir}/tuptime/*


%changelog
* Fri Jul 13 2024 Ricardo Fraile <rfraile@rfraile.eu> 5.2.4-1
- New release

* Fri Jan 05 2024 Ricardo Fraile <rfraile@rfraile.eu> 5.2.3-1
- New release

* Thu Jan 07 2023 Frank Crawford <frank@crawford.emu.id.au> 5.2.2-2
- Updated to SPDX license

* Thu Jan 05 2023 Ricardo Fraile <rfraile@rfraile.eu> 5.2.2-1
- New release

* Fri Aug 19 2022 Ricardo Fraile <rfraile@rfraile.eu> 5.2.1-1
- New release

* Mon Aug 15 2022 Ricardo Fraile <rfraile@rfraile.eu> 5.2.0-1
- New release

* Sun Jan 16 2022 Ricardo Fraile <rfraile@rfraile.eu> 5.1.0-1
- Bump new release

* Thu Jan 06 2022 Frank Crawford <frank@crawford.emu.id.au> 5.0.2-5
- First offical release in Fedora

* Tue Jan 04 2022 Frank Crawford <frank@crawford.emu.id.au> 5.0.2-4
- Futher updates to spec file following review comments

* Mon Dec 13 2021 Frank Crawford <frank@crawford.emu.id.au> 5.0.2-3
- Update spec file following review comments

* Sun Sep 26 2021 Frank Crawford <frank@crawford.emu.id.au> 5.0.2-2
- Update spec file for Fedora package review
- Copy all relevant documentation

* Sat Jan 02 2021 Ricardo Fraile <rfraile@rfraile.eu> 5.0.2-1
- RPM release
07070100000028000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001B00000000tuptime-5.2.4/misc/scripts07070100000029000081A40000000000000000000000016693DA19000005B2000000000000000000000000000000000000002600000000tuptime-5.2.4/misc/scripts/README.txt#### Scripts #####


____Tools____

tuptime_dbcheck.py
    Test database integrity. Catch and fix errors.

tuptime_join.py
    Join two tuptime db files into an other one.

tuptime_modify.py
    Modify registers preserving nearest values in sync.
    Allow change 'end status', 'startup timestamp' and 'shutdown timestamp'.



____DB migrations____

db-tuptime-migrate-2.0-to-3.0.sh
    Update tuptime database format from version 2.0.0 or above to 3.0.0.


db-tuptime-migrate-3.0-to-3.1.sh
    Update tuptime database format from version 3.0.0 to 3.1.0.


db-tuptime-migrate-3.1-to-4.0.sh
    Update tuptime database format from version 3.1.0 or above to 4.0.0.
    Tuptime v.4 do it automatically.


db-tuptime-migrate-4.0-to-5.0.sh
    Update tuptime database format from version 4.0.0 or above to 5.0.0.
    Tuptime v.5 do it automatically.



____Plots____

tuptime-plot1.py
    Graph a plot with the number of hours (default) or events (-x swich)
    per state along each day. It gets the info from tuptime csv output.
    Playground script.

tuptime-plot2.py
    Graph a plot with the state events per hour along each day (default)
    or accumulated events per hour (-x swich). It gets the info from
    tuptime csv output. Playground script.

tuptime-powerplot.py
    Graph a plot with the power consumption for every day uptime. At
    least, it needs as argument the -k kWh or -m mWh price and the power
    consumption value. Playground script. 
0707010000002A000081A40000000000000000000000016693DA1900000CD1000000000000000000000000000000000000003C00000000tuptime-5.2.4/misc/scripts/db-tuptime-migrate-2.0-to-3.0.sh#!/bin/bash


# This script update the tuptime database format from versions previous 3.0.00

# Change the db origin:
# 	uptime real, btime integer, shutdown integer
# to:
# 	btime integer, uptime real, offbtime integer, endst integer, downtime real

SOURCE_DB='/var/lib/tuptime/tuptime.db'

# Check bash execution
if [ ! -n "$BASH" ]; then
	echo "--- WARNING - execute only with BASH ---"
fi

# Test file permissions
if [ -w "${SOURCE_DB}" ]; then
	echo "Migrating tuptime database format"
else
	echo "Please, execute this script with a privileged user that can write in: ${SOURCE_DB}"
	exit 1
fi

# Test sqlite3 command
sqlite3 -version > /dev/null
if [ $? -ne 0 ]; then
	echo "Please, install \"sqlite3\" command for manage sqlite v3 databases"
	exit 2
fi

# Test bc command
bc -version > /dev/null
if [ $? -ne 0 ]; then
	echo "Please, install \"bc\" command"
	exit 2
fi

TMP_DB=$(mktemp)  # For temporary process db

cp "${SOURCE_DB}" "${TMP_DB}"

# Check db format
sqlite3 "${TMP_DB}" "PRAGMA table_info(tuptime);" | grep -E 'end_state|downtime|offbtime' > /dev/null 
if [ $? -eq 0 ]; then
	echo "Database is already in new format"
	exit 3
fi

# Change shutdown column to end_state
sqlite3 "${TMP_DB}" "ALTER TABLE tuptime RENAME TO tuptime_old;"
sqlite3 "${TMP_DB}" "CREATE TABLE tuptime (uptime REAL, btime INT, endst INT, downtime REAL, offbtime INT);"
sqlite3 "${TMP_DB}" "INSERT INTO tuptime(uptime, btime, endst) SELECT uptime, btime, shutdown FROM tuptime_old;"
sqlite3 "${TMP_DB}" "DROP TABLE tuptime_old;"

# Adding values for new columns downtime and offbtime
ROWS=$(sqlite3 "${TMP_DB}" "select max(oid) from tuptime;")

for I in $(seq 1 "${ROWS}"); do
	UPTIME=$(sqlite3 "${TMP_DB}" "SELECT uptime from tuptime where oid = ${I};")
	BTIME=$(sqlite3 "${TMP_DB}" "SELECT btime from tuptime where oid = ${I};")
	Z=$((I+1))
	NEXT_BTIME=$(sqlite3 "${TMP_DB}" "SELECT btime from tuptime where oid = ${Z};")

	OFFBTIME=$(echo "${UPTIME}" + "${BTIME}" | bc)
	DOWNBTIME=$(echo "${NEXT_BTIME}" - "${OFFBTIME}" | bc)

	sqlite3 "${TMP_DB}" "UPDATE tuptime SET downtime = ${DOWNBTIME}, offbtime = ${OFFBTIME} where oid = ${I}"
done

# Clear last row shutdown values
sqlite3 "${TMP_DB}" "UPDATE tuptime SET downtime = '-1', offbtime = '-1' where oid = ${I}" 

# Order columns
sqlite3 "${TMP_DB}" "ALTER TABLE tuptime RENAME TO tuptime_old;"
sqlite3 "${TMP_DB}" "CREATE TABLE tuptime (btime INT, uptime REAL, offbtime INT, endst INT, downtime REAL);"
sqlite3 "${TMP_DB}" "INSERT INTO tuptime(btime, uptime, offbtime, endst, downtime) SELECT btime, uptime, offbtime, endst, downtime FROM tuptime_old;"
sqlite3 "${TMP_DB}" "DROP TABLE tuptime_old;"

# Adding new column
sqlite3 "${TMP_DB}" "ALTER TABLE tuptime RENAME TO tuptime_old;"
sqlite3 "${TMP_DB}" "CREATE TABLE tuptime (btime INT, uptime REAL, offbtime INT, endst INT, downtime REAL, kernel TEXT);"
sqlite3 "${TMP_DB}" "INSERT INTO tuptime(btime, uptime, offbtime, endst, downtime, kernel) SELECT btime, uptime, offbtime, endst, downtime, '' FROM tuptime_old;"
sqlite3 "${TMP_DB}" "DROP TABLE tuptime_old;"

# Backup old db and restore the new
mv "${SOURCE_DB}" "${SOURCE_DB}".back
mv "${TMP_DB}" "${SOURCE_DB}"
chmod 644 "${SOURCE_DB}"

rm -f "${TMP_DB}"

echo "Backup file in: ${SOURCE_DB}.back"
echo "Process completed OK"
0707010000002B000081A40000000000000000000000016693DA19000007B9000000000000000000000000000000000000003C00000000tuptime-5.2.4/misc/scripts/db-tuptime-migrate-3.0-to-3.1.sh#!/bin/bash


# This script update the tuptime database format from version 3.0.00 to 3.1.00

# Change the db origin:
# 	btime integer, uptime real, offbtime integer, endst integer, downtime real
# to:
# 	btime integer, uptime real, offbtime integer, endst integer, downtime real, kernel text

SOURCE_DB='/var/lib/tuptime/tuptime.db'

# Check bash execution
if [ ! -n "$BASH" ]; then
	echo "--- WARNING - execute only with BASH ---"
fi

# Test file permissions
if [ -w "${SOURCE_DB}" ]; then
	echo "Migrating tuptime database format"
else
	echo "Please, execute this script with a privileged user that can write in: ${SOURCE_DB}"
	exit 1
fi

# Test sqlite3 command
sqlite3 -version > /dev/null
if [ $? -ne 0 ]; then
	echo "Please, install \"sqlite3\" command for manage sqlite v3 databases"
	exit 2
fi

# Test bc command
bc -version > /dev/null
if [ $? -ne 0 ]; then
	echo "Please, install \"bc\" command"
	exit 3
fi

TMP_DB=$(mktemp)  # For temporary process db

cp "${SOURCE_DB}" "${TMP_DB}" || exit 4

# Adding new column
sqlite3 "${TMP_DB}" "ALTER TABLE tuptime RENAME TO tuptime_old;" && \
sqlite3 "${TMP_DB}" "CREATE TABLE tuptime (btime INT, uptime REAL, offbtime INT, endst INT, downtime REAL, kernel TEXT);" && \
sqlite3 "${TMP_DB}" "INSERT INTO tuptime(btime, uptime, offbtime, endst, downtime, kernel) SELECT btime, uptime, offbtime, endst, downtime, '' FROM tuptime_old;" && \
sqlite3 "${TMP_DB}" "DROP TABLE tuptime_old;" || exit 5

## Adding values for new columns downtime and offbtime
#ROWS=`sqlite3 "${TMP_DB}" "select max(oid) from tuptime;"`
#
#for I in $(seq 1 ${ROWS}); do
#	KERNEL='Linux-3.16.0-4-amd64-x86_64-with-debian-8.0'
#	sqlite3 "${TMP_DB}" "UPDATE tuptime SET kernel = \'${KERNEL}\' where oid = ${I}"
#done

# Backup old db and restore the new
mv "${SOURCE_DB}" "${SOURCE_DB}".back && \
mv "${TMP_DB}" "${SOURCE_DB}" && \
chmod 644 "${SOURCE_DB}" || exit 6
echo "Backup file in: ${SOURCE_DB}.back"

rm -f "${TMP_DB}"

echo "Process completed OK"
0707010000002C000081A40000000000000000000000016693DA1900000996000000000000000000000000000000000000003C00000000tuptime-5.2.4/misc/scripts/db-tuptime-migrate-3.1-to-4.0.sh#!/bin/bash


# This script update the tuptime database format from version 3.1.0 or above to to 4.0.0
#
# Usage:
# 	Execute this script.
# 	It will update the db file on /var/lib/tuptime/tuptime.db
# 	The original db file will be renamed to /var/lib/tuptime/tuptime.[date].back
#
# Change the db origin:
# 	btime integer, uptime real, offbtime integer, endst integer, downtime real, kernel text
# to:
# 	btime integer, uptime real, rntime real, slptime real, offbtime integer, endst integer, downtime real, kernel text

SOURCE_DB='/var/lib/tuptime/tuptime.db'
USER_DB=$(stat -c '%U' "${SOURCE_DB}")
TMP_DBF=$(mktemp)
BKP_DATE=$(date +%s)

# Check bash execution
if [ ! -n "$BASH" ]; then
	echo "--- WARNING - execute only with BASH ---"
fi

# Test file permissions
if [ -w "${SOURCE_DB}" ]; then
	echo -e "\n## Migrating tuptime database format ##\n"
	echo "Source file: ${SOURCE_DB}"
else
	echo "Please, execute this script with a privileged user that can write in: ${SOURCE_DB}"
	exit 1
fi

# Test sqlite3 command
sqlite3 -version > /dev/null
if [ $? -ne 0 ]; then
	echo "Please, install \"sqlite3\" command for manage sqlite v3 databases."
	exit 2
fi

# Work with a db copy
cp "${SOURCE_DB}" "${TMP_DBF}" || exit 4

# Adding new columns
sqlite3 "${TMP_DBF}" "CREATE TABLE tuptimeNew (btime integer, uptime integer, rntime integer, slptime integer, offbtime integer, endst integer, downtime integer, kernel text);" && \
sqlite3 "${TMP_DBF}" "UPDATE tuptime SET offbtime = cast(round(offbtime) as int);" && \
sqlite3 "${TMP_DBF}" "UPDATE tuptime SET uptime = cast(round(uptime) as int);" && \
sqlite3 "${TMP_DBF}" "UPDATE tuptime SET downtime = cast(round(downtime) as int);" && \
sqlite3 "${TMP_DBF}" "INSERT INTO tuptimeNew(btime, uptime, offbtime, endst, downtime, kernel) SELECT btime, uptime, offbtime, endst, downtime, kernel FROM tuptime;" && \
sqlite3 "${TMP_DBF}" "UPDATE tuptimeNew SET rntime = uptime;" && \
sqlite3 "${TMP_DBF}" "UPDATE tuptimeNew SET slptime = 0;" && \
sqlite3 "${TMP_DBF}" "DROP TABLE tuptime;" && \
sqlite3 "${TMP_DBF}" "ALTER TABLE tuptimeNew RENAME TO tuptime;" || exit 5

# Backup original db and rename the temp db as source
mv "${SOURCE_DB}" "${SOURCE_DB}"."${BKP_DATE}".back && \
mv "${TMP_DBF}" "${SOURCE_DB}" || exit 6
echo "Backup file: ${SOURCE_DB}.${BKP_DATE}.back"

# Set permission and user
chmod 644 "${SOURCE_DB}" && \
chown "${USER_DB}":"${USER_DB}" "${SOURCE_DB}" || exit 7

echo "Process completed: OK"
0707010000002D000081A40000000000000000000000016693DA19000009AB000000000000000000000000000000000000003C00000000tuptime-5.2.4/misc/scripts/db-tuptime-migrate-4.0-to-5.0.sh#!/bin/bash


# This script update the tuptime database format from version 4.0.0 or above to to 5.0.0
#
# Usage:
# 	Execute this script.
# 	It will update the db file on /var/lib/tuptime/tuptime.db
# 	The original db file will be renamed to /var/lib/tuptime/tuptime.[date].back
#
# Change the db origin:
# 	btime integer, uptime real, rntime real, slptime real, offbtime integer, endst integer, downtime real, kernel text
# to:
# 	bootid text, btime integer, uptime real, rntime real, slptime real, offbtime integer, endst integer, downtime real, kernel text

SOURCE_DB='/var/lib/tuptime/tuptime.db'
USER_DB=$(stat -c '%U' "${SOURCE_DB}")
TMP_DBF=$(mktemp)
BKP_DATE=$(date +%s)
#BOOTID=$(cat /proc/sys/kernel/random/boot_id)

# Check bash execution
if [ ! -n "$BASH" ]; then
	echo "--- WARNING - execute only with BASH ---"
fi

# Test file permissions
if [ -w "${SOURCE_DB}" ]; then
	echo -e "\n## Migrating tuptime database format ##\n"
	echo "Source file: ${SOURCE_DB}"
else
	echo "Please, execute this script with a privileged user that can write in: ${SOURCE_DB}"
	exit 1
fi

# Test sqlite3 command
sqlite3 -version > /dev/null
if [ $? -ne 0 ]; then
	echo "Please, install \"sqlite3\" command for manage sqlite v3 databases."
	exit 2
fi

# Work with a db copy
cp "${SOURCE_DB}" "${TMP_DBF}" ||  exit 4

# Adding new columns
sqlite3 "${TMP_DBF}" "CREATE TABLE tuptimeNew (bootid text, btime integer, uptime integer, rntime integer, slptime integer, offbtime integer, endst integer, downtime integer, kernel text);" && \
sqlite3 "${TMP_DBF}" "INSERT INTO tuptimeNew(btime, uptime, rntime, slptime, offbtime, endst, downtime, kernel) SELECT btime, uptime, rntime, slptime, offbtime, endst, downtime, kernel FROM tuptime;" && \
#sqlite3 "${TMP_DBF}" "update tuptimeNew set bootid = \"${BOOTID}\" where rowid = (select max(rowid) from tuptimeNew)" && \
sqlite3 "${TMP_DBF}" "update tuptimeNew set bootid = 'None';" && \
sqlite3 "${TMP_DBF}" "update tuptimeNew set kernel = 'None' where kernel = '';" && \
sqlite3 "${TMP_DBF}" "DROP TABLE tuptime;" && \
sqlite3 "${TMP_DBF}" "ALTER TABLE tuptimeNew RENAME TO tuptime;" || exit 5

# Backup original db and rename the temp db as source
mv "${SOURCE_DB}" "${SOURCE_DB}"."${BKP_DATE}".back && \
mv "${TMP_DBF}" "${SOURCE_DB}" || exit 6
echo "Backup file: ${SOURCE_DB}.${BKP_DATE}.back"

# Set permission and user
chmod 644 "${SOURCE_DB}" && \
chown "${USER_DB}":"${USER_DB}" "${SOURCE_DB}" || exit 7

echo "Process completed: OK"
0707010000002E000081A40000000000000000000000016693DA190000045D000000000000000000000000000000000000002700000000tuptime-5.2.4/misc/scripts/runplots.sh#!/bin/bash


# This script executes all the plots available at the same time
# with the same arguments.

# Defaults
PyEx='python3'
Size='22x10'
pDays='30'
EndDT=$(date +"%d-%m-%y")

while getopts s:p:e: flag; do
    case "${flag}" in
        s)
	  Size=$(grep -oP '\d+x\d+' <<< ${OPTARG})
	;;
        p)
	  pDays=$(grep -oP '\d+' <<< ${OPTARG})
	;;
        e)
	  EndDT=$(grep -oP '\d+-\w+-\d+' <<< ${OPTARG})
	;;
	*)
	  echo "ERROR: Invalid argument flag" && exit -1
	;;
    esac
done

# Set X and Y size
Xcm=$(cut -dx -f1 <<< $Size)
Ycm=$(cut -dx -f2 <<< $Size)	
XnY="-W $Xcm -H $Ycm"

echo "Execution: $0 [-s Width x Height] [ -p Past Days] [ -e End Date]"
echo "Example:   $0 -s 22x10 -p 30 -e 31-Dec-20"
echo ""

echo -e "Making 4 plots in background...\n"
echo -e "Wide x Height:\t ${Xcm}x${Ycm}"
echo -e "Past Days:\t ${pDays}"
echo -e "End Date:\t ${EndDT}\n"

$PyEx ./tuptime-plot1.py $XnY -e $EndDT -p $pDays > /dev/null &
$PyEx ./tuptime-plot1.py $XnY -e $EndDT -p $pDays -x > /dev/null &
$PyEx ./tuptime-plot2.py $XnY -e $EndDT -p $pDays > /dev/null &
$PyEx ./tuptime-plot2.py $XnY -e $EndDT -p $pDays -x
0707010000002F000081A40000000000000000000000016693DA190000263F000000000000000000000000000000000000002C00000000tuptime-5.2.4/misc/scripts/tuptime-plot1.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""Sample plot that reports the number of hours/events per every state
in each day. It extracts the info from tuptime command execution"""


from datetime import datetime, timedelta
import subprocess, csv, argparse, tempfile
import numpy as np
import matplotlib.pyplot as plt
import dateutil.parser


def get_arguments():
    """Get arguments from command line"""

    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-b', '--bdate',
        dest='bdate',
        action='store',
        help='begin date to plot, format:"d-m-Y"',
        type=str
    )
    parser.add_argument(
        '-e', '--edate',
        dest='edate',
        action='store',
        help='end date to plot, format:"d-m-Y" (default today)',
        type=str
    )
    parser.add_argument(
        '-f', '--filedb',
        dest='dbfile',
        default=None,
        action='store',
        help='database file'
    )
    parser.add_argument(
        '-H', '--height',
        dest='height',
        default=13,
        action='store',
        help='window height in cm (default 13)',
        type=int
    )
    parser.add_argument(
        '-p', '--pastdays',
        dest='pdays',
        default=7,
        action='store',
        help='past days before edate to plot (default is 7)',
        type=int
    )
    parser.add_argument(
        '-W', '--width',
        dest='width',
        default=17,
        action='store',
        help='window width in cm (default 17)',
        type=int
    )
    parser.add_argument(
        '-x',
        dest='report_events',
        action='store_true',
        default=False,
        help='swich to report startup/shutdown events instead of hours'
    )
    arg = parser.parse_args()
    return arg


def date_check(arg):
    """Check and clean dates"""

    # Set user provided or default end date
    if arg.edate:
        end_date = dateutil.parser.parse(arg.edate, dayfirst=True)
    else:
        end_date = datetime.today()
        print('Default end:\tnow')

    # Set user provided or default begind date. Days ago...
    if arg.bdate:
        begin_date = dateutil.parser.parse(arg.bdate, dayfirst=True)
    else:
        begin_date = end_date - timedelta(days=arg.pdays)
        print('Default begin:\tsince ' + str(arg.pdays) + ' days ago')


    # Adjust date to the start or end time range and set the format
    begin_date = begin_date.replace(hour=0, minute=0, second=0).strftime("%d-%b-%Y %H:%M:%S")
    end_date = end_date.replace(hour=23, minute=59, second=59).strftime("%d-%b-%Y %H:%M:%S")

    print('Begin datetime:\t' + str(begin_date))
    print('End datetime:\t' + str(end_date))

    return([begin_date, end_date])


def date_range(date_limits):
    """Get the range of dates to apply"""

    dlimit = []  # date range in human date
    ranepo = []  # date range in epoch
    xlegend = []  # legend to x axis

    # Get datetime objects from dates
    dstart = dateutil.parser.parse(date_limits[0])
    dend = dateutil.parser.parse(date_limits[1])

    # Split time range in days
    while dstart <= dend:
        dlimit.append(dstart)
        dstart += timedelta(days=1)
    dlimit.append(dend)  # Finally add last day time range until midnight

    # Convert to epoch dates, pack two of them, begin and end for each split, and create a list with all
    for reg in range(1, len(dlimit)):
        ranepo.append([int(dlimit[reg-1].timestamp()), int(dlimit[reg].timestamp())])
        xlegend.append(datetime.fromtimestamp(dlimit[reg-1].timestamp()).strftime('%d-%b-%y'))

    print('Ranges on list:\t' + str(len(ranepo)))

    return(ranepo, xlegend)


def main():
    """Core logic"""

    arg = get_arguments()
    date_limits = date_check(arg)
    date_list, xlegend = date_range(date_limits)

    daysplt = []  # List for all day splits with their events
    ftmp = tempfile.NamedTemporaryFile().name  # File to store Tuptime csv
    shutst = None

    # Iterate over each element in (since, until) list
    for nran, _  in enumerate(date_list):
        tsince = str(int(date_list[nran][0]))  # Timestamp arg tsince
        tuntil = str(int(date_list[nran][1]))  # timestamp arg tuntil

        # Query tuptime for every (since, until) and save output to a file
        with open(ftmp, "wb", 0) as out:
            if arg.dbfile:  # If a file is passed, avoid update it
                subprocess.call(["tuptime", "-lsc", "--tsince", tsince, "--tuntil", tuntil, "-f", arg.dbfile, "-n"], stdout=out)
            else:
                subprocess.call(["tuptime", "-lsc", "--tsince", tsince, "--tuntil", tuntil], stdout=out)

        # Parse csv file
        daysplit_events = []
        with open(ftmp) as csv_file:
            csv_reader = csv.reader(csv_file, delimiter=',')

            for row in csv_reader:
                l_row = [0, 0, 0]  # Events in csv rows

                # Control how was the shutdown
                if row[0] == 'Shutdown':
                    if row[1] == 'BAD': shutst = 'BAD'
                    if row[1] == 'OK': shutst = 'OK'

                if arg.report_events:
                    # Populate list with (startup, shutdown ok, shutdown bad)
                    if ((row[0] == 'Startup') or (row[0] == 'Shutdown')) and len(row) > 2:

                        if row[0] == 'Startup' and row[2] == 'at':
                            l_row[0] = 1

                        if row[0] == 'Shutdown' and row[2] == 'at':
                            if shutst == 'BAD':
                                l_row[2] = 1
                            else:
                                l_row[1] = 1

                else:
                    # Populate list with (uptime, downtime ok, downtime bad)
                    if (row[0] == 'Uptime') or (row[0] == 'Downtime'):

                        if row[0] == 'Uptime':
                            l_row[0] = int(row[1])

                        if row[0] == 'Downtime':
                            if shutst == 'BAD':
                                l_row[2] = int(row[1])
                            else:
                                l_row[1] = int(row[1])

                # Add to events list per day
                daysplit_events.append(l_row)

            print(str(nran) + ' range --->\t' + str(len([i for i in daysplit_events if i != [0, 0, 0]])) + ' events')

            # Per day, get total value for each type of event
            if arg.report_events:
                daysplit_events = [(sum(j)) for j in zip(*daysplit_events)]
            else:
                # Convert seconds to hours
                daysplit_events = [(sum(j) / 3600) for j in zip(*daysplit_events)]

            # Populate daysplt list with totals
            daysplt.append(daysplit_events)

    print('Ranges got:\t' + str(len(daysplt)))

    # At this point daysplt have one of these:
    #
    # list_with_days[
    #   list_with_total_time of_each_type_of_event[
    #     uptime, downtime_ok, downtime_bad ]]
    #
    # list_with_days[
    #   list_with_total_counter of_each_type_of_event[
    #     startup, shutdown_ok, shutdown_bad ]]

    # Matplotlib requires stack with slices
    #
    # y
    # |  up         up         up
    # |  down_ok    down_ok    down_ok
    # |  down_bad   down_bad   down_bad
    # |----------------------------------x
    # |   day1       day2       dayN

    # Get each state values slice from each day
    days = {'up': [], 'down_ok': [], 'down_bad': []}
    for i in daysplt:
        days['up'].append(i[0])
        days['down_ok'].append(i[1])
        days['down_bad'].append(i[2])

    ind = np.arange(len(daysplt))  # number of days on x

    # Set width and height from inches to cm
    plt.figure(figsize=((arg.width / 2.54), (arg.height / 2.54)))

    if arg.report_events:
        plt.ylabel('Events Counter')
        plt.title('Events per State by Day')
        maxv = max(i for v in days.values() for i in v)  # Get max value on all ranges
        plt.yticks(np.arange(0, (maxv + 1), 1))
        plt.ylim(top=(maxv + 1))
        rlabel = ['Startup', 'Shutdown Ok', 'Shutdown Bad']
        width = 0.42  # column size

        # Set position of bar on X axis
        pst1 = np.arange(len(ind))
        pst2 = [x + width for x in pst1]

        plt.bar(pst1, days['up'], width, color='forestgreen', label=rlabel[0], edgecolor='white')
        plt.bar(pst2, days['down_ok'], width, color='grey', label=rlabel[1], edgecolor='white', bottom=days['down_bad'])
        plt.bar(pst2, days['down_bad'], width, color='black', label=rlabel[2], edgecolor='white')

        ind = ind + width / 2

    else:
        plt.ylabel('Hours Counter')
        plt.title('Hours per State by Day')
        plt.yticks(np.arange(0, 25, 2))
        plt.ylim(top=26)
        rlabel = ['Uptime', 'Downtime']

        # Merge all downtimes
        days['down'] = [x + y for x, y in zip(days['down_ok'], days['down_bad'])]

        # Old bar plot
        #width = 0.9  # column size
        #plt.bar(ind, days['up'], width, color='forestgreen', label=rlabel[0])
        #plt.bar(ind, days['down'], width, color='grey', label=rlabel[1], bottom=days['up'])

        plt.plot(ind, days['up'], linewidth=2, marker='o', color='forestgreen', label=rlabel[0])
        plt.plot(ind, days['down'], linewidth=2, marker='o', color='grey', linestyle='--', label=rlabel[1])

        plt.grid(color='lightblue', linestyle='--', linewidth=0.5, axis='x')

    plt.xticks(ind, xlegend)
    plt.gcf().autofmt_xdate()
    plt.margins(y=0, x=0.01)
    plt.grid(color='lightgrey', linestyle='--', linewidth=0.5, axis='y')
    plt.tight_layout()
    cfig = plt.get_current_fig_manager()
    cfig.canvas.manager.set_window_title("Tuptime")
    plt.legend()
    plt.show()


if __name__ == "__main__":
    main()
07070100000030000081A40000000000000000000000016693DA190000224D000000000000000000000000000000000000002C00000000tuptime-5.2.4/misc/scripts/tuptime-plot2.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import subprocess, csv, argparse, tempfile
from datetime import datetime, timedelta
from collections import Counter
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import dateutil.parser


def get_arguments():
    """Get arguments from command line"""

    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-b', '--bdate',
        dest='bdate',
        action='store',
        help='begin date to plot, format:"d-m-Y"',
        type=str
    )
    parser.add_argument(
        '-e', '--edate',
        dest='edate',
        action='store',
        help='end date to plot, format:"d-m-Y" (default today)',
        type=str
    )
    parser.add_argument(
        '-f', '--filedb',
        dest='dbfile',
        default=None,
        action='store',
        help='database file'
    )
    parser.add_argument(
        '-H', '--height',
        dest='height',
        default=13,
        action='store',
        help='window height in cm (default 13)',
        type=int
    )
    parser.add_argument(
        '-p', '--pastdays',
        dest='pdays',
        default=7,
        action='store',
        help='past days before edate to plot (default is 7)',
        type=int
    )
    parser.add_argument(
        '-W', '--width',
        dest='width',
        default=17,
        action='store',
        help='window width in cm (default 17)',
        type=int
    )
    parser.add_argument(
        '-x',
        dest='report_pie',
        action='store_true',
        default=False,
        help='swich to  pie report with accumulated hours'
    )
    arg = parser.parse_args()
    return arg


def date_check(arg):
    """Check and clean dates"""

    # Set user provided or default end date
    if arg.edate:
        end_date = dateutil.parser.parse(arg.edate, dayfirst=True)
    else:
        end_date = datetime.today()
        print('Default end:\tnow')

    # Set user provided or default begind date. Days ago...
    if arg.bdate:
        begin_date = dateutil.parser.parse(arg.bdate, dayfirst=True)
    else:
        begin_date = end_date - timedelta(days=arg.pdays)
        print('Default begin:\tsince ' + str(arg.pdays) + ' days ago')


    # Adjust date to the start or end time range and set the format
    begin_date = begin_date.replace(hour=0, minute=0, second=0).strftime("%d-%b-%Y %H:%M:%S")
    end_date = end_date.replace(hour=23, minute=59, second=59).strftime("%d-%b-%Y %H:%M:%S")

    print('Begin datetime:\t' + str(begin_date))
    print('End datetime:\t' + str(end_date))

    return([begin_date, end_date])



def main():
    """Core logic"""

    arg = get_arguments()
    date_limits = date_check(arg)

    ftmp = tempfile.NamedTemporaryFile().name  # For Tuptime csv file
    tst = {'up': [], 'down': [], 'down_ok': [], 'down_bad': []}  # Store events list on range

    # Get datetime objects from date limits in timestamp format
    tsince = str(int(dateutil.parser.parse(date_limits[0]).timestamp()))
    tuntil = str(int(dateutil.parser.parse(date_limits[1]).timestamp()))

    # Query tuptime for every (since, until) and save output to a file
    with open(ftmp, "wb", 0) as out:
        if arg.dbfile:  # If a file is passed, avoid update it
            subprocess.call(["tuptime", "-tsc", "--tsince", tsince, "--tuntil", tuntil, "-f", arg.dbfile, "-n"], stdout=out)
        else:
            subprocess.call(["tuptime", "-tsc", "--tsince", tsince, "--tuntil", tuntil], stdout=out)

    # Parse csv file
    with open(ftmp) as csv_file:
        csv_reader = csv.reader(csv_file, delimiter=',')

        for row in csv_reader:
            if row[0] == 'No.':
                continue

            #print('Startup T.: ' + row[1])
            #print('Uptime: ' + row[2])
            #print('Shutdown T.: ' + row[3])
            #print('End: ' + row[4])
            #print('Downtime: ' + row[5])

            if row[1] != '':
                tst['up'].append(row[1])

            if row[3] != '':
                if row[4] == 'BAD':
                    tst['down_bad'].append(row[3])
                else:
                    tst['down_ok'].append(row[3])

    # Set whole downtimes and convert to datetime object
    tst['down'] = tst['down_ok'] + tst['down_bad']
    for state in tst:
        tst[state] = [datetime.fromtimestamp(int(elem)) for elem in tst[state]]

    if arg.report_pie:
        pie = {'up': [], 'down': []}  # One plot for each type

        # From datetime, get only hour
        for elem in tst['up']: pie['up'].append(str(elem.hour))
        for elem in tst['down']: pie['down'].append(str(elem.hour))

        # Count elements on list or set '0' if empty. Get list with items
        pie['up'] = dict(Counter(pie['up'])).items() if pie['up'] else [('0', 0)]
        pie['down'] = dict(Counter(pie['down'])).items() if pie['down'] else [('0', 0)]

        # Values ordered by first element on list that was key on source dict
        pie['up'] = sorted(pie['up'], key=lambda ordr: int(ordr[0]))
        pie['down'] = sorted(pie['down'], key=lambda ordr: int(ordr[0]))

        # Set two plots and their frame size
        _, axs = plt.subplots(1, 2, figsize=((arg.width / 2.54), (arg.height / 2.54)))

        # Set values for each pie plot
        axs[0].pie([v[1] for v in pie['up']], labels=[k[0].rjust(2, '0') + str('h') for k in pie['up']],
                   autopct=lambda p : '{:.1f}%\n{:,.0f}'.format(p,p * sum([v[1] for v in pie['up']])/100),
                   startangle=90, counterclock=False,
                   textprops={'fontsize': 8}, wedgeprops={'alpha':0.85})
        axs[0].set(aspect="equal", title='Startup')

        axs[1].pie([v[1] for v in pie['down']], labels=[str(k[0]).rjust(2, '0') + str('h') for k in pie['down']],
                   autopct=lambda p : '{:.1f}%\n{:,.0f}'.format(p,p * sum([v[1] for v in pie['down']])/100),
                   startangle=90, counterclock=False,
                   textprops={'fontsize': 8}, wedgeprops={'alpha':0.85})
        axs[1].set(aspect="equal", title='Shutdown')

        plt.suptitle("Events per Hours in Range", fontsize=14)

    else:
        # Reset date allows position circles inside the same 00..24 range on y-axis
        scatt_y = {'up': [], 'down_ok': [], 'down_bad': []}
        scatt_y['up'] = [elem.replace(year=1970, month=1, day=1) for elem in tst['up']]
        scatt_y['down_ok'] = [elem.replace(year=1970, month=1, day=1) for elem in tst['down_ok']]
        scatt_y['down_bad'] = [elem.replace(year=1970, month=1, day=1) for elem in tst['down_bad']]

        # Reset hour allows position circles straight over the date tick on x-axis
        scatt_x = {'up': [], 'down_ok': [], 'down_bad': []}
        scatt_x['up'] = [elem.replace(hour=00, minute=00, second=00) for elem in tst['up']]
        scatt_x['down_ok'] = [elem.replace(hour=00, minute=00, second=00) for elem in tst['down_ok']]
        scatt_x['down_bad'] = [elem.replace(hour=00, minute=00, second=00) for elem in tst['down_bad']]

        # Set width and height from inches to cm
        plt.figure(figsize=((arg.width / 2.54), (arg.height / 2.54)))

        # Set scatter plot values
        plt.scatter(scatt_x['up'], scatt_y['up'], s=200, color='forestgreen', edgecolors='white', alpha=0.85, marker="X", label='Up')
        plt.scatter(scatt_x['down_ok'], scatt_y['down_ok'], s=200, color='grey', edgecolors='white', alpha=0.85, marker="X", label='Down ok')
        plt.scatter(scatt_x['down_bad'], scatt_y['down_bad'], s=200, color='black', edgecolors='white', alpha=0.85, marker="X", label='Down bad')

        # Format axes:
        plt.gcf().autofmt_xdate()
        axs = plt.gca()

        #  X as days and defined limits with their margin
        axs.xaxis.set_major_formatter(mdates.DateFormatter('%d-%b-%y'))
        axs.xaxis.set_major_locator(mdates.DayLocator())
        plt.xlim(datetime.strptime(date_limits[0], '%d-%b-%Y %H:%M:%S') - timedelta(hours=4),
                 datetime.strptime(date_limits[1], '%d-%b-%Y %H:%M:%S') - timedelta(hours=20))

        #  Y as 24 hours range
        axs.yaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
        axs.yaxis.set_major_locator(mdates.HourLocator(byhour=range(0, 24, 2)))
        plt.ylim([datetime(1970, 1, 1, 00, 00), datetime(1970, 1, 1, 23, 59, 59)])

        axs.set_axisbelow(True)
        axs.invert_yaxis()
        plt.grid(True)
        plt.ylabel('Day Time')
        plt.title('Events on Time by Day')
        plt.margins(y=0, x=0.01)
        plt.grid(color='lightgrey', linestyle='--', linewidth=0.9, axis='y')
        plt.legend()

    plt.tight_layout()
    cfig = plt.get_current_fig_manager()
    cfig.canvas.manager.set_window_title("Tuptime")
    plt.show()

if __name__ == "__main__":
    main()
07070100000031000081A40000000000000000000000016693DA1900001D7D000000000000000000000000000000000000003000000000tuptime-5.2.4/misc/scripts/tuptime-powerplot.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""Sample plot that reports the roughly electric cost per day based on uptime.
It extracts the info from tuptime command execution"""

# Basic usage:
#
# A power consumption of 23 kWh and a kWh cost of 0,66089€:
#
#    $ tuptime-powerplot.py -k 0.66089 23
#
# A power consumption of 55 kWh and a MWh cost of 150€:
#
#    $ tuptime-powerplot.py -m 150 55
#
# A power consumption of 35 kWh, a kWh cost of 0.59$ for last 15 days:
#
#    $ tuptime-powerplot.py -p 15 -k 0.59  35
#
# A power consumption of 40 kWh, a kWh cost of 0.44€ since 1-Jan-2020 to 1-Feb-2020:
#
#    $ tuptime-powerplot.py -k 0.44 -b "01-01-2020" -e "01-02-2020" 40
#
# A power consumption of 40 kWh, a kWh cost of 0.44€ for last 15 days:
#
#    $ tuptime-powerplot.py -k 0.44 -p 15 40
#

from datetime import datetime, timedelta
import subprocess, csv, argparse, tempfile
import numpy as np
import matplotlib.pyplot as plt
import dateutil.parser


def get_arguments():
    """Get arguments from command line"""

    parser = argparse.ArgumentParser()
    group = parser.add_mutually_exclusive_group()

    parser.add_argument(
        '-b', '--bdate',
        dest='bdate',
        action='store',
        help='begin date to plot, format:"d-m-Y"',
        type=str
    )
    parser.add_argument(
        'consum',
        default=None,
        help='power consumption of the device in Watts / hour',
        metavar='kWh_consumption',
        type=float
    )
    parser.add_argument(
        '-e', '--edate',
        dest='edate',
        action='store',
        help='end date to plot, format:"d-m-Y" (default today)',
        type=str
    )
    parser.add_argument(
        '-f', '--filedb',
        dest='dbfile',
        default=None,
        action='store',
        help='database file'
    )
    parser.add_argument(
        '-H', '--height',
        dest='height',
        default=13,
        action='store',
        help='window height in cm (default 13)',
        type=int
    )
    group.add_argument(
        '-k', '--kwh',
        dest='kwh',
        default=None,
        action='store',
        help='set price for Kilowatt hour',
        type=float,
        metavar='kWh'
    )
    group.add_argument(
        '-m', '--mwh',
        dest='mwh',
        default=None,
        action='store',
        help='set price for Megawatt hour',
        type=float,
        metavar='MWh'
    )
    parser.add_argument(
        '-p', '--pastdays',
        dest='pdays',
        default=7,
        action='store',
        help='past days before edate to plot (default is 7)',
        type=int
    )
    parser.add_argument(
        '-W', '--width',
        dest='width',
        default=17,
        action='store',
        help='window width in cm (default 17)',
        type=int
    )
    arg = parser.parse_args()

    if not (arg.kwh or arg.mwh):
        parser.error('Set almost one price for -m (MWh) or -k (kWh)')

    return arg


def date_check(arg):
    """Check and clean dates"""

    # Set user provided or default end date
    if arg.edate:
        end_date = dateutil.parser.parse(arg.edate, dayfirst=True)
    else:
        end_date = datetime.today()
        print('Default end:\tnow')

    # Set user provided or default begind date. Days ago...
    if arg.bdate:
        begin_date = dateutil.parser.parse(arg.bdate, dayfirst=True)
    else:
        begin_date = end_date - timedelta(days=arg.pdays)
        print('Default begin:\tsince ' + str(arg.pdays) + ' days ago')

    # Adjust date to the start or end time range and set the format
    begin_date = begin_date.replace(hour=0, minute=0, second=0).strftime("%d-%b-%Y %H:%M:%S")
    end_date = end_date.replace(hour=23, minute=59, second=59).strftime("%d-%b-%Y %H:%M:%S")

    print('Begin datetime:\t' + str(begin_date))
    print('End datetime:\t' + str(end_date))

    return([begin_date, end_date])


def date_range(date_limits):
    """Get the range of dates to apply"""

    dlimit = []  # date range in human date
    ranepo = []  # date range in epoch
    xlegend = []  # legend to x axis

    # Get datetime objects from dates
    dstart = dateutil.parser.parse(date_limits[0])
    dend = dateutil.parser.parse(date_limits[1])

    # Split time range in days
    while dstart <= dend:
        dlimit.append(dstart)
        dstart += timedelta(days=1)
    dlimit.append(dend)  # Finally add last day time range until midnight

    # Convert to epoch dates, pack two of them, begin and end for each split, and create a list with all
    for reg in range(1, len(dlimit)):
        ranepo.append([int(dlimit[reg-1].timestamp()), int(dlimit[reg].timestamp())])
        xlegend.append(datetime.fromtimestamp(dlimit[reg-1].timestamp()).strftime('%d-%b-%y'))

    print('Ranges on list:\t' + str(len(ranepo)))

    return(ranepo, xlegend)


def main():
    """Core logic"""

    arg = get_arguments()
    date_limits = date_check(arg)
    date_list, xlegend = date_range(date_limits)

    # Set price for Watts per second
    if arg.mwh:
        arg.kwh = arg.mwh / 1000
    wth = arg.kwh / 1000
    wts = wth / 3600
    consum = arg.consum

    days = []  # List for day values
    ftmp = tempfile.NamedTemporaryFile().name  # File to store Tuptime csv

    # Iterate over each element in (since, until) list
    for nran, _  in enumerate(date_list):
        tsince = str(int(date_list[nran][0]))  # Timestamp arg tsince
        tuntil = str(int(date_list[nran][1]))  # timestamp arg tuntil

        # Query tuptime for every (since, until) and save output into a file
        with open(ftmp, "wb", 0) as out:
            if arg.dbfile:  # If a file is passed, avoid update it
                subprocess.call(["tuptime", "-lsc", "--tsince", tsince, "--tuntil", tuntil, "-f", arg.dbfile, "-n"], stdout=out)
            else:
                subprocess.call(["tuptime", "-lsc", "--tsince", tsince, "--tuntil", tuntil], stdout=out)

        # Parse csv file
        with open(ftmp) as csv_file:
            csv_reader = csv.reader(csv_file, delimiter=',')

            uptimes = []  # Uptime events in csv rows
            for row in csv_reader:

                # Populate list with all uptimes inside each day
                if row[0] == 'Uptime':
                    uptimes.append(int(row[1]))

            # Populate with total price calculation
            days.append(sum(uptimes) * wts * consum)

            print(str(nran) + ' range --->\t' + str(sum(uptimes)) + 's - ' + str(days[-1]))

    print('Ranges got:\t' + str(len(days)))
    print('Power cons.:\t' + str(consum) + ' kWh')
    print('kWh price:\t' + str(arg.kwh))
    print('Total cost:\t' + str(round(sum(days), 2)))

    # Create plot

    ind = np.arange(len(days))  # number of days on x

    plt.figure(figsize=((arg.width / 2.54), (arg.height / 2.54)))  # Set values from inches to cm
    plt.title('Roughly Electric Cost per Day')
    plt.figtext(0.005, 0.005, "Total: " + str(round(sum(days), 2)), weight=1000)

    plt.bar(ind, days, color='steelblue')
    #plt.plot(ind, days, linewidth=2, marker='o', color='forestgreen')

    plt.grid(color='lightblue', linestyle='--', linewidth=0.5, axis='x')
    plt.xticks(ind, xlegend)
    plt.gcf().autofmt_xdate()
    plt.ylabel('Cost in currency')
    plt.margins(y=0.01, x=0.01)
    plt.grid(color='lightgrey', linestyle='--', linewidth=0.5, axis='y')
    plt.tight_layout()
    cfig = plt.get_current_fig_manager()
    cfig.canvas.manager.set_window_title("Tuptime")
    plt.show()


if __name__ == "__main__":
    main()
07070100000032000081A40000000000000000000000016693DA19000020A1000000000000000000000000000000000000002E00000000tuptime-5.2.4/misc/scripts/tuptime_dbcheck.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
Test database integrity. Try to catch weird errors and fix them.
'''

import sys, argparse, locale, signal, logging, sqlite3

__version__ = '1.1.1'
DB_FILE = '/var/lib/tuptime/tuptime.db'
fixcnt = 0
errcnt = 0

# List of tests to auto-execute
TESTS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Terminate when SIGPIPE signal is received
signal.signal(signal.SIGPIPE, signal.SIG_DFL)

# Set locale to the users default settings (LANG env. var)
locale.setlocale(locale.LC_ALL, '')


def get_arguments():
    """Get arguments from command line"""

    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-f', '--filedb',
        dest='db_file',
        default=DB_FILE,
        action='store',
        help='database file (' + DB_FILE + ')',
        metavar='FILE'
    )
    parser.add_argument(
        '--fix',
        dest='fix',
        default=False,
        action='store_true',
        help='fix errors on db file'
    )
    parser.add_argument(
        '-t', '--test',
        dest='test',
        nargs='+',
        type=int,
        default=TESTS,
        help='execute only this test'
    )
    parser.add_argument(
        '-v', '--verbose',
        dest='verbose',
        default=False,
        action='store_true',
        help='verbose output'
    )
    arg = parser.parse_args()

    if arg.verbose:
        logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
        logging.info('Version: %s', (__version__))

    logging.info('Arguments: %s', vars(arg))
    return arg


def err_cnt(arg):
    global errcnt
    global fixcnt
    if arg.fix:
        fixcnt += 1
    errcnt += 1


def test0(arg, db_rows, conn):
    if len(db_rows) != db_rows[-1]['startup']:
        print(' Possible deleted rows in db. Real startups are not equal to enumerate startups')

        if arg.fix:
            conn.execute('vacuum')
            print(' FIXED: vacuum')
        err_cnt(arg)


def test1(arg, row, conn):
    if row['offbtime'] and \
       row['btime'] > row['offbtime']:
        print(row['startup'])
        print(' row btime > offbtime')
        print(' ' + str(row['btime']) + ' > ' + str(row['offbtime']))

        if arg.fix:
            conn.execute('delete from tuptime where rowid =?', (row['startup'],))
            print(' FIXED: delete row = ' + str(row['startup']))
        err_cnt(arg)


def test2(arg, row, conn, prev_row):
    if prev_row['offbtime'] > row['btime']:
        print(row['startup'])
        print(' prev_row offbtime > btime')
        print(' ' + str(prev_row['offbtime']) + ' > ' + str(row['btime']))

        if arg.fix:
            conn.execute('delete from tuptime where rowid =?', (row['startup'],))
            print(' FIXED: delete row = ' + str(row['startup']))
        err_cnt(arg)


def test3(arg, row, conn, prev_row):
    if prev_row['offbtime'] + prev_row['downtime'] != row['btime']:
        print(row['startup'])
        print(' prev_row offbtime + prev_row downtime != btime')
        print(' ' + str(prev_row['offbtime']) + ' + ' + str(prev_row['downtime']) + ' != ' + str(row['btime']))

        if arg.fix:
            fixed = row['btime'] - prev_row['offbtime']
            conn.execute('update tuptime set downtime =? where rowid =?', (fixed, (row['startup'] - 1)))
            print(' FIXED: prev_row downtime = ' + str(fixed))
        err_cnt(arg)


def test4(arg, row, conn):
    if row['offbtime'] and \
       row['btime'] + row['uptime'] != row['offbtime']:
        print(row['startup'])
        print(' row btime + uptime != offbtime')
        print(' ' + str(row['btime']) + ' + ' + str(row['uptime']) + ' != ' + str(row['offbtime']))

        if arg.fix:
            fixed = row['offbtime'] - row['btime']
            conn.execute('update tuptime set uptime =? where rowid =?', (fixed, row['startup']))
            print(' FIXED: uptime = ' + str(fixed))
        err_cnt(arg)


def test5(arg, row, conn):
    if row['rntime'] + row['slptime'] != row['uptime']:
        print(row['startup'])
        print(' rntime + slptime != uptime')
        print(' ' + str(row['rntime']) + ' + ' + str(row['slptime']) + ' != ' + str(row['uptime']))

        if arg.fix:
            fixed = row['rntime'] + row['slptime'] - row['uptime']
            if row['rntime'] > row['slptime'] and row['rntime'] - fixed > 0:
                fixed2 = row['rntime'] - fixed
                conn.execute('update tuptime set rntime =? where rowid =?', (fixed2, row['startup']))
                print(' FIXED: rntime = ' + str(fixed2))
            elif row['slptime'] >= row['rntime'] and row['slptime'] - fixed > 0:
                fixed2 = row['slptime'] - fixed
                conn.execute('update tuptime set slptime =? where rowid =?', (fixed2, row['startup']))
                print(' FIXED: slptime = ' + str(fixed2))
            else:
                conn.execute('update tuptime set rntime =?, slptime = 0 where rowid =?', (row['uptime'], row['startup']))
                print(' FIXED: rntime = ' + str(row['uptime']))
                print(' FIXED: slptime = 0')
        err_cnt(arg)


def test6(arg, row, conn):
    if row['uptime'] < 0:
        print(row['startup'])
        print(' uptime < 0')
        print(' ' + str(row['uptime']) + ' < 0')

        if arg.fix:
            conn.execute('delete from tuptime where rowid =?', (row['startup'],))
            print(' FIXED: delete row = ' + str(row['startup']))
        err_cnt(arg)


def test7(arg, row, conn):
    if row['rntime'] < 0:
        print(row['startup'])
        print(' rntime < 0')
        print(' ' + str(row['rntime']) + ' < 0')

        if arg.fix:
            conn.execute('delete from tuptime where rowid =?', (row['startup'],))
            print(' FIXED: delete row = ' + str(row['startup']))
        err_cnt(arg)


def test8(arg, row, conn):
    if row['slptime'] < 0:
        print(row['startup'])
        print(' slptime < 0')
        print(' ' + str(row['slptime']) + ' < 0')

        if arg.fix:
            conn.execute('delete from tuptime where rowid =?', (row['startup'],))
            print(' FIXED: delete row = ' + str(row['startup']))
        err_cnt(arg)


def test9(arg, row, conn):
    if row['downtime'] and \
       row['downtime'] < 0:
        print(row['startup'])
        print(' downtime < 0')
        print(' ' + str(row['downtime']) + ' < 0')

        if arg.fix:
            conn.execute('delete from tuptime where rowid =?', (row['startup'],))
            print(' FIXED: delete row = ' + str(row['startup']))
        err_cnt(arg)


def main():

    arg = get_arguments()

    db_conn = sqlite3.connect(arg.db_file)
    db_conn.row_factory = sqlite3.Row
    db_conn.set_trace_callback(logging.debug)
    conn = db_conn.cursor()

    # Check if DB have the old format
    columns = [i[1] for i in conn.execute('PRAGMA table_info(tuptime)')]
    if 'rntime' and 'slptime' and 'bootid' not in columns:
        logging.error('DB format outdated')
        sys.exit(1)

    print('Processing ' + str(arg.db_file) + ' --->')

    for i in arg.test:
        print('\n### Test ' + str(i) + ' ###')

        conn.execute('select rowid as startup, * from tuptime')
        db_rows = conn.fetchall()
        for row in db_rows:

            if arg.verbose:
                print('\n' + str(row['startup']) + '\n ' + str(dict(row)))

            if i == 1:
                test1(arg, row, conn)

            if i == 2:
                if row != db_rows[0]:  # Only after first row
                    test2(arg, row, conn, prev_row)

            if i == 3:
                if row != db_rows[0]:  # Only after first row
                    test3(arg, row, conn, prev_row)

            if i == 4:
                test4(arg, row, conn)

            if i == 5:
                test5(arg, row, conn)

            if i == 6:
                test6(arg, row, conn)

            if i == 7:
                test7(arg, row, conn)

            if i == 8:
                test8(arg, row, conn)

            if i == 9:
                test9(arg, row, conn)

            prev_row = row

        if i == 10:
            test0(arg, db_rows, conn)

        db_conn.commit()

    db_conn.close()

    print('\n' + '-' * 25)
    print('Errors: ' + str(errcnt))
    print('Fixed: ' + str(fixcnt))
    print('')


if __name__ == "__main__":
    main()
07070100000033000081A40000000000000000000000016693DA1900001D40000000000000000000000000000000000000002B00000000tuptime-5.2.4/misc/scripts/tuptime_join.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
This script join two tuptime db files into an other one. It works as follows:

    - Takes two paths of db files and an output file as destination of the join.
    - Look for first btime on both files and take the one with the older value as starting DB
    - Look on starting DB for last register and sum (btime + uptime)
    - Look on the newer file for a btime greater than previous sum value
    - Insert registers that match on destination file DB

 # tuptime_join.py /path/to/old.db /path/to/new.db -d /path/to/joined.db

Maybe after upgrade your computer and install new stuff, you want to continue with the registers
that you have before. It is possible to join the new ones into the old ones without any awkward jump.
Please, see the following example:

Join registers of two db files into other:
    tuptime_join.py /backup/old/tuptime.db /var/lib/tuptime/tuptime.db -d /tmp/tt.db

Check if all is ok:
    tuptime --noup -t -f /tmp/tt.db

Check owner (usually tuptime:tuptime) and copy modified file to right location. Re-check owner:
    ls -al /var/lib/tuptime/tuptime.db
    cp /tmp/tt.db /var/lib/tuptime/tuptime.db
    ls -al /var/lib/tuptime/tuptime.db

'''

import sys, argparse, locale, signal, logging, sqlite3
from shutil import copyfile

__version__ = '1.2.1'

# Terminate when SIGPIPE signal is received
signal.signal(signal.SIGPIPE, signal.SIG_DFL)

# Set locale to the users default settings (LANG env. var)
locale.setlocale(locale.LC_ALL, '')


def get_arguments():
    """Get arguments from command line"""

    parser = argparse.ArgumentParser()
    parser.add_argument(
        'files',
        metavar='FILE',
        nargs=2,
        type=str,
        help='files to join'
    )
    parser.add_argument(
        '-d', '--dest',
        dest='dest',
        default=False,
        action='store',
        type=str,
        required=True,
        help='destination file to store join'
    )
    parser.add_argument(
        '-v', '--verbose',
        dest='verbose',
        action='count',
        default=0,
        help='verbose output (vv=detail)'
    )
    arg = parser.parse_args()

    if arg.verbose:
        logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
        logging.info('Version: %s', (__version__))

    logging.info('Arguments: %s', vars(arg))
    return arg


def order_files(arg):
    """Identify older file"""

    # Open file0 DB
    db_conn0 = sqlite3.connect(arg.files[0])
    db_conn0.row_factory = sqlite3.Row
    db_conn0.set_trace_callback(logging.debug)
    conn0 = db_conn0.cursor()

    # Open file1 DB
    db_conn1 = sqlite3.connect(arg.files[1])
    db_conn1.row_factory = sqlite3.Row
    db_conn1.set_trace_callback(logging.debug)
    conn1 = db_conn1.cursor()

    # Check if DBs have the old format
    for conn, fname in ((conn0, arg.files[0]), (conn1, arg.files[1])):
        columns = [i[1] for i in conn.execute('PRAGMA table_info(tuptime)')]
        if 'rntime' and 'slptime' and 'bootid' not in columns:
            logging.error('DB format outdated on file: %', str(fname))
            sys.exit(1)

    # Check older file
    conn0.execute('select btime from tuptime where rowid = (select min(rowid) from tuptime)')
    conn1.execute('select btime from tuptime where rowid = (select min(rowid) from tuptime)')

    # File with large btime is newer
    if (conn0.fetchone()[0]) > (conn1.fetchone()[0]):
        return arg.files[1], arg.files[0]
    return arg.files[0], arg.files[1]


def main():
    """Main logic"""

    arg = get_arguments()

    f_old, f_new = order_files(arg)

    print('Old source file: \t' + str(f_old))
    print('New source file:\t' + str(f_new))

    # Use old file as starting DB. Copy as destination file.
    copyfile(f_old, arg.dest)
    fl0 = {'path': arg.dest}   # file0 is destination file
    fl1 = {'path': f_new}  # file1 is source newer file
    print('Destination file: \t' + str(fl0['path']))

    # Open file0 DB
    db_conn0 = sqlite3.connect(fl0['path'])
    db_conn0.row_factory = sqlite3.Row
    db_conn0.set_trace_callback(logging.debug)
    conn0 = db_conn0.cursor()

    # Open file1 DB
    db_conn1 = sqlite3.connect(fl1['path'])
    db_conn1.row_factory = sqlite3.Row
    db_conn1.set_trace_callback(logging.debug)
    conn1 = db_conn1.cursor()

    # Get all rows from source file0 and print raw rows
    conn0.execute('select rowid as startup, * from tuptime')
    db_rows = conn0.fetchall()
    print('\nDestination rows before:\t' + str(len(db_rows)))
    if arg.verbose > 1:
        for row in db_rows:
            print("\t", end='')
            print(dict(row))

    # Get last startup, btime and uptime from last row on file0 to calculate offbtime
    conn0.execute('select rowid, btime, uptime from tuptime where rowid = (select max(rowid) from tuptime)')
    fl0['startup'], fl0['btime'], fl0['uptime'] = conn0.fetchone()
    fl0['offbtime'] = fl0['btime'] + fl0['uptime']

    # Get all rows from file1 where btime is greater than file0 offbtime
    conn1.execute('select rowid as startup, * from tuptime where btime >?', (fl0['offbtime'],))
    db_rows = conn1.fetchall()

    print('\nRows to add from newer file: \t' + str(len(db_rows)))

    # Insert rows on file0
    for row in db_rows:

        # At first row on file1, set offbtime, endst and downtime to last row on file0
        if row == db_rows[0]:

            print(' Fix shutdown values on row: ' + str(fl0['startup']))
            fl0['downtime'] = row['btime'] - fl0['offbtime']
            conn0.execute('update tuptime set offbtime =?, endst = 1, downtime =? '
                          'where rowid = (select max(rowid) from tuptime)', (fl0['offbtime'], fl0['downtime']))
            if arg.verbose:
                print('\toffbtime = ' + str(fl0['offbtime']))
                print('\tendst = 1')
                print('\tdowntime = ' + str(fl0['downtime']))

        # Add registers to file0
        print(' Adding startup row: ' + str(row['startup']))
        conn0.execute('insert into tuptime values (?,?,?,?,?,?,?,?,?)',
                      (row['bootid'], row['btime'], row['uptime'], row['rntime'], row['slptime'], row['offbtime'], row['endst'], row['downtime'], row['kernel']))
        if arg.verbose:
            print('\tbootid = ' + str(row['bootid']))
            print('\tbtime = ' + str(row['btime']))
            print('\tuptime = ' + str(row['uptime']))
            print('\trntime = ' + str(row['rntime']))
            print('\tslptime = ' + str(row['slptime']))
            print('\toffbtime = ' + str(row['offbtime']))
            print('\tendst = ' + str(row['endst']))
            print('\tdowntime = ' + str(row['downtime']))
            print('\tkernel = ' + str(row['kernel']))

        # At last row, empty offbtime, endst and downtime
        if row == db_rows[-1]:
            print(' Fix shutdown values on last row')
            conn0.execute('update tuptime set offbtime = NULL, endst = 0, downtime = NULL where rowid = (select max(rowid) from tuptime)')

    db_conn0.commit()

    # Print raw rows
    conn0.execute('select rowid as startup, * from tuptime')
    db_rows = conn0.fetchall()
    print('\nDestination rows after: \t' + str(len(db_rows)))
    if arg.verbose > 1:
        for row in db_rows:
            print("\t", end='')
            print(dict(row))

    db_conn0.close()
    db_conn1.close()
    print('\nDone.')


if __name__ == "__main__":
    main()
07070100000034000081A40000000000000000000000016693DA19000028A3000000000000000000000000000000000000002D00000000tuptime-5.2.4/misc/scripts/tuptime_modify.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
This script modify the "Startup Timestamp", "Shutdown Timestamp" and
"End Status" on Tuptime database preserving the other values around in sync.

Increase 60 secs the startup datetime on register number 1:
    tuptime_modify.py -c startup -r 1 -s 60

Decrease 100 secs the startup datetime on register number 4 on other file:
    tuptime_modify.py -c startup -r 4 -s -100 -f /tmp/test.db

Increase 30 secs the shutdown datetime on register number 12:
    tuptime_modify.py -c shutdown -r 12 -s 60

Decrease 300 secs the shutdown datetime on register number 47 with verbose:
    tuptime_modify.py -c shutdown -r 47 -s -300 -v

Swich end status value on register 54:
    tuptime_modify.py -c endst -r 54
'''

import sys, argparse, locale, signal, logging, sqlite3, tempfile
from shutil import copyfile

DB_FILE = '/var/lib/tuptime/tuptime.db'
DATE_FORMAT = '%X %x'
__version__ = '1.0.1'

# Terminate when SIGPIPE signal is received
signal.signal(signal.SIGPIPE, signal.SIG_DFL)

# Set locale to the users default settings (LANG env. var)
locale.setlocale(locale.LC_ALL, '')


def get_arguments():
    """Get arguments from command line"""

    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-c', '--change',
        dest='change',
        default=False,
        action='store',
        type=str,
        required=True,
        choices=['startup', 'shutdown', 'endst'],
        help='change startup or shutdown datetime [<startup|shutdown|endst>]'
    )
    parser.add_argument(
        '-f', '--filedb',
        dest='db_file',
        default=DB_FILE,
        action='store',
        help='database file (' + DB_FILE + ')',
        metavar='FILE'
    )
    parser.add_argument(
        '-n', '--nobackup',
        dest='nobackup',
        default=False,
        action='store_true',
        help='avoid create backup db file'
    )
    parser.add_argument(
        '-r', '--register',
        dest='register',
        default=False,
        action='store',
        type=int,
        required=True,
        help='startup register to modify'
    )
    parser.add_argument(
        '-s', '--seconds',
        dest='seconds',
        default=0,
        action='store',
        type=int,
        help='seconds to add or remove (+/-)'
    )
    parser.add_argument(
        '-v', '--verbose',
        dest='verbose',
        default=False,
        action='store_true',
        help='verbose output'
    )
    arg = parser.parse_args()

    if arg.verbose:
        logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
        logging.info('Version: %s', (__version__))

    if arg.change == 'endst' and arg.seconds:
        parser.error('Operator \"seconds\" can\'t be combined with \"endst\"')

    logging.info('Arguments: %s', vars(arg))
    return arg


def backup_dbf(arg):
    """Backup db file before modify"""

    try:
        tmp_file = tempfile.NamedTemporaryFile(prefix="tuptime_", suffix=".db").name

        copyfile(arg.db_file, tmp_file)
        print('Backup file:\t' + tmp_file)
    except Exception as exp:
        logging.error('Can\'t create backup file. %s', exp)
        sys.exit(1)


def fix_endst(arg, reg, conn, modt, orgt):
    """Swich end status register"""

    # Swich between values 1 to 0 and 0 to 1
    modt['endst'] = 1 - orgt['endst']

    print('\t   modified\tendst: ' + str(modt['endst']))

    # Update values
    conn.execute('update tuptime set endst =? where rowid =?', (modt['endst'], reg['target']))


def fix_shutdown(arg, reg, conn, modt, orgt):
    """Modify shutdown datetime register"""

    # Last row have None values
    if orgt['offbtime'] is None and orgt['downtime'] is None:
        modt['offbtime'] = None
        modt['downtime'] = None
    else:
        modt['offbtime'] = orgt['offbtime'] + arg.seconds
        modt['downtime'] = orgt['downtime'] - arg.seconds
    modt['uptime'] = orgt['uptime'] + arg.seconds
    
    if orgt['rntime'] + arg.seconds > 0:
        modt['rntime'] = orgt['rntime'] + arg.seconds
        modt['slptime'] = orgt['slptime']
    elif orgt['slptime'] + arg.seconds >= 0:
        modt['rntime'] = orgt['rntime']
        modt['slptime'] = orgt['slptime'] + arg.seconds
    else:
        modt['rntime'] = modt['uptime']
        modt['slptime'] = 0

    print('\t   modified\tbtime:   --n/a--  | uptime: ' + str(modt['uptime']) + ' | rntime: ' + str(modt['rntime']) + ' | slptime: ' + str(modt['slptime']) + ' | offbtime: ' + str(modt['offbtime']) + ' | downtime: ' + str(modt['downtime']))

    # Limit check
    if modt['downtime'] is None or modt['downtime'] < 0 or \
       modt['offbtime'] is None or modt['offbtime'] < 0 or \
       modt['uptime'] < 1 or modt['rntime'] < 1 or modt['slptime'] < 0:
        logging.error('modified values can\'t be None or under limits')
        sys.exit(1)

    # Update values
    conn.execute('update tuptime set uptime =? where rowid =?', (modt['uptime'], reg['target']))
    conn.execute('update tuptime set rntime =? where rowid =?', (modt['rntime'], reg['target']))
    conn.execute('update tuptime set slptime =? where rowid =?', (modt['slptime'], reg['target']))
    conn.execute('update tuptime set downtime =? where rowid =?', (modt['downtime'], reg['target']))
    conn.execute('update tuptime set offbtime =? where rowid =?', (modt['offbtime'], reg['target']))


def fix_startup(arg, reg, conn, modt, orgt, modp, orgp):
    """Modify startup datetime register"""

    modt['btime'] = orgt['btime'] + arg.seconds
    modt['uptime'] = orgt['uptime'] - arg.seconds

    if orgt['rntime'] - arg.seconds > 0:
        modt['rntime'] = orgt['rntime'] - arg.seconds
        modt['slptime'] = orgt['slptime']
    elif orgt['slptime'] - arg.seconds >= 0:
        modt['rntime'] = orgt['rntime']
        modt['slptime'] = orgt['slptime'] - arg.seconds
    else:
        modt['rntime'] = modt['uptime']
        modt['slptime'] = 0

    print('\t   modified\tbtime: ' + str(modt['btime']) + ' | uptime: ' + str(modt['uptime']) + ' | rntime: ' + str(modt['rntime']) + ' | slptime: ' + str(modt['slptime']) + ' | offbtime:   --n/a--  | downtime:  --n/a-- ')

    # Limit check
    if modt['uptime'] < 1 or modt['rntime'] < 1 or modt['slptime'] < 0:
        logging.error('modified values can\'t be under limits')
        sys.exit(1)

    # Get previous registers to modify except from first row
    if reg['prev'] > 0:

        # Get values from previous row
        conn.execute('select downtime from tuptime where rowid =?', (reg['prev'],))
        orgp['downtime'] = conn.fetchone()[0]

        modp['downtime'] = orgp['downtime'] + arg.seconds

        print('\tTarget row-1 \'' + str(reg['prev']) + '\':')
        print('\t   original\tdowntime: ' + str(orgp['downtime']))
        print('\t   modified\tdowntime: ' + str(modp['downtime']))

        # Limit check
        if modp['downtime'] < 0:
            logging.error('downtime can\'t be lower than 0')
            sys.exit(1)

        conn.execute('update tuptime set downtime =? where rowid =?', (modp['downtime'], reg['prev']))

    # Update values
    conn.execute('update tuptime set btime =? where rowid =?', (modt['btime'], reg['target']))
    conn.execute('update tuptime set uptime =? where rowid =?', (modt['uptime'], reg['target']))
    conn.execute('update tuptime set rntime =? where rowid =?', (modt['rntime'], reg['target']))
    conn.execute('update tuptime set slptime =? where rowid =?', (modt['slptime'], reg['target']))


def main():

    arg = get_arguments()
    orgt = {}  # Original target value
    orgp = {}  # Original previous (target-1) value
    modt = {}  # Modified target value
    modp = {}  # Modified previous (target-1) value
    reg = {'target': arg.register, 'prev': (arg.register - 1)}  # Target register to modify and previous one

    print('Target file:\t' + arg.db_file)
    if not arg.nobackup:
        backup_dbf(arg)

    db_conn = sqlite3.connect(arg.db_file)
    db_conn.row_factory = sqlite3.Row
    db_conn.set_trace_callback(logging.debug)
    conn = db_conn.cursor()

    # Check if DB have the old format
    columns = [i[1] for i in conn.execute('PRAGMA table_info(tuptime)')]
    if 'rntime' and 'slptime' and 'bootid' not in columns:
        logging.error('DB format outdated')
        sys.exit(1)

    # Print raw rows
    if arg.verbose:
        print('\nTarget registers before: ')
        conn.execute('select rowid as startup, * from tuptime where rowid between ? and ?', (reg['prev'], reg['target']))
        db_rows = conn.fetchall()
        for row in db_rows:
            print("\t", end='')
            print(dict(row))

    # Validate register to modify
    conn.execute('select max(rowid) from tuptime')
    max_register = conn.fetchone()[0]
    if reg['target'] > max_register or reg['target'] < 1:
        logging.error('Invalid register to modify. Out of range')
        sys.exit(1)

    # Get values from target row
    conn.execute('select btime, uptime, rntime, slptime, offbtime, endst, downtime from tuptime where rowid =?', (reg['target'],))
    orgt['btime'], orgt['uptime'], orgt['rntime'], orgt['slptime'], orgt['offbtime'], orgt['endst'], orgt['downtime'] = conn.fetchone()

    print('\nValues:')
    print('\tTarget row \'' + str(reg['target']) + '\':')

    # Choose value to modify: endst / startup / shutdown
    if arg.change == 'endst':

        print('\t   original\tendst: ' + str(orgt['endst']))
        fix_endst(arg, reg, conn, modt, orgt)

    else:

        print('\t   original\tbtime: ' + str(orgt['btime']) + ' | uptime: ' + str(orgt['uptime']) + ' | rntime: ' + str(orgt['rntime']) + ' | slptime: ' + str(orgt['slptime']) + ' | offbtime: ' + str(orgt['offbtime']) + ' | downtime: ' + str(orgt['downtime']))

        if arg.change == 'startup':
            fix_startup(arg, reg, conn, modt, orgt, modp, orgp)
        if arg.change == 'shutdown':
            fix_shutdown(arg, reg, conn, modt, orgt)

    db_conn.commit()

    # Print raw rows
    if arg.verbose:
        print('\nTarget registers after: ')
        conn.execute('select rowid as startup, * from tuptime where rowid between ? and ?', (reg['prev'], reg['target']))
        db_rows = conn.fetchall()
        for row in db_rows:
            print("\t", end='')
            print(dict(row))

    db_conn.close()
    print('\nDone.')


if __name__ == "__main__":
    main()
07070100000035000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001200000000tuptime-5.2.4/src07070100000036000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001900000000tuptime-5.2.4/src/cron.d07070100000037000081A40000000000000000000000016693DA19000000DA000000000000000000000000000000000000002100000000tuptime-5.2.4/src/cron.d/tuptime# /etc/cron.d/tuptime: crontab entry for tuptime update. 

# NOTE: Decrease the execution time for increase accuracity.


*/5 * * * *   _tuptime    if [ -x /usr/bin/tuptime ]; then /usr/bin/tuptime -q > /dev/null; fi

07070100000038000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001900000000tuptime-5.2.4/src/init.d07070100000039000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000002000000000tuptime-5.2.4/src/init.d/debian0707010000003A000081ED0000000000000000000000016693DA190000080F000000000000000000000000000000000000002800000000tuptime-5.2.4/src/init.d/debian/tuptime#!/bin/sh
# tuptime - Report historical and statistical real time of the system, preserving it between restarts.
# Author Ricardo Fraile - 2020

# This program 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 2 of the License, or
# (at your option) any later version.

# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
#

### BEGIN INIT INFO
# Provides:          tuptime
# Required-Start:    $local_fs $time $remote_fs
# Required-Stop:     $local_fs $time $remote_fs
# Should-Start:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start tuptime at boot time
# Description:       Update tuptime.
### END INIT INFO

PATH=/sbin:/usr/sbin:/bin:/usr/bin
. /lib/init/vars.sh
. /lib/lsb/init-functions

USER='_tuptime'
BIN_NAME='tuptime'
PATH_BIN="/usr/bin/$BIN_NAME"


do_start () {
        # Start service
        log_action_begin_msg "Starting $BIN_NAME"
        log_end_msg 0
        su -s /bin/sh $USER -c "$PATH_BIN -q" > /dev/null
}

do_stop () {
        # Stop service
        log_action_begin_msg "Stopping $BIN_NAME"
        log_end_msg 0
        su -s /bin/sh $USER -c "$PATH_BIN -qg" > /dev/null
}

do_status () {
        # Status service
        su -s /bin/sh $USER -c "$PATH_BIN"
}


case "$1" in
  start|"")
        do_start
        ;;
  restart|force-reload)
        log_action_begin_msg "Restarting $BIN_NAME"
        log_end_msg 0
        do_stop
        do_start
        ;;
  stop)
        do_stop
        ;;
  status)
        do_status
        exit $?
        ;;
  *)
        echo "Usage: $BIN_NAME [start|stop|restart|status]" >&2
        exit 3
        ;;
esac

0707010000003B000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000002100000000tuptime-5.2.4/src/init.d/openwrt0707010000003C000081A40000000000000000000000016693DA1900000112000000000000000000000000000000000000002900000000tuptime-5.2.4/src/init.d/openwrt/tuptime#!/bin/sh /etc/rc.common

START=99
STOP=60

start() {
        echo 'Tuptime start'
        ln -s /opt/tuptime/ /var/lib/tuptime 2> /dev/null
        /usr/bin/python3 /usr/bin/tuptime -q
}

stop() {
        echo 'Tuptime stop'
        /usr/bin/python3 /usr/bin/tuptime -qg
}
0707010000003D000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000002000000000tuptime-5.2.4/src/init.d/redhat0707010000003E000081ED0000000000000000000000016693DA19000005E2000000000000000000000000000000000000002800000000tuptime-5.2.4/src/init.d/redhat/tuptime#!/bin/sh
#
# tuptime - Historical and statistical real time of the system.
#
# chkconfig:   12345  25  90
# description: Report historical and statistical real time \
#              of the system, preserving it between restarts. 

### BEGIN INIT INFO
# Provides: tuptime
# Required-Start: $local_fs $time
# Required-Stop: $local_fs $time
# Short-Description: start and stop tuptime
# Description: Report historical and statistical real time
#  of the system, preserving it between restarts.
### END INIT INFO

# Source function library.
. /etc/rc.d/init.d/functions

USER='_tuptime'
BIN_NAME='tuptime'
PATH_BIN="/usr/bin/$BIN_NAME"
LOCKFILE="/var/lock/subsys/$BIN_NAME"
RETVAL=0

do_start () {
        # Start service
        echo -n $"Starting tuptime: "
        daemon --user $USER $PATH_BIN -q
        RETVAL=$?
         echo
        [ $RETVAL -eq 0 ] && touch ${LOCKFILE}
        return $RETVAL
}

do_stop () {
        # Stop service
        echo -n $"Stopping tuptime: "
        daemon --user $USER $PATH_BIN -qg
        $PATH_BIN -qg
        RETVAL=$?
         echo
        [ $RETVAL -eq 0 ] && rm -f ${LOCKFILE}
        return $RETVAL
}

do_status () {
        # Status service
        $PATH_BIN
}


case "$1" in
  start|"")
        do_start
        ;;
  restart)
        do_stop
        do_start
        ;;
  stop)
        do_stop
        ;;
  status)
        do_status
        exit $?
        ;;
  *)
        echo "Usage: $BIN_NAME [start|stop|restart|status]" >&2
        exit 3
        ;;
esac
0707010000003F000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001A00000000tuptime-5.2.4/src/launchd07070100000040000081A40000000000000000000000016693DA19000002FE000000000000000000000000000000000000003200000000tuptime-5.2.4/src/launchd/localhost.tuptime.plist<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>localhost.tuptime</string>
	<key>ProgramArguments</key>
	<array>
		<string>sh</string>
		<string>-c</string>
		<string>/usr/bin/tuptime -q</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
	<key>StartInterval</key>
	<integer>60</integer>
	<key>EnvironmentVariables</key>
	<dict>
		<key>PATH</key>
		<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
		<key>LC_ALL</key>
		<string>en_US.UTF-8</string>
		<key>LANG</key>
		<string>en_US.UTF-8</string>
	</dict>
	<key>StandardErrorPath</key>
	<string>/tmp/tuptime.stderr.log</string>
</dict>
</plist>
07070100000041000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001600000000tuptime-5.2.4/src/man07070100000042000081A40000000000000000000000016693DA1900001448000000000000000000000000000000000000002000000000tuptime-5.2.4/src/man/tuptime.1'\" t
.TH TUPTIME 1 "Jan 2024" "5.2.4" "General Commands Manual"

.SH NAME
tuptime \- Report historical and statistical real time of the system, preserving it between restarts. Total uptime.

.SH SYNOPSIS
tuptime [\-h] [\-A STARTUP] [\-b] [\-c] [\-d DATETIME_FMT] [\-e DECIMALS] [\-E STARTUP] [\-f FILE] [\-g] [\-i] [\-k] [\-l] [\-n] [\-o TYPE] [\-p] [\-q] [\-r] [\-s] [\-S STARTUP] [\-t] [\-\-tat TIMESTAMP] [\-\-tsince TIMESTAMP] [\-\-tuntil TIMESTAMP] [\-U STARTUP] [\-v] [\-V]

.SH DESCRIPTION
.RS
.RE
Tuptime reports historical and statistical real time of 
the system, preserving it between restarts. Indeed, it can:
.RS
- Count system startups
.RS
.RE
- Register first boot time (a.k.a. installation time)
.RS
.RE
- Count nicely and accidentally shutdowns
.RS
.RE
- Uptime and downtime percentage since first boot time
.RS
.RE
- Accumulated system uptime (running and sleeping), downtime and total
.RS
.RE
- Register used kernels and boot IDs
.RS
.RE
- Report current uptime
.RS
.RE
- Print formatted table or list with the system history
.RS
.RE
- Narrow reports since, until or at a given startup or timestamp
.RS
.RE
- Output in csv format

.SH OPTIONS
.SS ARGUMENTS
.TS
tab (@);
l lx.
\-h | \-\-help@T{
Show this help message and exit
T}
\-A | \-\-at STARTUP@T{
Limit to this startup number
T}
\-b | \-\-bootid@T{
Show boot identifier
T}
\-c | \-\-csv@T{
Output in csv format
T}
\-d | \-\-date DATETIME_FMT@T{
Datetime/timestamp format output
T}
\-e | \-\-dec DECIMALS@T{
Number of decimals in percentages
T}
\-E | \-\-exclude STARTUP@T{
Startup numbers to exclude
T}
\-f | \-\-file FILE@T{
Database file (file path)
T}
\-g | \-\-graceful@T{
Register a graceful shutdown
T}
\-i | \-\-invert@T{
Startup number in reverse count | swich between longest/shortest on default output
T}
\-k | \-\-kernel@T{
Show kernel version
T}
\-l | \-\-list@T{
Enumerate system life as list
T}
\-n | \-\-noup@T{
Avoid update values into DB
T}
\-o | \-\-order TYPE@T{
Order enumerate by [u|r|s|e|d|k] (u = uptime | r = runtime | s = sleep time | e = end status | d = downtime | k = kernel)
T}
\-p | \-\-power@T{
Show power states run + sleep
T}
\-q | \-\-quiet@T{
Update values into DB without output
T}
\-r | \-\-reverse@T{
Reverse order in listings
T}
\-s | \-\-seconds@T{
Output time in seconds and epoch
T}
\-S | \-\-since STARTUP@T{
Limit from this startup number
T}
\-t | \-\-table@T{
Enumerate system life as table
T}
\-\-tat TIMESTAMP@T{
Report system status at specific timestamp
T}
\-\-tsince TIMESTAMP@T{
Limit from this epoch timestamp
T}
\-\-tuntil TIMESTAMP@T{
Limit until this epoch timestamp
T}
\-U | \-\-until STARTUP@T{
Limit up until this startup number
T}
\-v | \-\-verbose@T{
Verbose output
T}
\-V | \-\-version@T{
Show version
T}
.TE
.SS ENVIRONMENT
.RE
TUPTIME_DBF
.RS
Set an alternative database file path. The argument -f, --filedb takes
precedence over this.
.TE

.SH DEFAULT OUTPUT
.RS
.RE
System startups:
.RS
Total number of system startups from since first timestamp available.

.RE
System shutdowns:
.RS
Total number of shutdowns done correctly or incorrectly.

.RE
System life:
.RS
Time counter since first startup timestamp available.

.RE
System uptime:
.RE
System downtime:
.RS
Percentage of time and time counter.

.RE
Longest uptime:
.RE
Longest downtime:
.RS
Time counter and date with the complete longest uptime/downtime register.

.RE
Average uptime:
.RE
Average downtime:
.RS
Average time counter.

.RE
Current uptime:
.RS
Actual time counter and datetime since registered boot timestamp.

.SH EXAMPLES
.TP 
.BI tuptime
Default output.
.TP 
.B tuptime -t
Enumerate system life as table.
.TP 
.B tuptime -l
Enumerate system life as list.
.TP
.B tuptime -k 
Add kernel information to the output.
.TP
.B tuptime --csv
Report in csv format.
.TP
.B tuptime -s
Change default human readable datetime/timestamp style and print times in
seconds and datetimes in epoch.
.TP
.B tuptime -d '%H:%M:%S   %m-%d-%Y'
Change the datetime/timestamp format. By default the output use the
configured system locales.
.TP
.B tuptime --tsince -31557600
Report since one year ago.

.SH FILES
.TP
.I /etc/cron.d/tuptime
Scheduled cron file.
.TP
.I /etc/init.d/tuptime
Init file.
.TP
.I /lib/systemd/system/tuptime.service
Systemd service unit file. Register time values into database.
.TP
.I /usr/bin/tuptime
Main and only executable file.
.TP
.I /usr/share/doc/tuptime/
Directory with multiple documentation files.
.TP
.I /lib/systemd/system/tuptime-sync.timer
Systemd .timer unit for use instead of cron. Only executes tuptime-sync.service.
.TP
.I /lib/systemd/system/tuptime-sync.service
Systemd .service unit required by tuptime-sync.timer. Updates time values into database.
.TP
.I /usr/share/man/man1/tuptime.1
Manual page.

.SH SEE ALSO
.TP
.I /usr/share/doc/tuptime/tuptime-manual.txt.gz
Detailed documentation.
.TP
.I https://github.com/rfmoz/tuptime/
Official repository.

.SH "AUTHOR"
.PP
Ricardo Fraile <r@rfmoz.eu>

.SH "COPYRIGHT"
.PP
Copyright (C) 2024 by Ricardo F. All Rights Reserved.

This product 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.
07070100000043000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001900000000tuptime-5.2.4/src/openrc07070100000044000081A40000000000000000000000016693DA1900000220000000000000000000000000000000000000002100000000tuptime-5.2.4/src/openrc/tuptime#!/sbin/openrc-run

name='tuptime'
command='/usr/bin/tuptime'
command_user='_tuptime'
command_args='-q'

depend() {
        need localmount
        after bootmisc
        after clock
}

start() {
        ebegin "Starting ${name}"
        su ${command_user} -s /bin/sh -c "${command} -q"
        eend $?
}

stop() {
        ebegin "Stopping ${name}"
        su ${command_user} -s /bin/sh -c "${command} -qg"
        eend $?
}

status() {
        ebegin "Status ${name}"
        su ${command_user} -s /bin/sh -c "${command} -q"
        eend $?
}
07070100000045000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001700000000tuptime-5.2.4/src/rc.d07070100000046000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001F00000000tuptime-5.2.4/src/rc.d/freebsd07070100000047000081ED0000000000000000000000016693DA1900000285000000000000000000000000000000000000002700000000tuptime-5.2.4/src/rc.d/freebsd/tuptime#!/bin/sh
#
# PROVIDE: tuptime
# REQUIRE: DAEMON
# KEYWORD: nojail shutdown

tuptime_enable=${tuptime_enable:-"NO"}

. /etc/rc.subr

name="tuptime"
rcvar=tuptime_enable
tuptime_user="_tuptime"

start_cmd="${name}_start"
stop_cmd="${name}_stop"

export PATH=$PATH:/usr/local/bin/

tuptime_start()
{
	echo "Starting $name."
	command_args="-q"

	su -m ${tuptime_user} -c "$name $command_args" 2> /dev/null || $name $command_args 2> /dev/null
}

tuptime_stop()
{
	echo "Stopping $name."
	command_args="-qg"

	su -m ${tuptime_user} -c "$name $command_args" 2> /dev/null || $name $command_args 2> /dev/null
}

load_rc_config $name
run_rc_command "$1"
07070100000048000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001F00000000tuptime-5.2.4/src/rc.d/openbsd07070100000049000081ED0000000000000000000000016693DA190000016A000000000000000000000000000000000000002700000000tuptime-5.2.4/src/rc.d/openbsd/tuptime#!/bin/ksh

daemon="/usr/local/bin/tuptime"
daemon_user="_tuptime"

. /etc/rc.d/rc.subr

rc_reload=NO

rc_check() {
	# Fake daemon, not running in background
	# Verify if started at boot
	if [[ -e ${_RC_RUNFILE} ]]; then
		return 0
	else
		return 1
	fi
}

rc_start() {
	rc_exec "${daemon} -q"
}

rc_stop() {
	rc_exec "${daemon} -qg"
	_rc_rm_runfile
}

rc_cmd $1
0707010000004A000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000001A00000000tuptime-5.2.4/src/systemd0707010000004B000041ED0000000000000000000000026693DA1900000000000000000000000000000000000000000000002500000000tuptime-5.2.4/src/systemd/sysusers.d0707010000004C000081A40000000000000000000000016693DA1900000125000000000000000000000000000000000000003200000000tuptime-5.2.4/src/systemd/sysusers.d/tuptime.confu _tuptime - "Tuptime execution user" /var/lib/tuptime

# Note:
#   systemd-sysusers doesn't create home directories. Use in combination with
#   StateDirectory=tuptime under [Service] in tuptime.service unit.
#   https://lists.freedesktop.org/archives/systemd-devel/2018-February/040348.html
0707010000004D000081A40000000000000000000000016693DA1900000120000000000000000000000000000000000000002F00000000tuptime-5.2.4/src/systemd/tuptime-sync.service[Unit]
Description=Tuptime scheduled sync service
Documentation=man:tuptime(1) file:///usr/share/doc/tuptime/tuptime-manual.txt.gz
Requires=tuptime.service
After=tuptime.service
#ConditionPathExists=!/etc/cron.d/tuptime

[Service]
Type=oneshot
User=_tuptime
ExecStart=/usr/bin/tuptime -q
0707010000004E000081A40000000000000000000000016693DA1900000095000000000000000000000000000000000000002D00000000tuptime-5.2.4/src/systemd/tuptime-sync.timer[Unit]
Description=Tuptime scheduled sync timer

[Timer]
OnBootSec=1min
OnCalendar=*:0/5
Unit=tuptime-sync.service

[Install]
WantedBy=timers.target
0707010000004F000081A40000000000000000000000016693DA190000002A000000000000000000000000000000000000003000000000tuptime-5.2.4/src/systemd/tuptime-tmpfiles.confd	/var/lib/tuptime	0755	_tuptime _tuptime
07070100000050000081A40000000000000000000000016693DA1900000156000000000000000000000000000000000000002A00000000tuptime-5.2.4/src/systemd/tuptime.service[Unit]
Description=Tuptime service
Documentation=man:tuptime(1) file:///usr/share/doc/tuptime/tuptime-manual.txt.gz
After=time-sync.target
Wants=time-sync.target

[Service]
Type=oneshot
User=_tuptime
RemainAfterExit=true
ExecStart=/usr/bin/tuptime -q
ExecStop=/usr/bin/tuptime -qg
StateDirectory=tuptime

[Install]
WantedBy=multi-user.target
07070100000051000081A40000000000000000000000016693DA190000BE30000000000000000000000000000000000000001A00000000tuptime-5.2.4/src/tuptime#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""tuptime - Report historical and statistical real time of the system,
preserving it between restarts"""
# Copyright (C) 2011-2024 - Ricardo F.

# This program 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 2 of the License, or
# (at your option) any later version.

# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

import sys, os, argparse, locale, platform, signal, logging, sqlite3, time
from datetime import datetime
# On os_bsd(): import subprocess


DB_FILE = '/var/lib/tuptime/tuptime.db'
DATETIME_FMT = '%X %x'
__version__ = '5.2.4'

# Terminate gracefully when SIGPIPE signal is received.
try:
    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except Exception:
    pass

# Set locale to the user’s default settings (LANG env. var)
try:
    locale.setlocale(locale.LC_ALL, '')
except Exception:
    pass  # Falling back to default locale.setlocale(locale.LC_ALL, 'C')


def get_arguments():
    """Get arguments from command line"""

    parser = argparse.ArgumentParser()
    group = parser.add_mutually_exclusive_group()
    parser.add_argument(
        '-A', '--at',
        dest='at',
        default=None,
        action='store',
        metavar='STARTUP',
        type=int,
        help='limit to this startup number'
    )
    parser.add_argument(
        '-b', '--bootid',
        dest='bootid',
        action='store_true',
        default=False,
        help='show boot identifier'
    )
    parser.add_argument(
        '-c', '--csv',
        dest='csv',
        action='store_true',
        default=False,
        help='csv output'
    )
    parser.add_argument(
        '-d', '--date',
        dest='dtm_format',
        metavar='DATETIME_FMT',
        default=DATETIME_FMT,
        action='store',
        help='datetime/timestamp format output'
    )
    parser.add_argument(
        '-e', '--dec',
        dest='dec',
        default=2,
        metavar='DECIMALS',
        action='store',
        type=int,
        help='number of decimals in percentages'
    )
    parser.add_argument(
        '-E', '--exclude',
        dest='exclude',
        default=None,
        action='store',
        metavar='STARTUP',
        help='startup numbers to exclude'
    )
    parser.add_argument(
        '--decp',
        dest='decp',
        default=None,
        action='store',
        type=int,
        help=argparse.SUPPRESS
    )
    parser.add_argument(
        '-f', '--filedb',
        dest='db_file',
        default=DB_FILE,
        action='store',
        help='database file (' + DB_FILE + ')',
        metavar='FILE'
    )
    parser.add_argument(
        '-g', '--graceful',
        dest='endst',
        action='store_const',
        default=int(0),
        const=int(1),
        help='register a graceful shutdown'
    )
    parser.add_argument(
        '-i', '--invert',
        dest='invert',
        action='store_true',
        default=False,
        help='startup number in reverse count | swich between longest/shortest on default output'
    )
    parser.add_argument(
        '-k', '--kernel',
        dest='kernel',
        action='store_true',
        default=False,
        help='show kernel version'
    )
    group.add_argument(
        '-l', '--list',
        dest='list',
        default=False,
        action='store_true',
        help='enumerate system life as list'
    )
    parser.add_argument(
        '-n', '--noup',
        dest='update',
        default=True,
        action='store_false',
        help='avoid update values into DB'
    )
    parser.add_argument(
        '-o', '--order',
        dest='order',
        metavar='TYPE',
        default=False,
        action='store',
        type=str,
        choices=['u', 'r', 's', 'e', 'd', 'k'],
        help='order enumerate by [u|r|s|e|d|k]'
    )
    parser.add_argument(
        '-p', '--power',
        dest='power',
        default=False,
        action='store_true',
        help='show power states run + sleep'
    )
    parser.add_argument(
        '--pctl',
        dest='percentile',
        default=None,
        action='store',
        type=int,
        help=argparse.SUPPRESS
    )
    parser.add_argument(
        '-q', '--quiet',
        dest='quiet',
        default=False,
        action='store_true',
        help='update values into DB without output'
    )
    parser.add_argument(
        '-r', '--reverse',
        dest='reverse',
        default=False,
        action='store_true',
        help='reverse order in listings'
    )
    parser.add_argument(
        '-s', '--seconds',
        dest='seconds',
        default=False,
        action='store_true',
        help='output time in seconds and epoch'
    )
    parser.add_argument(
        '-S', '--since',
        dest='since',
        default=None,
        action='store',
        metavar='STARTUP',
        type=int,
        help='limit from this startup number'
    )
    group.add_argument(
        '-t', '--table',
        dest='table',
        default=False,
        action='store_true',
        help='enumerate system life as table'
    )
    group.add_argument(
        '--tat',
        dest='tat',
        metavar='TIMESTAMP',
        default=None,
        action='store',
        type=int,
        help='system status at epoch timestamp'
    )
    parser.add_argument(
        '--tsince',
        dest='ts',
        metavar='TIMESTAMP',
        default=None,
        action='store',
        type=int,
        help='limit from this epoch timestamp'
    )
    parser.add_argument(
        '--tuntil',
        dest='tu',
        metavar='TIMESTAMP',
        default=None,
        action='store',
        type=int,
        help='limit until this epoch timestamp'
    )
    parser.add_argument(
        '-U', '--until',
        dest='until',
        default=None,
        action='store',
        metavar='STARTUP',
        type=int,
        help='limit up until this startup number'
    )
    parser.add_argument(
        '-v', '--verbose',
        dest='verbose',
        default=False,
        action='store_true',
        help='verbose output'
    )
    parser.add_argument(
        '-V', '--version',
        action='version',
        version='tuptime version ' + (__version__),
        help='show version'
    )

    parser.add_argument(
        '-x', '--silent',
        dest='silent',
        default=False,
        action='store_true',
        help=argparse.SUPPRESS
    )
    arg = parser.parse_args()

    # Check enable verbose
    if arg.verbose:
        logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
        logging.info('Version = %s', (__version__))

    if (arg.power and (arg.ts or arg.tu or arg.tat)) or (arg.tat and (arg.ts or arg.tu)):
        # - power states report accumulated time across an uptime range, it isn't possible to
        # know if the state was running or sleeping between specific points inside it.
        # - tat work within startups numbers, not in narrow ranges.
        parser.error('Invalid argument combination')

    # Wrap 'at' over since and until
    if arg.at is not None:
        arg.since = arg.until = arg.at

    # Expand exclude range
    if arg.exclude is not None:
        try:
            ex_extend = [ss.split('-') for ss in arg.exclude.split(',')]
            ex_extend = [range(int(i[0]), int(i[1])+1) if len(i) == 2 else i for i in ex_extend]
            arg.exclude = sorted({int(item) for sublist in ex_extend for item in sublist})
        except:
            logging.warning('Invalid exclude argument value. Not applied')
            arg.exclude = None

    if arg.decp:
        arg.dec = arg.decp
        logging.warning('Argument \'--decp\' is deprecated in favour of \'--dec\'')
    if arg.percentile:
        logging.warning('Argument \'--pctl\' deprecated without functionality')
    if arg.silent:
        arg.quiet = arg.silent
        logging.warning('Argument \'-x\' is deprecated in favour of \'-q\'')

    logging.info('Arguments = %s', vars(arg))
    return arg


def get_os_values():
    """Get values from each type of operating system"""

    sis = {'bootid': None, 'btime': None, 'uptime': None, 'rntime': None, 'slptime': None, 'kernel': None}

    def os_bsd(sis):
        """Get values from BSD"""

        logging.info('System = BSD')
        import subprocess

        try:
            sis['btime'] = time.clock_gettime(time.CLOCK_REALTIME) - time.clock_gettime(time.CLOCK_MONOTONIC)
        except Exception as exp:
            logging.info('Old btime assignment. %s', exp)
            sysctl_out = subprocess.run(['sysctl', '-n', 'kern.boottime'], stdout=subprocess.PIPE, text=True, check=True).stdout
            # Some BSDs report the value assigned to 'sec', others do it directly
            if 'sec' in sysctl_out:  # FreeBSD, Darwin
                sis['btime'] = sysctl_out.split(' sec = ')[1].split(',')[0]
            else:  # OpenBSD, NetBSD
                sis['btime'] = sysctl_out

        try:
            # Time since some unspecified starting point. Contains sleep time on BSDs
            sis['uptime'] = time.clock_gettime(time.CLOCK_MONOTONIC)
            if sys.platform.startswith(('darwin')):
                # OSX > 10.12 has only UPTIME_RAW. Avoid mix it with non _RAW counters, extract _RAW from MONOTONIC_ and remove it
                sis['rntime'] = time.clock_gettime(time.CLOCK_UPTIME_RAW) + (sis['uptime'] - time.clock_gettime(time.CLOCK_MONOTONIC_RAW))
            else:
                # Time the system has been running. Not contains sleep time on BSDs
                sis['rntime'] = time.clock_gettime(time.CLOCK_UPTIME)
        except Exception as exp:
            logging.info('Old uptime/rntime assignment. %s', exp)
            logging.info('Power states disabled, values assigned from uptime')
            sis['uptime'] = time.time() - sis['btime']
            sis['rntime'] = sis['uptime']

        try:
            sysctl_out = subprocess.run(['sysctl', '-xn', 'kern.boot_id'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, check=False).stdout
            if 'Dump' in sysctl_out:
                sis['bootid'] = sysctl_out.split('Dump:')[-1].rstrip()
            else:
                raise ValueError
        except Exception:
            logging.info('BSD boot_id not assigned')

        return sis

    def os_linux(sis):
        """Get values from Linux"""

        logging.info('System = Linux')

        try:
            sis['btime'] = time.clock_gettime(time.CLOCK_REALTIME) - time.clock_gettime(time.CLOCK_BOOTTIME)
        except Exception as exp:
            logging.info('Old btime assignment. %s', exp)
            with open('/proc/stat', encoding='utf-8') as fl2:
                for line in fl2:
                    if line.startswith('btime'): sis['btime'] = line.split()[1]

        try:  # uptime and rntime must be together to avoid time mismatch between them
            # Time since some unspecified starting point. Contains sleep time on linux
            sis['uptime'] = time.clock_gettime(time.CLOCK_BOOTTIME)
            # Time since some unspecified starting point. Not contains sleep time on linux
            sis['rntime'] = time.clock_gettime(time.CLOCK_MONOTONIC)
        except Exception as exp:
            logging.info('Old uptime/rntime assignment. %s', exp)
            logging.info('Power states disabled, values assigned from uptime')
            with open('/proc/uptime', encoding='utf-8') as fl1:
                sis['uptime'] = fl1.readline().split()[0]
            sis['rntime'] = sis['uptime']

        try:
            with open('/proc/sys/kernel/random/boot_id', encoding='utf-8') as fl3:
                sis['bootid'] = fl3.readline().split()[0]
        except Exception:
            logging.info('Linux boot_id not assigned')

        try:
            # Way to indicate a synchronized clock by SystemD, see filename notes on systemd/src/time-wait-sync/time-wait-sync.c
            # Check if SystemD is used, but file not exists. Could be mandatory, outstripping control_drift()
            if not os.path.exists('/run/systemd/timesync/synchronized') and os.path.exists('/run/systemd'):
                logging.info('Timesync state file not found = /run/systemd/timesync/synchronized')
                #logging.error('Clock no synchronized')
                #sys.exit(1)
        except Exception as exp:
            logging.info('Timesync check fails. %s', exp)

        return sis

    # Linux
    if sys.platform.startswith('linux'):
        sis = os_linux(sis)
    # BSD and related
    elif sys.platform.startswith(('freebsd', 'darwin', 'dragonfly', 'openbsd', 'netbsd', 'sunos')):
        sis = os_bsd(sis)
    # elif:
    #     other_os()
    else:
        logging.error('System = %s not supported', sys.platform)
        sys.exit(1)

    # Check right allocation of core variables before continue
    for key, value in sis.items():
        if key in ('btime', 'uptime', 'rntime') and value is None:
            logging.error('"%s" value unallocate from system. Execution aborted', key)
            sys.exit(1)
        if key in ('uptime', 'rntime') and float(value) < 0:
            logging.error('"%s" value "%s" below zero. Execution aborted', key, value)
            sys.exit(1)

    # Set number OS values as integer
    for key in ('btime', 'uptime', 'rntime'):
        sis[key] = int(round(float(sis[key])))

    # Avoid mismatch with elapsed time between getting counters and/or rounded values,
    # with less than 1 seconds, values are equal
    if (sis['uptime'] - 1) <= sis['rntime'] <= (sis['uptime'] + 1):
        sis['rntime'] = sis['uptime']

    # Get sleep time from runtime
    sis['slptime'] = sis['uptime'] - sis['rntime']

    # Set text OS values
    sis['bootid'] = str(sis['bootid'])
    sis['kernel'] = str(platform.platform())

    logging.info('Python = %s', platform.python_version())
    try:
        logging.info('Current locale = %s', locale.getlocale())
    except Exception:
        logging.info('Current locale = None')
    logging.info('Sys values = %s', sis)
    logging.info('Execution uid,gid = %s,%s', os.getuid(), os.getgid())

    # Avoid executing when OS clock is too out of phase
    if sis['btime'] < 946684800:   # 01/01/2000 00:00
        logging.error('Epoch boot time value is too old \'%s\'. Check system clock sync', sis['btime'])
        logging.error('Tuptime execution aborted')
        sys.exit(1)

    return sis


def gain_db(sis, arg):
    """Assure DB state and get DB connection"""

    # If db_file keeps default value, check for DB environment variable
    if arg.db_file == DB_FILE:
        if os.environ.get('TUPTIME_DBF'):
            arg.db_file = os.environ.get('TUPTIME_DBF')
            logging.info('DB environ var = %s', arg.db_file)

    # Test path
    arg.db_file = os.path.abspath(arg.db_file)  # Get absolute or relative path
    try:
        if os.makedirs(os.path.dirname(arg.db_file), exist_ok=True):
            logging.info('Making path = %s', os.path.dirname(arg.db_file))
    except Exception as exp_path:
        logging.error('Check DB path "%s": %s', os.path.dirname(arg.db_file), exp_path)
        sys.exit(1)

    # Test and create DB with the initial values
    try:
        if os.path.isfile(arg.db_file):
            logging.info('DB file exists = %s (uid=%s,gid=%s)', arg.db_file, os.stat(arg.db_file).st_uid, os.stat(arg.db_file).st_gid)


        else:
            logging.info('Making DB file = %s', arg.db_file)
            db_conn = sqlite3.connect(arg.db_file)
            db_conn.set_trace_callback(logging.debug)
            conn = db_conn.cursor()
            conn.execute('BEGIN deferred')
            conn.execute('create table tuptime'
                         ' (bootid text, btime integer, uptime integer, rntime integer, slptime integer,'
                         ' offbtime integer, endst integer, downtime integer, kernel text)')
            conn.execute('insert into tuptime values (?,?,?,?,?,?,?,?,?) ',
                         (sis['bootid'], sis['btime'], sis['uptime'], sis['rntime'],
                          sis['slptime'], None, arg.endst, None, sis['kernel']))
            conn.execute('PRAGMA user_version = {}'.format(__version__.partition('.')[0]))
            db_conn.commit()
            db_conn.close()
    except Exception as exp_file:
        logging.error('Check DB file "%s": %s', arg.db_file, exp_file)
        sys.exit(1)

    # Get DB connection and begin transaction
    try:
        logging.info('Getting DB connection')
        db_conn = sqlite3.connect(arg.db_file)
        db_conn.row_factory = sqlite3.Row
        db_conn.set_trace_callback(logging.debug)
        conn = db_conn.cursor()
        conn.execute('BEGIN deferred')
    except Exception as exp_conn:
        logging.error('DB connection failed: %s', exp_conn)
        sys.exit(1)

    # Check if DB has the old format
    user_version = conn.execute('PRAGMA user_version').fetchone()[0]
    if arg.verbose:
        logging.info('DB user_version: %s', user_version)
    if user_version < 5:
        logging.warning('DB format outdated')
        upgrade_db(db_conn, conn, arg)

    return db_conn, conn


def upgrade_db(db_conn, conn, arg):
    """Upgrade DB to current format"""

    if not os.access(arg.db_file, os.W_OK):
        logging.error('"%s" file not writable by execution user', arg.db_file)
        sys.exit(1)
    logging.warning('Upgrading DB file = %s', arg.db_file)

    try:
        columns = [i[1] for i in conn.execute('PRAGMA table_info(tuptime)')]

        if 'rntime' not in columns or 'slptime' not in columns:  # new in tuptime v4
            logging.warning('Upgrading DB with power states')
            conn.execute('create table if not exists tuptimeNew'
                         ' (btime integer, uptime integer, rntime integer, slptime integer,'
                         ' offbtime integer, endst integer, downtime integer, kernel text)')
            conn.execute('update tuptime set uptime = cast(round(uptime) as int)')
            conn.execute('update tuptime set offbtime = cast(round(offbtime) as int)')
            conn.execute('update tuptime set downtime = cast(round(downtime) as int)')
            conn.execute('insert into tuptimeNew'
                         ' (btime, uptime, offbtime, endst, downtime, kernel)'
                         ' SELECT btime, uptime, offbtime, endst, downtime, kernel'
                         ' FROM tuptime')
            conn.execute('update tuptimeNew set rntime = uptime')
            conn.execute('update tuptimeNew set slptime = 0')
            conn.execute('drop table tuptime')
            conn.execute('alter table tuptimeNew RENAME TO tuptime')
            conn.execute('PRAGMA user_version = 4')
            db_conn.commit()

        if 'bootid' not in columns:  # new in tuptime v5
            logging.warning('Upgrading DB with boot ID')
            conn.execute('create table if not exists tuptimeNew'
                         ' (bootid text, btime integer, uptime integer, rntime integer, slptime integer,'
                         ' offbtime integer, endst integer, downtime integer, kernel text)')
            conn.execute('insert into tuptimeNew'
                         ' (btime, uptime, rntime, slptime, offbtime, endst, downtime, kernel)'
                         ' SELECT btime, uptime, rntime, slptime, offbtime, endst, downtime, kernel'
                         ' FROM tuptime')
            conn.execute('update tuptimeNew set bootid = "None"')
            conn.execute('update tuptimeNew set kernel = "None" where kernel = ""')
            conn.execute('drop table tuptime')
            conn.execute('alter table tuptimeNew RENAME TO tuptime')
            conn.execute('PRAGMA user_version = 5')
            db_conn.commit()

        logging.warning('Set DB user_version')
        conn.execute('PRAGMA user_version = 5')
        db_conn.commit()

    except Exception as exp_db:
        logging.error('Upgrading DB format failed. "%s"', exp_db)
        sys.exit(1)

    logging.warning('Upgraded')


def control_drift(lastdb, sis):
    """Check time drift due inconsistencies with system clock"""

    offset = sis['btime'] - lastdb['btime']
    logging.info('Drift over btime = %s', offset)

    # offset > 0 fixed now over current uptime value
    # offset < 0 fixed later over next downtime value
    if offset:
        #logging.info('System timestamp = %s', sis['btime'] + sis['uptime'])
        #btime value registered on DB is a startup reference, keep it
        sis['btime'] = lastdb['btime']

        if offset > 0:
            sis['uptime'] = sis['uptime'] + offset
            sis['rntime'] = sis['rntime'] + offset
            # After drift values, fixed timestamp must be equal to system timestamp
            #logging.info('Fixed timestamp = %s', sis['btime'] + sis['uptime'])
            logging.info("Drifted sys values = {'btime': %s, 'uptime': %s, 'rntime': %s}", sis['btime'], sis['uptime'], sis['rntime'])
        else:
            logging.info("Drifted sys values = {'btime': %s}", sis['btime'])

    return sis


def time_conv(secs):
    """Convert seconds to human readable style"""

    dtm = {'yr': 0, 'd': 0, 'h': 0, 'm': 0, 's': 0}
    line = ''

    # Get human values from seconds
    dtm['m'], dtm['s'] = divmod(secs, 60)
    dtm['h'], dtm['m'] = divmod(dtm['m'], 60)
    dtm['d'], dtm['h'] = divmod(dtm['h'], 24)
    dtm['yr'], dtm['d'] = divmod(dtm['d'], 365)

    # Build datetime sentence with this order
    for key in ('yr', 'd', 'h', 'm', 's'):

        # Avoid print empty values at the beginning, except seconds
        if (dtm[key] == 0) and (line == '') and (key != 's'):
            continue
        else:
            line += str(dtm[key]) + key + ' '

    # Return without last space char
    return str(line[:-1])


def trim_rows(db_rows, sis, last_st, arg):
    """Report rows since or until a given startup number or timestamp

    Conventions:
        - Keep startup number, boot ID and kernel
        - Empty values are False
    """

    def tuntil(db_rows, arg):
        for row in (*db_rows,):  # Parse rows trying to look for the rightmost (older) value

            if arg.tu > row['offbtime'] and arg.tu <= (row['offbtime'] + row['downtime']):
                row['downtime'] = arg.tu - row['offbtime']

            elif arg.tu > row['btime'] and arg.tu <= (row['btime'] + row['uptime']):
                row['uptime'] = arg.tu - row['btime']
                for key in ('rntime', 'slptime', 'offbtime', 'endst', 'downtime'):
                    row[key] = False

            elif arg.tu <= row['btime']:
                db_rows.remove(row)

            else:
                continue
        return db_rows

    def tsince(db_rows, arg):
        for row in (*db_rows,):  # Parse rows trying to look for the leftmost (newer) value

            if arg.ts <= row['btime']:
                continue

            elif arg.ts > row['btime'] and arg.ts < (row['btime'] + row['uptime']):
                row['uptime'] = row['btime'] + row['uptime'] - arg.ts
                for key in ('btime', 'rntime', 'slptime'):
                    row[key] = False

            elif arg.ts == row['offbtime']:
                for key in ('btime', 'uptime', 'rntime', 'slptime'):
                    row[key] = False

            elif arg.ts > row['offbtime'] and arg.ts < (row['offbtime'] + row['downtime']):
                row['downtime'] = row['offbtime'] + row['downtime'] - arg.ts
                for key in ('btime', 'uptime', 'rntime', 'slptime', 'offbtime', 'endst'):
                    row[key] = False

            else:
                db_rows.remove(row)
        return db_rows

    # Filter based on argument
    if arg.until is not None:
        if arg.until <= 0:  # Negative value start from bottom
            arg.until = last_st + arg.until
        db_rows = [row for row in db_rows if arg.until >= row['startup']]

    if arg.since is not None:
        if arg.since <= 0:  # Negative value start from bottom
            arg.since = last_st + arg.since
        db_rows = [row for row in db_rows if arg.since <= row['startup']]

    if arg.tu is not None:
        if arg.tu < 0:  # Negative value decrease actual timestamp
            arg.tu = sis['btime'] + sis['uptime'] + arg.tu
        db_rows = tuntil(db_rows, arg)

    if arg.ts is not None:
        if arg.ts < 0:  # Negative value decrease actual timestamp
            arg.ts = sis['btime'] + sis['uptime'] + arg.ts
        db_rows = tsince(db_rows, arg)

    if arg.exclude is not None:
        db_rows = [row for row in db_rows if row['startup'] not in arg.exclude]

    return db_rows, arg


def reorder(db_rows, arg, last_st):
    """Order and/or revert and/or invert output"""

    if db_rows:

        if arg.order:
            match_value = {'u': 'uptime', 'r': 'rntime', 's': 'slptime', 'e': 'endst', 'd': 'downtime', 'k': 'kernel'}
            db_rows = sorted(db_rows, key=lambda x: (x[match_value[arg.order]]))

        if arg.reverse:
            db_rows = list(reversed(db_rows))

        if arg.invert:
            for ind, _ in enumerate(db_rows):
                db_rows[ind]['startup'] = db_rows[ind]['startup'] - last_st

    return db_rows


def format_output(db_rows, arg):
    """Set the right output format"""

    for row in db_rows:

        for key in ('bootid', 'kernel'):
            if row[key] is False:
                row[key] = ''

        if row['uptime'] is False:
            row['uptime'] = row['rntime'] = row['slptime'] = ''
        else:
            if not arg.seconds:
                for key in ('uptime', 'rntime', 'slptime'):
                    row[key] = time_conv(row[key])

        if row['endst'] is False:
            row['endst'] = ''
        else:
            if row['offbtime'] is False or row['downtime'] is False:
                row['endst'] = ''
            else:
                if row['endst'] == 1:
                    row['endst'] = 'OK'
                elif row['endst'] == 0:
                    row['endst'] = 'BAD'

        for key in ('btime', 'offbtime', 'downtime'):
            if row[key] is False:
                row[key] = ''
            else:
                if not arg.seconds:
                    if key == 'downtime':
                        row[key] = time_conv(row[key])
                    else:
                        row[key] = datetime.fromtimestamp(row[key]).strftime(arg.dtm_format)

    return db_rows


def print_list(db_rows, arg):
    """Print values as list"""

    if arg.csv:  # Set content/spaces between values
        sp0, sp5 = '"', ''
        sp1 = sp2 = '","'
    else:
        sp0, sp1, sp2, sp5 = '', '  ', ': ', ' '

    for row_dict in format_output(db_rows, arg):

        print(sp0 + 'Startup' + sp2 + sp5 + str(row_dict['startup']), end='')
        if row_dict['btime']:
            print(sp1 + 'at' + sp1 + str(row_dict['btime']), end='')
        print(sp0)

        if arg.bootid and row_dict['bootid']:
            print(sp0 + 'Boot ID' + sp2 + sp5 + str(row_dict['bootid']) + sp0)

        if row_dict['uptime']:
            print(sp0 + 'Uptime' + sp2 + (sp5 * 2) + str(row_dict['uptime']) + sp0)

            if arg.power:
                print(sp0 + 'Running' + sp2 + sp5 + str(row_dict['rntime']) + sp0)
                print(sp0 + 'Sleeping' + sp2 + str(row_dict['slptime']) + sp0)

        if row_dict['offbtime']:
            print(sp0 + 'Shutdown' + sp2 + str(row_dict['endst']) + sp1 + 'at' + sp1 + str(row_dict['offbtime']) + sp0)

        if row_dict['downtime']:
            print(sp0 + 'Downtime' + sp2 + str(row_dict['downtime']) + sp0)

        if arg.kernel:
            print(sp0 + 'Kernel' + sp2 + (sp5 * 2) + str(row_dict['kernel']) + sp0)

        if not arg.csv: print('')


def print_table(db_rows, arg):
    """Print values as a table"""

    tops = {'startup': 'No.', 'bootid': 'Boot ID', 'btime': 'Startup T.', 'uptime': 'Uptime', 'rntime': 'Running', 'slptime': 'Sleeping',
            'offbtime': 'Shutdown T.', 'endst': 'End', 'downtime': 'Downtime', 'kernel': 'Kernel'}
    side_spaces = 3

    # Remove unused optional values
    if not arg.bootid:
        tops.pop('bootid')

    if not arg.power:
        tops.pop('rntime')
        tops.pop('slptime')

    if not arg.kernel:
        tops.pop('kernel')

    # Assign remaining values for print the header
    tbl = [tuple(tops.values())]

    # Add empty brake up line if csv is not used
    if not arg.csv:
        tbl.append(tuple(' ') * len(tbl[0]))

    # Assign table values for print
    for row_dict in format_output(db_rows, arg):
        rowd = tuple(row_dict[i] for i in tops)
        tbl.append(rowd)

    # Print table values
    if arg.csv:
        for row in tbl:
            for key, value in enumerate(row):
                sys.stdout.write('"' + str(value) + '"')
                if (key + 1) != len(row):
                    sys.stdout.write(',')
            print('')

    else:
        # Get index position of elements left aligned
        align_left = [tbl[0].index(i) for i in ('End', 'Kernel') if i in tbl[0]]

        # Get the maximum width of the given column index
        colpad = [max([len(str(row[i])) for row in tbl]) for i in range(len(tbl[0]))]

        # Print cols by row
        for row in tbl:

            # Convert the entire row to strings
            row = [str(cell) for cell in row]

            # First in raw and next ones with side spaces
            sys.stdout.write(row[0].rjust(colpad[0]))
            for i in range(1, len(row)):
                if i in align_left:
                    col = (side_spaces * ' ') + row[i].ljust(colpad[i])
                else:
                    col = row[i].rjust(colpad[i] + side_spaces)
                sys.stdout.write(col)
            print('')


def print_tat(db_rows, sis, last_st, arg):
    """Report system status at specific timestamp"""

    # Negative value decrease actual timestamp
    if arg.tat < 0:
        arg.tat = sis['btime'] + sis['uptime'] + arg.tat

    report = {'at': arg.tat, 'status': None}

    for row in db_rows:
        for key in ('startup', 'bootid', 'kernel'):
            report[key] = row[key]

        # Report UP if tat fall into btime + uptime range
        if (arg.tat >= row['btime']) and (arg.tat < (row['btime'] + row['uptime'])):
            report['status'] = 'UP'
            report['time'] = arg.tat - row['btime']
            report['time_fwd'] = row['uptime'] - report['time']
            report['time_total'] = row['uptime']
            break

        # Report DOWN if tat fall into offbtime + downtime range
        elif (arg.tat >= row['offbtime']) and (arg.tat < (row['offbtime'] + row['downtime'])):
            report['time'] = arg.tat - row['offbtime']
            report['time_fwd'] = row['downtime'] - report['time']
            if row['endst'] == 1:
                report['status'] = 'DOWN-OK'
            elif row['endst'] == 0:
                report['status'] = 'DOWN-BAD'
            report['time_total'] = row['downtime']
            break

    # If status keep their default value, no match, clean all other variables. Also, cover unexpected division by zero
    if report['status'] is None or report['time_total'] == 0:
        report['startup'] = report['time'] = report['time_fwd'] = 0
        report['bootid'] = report['kernel'] = 'None'
        perctg_1 = perctg_2 = 0.0
    else:
        perctg_1 = round(report['time'] * 100 / report['time_total'], arg.dec)
        perctg_2 = round(report['time_fwd'] * 100 / report['time_total'], arg.dec)

        if arg.invert:
            report['startup'] = report['startup'] - last_st

    if not arg.seconds:
        report['at'] = datetime.fromtimestamp(report['at']).strftime(arg.dtm_format)
        report['time'] = time_conv(report['time'])
        report['time_fwd'] = time_conv(report['time_fwd'])

    if arg.csv:  # Set content/spaces between values
        sp0, sp5 = '"', ''
        sp2 = sp3 = '","'
    else:
        sp0, sp2 = '', ':\t\t'
        sp5, sp3 = ' ', '  '

    print(sp0 + 'System status' + sp2 + str(report['status']) + sp3 + 'at' + sp3 + str(report['at']) + sp3 + 'on' + sp3 + str(report['startup']) + sp0)
    if arg.bootid:
        print(sp0 + (sp5 * 3) + '...boot ID' + sp2 + str(report['bootid']) + sp0)
    if arg.kernel:
        print(sp0 + (sp5 * 4) + '...kernel' + sp2 + str(report['kernel']) + sp0)
    print(sp0 + (sp5 * 3) + 'elapsed in' + sp2 + str(perctg_1) + '%' + sp3 + '=' + sp3 + str(report['time']) + sp0)
    print(sp0 + (sp5 * 1) + 'remaining in' + sp2 + str(perctg_2) + '%' + sp3 + '=' + sp3 + str(report['time_fwd']) + sp0)


def print_default(db_rows, sis, arg):
    """Print values with default output"""

    def parse_rows(db_rows, updown, cnt, lmt):
        """Loop along all DB rows"""

        for row in db_rows:

            # Sum counters
            if row['btime'] is not False:
                updown['startups'] += 1
            if row['offbtime'] is not False:
                if row['endst'] == 0:
                    updown['bad'] += 1
                elif row['endst'] == 1:
                    updown['ok'] += 1
                updown['shutdowns'] += 1

            # Get lists with all values for later sum and count
            for key in cnt:
                if row[key] is not False:
                    cnt[key].append(row[key])

            # Get limits for uptime and downtime values
            if row['uptime']:
                if lmt['max-up']['bootid'] is None or lmt['max-up']['uptime'] <= row['uptime']:
                    lmt['max-up'] = row.copy()
                if lmt['min-up']['bootid'] is None or lmt['min-up']['uptime'] >= row['uptime']:
                    lmt['min-up'] = row.copy()
            if row['downtime']:
                if lmt['max-down']['bootid'] is None or lmt['max-down']['downtime'] <= row['downtime']:
                    lmt['max-down'] = row.copy()
                if lmt['min-down']['bootid'] is None or lmt['min-down']['downtime'] >= row['downtime']:
                    lmt['min-down'] = row.copy()

        return updown, cnt, lmt

    # Set default values
    updown = {'startups': 0, 'shutdowns': 0, 'ok': 0, 'bad': 0}
    tstamp = {'min': arg.ts, 'max': arg.tu}  # Args as init values
    cnt = {'bootid': [], 'uptime': [], 'rntime': [], 'slptime': [], 'downtime': [], 'kernel': []}
    range_trim = False
    def1 = {'uptime': 0, 'rntime': 0, 'slptime': 0, 'downtime': 0}
    def2 = {**def1, 'bootid': None, 'btime': False, 'offbtime': False, 'kernel': None, 'startup': None}

    cal = {'tot': def1.copy(), 'ave': def1.copy()}
    lmt = {'max-up': def2.copy(), 'max-down': def2.copy(), 'min-up': def2.copy(), 'min-down': def2.copy()}
    rate = {k: float(v) for k, v in def1.items()}  # Float values of def1

    # Get values from all DB rows
    updown, cnt, lmt = parse_rows(db_rows, updown, cnt, lmt)

    # Max timestamp - until datetime
    # Get rightmost (older) value from last row if arg.tu is not used
    if db_rows and tstamp['max'] is None:
        if db_rows[-1]['offbtime'] is not False:
            tstamp['max'] = db_rows[-1]['offbtime'] + db_rows[-1]['downtime']
        elif db_rows[-1]['btime'] is not False:
            tstamp['max'] = db_rows[-1]['btime'] + db_rows[-1]['uptime']

    # Min timestamp - since datetime
    # Get leftmost (newer) value, always btime, from first row value if arg.ts is not used
    if db_rows and tstamp['min'] is None:
        tstamp['min'] = db_rows[0]['btime']

    # Check if range is trimmed
    if db_rows and arg.exclude:
        if db_rows[-1]['startup'] - db_rows[0]['startup'] != len(db_rows) - 1:
            range_trim = True

    # Get totals and system life
    for key in cal['tot']:
        cal['tot'][key] += sum(cnt[key])
    sys_life = cal['tot']['uptime'] + cal['tot']['downtime']

    # Get rates and average uptime / downtime
    if sys_life > 0:
        for key in rate:
            rate[key] = round((cal['tot'][key] * 100) / sys_life, arg.dec)

        if cnt['uptime']:
            for key in ('uptime', 'rntime', 'slptime'):
                cal['ave'][key] = int(round(cal['tot'][key] / len(cnt['uptime'])))

        if cnt['downtime']:
            cal['ave']['downtime'] = int(round(cal['tot']['downtime'] / len(cnt['downtime'])))

    # Output style: Apply human printable values or keep seconds
    if not arg.seconds:
        sys_life = time_conv(sys_life)

        for key, val in tstamp.items():
            if val is not None:
                tstamp[key] = datetime.fromtimestamp(val).strftime(arg.dtm_format)

        for key in ('uptime', 'rntime', 'slptime', 'downtime'):
            if key != 'downtime' and sis[key] is not None:
                sis[key] = time_conv(sis[key])
            for dval in (cal, lmt):
                for val in dval.values():
                    if val[key] is not False:
                        val[key] = time_conv(val[key])

        for key in ('btime', 'offbtime'):
            if key != 'offbtime' and sis[key] is not None:
                sis[key] = datetime.fromtimestamp(sis[key]).strftime(arg.dtm_format)
            for val in lmt.values():
                if val[key] is not False:
                    val[key] = datetime.fromtimestamp(val[key]).strftime(arg.dtm_format)

    # Prepare values for print
    if arg.csv:  # Set content/spaces between values
        sp0, sp5 = '"', ''
        sp1 = sp4 = sp3 = '","'
    else:
        sp0, sp1, sp3 = '', ': \t', '  '
        sp4 = sp5 = ' '

    uptime = {'average': str(cal['ave']['uptime']), 'long': str(lmt['max-up']['uptime']), 'short': str(lmt['min-up']['uptime']),
              'sys_time': str(cal['tot']['uptime']), 'sys_rate': str(rate['uptime']) + '%', 'current': str(sis['uptime'])}

    downtime = {'average': str(cal['ave']['downtime']), 'long': str(lmt['max-down']['downtime']), 'short': str(lmt['min-down']['downtime']),
                'sys_time': str(cal['tot']['downtime']), 'sys_rate': str(rate['downtime']) + '%'}

    if arg.power:  # Add power values if needed
        uptime['average'] += sp4 + '(rn: ' + str(cal['ave']['rntime']) + ' + slp: ' + str(cal['ave']['slptime']) + ')'
        uptime['long'] += sp4 + '(rn: ' + str(lmt['max-up']['rntime']) + ' + slp: ' + str(lmt['max-up']['slptime']) + ')'
        uptime['short'] += sp4 + '(rn: ' + str(lmt['min-up']['rntime']) + ' + slp: ' + str(lmt['min-up']['slptime']) + ')'
        uptime['sys_time'] += sp4 + '(rn: ' + str(cal['tot']['rntime']) + ' + slp: ' + str(cal['tot']['slptime']) + ')'
        uptime['sys_rate'] += sp4 + '(rn: ' + str(rate['rntime']) + '% + slp: ' + str(rate['slptime']) + '%)'
        uptime['current'] += sp4 + '(rn: ' + str(sis['rntime']) + ' + slp: ' + str(sis['slptime']) + ')'

    if arg.invert:  # Set longest or shortest values for uptime/downtime
        bfp = {'name': 'Shortest', 'key': 'short', 'up': lmt['min-up'], 'down': lmt['min-down']}
    else:
        bfp = {'name': 'Longest', 'key': 'long', 'up': lmt['max-up'], 'down': lmt['max-down']}

    # System block print
    print(sp0 + 'System startups' + sp1 + str(updown['startups']) + sp3 + 'since' + sp3 + str(tstamp['min']), end='')
    if arg.tu or arg.until:
        print(sp3 + 'until' + sp3 + str(tstamp['max']), end='')
    if range_trim:
        print(sp3 + 'trimmed', end='')
    print(sp0)

    print(sp0 + 'System shutdowns' + sp1 + str(updown['ok']) + sp4 + 'ok' + sp3 + '+' + sp3 + str(updown['bad']) + sp4 + 'bad' + sp0)
    print(sp0 + 'System life' + sp1 + (sp5 * 8) + str(sys_life) + sp0)
    if arg.bootid:
        print(sp0 + 'System boot IDs' + sp1 + str(len(set(cnt['bootid']))) + sp0)
    if arg.kernel:
        print(sp0 + 'System kernels' + sp1 + str(len(set(cnt['kernel']))) + sp0)
    if not arg.csv: print('')

    # Uptime block print
    if bfp['up']['btime'] is not False:
        print(sp0 + bfp['name'] + ' uptime' + sp1 + uptime[bfp['key']] + sp3 + 'from' + sp3 + str(bfp['up']['btime']) + sp0)
    else:
        print(sp0 + bfp['name'] + ' uptime' + sp1 + uptime[bfp['key']] + sp0)
    if arg.bootid:
        print(sp0 + (sp5 * 4) + '...boot ID' + sp1 + str(bfp['up']['bootid']) + sp0)
    if arg.kernel:
        print(sp0 + (sp5 * 5) + '...kernel' + sp1 + str(bfp['up']['kernel']) + sp0)
    print(sp0 + 'Average uptime' + sp1 + uptime['average'] + sp0)
    print(sp0 + 'System uptime' + sp1 + (sp5 * 8) + uptime['sys_rate'] + sp3 + '=' + sp3 + uptime['sys_time'] + sp0)
    if not arg.csv: print('')

    # Downtime block print
    if bfp['down']['offbtime'] is not False:
        print(sp0 + bfp['name'] + ' downtime' + sp1 + downtime[bfp['key']] + sp3 + 'from' + sp3 + str(bfp['down']['offbtime']) + sp0)
    else:
        print(sp0 + bfp['name'] + ' downtime' + sp1 + downtime[bfp['key']] + sp0)
    if arg.bootid:
        print(sp0 + (sp5 * 6) + '...boot ID' + sp1 + str(bfp['down']['bootid']) + sp0)
    if arg.kernel:
        print(sp0 + (sp5 * 7) + '...kernel' + sp1 + str(bfp['down']['kernel']) + sp0)
    print(sp0 + 'Average downtime' + sp1 + downtime['average'] + sp0)
    print(sp0 + 'System downtime' + sp1 + downtime['sys_rate'] + sp3 + '=' + sp3 + downtime['sys_time'] + sp0)

    # Current block print
    if arg.update:
        if not arg.csv: print('')
        print(sp0 + 'Current uptime' + sp1 + uptime['current'] + sp3 + 'since' + sp3 + str(sis['btime']) + sp0)
        if arg.bootid:
            print(sp0 + (sp5 * 4) + '...boot ID' + sp1 + str(sis['bootid']) + sp0)
        if arg.kernel:
            print(sp0 + (sp5 * 5) + '...kernel' + sp1 + str(sis['kernel']) + sp0)


def output_hub(db_rows, sis, arg):
    """Manage values for print"""

    last_st = db_rows[-1]['startup']
    if len(db_rows) != last_st:
        logging.info('Real startups are not equal to enumerate startups. Deleted rows in DB')

    if arg.endst:
        if arg.endst != db_rows[-1]['endst']:
            # Graceful register must be performed at shutdown by the init manager, with _tuptime user
            # or a privileged one. No one needs to do in other moment. A normal user can't write into DB
            print('Graceful shutdown in DB = mismatch')
            sys.exit(1)
        print('Graceful shutdown in DB = ok')
        return

    if arg.update:
        # If the user can only read DB, the select query over DB return outdated numbers in last row
        # because the DB was not updated previously. The following snippet update them in memory
        # If the user wrote into DB, the values are the same
        for key in ('uptime', 'rntime', 'slptime', 'kernel'):
            db_rows[-1][key] = sis[key]
        db_rows[-1]['endst'] = arg.endst
        logging.info('Refresh last row values = %s', db_rows[-1])

    # Convert last line None sqlite registers to False
    for key, value in db_rows[-1].items():
        if value is None:
            db_rows[-1][key] = False

    # Get narrow range of rows if it applies
    if not (None is arg.exclude is arg.until is arg.since is arg.tu is arg.ts):
        db_rows, arg = trim_rows(db_rows, sis, last_st, arg)

    # Print values with the chosen output
    if arg.list:
        print_list(reorder(db_rows, arg, last_st), arg)
    elif arg.table:
        print_table(reorder(db_rows, arg, last_st), arg)
    elif arg.tat is not None:
        print_tat(db_rows, sis, last_st, arg)
    else:
        print_default(db_rows, sis, arg)


def check_new_boot(lastdb, sis):
    """Test if system has new boot"""

    # How tuptime does it:
    #
    #    If boot id exists (only on Linux and FreeBSD), checking if its value has changed
    #
    #    If not exists, checking if the value resultant from previous btime plus previous uptime (both
    #    saved into DB) is lower than current btime with at least 1 second of diference.
    #
    # Checking boot id is the most secure way to detect a new boot. Working with time values is not 100% relialible.
    # In some particular cases the btime value from /proc/stat or from the system clock functions may change.
    # When tuptime doesn't register a new boot, only an update of the records, it tries to fix the drift.
    #
    # To avoid lost an uptime record, please be sure that the system has time sync enabled, the init/systemd
    # script and the cron task works as expected.

    if lastdb['bootid'] != 'None' and sis['bootid'] != 'None':
        if lastdb['bootid'] != sis['bootid']:
            logging.info('System restarted = True from bootid')
            return True
        else:
            logging.info('System restarted = False from bootid')
            return False

    elif lastdb['buptime'] < sis['btime']:
        logging.info('System restarted = True from btime')
        return True

    else:
        logging.info('System restarted = False')
        return False


def main():
    """Main entry point, core logic"""

    arg = get_arguments()
    sis = get_os_values()
    db_conn, conn = gain_db(sis, arg)

    conn.execute('select rowid, bootid, btime, uptime, endst from tuptime where rowid = (select max(rowid) from tuptime)')
    lastdb = dict(zip(['rowid', 'bootid', 'btime', 'uptime', 'endst'], conn.fetchone()))
    lastdb['buptime'] = lastdb['btime'] + lastdb['uptime']
    logging.info('Last DB values = %s', lastdb)

    # Check if system was restarted
    if arg.update and check_new_boot(lastdb, sis):

        if lastdb['buptime'] > sis['btime']:  # Assure btime. Never lower than shutdown
            sis['btime'] = lastdb['buptime']
        lastdb['downtime'] = sis['btime'] - lastdb['buptime']

        try:
            # Save downtimes for previous boot
            conn.execute('update tuptime set offbtime =?, downtime =? where rowid =? ',
                         (lastdb['buptime'], lastdb['downtime'], lastdb['rowid']))

            # Create a new boot register
            conn.execute('insert into tuptime values (?,?,?,?,?,?,?,?,?)',
                         (sis['bootid'], sis['btime'], sis['uptime'], sis['rntime'],
                          sis['slptime'], None, arg.endst, None, sis['kernel']))
            db_conn.commit()
            logging.info('DB info = insert ok')

        except Exception as exp:
            # If you see this error, maybe the systemd script isn't executed at startup
            # or the DB file (DB_FILE) has wrong permissions
            logging.error('Detected a new system startup but the values have not been saved into DB')
            logging.error('Tuptime execution user failed to write into DB file: %s', arg.db_file)
            logging.error('%s', exp)
            sys.exit(1)

    elif arg.update:
        # Adjust time drift. Check only when system wasn't restarted
        sis = control_drift(lastdb, sis)

        # If a graceful shutdown was just registered before, let 5 seconds to next update to avoid being overlapped
        # with regular schedule execution (it can happen at shutdown)
        if lastdb['endst'] and (lastdb['uptime'] + 5 > sis['uptime']) and not arg.endst:
            logging.info('DB info = graceful pass')
        else:
            try:
                # Update current boot records
                conn.execute('update tuptime set uptime =?, rntime =?, slptime =?, endst =?, kernel =? where rowid =?',
                             (sis['uptime'], sis['rntime'], sis['slptime'], arg.endst, sis['kernel'], lastdb['rowid']))
                db_conn.commit()
                logging.info('DB info = update ok')

            except sqlite3.OperationalError as exc:
                if arg.endst:
                    logging.warning('DB warning = skip register a graceful shutdown, %s', exc)
                else:
                    logging.info('DB info = skip update values, %s', exc)

    else:
        sis = control_drift(lastdb, sis)
        logging.info('DB info = skip by arg.update')

    if arg.quiet:
        db_conn.close()
        logging.info('Quiet mode')

    else:
        # Get all rows to determine print values. Convert from sqlite row object to dict to allow item allocation
        conn.execute('select rowid as startup, * from tuptime')
        db_rows = [dict(row) for row in conn.fetchall()]
        db_conn.close()

        output_hub(db_rows, sis, arg)


if __name__ == "__main__":
    main()
07070100000052000081A40000000000000000000000016693DA1900001A16000000000000000000000000000000000000002100000000tuptime-5.2.4/tuptime-install.sh#!/bin/bash
set -e

#
# Tuptime installation linux script
#
# Usage:
# 	 bash tuptime-install.sh	Default master install
# 	 bash tuptime-install.sh -d	Install using dev branch
#

VERSION=1.9.2

# Execution user
EXUSR='_tuptime'

# Destination dir for executable file
D_BIN='/usr/bin'

# Swich dev branch
DEV=0


# Check bash execution
if [ -z "$BASH" ]; then
	echo "--- ERROR - execute only with BASH ---"
	exit 1
fi

# Check root execution
if [ "$(id -u)" != "0" ]; then
	echo "Please run this script as root"
	exit 1
fi

# Test arguments
while getopts ":d" opt; do
	case $opt in
	d)
		DEV=1
		;;
	\?)
		echo "Invalid option: -$OPTARG"
		exit 1
		;;
	esac
done

# Test if it is a linux system
if [ $(uname -s) != "Linux" ]; then
	echo "Sorry, only for Linux systems"
	exit 1
fi

# Test required commands
check_command() {
	if ! command -v "$1" &> /dev/null; then
		echo "ERROR: "$1" command not found"
		echo "Please install it"
		exit 1
	fi
}

check_command "curl"
check_command "tar"
check_command "python3"

# Test python version
PYTHON_VERSION=$(python3 --version | awk '{print $2}')
if [[ "$(cut -d'.' -f1 <<<"$PYTHON_VERSION")" -lt 3 ]]; then
	echo "ERROR: Python 3 or later is required"
	echo "Please upgrade your Python installation"
	exit 1
else
	# Test if all modules needed are available
	REQUIRED_PYTHON_MODULES=("sys" "os" "argparse" "locale" "platform" "signal" "logging" "sqlite3" "datetime")
	for module in "${REQUIRED_PYTHON_MODULES[@]}"; do
		if ! python3 -c "import $module" &> /dev/null; then
			echo "ERROR: Required Python module '$module' is not available."
			exit 1
		fi
	done
fi

# Set SystemD path
if [ -d /usr/lib/systemd/system/ ]; then
	SYSDPATH='/usr/lib/systemd/system/'
else
	SYSDPATH='/lib/systemd/system/'
fi

# Set Selinux swich
if getenforce 2> /dev/null | grep -q 'Enforcing'; then
       	echo "Selinux enabled in Enforcing"
	SELX=1
else
	SELX=0
fi

# Temporary dir to download sources
F_TMP1=$(mktemp -d)

echo ""
echo "++ Tuptime installation script v.$VERSION ++"
echo ""

echo "+ Getting source tar file"
if [ ${DEV} -eq 1 ]; then
	echo "  ...using dev branch"
	tar xz --strip 1 -C "${F_TMP1}" -f <(curl -sL https://github.com/rfmoz/tuptime/archive/dev.tar.gz)
else
	tar xz --strip 1 -C "${F_TMP1}" -f <(curl -sL https://github.com/rfmoz/tuptime/archive/master.tar.gz)
fi
echo '  [OK]'

echo "+ Copying files"
install -m 755 "${F_TMP1}"/src/tuptime "${D_BIN}"/tuptime
((SELX)) && restorecon -vF "${D_BIN}"/tuptime
echo '  [OK]'

echo "+ Creating Tuptime execution user '_tuptime'"
if systemd-sysusers --version > /dev/null 2>&1; then
	echo "  ...using systemd-sysusers"
        install -m 644 "${F_TMP1}"/src/systemd/sysusers.d/tuptime.conf /usr/lib/sysusers.d/
        ((SELX)) && restorecon -vF /usr/lib/sysusers.d/tuptime.conf
	systemd-sysusers /usr/lib/sysusers.d/tuptime.conf
	echo '  [OK]'

elif useradd -h > /dev/null 2>&1; then
	echo "  ...using useradd"
	useradd --system --no-create-home --home-dir '/var/lib/tuptime' \
        	--shell '/bin/false' --comment 'Tuptime execution user' "${EXUSR}" && echo '  [OK]'
elif adduser -h > /dev/null 2>&1; then
	echo "  ...using adduser"
	adduser -S -H -h '/var/lib/tuptime' -s '/bin/false' "${EXUSR}" && echo '  [OK]'
else
	echo "#######################################"
	echo " WARNING - _tuptime user not available"
	echo "#######################################"
	echo '  [BAD]'
fi

echo "+ Creating Tuptime db"
tuptime -q
echo '  [OK]'

echo "+ Setting Tuptime db ownership"
chown -R "${EXUSR}":"${EXUSR}" /var/lib/tuptime || chown -R "${EXUSR}" /var/lib/tuptime
chmod 755 /var/lib/tuptime
echo '  [OK]'

echo "+ Executing Tuptime with '_tuptime' user for testing"
su -s /bin/sh "${EXUSR}" -c "tuptime -q"
echo '  [OK]'

# Install init
PID1=$(grep 'Name' /proc/1/status | cut -f2)
if [ "${PID1}" = 'systemd' ]; then
	echo "+ Copying Systemd file"
	install -m 644 "${F_TMP1}"/src/systemd/tuptime.service "${SYSDPATH}"
	((SELX)) && restorecon -vF "${SYSDPATH}"tuptime.service
	systemctl daemon-reload
	systemctl enable tuptime.service
	systemctl start tuptime.service
	echo '  [OK]'

elif [ "${PID1}" = 'init' ] && [ -f /etc/rc.d/init.d/functions ]; then
	echo "+ Copying  SysV init RedHat file"
	install -m 755 "${F_TMP1}"/src/init.d/redhat/tuptime /etc/init.d/tuptime
	((SELX)) && restorecon -vF /etc/init.d/tuptime
	chkconfig --add tuptime
	chkconfig tuptime on
	echo '  [OK]'

elif [ "${PID1}" = 'init' ] && [ -f /lib/lsb/init-functions ]; then
	echo "+ Copying SysV init Debian file"
	install -m 755 "${F_TMP1}"/src/init.d/debian/tuptime /etc/init.d/tuptime
	((SELX)) && restorecon -vF /etc/init.d/tuptime
	update-rc.d tuptime defaults
	echo '  [OK]'

elif [ "${PID1}" = 'init' ] && [ -f /etc/rc.conf ]; then
	echo "+ Copying OpenRC file for init"
	install -m 755 "${F_TMP1}"/src/openrc/tuptime /etc/init.d/
	((SELX)) && restorecon -vF /etc/init.d/tuptime
	rc-update add tuptime default
	rc-service tuptime start
	echo '  [OK]'

elif [ "${PID1}" = 'openrc-init' ]; then
	echo "+ Copying OpenRC file for openrc-init"
	install -m 755 "${F_TMP1}"/src/openrc/tuptime /etc/init.d/
	((SELX)) && restorecon -vF /etc/init.d/tuptime
	rc-update add tuptime default
	rc-service tuptime start
	echo '  [OK]'

elif [ "${PID1}" = 'runit' ] && [ -f /etc/rc.local ] && [ -f /etc/rc.shutdown ]; then
	echo "+ Runit startup and shutdown execution"
	echo 'tuptime -q' >> /etc/rc.local
	echo 'tuptime -qg' >> /etc/rc.shutdown

else
	echo "#########################################"
	echo " WARNING - Any init file for your system"
	echo "#########################################"
	echo '  [BAD]'
fi

# Install cron
if [ -d "${SYSDPATH}" ]; then
	echo "+ Copying tuptime-sync.timer and .service"
	install -m 644 "${F_TMP1}"/src/systemd/tuptime-sync.*  "${SYSDPATH}"
	((SELX)) && restorecon -vF "${SYSDPATH}"tuptime-sync.*
	systemctl enable tuptime-sync.timer
	systemctl start tuptime-sync.timer
	echo '  [OK]'

elif [ -d /etc/cron.d/ ]; then
	echo "+ Copying Cron file"
	install -m 644 "${F_TMP1}"/src/cron.d/tuptime /etc/cron.d/tuptime
	((SELX)) && restorecon -vF /etc/cron.d/tuptime
	echo '  [OK]'

elif [ -d /etc/cron.hourly/ ]; then
	echo "+ Cron hourly execution"
	printf '#!/bin/sh \n tuptime -q' > /etc/cron.hourly/tuptime
	chmod 744 /etc/cron.hourly/tuptime
	echo '  [OK]'

elif [ -d /etc/periodic/15min/ ]; then
	echo "+ Periodic execution"
	printf '#!/bin/sh \n tuptime -q' > /etc/periodic/15min/tuptime
	chmod 744 /etc/periodic/15min/tuptime
	echo '  [OK]'

else
	echo "#########################################"
	echo " WARNING - Any cron file for your system"
	echo "#########################################"
	echo '  [BAD]'
fi

echo "+ Finished, all steps done."
echo ""

tuptime
07070100000053000081A40000000000000000000000016693DA190000674A000000000000000000000000000000000000002100000000tuptime-5.2.4/tuptime-manual.txt                         ----------------------
                            Tuptime    Manual
                         ----------------------
                              version 5.2.4
                                Ricardo F.
                               01/May/2024



============
| Abstract |
============

Tuptime reports historical and statistical real time of the system, preserving it between restarts.
Indeed, it can:

  - Count system startups
  - Register first boot time (since installation time)
  - Count nicely and accidentally shutdowns 
  - Uptime and downtime percentage since first boot time
  - Accumulated system uptime (running and sleeping), downtime and total
  - Register used kernels and boot IDs
  - Report current uptime
  - Print formatted table or list with the system history
  - Narrow reports since, until or at a given startup or timestamp
  - Output in csv format



=================
| Prerequisites |
=================

Operating systems:
   - Linux
   - BSD

Software:
   - Python 3.x
   - Python modules (most included in python core by default):
       sys, os, argparse, locale, platform, signal, logging, sqlite3, datetime

If a distro is used, all is met with:
    - In Linux:  'python' package >= 3.0
    - In FreeBSD: 'python3' and 'py36-sqlite3'

Also, execution via Docker is available, see note below.


================
| Installation |
================

For Linux systems:

   + From distribution repository:

       Debian  - https://packages.debian.org/tuptime
       Ubuntu  - https://packages.ubuntu.com/tuptime

           # apt install tuptime

       Fedora, EPEL  - https://src.fedoraproject.org/rpms/tuptime

           # dnf install tuptime
           # systemctl enable tuptime.service && systemctl start tuptime.service
           # systemctl enable tuptime-sync.timer && systemctl start tuptime-sync.timer


   + Using bash script:
      
       Download and execute the installation script:

           # bash < <(curl -Ls https://git.io/tuptime-install.sh)

   + Manual install:

       Clone repository and copy executable file:

           # install -m 755 tuptime/src/tuptime /usr/bin/tuptime

       Add _tuptime user with sysusers or useradd, choose one:

           With sysusers:

               # install -m 644 tuptime/src/systemd/tuptime.sysusers /usr/lib/sysusers.d/tuptime.conf
               # systemd-sysusers /usr/lib/sysusers.d/tuptime.conf

           With useradd:

               # useradd --system --no-create-home --home-dir '/var/lib/tuptime' \
                 --shell '/bin/false' --comment 'Tuptime execution user' _tuptime

       Execute tuptime with a privilege user for create DB path:

           # tuptime

       Change owner of the DB path and file:

           # chown -R _tuptime:_tuptime /var/lib/tuptime

       Add sync execution with cron or timer, choose one:

           With cron file:

               # install -m 644 tuptime/src/cron.d/tuptime /etc/cron.d/tuptime

           With systemd timer:

               # install -m 644 tuptime/src/systemd/tuptime-sync.* /lib/systemd/system/
               # systemctl enable tuptime-sync.timer && systemctl start tuptime-sync.timer

       Copy systemd service file and enable it:

           # install -m 644 tuptime/src/systemd/tuptime.service /lib/systemd/system/tuptime.service
           # systemctl enable tuptime.service && systemctl start tuptime.service

       Or if it is an old RedHat system or derivate, copy the init file:

           # install -m 755 tuptime/src/init.d/redhat/tuptime /etc/init.d/tuptime
           # chkconfig --add tuptime && chkconfig tuptime on

       Or if it is and old Debian system or derivate, copy the init file:

           # install -m 755 tuptime/src/init.d/debian/tuptime /etc/init.d/tuptime
           # update-rc.d tuptime defaults
           # /etc/init.d/tuptime start

       That's all, enjoy it.


For BSDs systems:

   + On FreeBSD and derivates using packages or ports:

           # pkg install tuptime
      
           (or)
       
           # /usr/ports/sysutils/tuptime

       Add the suggested cron entry and enable it on rc:

           # sysrc tuptime_enable=YES
           # service tuptime start

   + Manual install on FreeBSD, OpenBSD, NetBSD and derivates:

       Please, read misc/bsd-manual-install.txt


Via Docker:

   Tuptime could be executed inside a container via Docker, but it still
   requires the Systemd integration on the monitored host.
   Please, read misc/docker/docker-notes.txt


Partial support for OSX (Darwin) system:

   Tuptime is compatible with OSX, but the integration with LaunchD is not
   complete. Please, read misc/osx-notes.txt


Note about Python 2.7:

   Tuptime <= 3.5.0 can be executed with Python 2.7.
   Set the shebang line to '#!/usr/bin/env python' to get it.



===========================
| Command line parameters |
===========================

These are the command line options, no configuration file is used:

  -h, --help            show this help message and exit
  -A STARTUP, --at STARTUP
                        limit to this startup number
  -b, --bootid          show boot identifier
  -c, --csv             csv output
  -d DATETIME_FMT, --date DATETIME_FMT
                        datetime/timestamp format output
  -e DECIMALS, --dec DECIMALS
                        number of decimals in percentages
  -E, --exclude STARTUP
                        startup numbers to exclude
  -f FILE, --filedb FILE
                        database file (/var/lib/tuptime/tuptime.db)
  -g, --graceful        register a graceful shutdown
  -i, --invert          startup number in reverse count | swich between
                        longest/shortest on default output
  -k, --kernel          show kernel version
  -l, --list            enumerate system life as list
  -n, --noup            avoid update values into DB
  -o TYPE, --order TYPE
                        order enumerate by [u|r|s|e|d|k]
  -p, --power           show power states run + sleep
  -q, --quiet           update values into DB without output
  -r, --reverse         reverse order in listings
  -s, --seconds         output time in seconds and epoch
  -S STARTUP, --since STARTUP
                        limit from this startup number
  -t, --table           enumerate system life as table
  --tat TIMESTAMP       system status at epoch timestamp
  --tsince TIMESTAMP    limit from this epoch timestamp
  --tuntil TIMESTAMP    limit until this epoch timestamp
  -U STARTUP, --until STARTUP
                        limit up until this startup number
  -v, --verbose         verbose output
  -V, --version         show version


-A <STARTUP>, --at <STARTUP>
  Limit values only to this startup number. Take it from (-t) table or
  (-l) list reports. Negative values are allowed.

  Examples:
          tuptime -A 34
          tuptime --at 22
          tuptime -l -A -1  # List last register


-b, --bootid
  Add boot identifier to the output.

  Examples:
         tuptime -b
         tuptime -t --bootid


-h, --help
  Print a quick reference of the command line parameters.

  Examples:
         tuptime -h
         tuptime --help


-c, --csv
  Report in csv format.

  Examples:
         tuptime --csv
         tuptime --csv -t


-d <DATETIME_FMT>, --date=<DATETIME_FMT>
  Change the datetime/timestamp format.
  By default the output use the configured system locales.
  The following is a list of all the format codes that the 1989 C standard
  requires, and these work on all platforms with a standard C implementation.

        %a	Weekday as locale’s abbreviated name.
        %A	Weekday as locale’s full name.
        %w	Weekday as a decimal number, where 0 is Sunday and 6 is 
        	Saturday.
        %d	Day of the month as a zero-padded decimal number.
        %b	Month as locale’s abbreviated name.
        %B	Month as locale’s full name.
        %m	Month as a zero-padded decimal number.
        %y	Year without century as a zero-padded decimal number.
        %Y	Year with century as a decimal number.
        %H	Hour (24-hour clock) as a zero-padded decimal number.
        %I	Hour (12-hour clock) as a zero-padded decimal number.
        %p	Locale’s equivalent of either AM or PM.
        %M	Minute as a zero-padded decimal number.
        %S	Second as a zero-padded decimal number.
        %f	Microsecond as a decimal number, zero-padded on the left.
        %z	UTC offset in the form +HHMM or -HHMM (empty string if the 
        	the object is naive).
        %Z	Time zone name (empty string if the object is naive).
        %j	Day of the year as a zero-padded decimal number.
        %U	Week number of the year (Sunday as the first day of the week) 
        	as a zero padded decimal number. All days in a new year 
        	preceding the first Sunday are considered to be in week 0.
        %W	Week number of the year (Monday as the first day of the week) 
        	as a decimal number. All days in a new year preceding the 
        	first Monday are considered to be in week 0.
        %c	Locale’s appropriate date and time representation.
        %x	Locale’s appropriate date representation.
        %X	Locale’s appropriate time representation.
        %%	A literal '%' character.

  To see the full set of format codes supported on your platform, consult the
  strftime(3) documentation.

  Examples:
         tuptime -d '%X %x'  # (locale by default)
         tuptime -d '%H:%M:%S   %m-%d-%Y'  # MDY style
         tuptime -d '%H:%M:%S   %d-%b-%Y'  # DMY style


-e DECIMALS, --dec DECIMALS
  Change the decimal length in percentages. The number is rounded to this value.

  Examples:
         tuptime --dec 9
         tuptime --power --dec 9


-E <STARTUP>, --exclude <STARTUP>
  Avoid take into account the startup numbers defined. Multiple values must be
  separated by comma ',' and ranges with dash '-'. Default output prints
  'trimmed' if the excluded numbers are in middle of the range.

  Examples:
         tuptime -E 9
         tuptime -E '9-20'
         tuptime --exclude '2,10-20,50'


-f <FILE>, --filedb=<FILE>
  Define an alternative database file. Default is located in 
  '/var/lib/tuptime/tuptime.db'. It takes precedence over environmental variable
  'TUPTIME_DBF'.

  Examples:
         tuptime -f /var/lib/tuptime/tuptime.db  # (Default)
         tuptime -f /tmp/test1.db
         tuptime --filedb /tmp/test2.db

  
-g, --graceful
  Register the time in DB as a graceful shutdown. This option is the way that
  tuptime have for know if is a good or a bad shutdown. This is used in the 
  init.d and systemd files at the shutdown process.

  Examples:
         tuptime -g
         tuptime --graceful


-i, --invert
  Print startup number in reverse count starting since last boot when it's
  used in conjunction with (-t), (-l), or (--tat) options. Equal than the output of
  'journalctl --list-boots'.
  Change Longest to Shortest values on default output report.
  Change Total Uptime to Downtime on (-m) option.

  Examples:
         tuptime -t -i
         tuptime -tib
         tuptime -l --invert
         tuptime -i


-k, --kernel
  Add kernel information to the output.

  Examples:
         tuptime -k
         tuptime -lk
         tuptime --kernel
         tuptime -t --kernel


-l, --list
  Enumerate system life as a list.

  Examples:
         tuptime -l
         tuptime --list


-n, --noup
  Avoid update values in the DB with the current btime, uptime, etc. Useful 
  when reporting modified DB files.

  Examples:
         tuptime -n
         tuptime --noup


-o [u|r|s|e|d|k], --order=[u|r|s|e|d|k]
  Order enumerate output from table or list by:
    <u>  uptime
    <r>  runtime
    <s>  sleep time
    <e>  end status
    <d>  downtime
    <k>  kernel
  Therefore, only works in conjunction with (-t) or (-l) options.

  Examples:
         tuptime -t -o e
         tuptime -l -o k
         tuptime -t --order u
         tuptime -l --order d


-p, --power
  Print the running and sleeping accumulated time for the correspondent uptime slot.
  Note that only Python >= 3.6 can register this values.

  Examples:
         tuptime -p
         tuptime --power
         tuptime -tp
         tuptime -l --power


-q, --quiet
  Update values into database without compute and print output values.

  Examples:
         tuptime -q
         tuptime --quiet


-r, --reverse
  Reverse order for table or list output.

  Examples:
         tuptime -r
         tuptime -lur
         tuptime -teo --reverse
         tuptime -l --reverse


-s, --seconds
  Change default human readable datetime/timestamp style and print times in seconds
  and datetimes in epoch.

  Examples:
         tuptime -s
         tuptime --seconds


-S <STARTUP>, --since <STARTUP>
  Limit values only from this startup number. Take it from (-t) table or
  (-l) list reports. Negative values are allowed.

  Examples:
          tuptime -S 34
          tuptime --since 22
          tuptime -l -S -10

  
-t, --table
  Enumerate system life as a table.

  Examples:
         tuptime -t
         tuptime --table


--tat
  Report system status at specific timestamp, register number enclosed and time
  elapsed and remaining in that matched status.

  Examples:
         tuptime --tat 1555280149
         tuptime --tat `date -d "1 day ago" +%s`
         tuptime -cs --tat 1555281311


--tsince <TIMESTAMP>
  Limit report from the given epoch timestamp. Negative values are allowed.
  Options (-S) and (-U) have precedence over this one.

  Examples:
         tuptime --tsince 1454998872
         tuptime --tsince `date -d "20-JAN-18" +%s`
         tuptime --tsince -31536000  # Since one year ago


--tuntil <TIMESTAMP>
  Limit report until the given epoch timestamp. Negative values are allowed.
  Options (-S) and (-U) have precedence over this one.

  Examples:
         tuptime --tuntil 1454999619
         tuptime --tuntil `date -d "2018-01-20 16:21:42" +%s`
         tuptime --tuntil -2592000  # Until one month ago


-U <STARTUP>, --until <STARTUP>
  Limit values only up until this startup number. Take it from (-t) table or
  (-l) list reports. Negative values are allowed.

  Examples:
          tuptime -U 45
          tuptime --until 11
          tuptime -l -U -10


-v, --verbose
  Print information about the internals of tuptime. It's good for debugging 
  how it gets the variables.

  Examples:
         tuptime -v
         tuptime --verbose


-V, --version:
  Print version number and exit.

  Examples:
         tuptime -V
         tuptime --version



=========================
| Environment variables |
=========================

List of supported environmental variables:

TUPTIME_DBF
  Set an alternative database file path. The argument -f, --filedb takes
  precedence over this.

  Example for ksh / sh / bash:
         export TUPTIME_DBF="/opt/tuptime.db"

  Example for csh / tcsh:
         setenv TUPTIME_DBF /opt/tuptime.db 

  Example for systemd unit:
         Environment="TUPTIME_DBF='/opt/tuptime.db'"



======================
| Schedule execution |
======================

It's important to have a schedule execution. If the init or systemd scripts
are installed as it needs, the program only update values at startup and 
shutdown, but if the system fails, hangs or whatever, the uptime time will
be lost. Be sure that the cron entry or, in other case, the systemd
tuptime-sync.[timer|service] units are installed as expected.

Why is it needed the use of an other .service file with the name tuptime-sync?
The default tuptime.service file uses the option 'RemainAfterExit=true' and
nowadays the systemd timer can't restart an already running service, so, this
is the best workaround.

By default, both have a 5 minutes scheduled execution. Feel free to lower 
that value if your requirements are narrow.



==================
| Default output |
==================

System startups:
 Total number of system startups registered since first timestamp available.

System shutdowns:
  Total number of shutdowns done correctly or incorrectly.

System life:
  Time counter since first startup timestamp available.

System uptime:
System downtime:
  Percentage of time and time counter.

Longest uptime:
Longest downtime:
  Time counter and date with the longest/shortest uptime register. 

Average uptime:
Average downtime: 
  Average time counter.

Current uptime:
  Actual time counter and datetime since registered boot timestamp.



======================================
| DB format changes between versions |
======================================

From 2.x to 3.x:
    From 2.x to 3.0, reorder columns, added offbtime, endst and downtime. From 3.0 to 3.1,
    added kernel column. The migration scripts are loated at:
    
    https://github.com/rfmoz/tuptime/blob/master/misc/cripts/db-tuptime-migrate-2.0-to-3.0.sh
    https://github.com/rfmoz/tuptime/blob/master/misc/cripts/db-tuptime-migrate-3.0-to-3.1.sh
 
From 3.x to 4.x:
    Added rntime and slptime registers, also uptime, rntime, slptime, downtime are now integers.
    The migrations is done automatically from Tuptime. Also, the migration script
    is located at:

    https://github.com/rfmoz/tuptime/blob/master/misc/cripts/db-tuptime-migrate-3.1-to-4.0.sh
 
From 4.x to 5.x:
    Added bootid register. The migrations is done automatically from Tuptime.
    Also, the migration script is located at:

    https://github.com/rfmoz/tuptime/blob/master/misc/cripts/db-tuptime-migrate-4.0-to-5.0.sh


==================
| Database Specs |
==================

Tuptime use a sqlite3 database located in '/var/lib/tuptime/tuptime.db' with
the following format:

tuptime (bootid text,
         btime integer,
         uptime integer,
         rntime integer,
         slptime integer,
         offbtime integer,
         endst integer,
         downtime integer,
         kernel text)

bootid   Unique boot identifier (if it exists)
btime    Startup timestamp in epoch
uptime   Uptime seconds  
rntime   Running time seconds
slptime  Sleeping time seconds
offbtime Shutdown timestamp in epoch
endst    Type of shutdown [1 ok | 0 bad]
downtime Downtime seconds
kernel   Name of the kernel

The number of startup sequence is extracted from 'rowid', the signed integer 
key that uniquely identifies the row within its table in sqlite.

The bootid value is available since Linux 2.6.20 and FreeBSD 11.0.

For reset all the values, simply delete the database file.

It is possible to query and modify it directly, but it's better to use any of
the scripts located under 'misc/scripts' for modify, join or check it.

If a complete row is deleted and the database is not recreate completely (vacuum),
rowid keep the real information about startup number. Tuptime will notify about
'Deleted rows in DB' if the verbose mode is enabled.



============================
| About sync date and time |
============================

Substantial time jumps within a running system may trigger unespected behavior.
Take care of the hardware clock RTC (if the system have), or how the operating
system initializes their clock at startup.

If the system starts with a incorrect datetime and some external time sync is
used, like ntpd or timesyncd, a few time after boot the btime reported by the
system may change (it is a live value computed by now() - uptime). This behaviour
is also related with adjustments performed by adjtime(3), systems running inside
virtualized environments, servers with high load or with high disk I/O, wrong
computation of jiffies / HZ and the problema of lost ticks.

A slightly drift is compensate over uptime/downtime values. Large drift can 
produce unreal registers and output. For example, Raspberry Pi doesn't have any
clock and initializes from 1 January 1970. A downtime of 0, when it's evident
that it was physically impossible, it's a common indicator than the clock had a
large sync jump.

As Tuptime have time dependency, its Systemd service unit pulls time-sync.target
and, therefore, is ordered to start before it. Note that it doesn't assure that 
the clock synchronization is achieved, only that it needs the start of the 
synchronization service and the date isn't going to be before last shutdown. On
last releases of Systemd, there is a new unit that delays reaching time-sync.target 
until stay on sync 'systemd-time-wait-sync', but it isn't enable by default nowadays.

On Linux systems running Systemd, when the system clock has been synchronized, the
file /run/systemd/timesync/synchronized is created. Tuptime check it and print a
verbose line when it doesn't exists 'INFO:Timesync state file not found'.

A few recommendations / workarounds are:

Enable 'systemd-time-wait-sync'. But take care because it can delay the execution
more than expected and a startup can be lost. Note that if it couldn't reach the 
synchronization before shutdown, Tuptime isn't going to be executed on that startup. 
Check open issues on Systemd's GitHub related to this scenario.

Use 'systemd-timesyncd' if it is available (timedatectl set-ntp true), it can also
keep the time in systems without hardware clock. Disable any other time sync service
like 'ntp', 'chrony', 'openntpd' to avoid issues, most related with the use in
conjunction with 'systemd-time-wait-sync'.

Use any time sync service like 'ntp', 'chrony' or 'openntpd' and disable
'systemd-timesyncd'. Add the time sync service requirement on 'tuptime.service' at
the end of 'After=' and 'Wants=' lines.

Force a time sync in the 'tuptime.unit' file. Add under '[Service]' definition the 
line 'ExecStartPre=/usr/bin/ntpdate pool.ntp.org'

If the systems is a single-board computer, like Raspberry Pi or Beaglebone, use a
RTC module and configure it properly.

Without Systemd, use a ntp client and delay the execution of tuptime until the time
is synced. Also, 'ntp-wait' can be added to the init script and the cron line
preceeding the tuptime execution.

Try and failure is the best way to find the right solution for the environment in 
which the system is running.

The drift value that Tuptime have reference is reported in verbose mode 'tuptime -v'
in the line 'INFO:Drift over btime'. Values around +-1 are common and can be
considered as normal.

Systemd timer issues:

    https://github.com/systemd/systemd/issues/14061
    https://github.com/systemd/systemd/issues/8683
    https://github.com/systemd/systemd/issues/5097

Good reads:

    https://tools.ietf.org/html/rfc1589
    https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=119971
    http://man7.org/linux/man-pages/man2/clock_gettime.2.html
    https://unix.stackexchange.com/questions/118631/how-can-i-measure-and-prevent-clock-drift
    Timekeeping in VMware Virtual Machines (Document)
    https://venam.nixers.net/blog/unix/2020/05/02/time-on-unix.html
    https://papers.freebsd.org/2002/phk-timecounters.files/timecounter.pdf



========================
| Date and Epoch notes |
========================

The options --tsince, --tuntil and --tat use epoch timestamp format, as quick
reference here are some notes:

Seconds equivalences:
        1 year    31536000 seconds
        1 month    2592000 seconds
        1 week      604800 seconds
        1 day        86400 seconds
        1 hour        3600 seconds

Convert human datetime to epoch:
        date -d "JAN-18-2018" +%s
        date -d "20-JAN-18" +%s
        date -d "2018-01-20 16:21:42" +%s
        date -d "2018-01-20 16:21:42 GMT+2" +%s
        date -d "3 week ago" +%s
        date -d "1 year ago" +%s
        date -d "now" +%s

Convert epoch to human datetime:
        date -d "@1516748400"

Preceding conversions uses GNU date, on FreeBSD install "coreutils" and call
them with "gdate".

Examples of using it directly:

         tuptime --tsince `date -d "1 day ago" +%s`
         tuptime --tuntil `date -d "1 day ago" +%s`
         tuptime --tsince `date -d "1 week ago" +%s` --tuntil `date -d "1 day ago" +%s`
         tuptime --tat `date -d "1 day ago" +%s`

More info about datetime input formats:
        man date
        'https://www.gnu.org/software/coreutils/manual/html_node/
        Date-input-formats.html#Date-input-formats'



===========
| Caveats |
===========

Tuptime can be executed with 'root' user, but it's recommendable the use of an
unprivileged user, like '_tuptime' as the installation section points to avoid
security problems.

Tuptime tries to save values on DB in every execution. If the user doesn't have
permissions over the file, an informational verbose message is generated. This
situation is handled for print values, but not when register a graceful shutdown.
That's why a user who can write in the database file must execute tuptime, usually
from the init manager, to update values at startup and shutdown, at least.

Tuptime register a new startup time (btime) reported by the system at first
execution after boot, likewise the new startup number and bootid. Also the
previous startup offbtime and downtime are also written. None of them changes
after that. Uptime may shift to accommodate drift and kernel name could be
replaced if live patching is used.

Tuptime is backed by a SQLite database. It is highly resilient but fails can
occur in any software and hardware level, producing a database corruption. Here
is an excellent documentation about it https://www.sqlite.org/howtocorrupt.html

If a backup of the DB file is needed in other place, take a look to Litestream.
This is a streaming replication tool for SQLite databases, it works fine with
Tuptime. https://litestream.io/

The uptime reference is linked with the wall clock time elapsed since boot, not
the running effective time. Both the running (effective time) and hibernate
power states fall within the applicable uptime slot.

If the system have configured a time zone with daylight saving time (DST),
to avoid problems when the time change, the real time clock (RTC) should have
set to universal time coordinated (UTC), not local time. Check it with 
'timedatectl' command.  Reports involving DST changes are completely right.
The system current local time zone is the reference for all timestamps printed.
https://tldp.org/HOWTO/Clock-2.html

If the Linux system execute a full force shutdown or reboot, like for example
"systemctl --force --force reboot", as it doesn't follow the steps to produce
a clean shutdown, the "End status" remains in bad state.

The name of the tool is based on the contraction of the words Total Uptime.
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!471 blocks
openSUSE Build Service is sponsored by