Skip to content

Commit d5c8a1a

Browse files
committed
Add Arch packaging and robust chrony handling
1 parent 75e6ea3 commit d5c8a1a

4 files changed

Lines changed: 242 additions & 15 deletions

File tree

PKGBUILD

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# **********************************************************************
2+
# * Copyright (C) 2025 MX Authors
3+
# *
4+
# * Authors: Adrian <adrian@mxlinux.org>
5+
# * MX Linux <http://mxlinux.org>
6+
# *
7+
# * This file is part of mx-datetime.
8+
# *
9+
# * Licensed under the Apache License, Version 2.0 (the "License");
10+
# * you may not use this file except in compliance with the License.
11+
# * You may obtain a copy of the License at
12+
# *
13+
# * http://www.apache.org/licenses/LICENSE-2.0
14+
# *
15+
# * Unless required by applicable law or agreed to in writing, software
16+
# * distributed under the License is distributed on an "AS IS" BASIS,
17+
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
# * See the License for the specific language governing permissions and
19+
# * limitations under the License.
20+
# **********************************************************************/
21+
22+
# Maintainer: Adrian <adrian@mxlinux.org>
23+
pkgname=mx-datetime
24+
pkgver=${PKGVER:-25.11mx23}
25+
pkgrel=1
26+
pkgdesc="Date and time configuration tool for MX Linux"
27+
arch=('x86_64' 'i686')
28+
url="https://github.com/MX-Linux/mx-datetime"
29+
license=('Apache')
30+
depends=('chrony' 'util-linux' 'xdg-utils' 'qt6-base' 'polkit')
31+
makedepends=('cmake' 'ninja' 'qt6-tools')
32+
source=()
33+
sha256sums=()
34+
35+
build() {
36+
cd "${startdir}"
37+
38+
rm -rf build
39+
40+
cmake -G Ninja \
41+
-B build \
42+
-DCMAKE_BUILD_TYPE=Release \
43+
-DCMAKE_INSTALL_PREFIX=/usr \
44+
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
45+
-DPROJECT_VERSION_OVERRIDE="${pkgver}"
46+
47+
cmake --build build --parallel
48+
}
49+
50+
package() {
51+
cd "${startdir}"
52+
53+
install -Dm755 build/mx-datetime "${pkgdir}/usr/bin/mx-datetime"
54+
55+
install -dm755 "${pkgdir}/usr/share/mx-datetime/locale"
56+
install -Dm644 build/*.qm "${pkgdir}/usr/share/mx-datetime/locale/" 2>/dev/null || true
57+
58+
install -dm755 "${pkgdir}/usr/lib/mx-datetime"
59+
install -Dm755 scripts/helper "${pkgdir}/usr/lib/mx-datetime/helper"
60+
61+
install -Dm644 scripts/org.mxlinux.pkexec.mx-datetime-helper.policy \
62+
"${pkgdir}/usr/share/polkit-1/actions/org.mxlinux.pkexec.mx-datetime-helper.policy"
63+
64+
install -Dm644 mx-datetime.desktop "${pkgdir}/usr/share/applications/mx-datetime.desktop"
65+
66+
install -Dm644 images/mx-datetime.png "${pkgdir}/usr/share/icons/hicolor/48x48/apps/mx-datetime.png"
67+
install -Dm644 images/mx-datetime.png "${pkgdir}/usr/share/pixmaps/mx-datetime.png"
68+
install -Dm644 images/mx-datetime.svg "${pkgdir}/usr/share/icons/hicolor/scalable/apps/mx-datetime.svg"
69+
70+
install -dm755 "${pkgdir}/usr/share/doc/mx-datetime"
71+
if [ -d help ]; then
72+
cp -r help/* "${pkgdir}/usr/share/doc/mx-datetime/" 2>/dev/null || true
73+
fi
74+
}

build.sh

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,61 @@
11
#!/bin/bash
22
# Build script - see below for command information.
33
set -e
4+
5+
ARCH_BUILD=false
6+
7+
ARGS=()
8+
for arg in "$@"; do
9+
case "$arg" in
10+
--arch)
11+
ARCH_BUILD=true
12+
;;
13+
*)
14+
ARGS+=("$arg")
15+
;;
16+
esac
17+
done
18+
set -- "${ARGS[@]}"
19+
20+
if [ "$ARCH_BUILD" = true ]; then
21+
echo "Building Arch Linux package..."
22+
23+
if ! command -v makepkg &> /dev/null; then
24+
echo "Error: makepkg not found. Please install base-devel package."
25+
exit 1
26+
fi
27+
28+
if [ ! -f debian/changelog ]; then
29+
echo "Error: debian/changelog not found; cannot determine version for Arch build."
30+
exit 1
31+
fi
32+
33+
ARCH_VERSION=$(sed -n '1{s/^[^(]*(\([^)]*\)).*/\1/p}' debian/changelog)
34+
if [ -z "$ARCH_VERSION" ]; then
35+
echo "Error: could not parse version from debian/changelog."
36+
exit 1
37+
fi
38+
echo "Using version ${ARCH_VERSION} from debian/changelog"
39+
40+
ARCH_BUILDDIR=$(mktemp -d -p "$PWD" archpkgbuild.XXXXXX)
41+
trap 'rm -rf "$ARCH_BUILDDIR"' EXIT
42+
43+
rm -rf pkg *.pkg.tar.zst
44+
45+
PKG_DEST_DIR="$PWD/build"
46+
mkdir -p "$PKG_DEST_DIR"
47+
48+
BUILDDIR="$ARCH_BUILDDIR" PKGDEST="$PKG_DEST_DIR" PKGVER="$ARCH_VERSION" makepkg -f
49+
50+
echo "Cleaning makepkg artifacts..."
51+
rm -rf pkg
52+
53+
echo "Arch Linux package build completed!"
54+
echo "Package: $(ls "$PKG_DEST_DIR"/*.pkg.tar.zst 2>/dev/null || echo 'not found')"
55+
echo "Binary available at: build/mx-datetime"
56+
exit 0
57+
fi
58+
459
case "${1:-all}" in
560
clean)
661
echo "Performing ultimate clean..."
@@ -41,6 +96,8 @@ case "${1:-all}" in
4196
echo " make - Build only"
4297
echo " all - Configure and build (default)"
4398
echo " fresh - Clean first then configure and build"
99+
echo "Options:"
100+
echo " --arch - Build Arch Linux package"
44101
exit 1
45102
;;
46-
esac
103+
esac

datetime.cpp

Lines changed: 107 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
#include <QApplication>
2121
#include <QDateTime>
2222
#include <QDebug>
23+
#include <QDir>
2324
#include <QFile>
25+
#include <QFileInfo>
2426
#include <QLineEdit>
2527
#include <QMessageBox>
2628
#include <QProcess>
@@ -603,13 +605,65 @@ bool MXDateTime::validateServerList()
603605
return true;
604606
}
605607

608+
QString MXDateTime::systemdChronyUnit()
609+
{
610+
const QStringList candidates {u"chronyd.service"_s, u"chrony.service"_s};
611+
for (const QString &unit : candidates) {
612+
QByteArray output;
613+
execute(u"systemctl"_s, {u"show"_s, u"-p"_s, u"LoadState"_s, u"--value"_s, unit}, &output, nullptr);
614+
if (QString::fromUtf8(output).trimmed() == u"loaded"_s) {
615+
return unit;
616+
}
617+
}
618+
return {};
619+
}
620+
621+
QString MXDateTime::chronyConfigFile()
622+
{
623+
if (QFile::exists(u"/etc/chrony/chrony.conf"_s)) {
624+
return u"/etc/chrony/chrony.conf"_s;
625+
}
626+
if (QFile::exists(u"/etc/chrony.conf"_s)) {
627+
return u"/etc/chrony.conf"_s;
628+
}
629+
return u"/etc/chrony/chrony.conf"_s;
630+
}
631+
632+
QString MXDateTime::chronySourcesFile()
633+
{
634+
const QString configFile = chronyConfigFile();
635+
QFile file(configFile);
636+
if (file.open(QFile::ReadOnly | QFile::Text)) {
637+
while (!file.atEnd()) {
638+
QString line = QString::fromUtf8(file.readLine()).trimmed();
639+
line = line.section('#', 0, 0).trimmed();
640+
if (line.startsWith(u"sourcedir "_s)) {
641+
const QStringList parts = line.split(' ', Qt::SkipEmptyParts);
642+
if (parts.size() >= 2) {
643+
const QString dirPath = parts.at(1);
644+
return QDir(dirPath).filePath(u"mx-datetime.sources"_s);
645+
}
646+
}
647+
}
648+
}
649+
if (QFileInfo(u"/etc/chrony/sources.d"_s).isDir()) {
650+
return u"/etc/chrony/sources.d/mx-datetime.sources"_s;
651+
}
652+
if (QFileInfo(u"/etc/chrony.d"_s).isDir()) {
653+
return u"/etc/chrony.d/mx-datetime.sources"_s;
654+
}
655+
return u"/etc/chrony/sources.d/mx-datetime.sources"_s;
656+
}
657+
606658
void MXDateTime::loadNetworkTime()
607659
{
608660
while (tableServers->rowCount() > 0) {
609661
tableServers->removeRow(0);
610662
}
611-
loadSources(u"/etc/chrony/sources.d/mx-datetime.sources"_s);
612-
const bool move = loadSources(u"/etc/chrony/chrony.conf"_s);
663+
const QString sourcesFile = chronySourcesFile();
664+
const QString configFile = chronyConfigFile();
665+
loadSources(sourcesFile);
666+
const bool move = loadSources(configFile);
613667

614668
// Stray change signals may have caused changedServers to be set.
615669
QApplication::processEvents();
@@ -618,8 +672,15 @@ void MXDateTime::loadNetworkTime()
618672
if (sysInit != SystemD) {
619673
enabledNTP = shell(u"ls /etc/rc*.d | grep chrony | grep '^S'"_s);
620674
} else {
621-
enabledNTP = shell(u"LANG=C systemctl status chrony | grep Loaded"_s
622-
u"| grep service |cut -d';' -f2 |grep -q enabled"_s);
675+
const QString unit = systemdChronyUnit();
676+
if (unit.isEmpty()) {
677+
enabledNTP = false;
678+
} else {
679+
QByteArray output;
680+
execute(u"systemctl"_s, {u"show"_s, u"-p"_s, u"UnitFileState"_s, u"--value"_s, unit}, &output, nullptr);
681+
const QString state = QString::fromUtf8(output).trimmed();
682+
enabledNTP = (state == u"enabled"_s || state == u"enabled-runtime"_s);
683+
}
623684
}
624685
checkAutoSync->setChecked(enabledNTP);
625686
}
@@ -631,8 +692,11 @@ void MXDateTime::saveNetworkTime()
631692
const QString enable_disable(ntp ? u"enable"_s : u"disable"_s);
632693
const QString start_stop(ntp ? u"start"_s : u"stop"_s);
633694
if (sysInit == SystemD) {
634-
executeAsRoot(u"systemctl"_s, {enable_disable, u"chrony"_s});
635-
executeAsRoot(u"systemctl"_s, {start_stop, u"chrony"_s});
695+
const QString unit = systemdChronyUnit();
696+
if (!unit.isEmpty()) {
697+
executeAsRoot(u"systemctl"_s, {enable_disable, unit});
698+
executeAsRoot(u"systemctl"_s, {start_stop, unit});
699+
}
636700
} else if (sysInit == OpenRC) {
637701
if (QFile::exists(u"/etc/init.d/chronyd"_s)) {
638702
executeAsRoot(u"rc-update"_s, {ntp ? u"add"_s : u"del"_s, u"chronyd"_s});
@@ -646,6 +710,8 @@ void MXDateTime::saveNetworkTime()
646710

647711
// Generate chrony sources file.
648712
if (changedServers) {
713+
const QString sourcesFile = chronySourcesFile();
714+
const QString configFile = chronyConfigFile();
649715
QTemporaryFile file;
650716
if (file.open()) {
651717
file.write("# Generated by MX Date & Time - ");
@@ -668,16 +734,23 @@ void MXDateTime::saveNetworkTime()
668734
file.write("\n");
669735
}
670736
file.close();
671-
executeAsRoot(u"mv"_s, {file.fileName(), u"/etc/chrony/sources.d/mx-datetime.sources"_s});
672-
executeAsRoot(u"chown"_s, {u"root:"_s, u"/etc/chrony/sources.d/mx-datetime.sources"_s});
673-
executeAsRoot(u"chmod"_s, {u"+r"_s, u"/etc/chrony/sources.d/mx-datetime.sources"_s});
737+
const QDir sourcesDir = QFileInfo(sourcesFile).dir();
738+
if (!sourcesDir.exists()) {
739+
executeAsRoot(u"mkdir"_s, {u"-p"_s, sourcesDir.absolutePath()});
740+
}
741+
executeAsRoot(u"mv"_s, {file.fileName(), sourcesFile});
742+
executeAsRoot(u"chown"_s, {u"root:"_s, sourcesFile});
743+
executeAsRoot(u"chmod"_s, {u"+r"_s, sourcesFile});
674744
}
675745
if (ntp) {
676-
if (!clearSources(u"/etc/chrony/chrony.conf"_s)) {
746+
if (!clearSources(configFile)) {
677747
executeAsRoot(u"chronyc"_s, {u"reload"_s, u"sources"_s});
678748
} else {
679749
if (sysInit == SystemD) {
680-
executeAsRoot(u"systemctl"_s, {u"restart"_s, u"chrony"_s});
750+
const QString unit = systemdChronyUnit();
751+
if (!unit.isEmpty()) {
752+
executeAsRoot(u"systemctl"_s, {u"restart"_s, unit});
753+
}
681754
} else {
682755
executeAsRoot(u"service"_s, {u"chrony"_s, u"restart"_s});
683756
}
@@ -692,6 +765,22 @@ bool MXDateTime::loadSources(const QString &filename)
692765
if (!file.open(QFile::ReadOnly | QFile::Text)) {
693766
return false;
694767
}
768+
const auto isDuplicate = [this](const QString &type, const QString &address, const QString &options) {
769+
const int serverCount = tableServers->rowCount();
770+
for (int ixi = 0; ixi < serverCount; ++ixi) {
771+
auto *comboType = qobject_cast<QComboBox *>(tableServers->cellWidget(ixi, 0));
772+
if (!comboType) {
773+
continue;
774+
}
775+
const QString rowType = comboType->currentData().toString();
776+
const QString rowAddress = tableServers->item(ixi, 1)->text().trimmed();
777+
const QString rowOptions = tableServers->item(ixi, 2)->text().simplified();
778+
if (rowType == type && rowAddress == address && rowOptions == options) {
779+
return true;
780+
}
781+
}
782+
return false;
783+
};
695784
bool hassrc = false;
696785
while (!file.atEnd()) {
697786
const QByteArray &bline = file.readLine();
@@ -710,7 +799,11 @@ bool MXDateTime::loadSources(const QString &filename)
710799
options.append(' ');
711800
options.append(args.at(ixi));
712801
}
713-
addServerRow(enabled, curarg, args.at(1), options.trimmed());
802+
const QString address = args.at(1);
803+
const QString trimmedOptions = options.trimmed();
804+
if (!isDuplicate(curarg, address, trimmedOptions)) {
805+
addServerRow(enabled, curarg, address, trimmedOptions);
806+
}
714807
hassrc = true;
715808
}
716809
}
@@ -730,8 +823,8 @@ bool MXDateTime::clearSources(const QString &filename)
730823
}
731824
while (!file.atEnd()) {
732825
const QByteArray &bline = file.readLine();
733-
static const QRegularExpression tregex(u"^\\s?(pool|server|peer)\\s"_s);
734-
if (QString(bline).contains(tregex)) {
826+
static const QRegularExpression tregex(u"^\\s*(pool|server|peer)\\s"_s);
827+
if (QString::fromUtf8(bline).contains(tregex)) {
735828
confdata.append("##");
736829
changed = true;
737830
}

datetime.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ class MXDateTime : public QDialog, private Ui::MXDateTime
8484
bool validateServerList();
8585
void loadNetworkTime();
8686
void saveNetworkTime();
87+
QString systemdChronyUnit();
88+
QString chronyConfigFile();
89+
QString chronySourcesFile();
8790
bool loadSources(const QString &filename);
8891
bool clearSources(const QString &filename);
8992

0 commit comments

Comments
 (0)