Commit 7eecfc31868f
Changed files (4)
home/common/services/gcal-to-org.nix
@@ -0,0 +1,48 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+
+with lib;
+
+let
+ cfg = config.services.gcal-to-org;
+in
+{
+ options.services.gcal-to-org = {
+ enable = mkEnableOption "automatic Google Calendar sync to org-mode";
+
+ interval = mkOption {
+ type = types.str;
+ default = "hourly";
+ description = "How often to sync the calendar (systemd timer format)";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.gcal-to-org = {
+ Unit = {
+ Description = "Sync Google Calendar to org-mode file";
+ };
+ Service = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.gcal-to-org}/bin/gcal-to-org";
+ };
+ };
+
+ systemd.user.timers.gcal-to-org = {
+ Unit = {
+ Description = "Timer for syncing Google Calendar to org-mode";
+ };
+ Timer = {
+ OnCalendar = cfg.interval;
+ Persistent = true;
+ };
+ Install = {
+ WantedBy = [ "timers.target" ];
+ };
+ };
+ };
+}
systems/kyushu/home.nix
@@ -11,6 +11,7 @@ in
../../home/common/dev/containers.nix
../../home/common/dev/tektoncd.nix
../../home/common/services/color-scheme-timer.nix
+ ../../home/common/services/gcal-to-org.nix
../../home/common/services/goimapnotify.nix
../../home/common/services/mail-monitor.nix
../../home/common/services/redhat.nix
@@ -83,6 +84,12 @@ in
darkTime = "19:00"; # Switch to dark mode at 7pm
};
+ # Google Calendar sync to org-mode
+ services.gcal-to-org = {
+ enable = true;
+ interval = "hourly";
+ };
+
# ntfy notification subscriber
systemd.user.services.ntfy-subscriber = {
Unit = {
tools/gcal-to-org/main.go
@@ -40,6 +40,7 @@ type Event struct {
EndDate string
EndTime string
Title string
+ Calendar string
}
// calendarsFlag implements flag.Value for string slice
@@ -119,7 +120,7 @@ func run(config *Config) error {
func fetchCalendarData(calendars []string, startDate, endDate string) ([]Event, error) {
// Build gcalcli command
- args := []string{"agenda", "--tsv"}
+ args := []string{"agenda", "--tsv", "--details", "calendar", "--nodeclined"}
// Add calendar filters
for _, cal := range calendars {
@@ -156,7 +157,7 @@ func fetchCalendarData(calendars []string, startDate, endDate string) ([]Event,
// First row is header
header := records[0]
- var startDateIdx, startTimeIdx, endDateIdx, endTimeIdx, titleIdx int
+ var startDateIdx, startTimeIdx, endDateIdx, endTimeIdx, titleIdx, calendarIdx int
for i, h := range header {
switch h {
case "start_date":
@@ -169,6 +170,8 @@ func fetchCalendarData(calendars []string, startDate, endDate string) ([]Event,
endTimeIdx = i
case "title":
titleIdx = i
+ case "calendar":
+ calendarIdx = i
}
}
@@ -185,6 +188,7 @@ func fetchCalendarData(calendars []string, startDate, endDate string) ([]Event,
EndDate: getField(record, endDateIdx),
EndTime: getField(record, endTimeIdx),
Title: getField(record, titleIdx),
+ Calendar: getField(record, calendarIdx),
}
events = append(events, event)
@@ -200,6 +204,22 @@ func getField(record []string, idx int) string {
return record[idx]
}
+// categorizeCalendar returns "work" or "personal" based on calendar name
+func categorizeCalendar(calendarName string) string {
+ lowerName := strings.ToLower(calendarName)
+
+ // Work calendar indicators
+ workKeywords := []string{"redhat", "work", "vdemeest@redhat"}
+ for _, keyword := range workKeywords {
+ if strings.Contains(lowerName, keyword) {
+ return "work"
+ }
+ }
+
+ // Default to personal
+ return "personal"
+}
+
func generateOrgFile(events []Event, outputFile, startDate, endDate string) error {
// Group events by date
eventsByDate := make(map[string][]Event)
@@ -277,6 +297,14 @@ func eventToOrgMode(event Event) string {
fmt.Fprintf(&buf, "* %s\n", title)
+ // Add PROPERTIES drawer with CATEGORY
+ if event.Calendar != "" {
+ category := categorizeCalendar(event.Calendar)
+ fmt.Fprintf(&buf, ":PROPERTIES:\n")
+ fmt.Fprintf(&buf, ":CATEGORY: %s\n", category)
+ fmt.Fprintf(&buf, ":END:\n")
+ }
+
// Build timestamp
timestamp := formatTimestamp(event)
if timestamp != "" {
tools/gcal-to-org/README.md
@@ -8,6 +8,8 @@ One-way sync from Google Calendar to org-mode file.
- **Separate file**: Generates `~/desktop/org/calendar.org` which is easily regenerated
- **Automatic cleanup**: Keeps only events from 2 weeks ago onward (configurable)
- **Works with gcalcli**: Uses your existing gcalcli setup and authentication
+- **Automatic categorization**: Events are automatically categorized as "personal" or "work" based on their source calendar
+- **Smart filtering**: Only includes events you've accepted or marked as "maybe" (declined events are excluded)
## Usage
@@ -72,7 +74,35 @@ const (
## Automation
-### Systemd timer (recommended)
+### NixOS/home-manager (recommended)
+
+If using NixOS with home-manager, add to your home configuration:
+
+```nix
+{
+ imports = [
+ ../../home/common/services/gcal-to-org.nix
+ ];
+
+ services.gcal-to-org = {
+ enable = true;
+ interval = "hourly"; # Can be "hourly", "daily", or any systemd timer format
+ };
+}
+```
+
+This will automatically:
+- Create a systemd service to run gcal-to-org
+- Create a systemd timer to run it on your chosen schedule
+- Start the timer on login
+
+Check timer status:
+```bash
+systemctl --user list-timers gcal-to-org
+systemctl --user status gcal-to-org.service
+```
+
+### Manual systemd timer
Create `~/.config/systemd/user/gcal-sync.service`:
@@ -130,15 +160,36 @@ DO NOT EDIT MANUALLY - changes will be overwritten.
* 2026-01-14 Wednesday
* Meeting with team
+:PROPERTIES:
+:CATEGORY: work
+:END:
<2026-01-14 Wed 09:00-10:00>
* All-day event
+:PROPERTIES:
+:CATEGORY: personal
+:END:
<2026-01-14 Wed>
* Multi-day conference
+:PROPERTIES:
+:CATEGORY: work
+:END:
<2026-01-14 Wed>--<2026-01-16 Fri>
```
+### Event Categorization
+
+Events are automatically categorized based on their source calendar:
+
+- **Work**: Calendars containing "redhat", "work", or "vdemeest@redhat" are tagged with `CATEGORY: work`
+- **Personal**: All other calendars are tagged with `CATEGORY: personal`
+
+This allows you to:
+- Filter events by category in org-agenda views
+- Color-code events based on category
+- Create separate agenda views for personal vs work events
+
## Building
```bash
@@ -191,6 +242,18 @@ To use the calendar in your agenda views, add to your Emacs config:
;; Optional: exclude from refile targets (read-only)
(setq org-refile-targets
'((org-agenda-files :maxlevel . 3)))
+
+;; Optional: Color-code categories in agenda
+(setq org-agenda-category-icon-alist
+ '(("work" "๐ผ" nil nil :ascent center)
+ ("personal" "๐ " nil nil :ascent center)))
+
+;; Optional: Create custom agenda views by category
+(setq org-agenda-custom-commands
+ '(("w" "Work events" agenda ""
+ ((org-agenda-category-filter-preset '("+work"))))
+ ("p" "Personal events" agenda ""
+ ((org-agenda-category-filter-preset '("+personal"))))))
```
## License