From 0e4b15c768f2147e09ff16c21a85990565cdd645 Mon Sep 17 00:00:00 2001 From: Ronny Friedland Date: Thu, 25 Jan 2024 20:14:22 +0100 Subject: [PATCH] Introduce timetracker application - source code - systemd files - readme - debian install scripts - github workflows --- .github/workflows/anchore-syft.yml | 33 +++++++++ .github/workflows/codeql.yml | 81 ++++++++++++++++++++++ .github/workflows/debian.yml | 38 +++++++++++ .github/workflows/go.yml | 28 ++++++++ .gitignore | 2 + DEBIAN/control | 7 ++ DEBIAN/postinst | 19 ++++++ DEBIAN/preinst | 24 +++++++ DEBIAN/prerm | 24 +++++++ LICENSE | 21 ++++++ Readme.md | 19 ++++++ go.mod | 3 + main.go | 106 +++++++++++++++++++++++++++++ systemd/timetracker.service | 8 +++ systemd/timetracker.timer | 8 +++ 15 files changed, 421 insertions(+) create mode 100644 .github/workflows/anchore-syft.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/debian.yml create mode 100644 .github/workflows/go.yml create mode 100644 .gitignore create mode 100644 DEBIAN/control create mode 100755 DEBIAN/postinst create mode 100755 DEBIAN/preinst create mode 100755 DEBIAN/prerm create mode 100644 LICENSE create mode 100644 Readme.md create mode 100644 go.mod create mode 100644 main.go create mode 100644 systemd/timetracker.service create mode 100644 systemd/timetracker.timer diff --git a/.github/workflows/anchore-syft.yml b/.github/workflows/anchore-syft.yml new file mode 100644 index 0000000..9fb52ac --- /dev/null +++ b/.github/workflows/anchore-syft.yml @@ -0,0 +1,33 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# This workflow checks out code, builds an image, performs a container image +# scan with Anchore's Syft tool, and uploads the results to the GitHub Dependency +# submission API. + +# For more information on the Anchore sbom-action usage +# and parameters, see https://github.com/anchore/sbom-action. For more +# information about the Anchore SBOM tool, Syft, see +# https://github.com/anchore/syft +name: Anchore Syft SBOM scan + +on: + push: + branches: [ "main" ] + +permissions: + contents: write + +jobs: + Anchore-Build-Scan: + permissions: + contents: write # required to upload to the Dependency submission API + runs-on: ubuntu-latest + steps: + - name: Checkout the code + uses: actions/checkout@v3 + - name: Scan the code + uses: anchore/sbom-action@v0 + diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..5edf3c0 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,81 @@ +# 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. +# +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '45 15 * * 4' + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + 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. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml new file mode 100644 index 0000000..820fc56 --- /dev/null +++ b/.github/workflows/debian.yml @@ -0,0 +1,38 @@ +name: Create the timetracker DEB + +permissions: + contents: write + +on: + push: + tags: + - 'v*' + +jobs: + Create_Packages: + name: Create Package + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Build + run: go build -v . + + - name: Copy necessary files + run: | + mkdir -p PKG_SOURCE/usr/local/bin + mkdir -p PKG_SOURCE/var/lib/timetracker + mkdir -p PKG_SOURCE/lib/systemd/system + cp -Rf ./DEBIAN PKG_SOURCE/ + cp -Rf ./timetracker PKG_SOURCE/usr/local/bin/ + cp -Rf ./systemd/* PKG_SOURCE/lib/systemd/system/ + + - name: Create Deb package + run: | + dpkg-deb --build PKG_SOURCE timetracker_${{github.ref_name}}.deb + + - name: Release the Deb package + uses: softprops/action-gh-release@v1 + with: + files: timetracker_${{github.ref_name}}.deb \ No newline at end of file diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..bb09826 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,28 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Go + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.18' + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a7e3f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +status diff --git a/DEBIAN/control b/DEBIAN/control new file mode 100644 index 0000000..e06a448 --- /dev/null +++ b/DEBIAN/control @@ -0,0 +1,7 @@ +Package: timetracker +Version: 1.0.0 +Section: misc +Priority: optional +Architecture: all +Maintainer: Ronny Friedland +Description: Time tracker service diff --git a/DEBIAN/postinst b/DEBIAN/postinst new file mode 100755 index 0000000..3798664 --- /dev/null +++ b/DEBIAN/postinst @@ -0,0 +1,19 @@ +#!/bin/sh + +set -e + +case "$1" in + configure) + chown -R timetracker:timetracker /var/lib/timetracker + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 \ No newline at end of file diff --git a/DEBIAN/preinst b/DEBIAN/preinst new file mode 100755 index 0000000..70dff54 --- /dev/null +++ b/DEBIAN/preinst @@ -0,0 +1,24 @@ +#!/bin/sh + +set -e + +case "$1" in + install|upgrade) + if ! getent group timetracker >/dev/null; then + addgroup --system timetracker + fi + if ! getent passwd timetracker >/dev/null; then + adduser --system timetracker --ingroup timetracker --no-create-home --disabled-password + fi + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 \ No newline at end of file diff --git a/DEBIAN/prerm b/DEBIAN/prerm new file mode 100755 index 0000000..aa2647b --- /dev/null +++ b/DEBIAN/prerm @@ -0,0 +1,24 @@ +#!/bin/sh + +set -e + +case "$1" in + remove|upgrade|deconfigure) + if getent passwd timetracker >/dev/null; then + deluser --system timetracker + fi + if getent group timetracker >/dev/null; then + delgroup --system timetracker + fi + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dc9200 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Ronny Friedland + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..6279a28 --- /dev/null +++ b/Readme.md @@ -0,0 +1,19 @@ +# Timetracker + +Tracks every 5 minutes if system is running to store the beginning and end of using the system. + +## Parameters + +The timetracker application provides the following arguments which can be passed: + +| Property | Description | +|---------------|----------------------------------------------------------------------------| +| configpath | Defines the location of the necessary files, default: /var/lib/timetracker | + +## Execution + +The application is triggered by a systemd timer which triggers the application via systemd unit. + +## License + +This application is published under the [MIT license](LICENSE). \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..11b91b5 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module ronnyfriedland/timetracker/v2 + +go 1.18 diff --git a/main.go b/main.go new file mode 100644 index 0000000..cd18e40 --- /dev/null +++ b/main.go @@ -0,0 +1,106 @@ +package main + +import ( + "bufio" + "flag" + "log" + "math" + "os" + "time" +) + +const dateLayout = "02.01.2006" + +func main() { + configPath := flag.String("configpath", "/var/lib/timetracker", "the config path") + flag.Parse() + + var currentTime = time.Now() + var modificationTime time.Time + + timeTrackerStatusFile := *configPath + "/status" + fileInfo, err := os.Stat(timeTrackerStatusFile) + if err != nil { + log.Printf("Error getting file status: %v", err) + modificationTime = currentTime + } else { + modificationTime = fileInfo.ModTime() + } + + if math.Abs(float64(modificationTime.Day()-currentTime.Day())) == 0 { + // same day, add to status + addCurrentToStatus(timeTrackerStatusFile, currentTime) + } else { + duration, from, to := getDurationFromStatus(timeTrackerStatusFile) + log.Printf("[%s] - Work duration: %2.2fh", + modificationTime.Format(dateLayout), + duration.Hours()) + log.Printf("[%s] - Start: %s, End: %s", + modificationTime.Format(dateLayout), + from.Format(dateLayout), + to.Format(dateLayout)) + + // remove data from status + cleanupStatus(timeTrackerStatusFile) + + // new day, add first status + addCurrentToStatus(timeTrackerStatusFile, currentTime) + } +} + +func cleanupStatus(timeTrackerStatusFile string) { + if err := os.Truncate(timeTrackerStatusFile, 0); err != nil { + log.Fatalf("Failed to cleanup status: %v", err) + } +} + +func getDurationFromStatus(timeTrackerStatusFile string) (time.Duration, time.Time, time.Time) { + file, err := os.Open(timeTrackerStatusFile) + if err != nil { + log.Fatalf("Error opening status: %v", err) + } + defer func(file *os.File) { + err := file.Close() + if err != nil { + log.Fatalf("Unable to close file %v", err) + } + }(file) + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + first, err := time.Parse(time.RFC3339, lines[0]) + if err != nil { + log.Fatalf("Error getting first date of status: %v", err) + } + last, err := time.Parse(time.RFC3339, lines[len(lines)-1]) + if err != nil { + log.Fatalf("Error getting last date of status: %v", err) + } + duration := last.Sub(first) + return duration, first, last +} + +func addCurrentToStatus(timeTrackerStatusFile string, currentTime time.Time) { + statusDateString := currentTime.Format(time.RFC3339) + "\n" + + file, err := os.OpenFile(timeTrackerStatusFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644) + if err != nil { + log.Fatalf("Unable to open file %v", err) + } + + defer func(f *os.File) { + err := f.Close() + if err != nil { + log.Fatalf("Unable to close file %v", err) + } + }(file) + + _, err = file.WriteString(statusDateString) + if err != nil { + log.Fatalf("Unable to write to status file %v", err) + } +} diff --git a/systemd/timetracker.service b/systemd/timetracker.service new file mode 100644 index 0000000..3160253 --- /dev/null +++ b/systemd/timetracker.service @@ -0,0 +1,8 @@ +[Unit] +Description=track time + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/timetracker +User=timetracker +Group=systemd-journal \ No newline at end of file diff --git a/systemd/timetracker.timer b/systemd/timetracker.timer new file mode 100644 index 0000000..5955dd2 --- /dev/null +++ b/systemd/timetracker.timer @@ -0,0 +1,8 @@ +[Unit] +Description=Run timetracker every 5 minutes + +[Timer] +OnCalendar=*:0/15 + +[Install] +WantedBy=timers.target \ No newline at end of file